How to draw 2D pixel-by-pixel in XNA?
Asked Answered
E

3

7

I'm trying to draw on the screen pixel-by-pixel using XNA, but am having problems with resources. I thought the best way would be to have 1 texture that updates every frame, but I'm having trouble updating it. Here's what I've got so far, just as a test:

Texture2D canvas;
Rectangle tracedSize;
UInt32[] pixels;

protected override void Initialize()
    {
        tracedSize = GraphicsDevice.PresentationParameters.Bounds;
        canvas = new Texture2D(GraphicsDevice, tracedSize.Width, tracedSize.Height, false, SurfaceFormat.Color);
        pixels = new UInt32[tracedSize.Width * tracedSize.Height];               

        base.Initialize();
    }

protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);

        pixels[100] = 0xFF00FF00;
        canvas.SetData<UInt32>(pixels, 0, tracedSize.Width * tracedSize.Height);

        spriteBatch.Begin();
        spriteBatch.Draw(canvas, new Rectangle(0, 0, tracedSize.Width, tracedSize.Height), Color.White);
        spriteBatch.End();

        base.Draw(gameTime);
    }

When Draw() is called the second time, I get the following error:

"The operation was aborted. You may not modify a resource that has been set on a device, or after it has been used within a tiling bracket."

If I try to make a new Texture2D in Draw(), I quickly get an out of memory error. This is for Windows Phone. It seems like I'm trying to do it the wrong way, what other options do I have to make it work?

Enchanting answered 29/1, 2011 at 14:46 Comment(2)
I didn't know the answer so I googled "xna procedural texture" (which is what this sort of thing is called). There doesn't seem to be a simple, built-in way to do this. There are at least three common suggestions (buffering, GPU texture commands, doing it in a shader). The last is obviously optimal, but requires shader programming knowledge. You might want to perform the same search and browse what's out there.Cosec
For people looking to draw on the fly pixel-by-pixel 2D textures, it has been answered here #70023199Grivation
H
6

Try setting GraphicsDevice.Textures[0] = null before you call SetData. Depending on the effect you're after there may be a more performant method, you could also consider Silverlights WriteableBitmap.

Edit: This is the code I tested in the emulator:

Texture2D canvas;
Rectangle tracedSize;
UInt32[] pixels;

protected override void Initialize()
{
    tracedSize = GraphicsDevice.PresentationParameters.Bounds;
    canvas = new Texture2D(GraphicsDevice, tracedSize.Width, tracedSize.Height, false, SurfaceFormat.Color);
    pixels = new UInt32[tracedSize.Width * tracedSize.Height];

    base.Initialize();
}
Random rnd = new Random();
protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    GraphicsDevice.Textures[0] = null;
    pixels[rnd.Next(pixels.Length)] = 0xFF00FF00;
    canvas.SetData<UInt32>(pixels, 0, tracedSize.Width * tracedSize.Height);

    spriteBatch.Begin();
    spriteBatch.Draw(canvas, new Rectangle(0, 0, tracedSize.Width, tracedSize.Height), Color.White);
    spriteBatch.End();

    base.Draw(gameTime);
}
Hyman answered 29/1, 2011 at 16:46 Comment(1)
Thanks very much, I got it to work from your example. I think the first time I tried it I may have been newing a Texture2D in Draw().Enchanting
U
3

You basically need to do as it asks in the exception:

To ensure that the texture is not set on the graphics device, put this at the end of Draw:

GraphicsDevice.Textures[0] = null;

To ensure you are not drawing inside a tiling bracket, do not use SetData inside of Draw at all. Move the call to SetData into Update.


Bonus info: Your out-of-memory error is because you are not releasing the unmanaged resources that Texture2D allocates (the garbage collector can't track them, so it doesn't know you're running out of memory). You need to call Dispose on the texture. However making a new texture each frame is a bad idea anyway (there's no way to avoid the performance and memory fragmentation problems it causes).

Underage answered 30/1, 2011 at 8:26 Comment(0)
F
1

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.

Reuse a Texure2D object declared at the class level.

Firstling answered 21/11, 2021 at 0:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.