How to draw on the fly 2D pixel-by-pixel in MonoGame/XNA?
Asked Answered
D

2

2

I was wondering how to draw on the fly pixel by pixel in XNA/MonoGame and could only find this. Problem is, the question wasn't centered about how to actually draw pixel by pixel but rather manage resources and updating stuff.

Because I couldn't find any simple and short answer and was wondering how to do for some time now, I'll share a snippet I stumbled upon (see answer).

NB: If anyone having a better approach or an alternative method to do it, feel free to post or comment!

Dionne answered 18/11, 2021 at 15:39 Comment(0)
A
1

Declare a class level variable for your texture and sizes:

Texture2D Tex, Tex1, Tex2;
const int WIDTH = 32;
const int HEIGHT = 32;
Color[] TexSource = new Color[WIDTH * HEIGHT];

Initialize the variables in LoadContent()

int radius = 5;
point center = new Point(WIDTH >> 2, HEIGHT >> 2);
Tex = new Texture2D(GraphicsDevice, WIDTH, HEIGHT);
for(int x = 0 ; x < WIDTH; x++)
  for(int y = 0 ; y < HEIGHT; y++)
    if(Math.Sqrt((x - center.x)* (x - center.x)) < radius && Math.Sqrt((y - center.y) * (y - center.y) < radius))
       TexSource[x + y * WIDTH] = Color.Red;
    else
       TexSource[x + y * WIDTH] = Color.White;
  Tex = SetData<Color>(TexSource);    
  //The resulting texture is a red circle on a white background.

  //If you want to draw on top of an existing texture Tex1 as Tex2:
  Tex1 = Content.Load<Texture2D>(Tex1Name);
  Tex2 = new Texture2D(GraphicsDevice, Tex1.Width, Tex1.Height);
  TexSource = Tex1.GetData<Color>();
  // Draw a white \ on the image, we will assume tex1 is square for this task
  for(int x = 0 ; x < Tex1.Width; x++)
    for(int y = 0 ; y < Tex1.Width; y++)
       if(x==y) TexSource[x + y * Tex1.Width] = Color.White;

You may change the textures in Update()

But, never create or modify a texture in Draw() ever.

The SpriteBatch.Draw() call expects the GraphicsDevice buffer to contain all of the texture data (by extension to the GPU buffer), any changes still happening(from Update and IsRunningSlowly == true) at this point will cause tearing when rendered.

The workaround of GraphicsDevice.Textures[0] = null; blocks the Draw call and the game until transfer to the GPU is complete, thus slowing the entire game loop.

Aleph answered 20/11, 2021 at 23:55 Comment(1)
@atrefeu, you are welcome, just to clarify, a 1x1 image may lead to scaling artifacts depending on Blend Mode, and given the efficiency gains of transfer and storage to the GPU there would never be an advantage to using a texture with a size less than 4x4. (depends on many factors, foremost is SurfaceFormat), but larger sizes transfer faster, with less overhead, up to 256MB per PCIE cycle.This is why my answer uses 32x32.Aleph
D
2

You need to use the SetData() method of a Texture2D object you previously initialized with a 1 by 1 size.

Texture2D pixel; //ie. in class declaration

And later, for instance in the Initialize method:

pixel = new Texture2D(GraphicsDevice, 1, 1);
pixel.SetData(new Color[] {Color.White});

Edit: As Strom mentionned in his answer, the Texture2D object must be declared at the class level. You should avoid declaring or modifying a texture in the Draw() method as it'll slow the game loop.

Dionne answered 18/11, 2021 at 15:39 Comment(0)
A
1

Declare a class level variable for your texture and sizes:

Texture2D Tex, Tex1, Tex2;
const int WIDTH = 32;
const int HEIGHT = 32;
Color[] TexSource = new Color[WIDTH * HEIGHT];

Initialize the variables in LoadContent()

int radius = 5;
point center = new Point(WIDTH >> 2, HEIGHT >> 2);
Tex = new Texture2D(GraphicsDevice, WIDTH, HEIGHT);
for(int x = 0 ; x < WIDTH; x++)
  for(int y = 0 ; y < HEIGHT; y++)
    if(Math.Sqrt((x - center.x)* (x - center.x)) < radius && Math.Sqrt((y - center.y) * (y - center.y) < radius))
       TexSource[x + y * WIDTH] = Color.Red;
    else
       TexSource[x + y * WIDTH] = Color.White;
  Tex = SetData<Color>(TexSource);    
  //The resulting texture is a red circle on a white background.

  //If you want to draw on top of an existing texture Tex1 as Tex2:
  Tex1 = Content.Load<Texture2D>(Tex1Name);
  Tex2 = new Texture2D(GraphicsDevice, Tex1.Width, Tex1.Height);
  TexSource = Tex1.GetData<Color>();
  // Draw a white \ on the image, we will assume tex1 is square for this task
  for(int x = 0 ; x < Tex1.Width; x++)
    for(int y = 0 ; y < Tex1.Width; y++)
       if(x==y) TexSource[x + y * Tex1.Width] = Color.White;

You may change the textures in Update()

But, never create or modify a texture in Draw() ever.

The SpriteBatch.Draw() call expects the GraphicsDevice buffer to contain all of the texture data (by extension to the GPU buffer), any changes still happening(from Update and IsRunningSlowly == true) at this point will cause tearing when rendered.

The workaround of GraphicsDevice.Textures[0] = null; blocks the Draw call and the game until transfer to the GPU is complete, thus slowing the entire game loop.

Aleph answered 20/11, 2021 at 23:55 Comment(1)
@atrefeu, you are welcome, just to clarify, a 1x1 image may lead to scaling artifacts depending on Blend Mode, and given the efficiency gains of transfer and storage to the GPU there would never be an advantage to using a texture with a size less than 4x4. (depends on many factors, foremost is SurfaceFormat), but larger sizes transfer faster, with less overhead, up to 256MB per PCIE cycle.This is why my answer uses 32x32.Aleph

© 2022 - 2024 — McMap. All rights reserved.