Xbox LIVE Indie Games
Sort Discussions: Previous Discussion Next Discussion
Page 1 of 1 (6 posts)

[Solved] Preserved RenderTarget2D experiences "drift" or "spills paint" effect in pixel shader.

Last post 3/7/2012 9:45 PM by BinaryFreeze. 5 replies.
  • 3/6/2012 3:00 AM

    [Solved] Preserved RenderTarget2D experiences "drift" or "spills paint" effect in pixel shader.

    I've been working on creating a pixel shader that performs animation for my game.  I've had a previous thread open about performance problems using other methods.

    I got my basic render order setup along with a basic vertex/pixel shader.  I'm setting up a RenderTarget2D, render the previous frame in it, draw input, and then render the RenderTarget to screen.

    My issue is that the previous frame seems to drift a little (or the texture sampling isn't accurate).
    Picture of my issue before-middle-after:


    Instead of using RenderTargetUsage.PreserveContents, I opt'ed to use a double-buffer method similar to how backbuffers work.

    I have a few ideas of what it might be.
    1. The textured quad is causing the problem (ScreenPrimitive) I've tested with both a TriangleStrip and TriangleList
    2. I haven't created my RenderTarget correctly.  I've tested it with PreserverContents instead of the double-buffer.
    3. The pixel shader's texture sampler is incorrect. I've tried playing with the values but I just don't know enough to deal with it.

    Finally, I can reproduce this "drifting" behaviour on both my laptop and Xbox.  I'm concerned it could also be a problem with XNA itself.  If so then how would this work

    Here's my code and associated shader file.
    public class Game1 : Microsoft.Xna.Framework.Game 
        { 
            GraphicsDeviceManager graphics; 
            SpriteBatch spriteBatch; 
            GraphicsDevice device; 
     
            Effect effect; 
            VertexPositionColorTexture[] vertices; 
            VertexPositionColorTexture[] screenVertices; 
     
            float width, height; 
            int Offset = 0; 
     
            RenderTarget2D latestRender; 
            RenderTarget2D previousRender; 
     
            public Game1() 
            { 
                graphics = new GraphicsDeviceManager(this); 
                Content.RootDirectory = "Content"
            } 
     
            protected override void Initialize() 
            { 
                graphics.PreferredBackBufferWidth = 500; 
                graphics.PreferredBackBufferHeight = 500; 
                graphics.IsFullScreen = false
                graphics.ApplyChanges(); 
                Window.Title = "Arena of Fire"
     
                base.Initialize(); 
            } 
     
            protected override void LoadContent() 
            { 
                spriteBatch = new SpriteBatch(GraphicsDevice); 
     
                device = graphics.GraphicsDevice; 
     
                latestRender = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, false, SurfaceFormat.Color, DepthFormat.Depth24, 0, RenderTargetUsage.PlatformContents); 
                previousRender = new RenderTarget2D(GraphicsDevice, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height, false, SurfaceFormat.Color, DepthFormat.Depth24, 0, RenderTargetUsage.DiscardContents); 
     
                effect = Content.Load<Effect>("DefaultEffect"); 
                effect.Parameters["ViewportWidth"].SetValue(GraphicsDevice.Viewport.Width); 
                effect.Parameters["ViewportHeight"].SetValue(GraphicsDevice.Viewport.Height); 
     
                width = GraphicsDevice.Viewport.Width; 
                height = GraphicsDevice.Viewport.Height; 
     
                SetUpVertices(); 
            } 
     
            protected override void UnloadContent() 
            { 
            } 
     
            private void SetUpVertices() 
            { 
                const int amount = 1; 
                vertices = new VertexPositionColorTexture[amount * 3]; // TriangleList triangle at half size 
     
                vertices[0].Position = new Vector3(0, 0.5f, 0); 
                vertices[0].Color = Color.Red; 
                vertices[1].Position = new Vector3(0.5f, -0.5f, 0); 
                vertices[1].Color = Color.Green; 
                vertices[2].Position = new Vector3(-0.5f, -0.5f, 0); 
                vertices[2].Color = Color.Blue; 
     
     
                screenVertices = new VertexPositionColorTexture[4]; // TriangleStrip quad 
                 
                screenVertices[0].Position =          new Vector3 (1.0f, 1.0f, 0); // top right 
                screenVertices[0].TextureCoordinate = new Vector2(1, 0); 
                screenVertices[0].Color = Color.Magenta; 
     
                screenVertices[1].Position =          new Vector3 (1.0f, -1.0f, 0); // bottom right 
                screenVertices[1].TextureCoordinate = new Vector2(1, 1); 
                screenVertices[1].Color = Color.Cyan; 
                 
                screenVertices[2].Position =          new Vector3 (-1.0f, 1.0f, 0); // top left 
                screenVertices[2].TextureCoordinate = new Vector2(0, 0); 
                screenVertices[2].Color = Color.Yellow; 
     
                // second tess 
                screenVertices[3].Position =          new Vector3 (-1.0f, -1.0f, 0); // bottom left 
                screenVertices[3].TextureCoordinate = new Vector2(0, 1); 
                screenVertices[3].Color = Color.Gray; 
            } 
     
            protected override void Update(GameTime gameTime) 
            { 
                if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || 
                    Keyboard.GetState().IsKeyDown(Keys.Escape)) 
                    this.Exit(); 
     
                base.Update(gameTime); 
            } 
     
            protected override void Draw(GameTime gameTime) 
            { 
                // create looping offset for animation tests 
                Offset++; 
                if (Offset > 100) 
                { 
                    Offset = 0; 
                } 
                effect.Parameters["Offset"].SetValue(Offset); 
     
                // swap buffers, setting latest render to previous render. 
                SwapRenderTargets(); 
     
                // set default technique 
                effect.CurrentTechnique = effect.Techniques["DefaultTechnique"]; 
     
                // begin draw 
                DrawData(); 
     
                DrawRender(); 
                base.Draw(gameTime); 
            } 
     
            void DrawData() 
            { 
                effect.Parameters["frameTexture"].SetValue(previousRender); // pass previous render to textures 
     
                device.SetRenderTarget(latestRender); // set as working render 
                device.Clear(Color.Black); // clear "undefined" render target 
     
                effect.CurrentTechnique.Passes[1].Apply(); // Texture 
                DrawScreenPrimitive(); // draw previous frame as texture 
     
                effect.CurrentTechnique.Passes[0].Apply(); // Color 
                DrawInput(); // draw colorized triangle 
     
                device.SetRenderTarget(null); // undo RenderTarget 
                effect.Parameters["frameTexture"].SetValue(latestRender); 
            } 
     
            void SwapRenderTargets() 
            { 
                RenderTarget2D buffer = previousRender; 
                previousRender = latestRender; 
                latestRender = buffer; 
            } 
     
            void DrawScreenPrimitive() 
            { 
                device.DrawUserPrimitives(PrimitiveType.TriangleStrip, screenVertices, 0, 2, VertexPositionColorTexture.VertexDeclaration); 
            } 
     
            void DrawInput() 
            { 
                device.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length / 3, VertexPositionColorTexture.VertexDeclaration); 
            } 
     
            void DrawRender() 
            { 
                effect.Parameters["frameTexture"].SetValue(latestRender); // prepare current render as texture 
                effect.CurrentTechnique.Passes[1].Apply(); // Render data 
                device.Clear(Color.Purple); 
     
                DrawScreenPrimitive(); // draw 
            } 
        } 

    int ViewportWidth; 
    int ViewportHeight; 
    int Offset; 
     
    Texture frameTexture; 
     
    sampler TextureSampler = sampler_state { 
    texture = <frameTexture>; 
    magfilter = LINEAR; 
    minfilter = LINEAR; 
    AddressU = mirror; 
    AddressV = mirror; 
    }; 
     
    struct PositionColor 
        float4 Position : POSITION0; 
        float4 Color : COLOR; 
        // TODO: add input channels such as texture 
        // coordinates and vertex colors here. 
    }; 
     
    struct PositionTex 
        float4 Position : POSITION0; 
        float2 Tex : TEXCOORD0; 
    }; 
     
    struct PositionColorTex 
        float4 Position : POSITION0; 
        float4 Color : COLOR0; 
        float2 Tex : TEXCOORD0; 
    }; 
     
    PositionColorTex VertShader(PositionColorTex input) 
        return input; 
     
    float4 ColorShader(PositionColorTex input) : COLOR0 
        return input.Color; 
     
    float4 TextureShader(PositionColorTex input) : COLOR0 
        float4 Color = tex2D(TextureSampler, input.Tex.xy); 
        return Color; 
     
    technique DefaultTechnique 
        pass Color 
        { 
            VertexShader = compile vs_2_0 VertShader(); 
            PixelShader = compile ps_2_0 ColorShader(); 
        } 
     
        pass Texture 
        { 
            VertexShader = compile vs_2_0 VertShader(); 
            PixelShader = compile ps_2_0 TextureShader(); 
        } 
     

  • 3/6/2012 3:26 AM In reply to

    Re: [Solved] Preserved RenderTarget2D experiences "drift" or "spills paint" effect in pixel shader.

    I feel like a complete idiot, but at least I fixed the problem.  I re-read the Sampler Type and Effect States (Direct3D 9) and made this change to my texture sampler:
    Texture frameTexture; 
     
    sampler TextureSampler = sampler_state { 
    texture = <frameTexture>; 
    magfilter = POINT; // From LINEAR ***
    minfilter = POINT; // From LINEAR ***
    AddressU = mirror; 
    AddressV = mirror; 
    }; 
  • 3/6/2012 6:10 PM In reply to

    Re: [Solved] Preserved RenderTarget2D experiences "drift" or "spills paint" effect in pixel shader.

    In XNA, when sampling from a texture and wanting to get a particular texel of it, you need to offset the texture coordinate by half a texel so that you're sampling from the center of it.

    For instance, if you want to grab the top left pixel of from a 100 x 100 texture reliably, you actually need to sample from (0.005, 0.005). (If you were using SpriteBatch, it already does this adjustment for you).

    Using point-sampling (as you are now, and I think that's what you want anyway) will hide this problem much of the time, but it still may crop up. Essentially right now you are sampling from the edge between two texels. If you're lucky things will turn out right, but if you're unlucky then floating point precision issues will mean that sometimes you get the pixel on the left and sometimes the pixel on the right. When drawing a quad like you are, this most often shows up as one of the triangles being "shifted" slightly and a seam being visible between the two.
  • 3/7/2012 12:04 AM In reply to

    Re: [Solved] Preserved RenderTarget2D experiences "drift" or "spills paint" effect in pixel shader.

    Phil Fortier:
    In XNA, when sampling from a texture and wanting to get a particular texel of it, you need to offset the texture coordinate by half a texel so that you're sampling from the center of it.

    For instance, if you want to grab the top left pixel of from a 100 x 100 texture reliably, you actually need to sample from (0.005, 0.005). (If you were using SpriteBatch, it already does this adjustment for you).

    Using point-sampling (as you are now, and I think that's what you want anyway) will hide this problem much of the time, but it still may crop up. Essentially right now you are sampling from the edge between two texels. If you're lucky things will turn out right, but if you're unlucky then floating point precision issues will mean that sometimes you get the pixel on the left and sometimes the pixel on the right. When drawing a quad like you are, this most often shows up as one of the triangles being "shifted" slightly and a seam being visible between the two.

    Let me put this plainly, it does not show up on my PC, but destroys the Xbox render.  I've spent hours working on this, until I came across this MSDN article on texels and pixels.  I solved it C# side by using:
    for (int i = 0; i < screenVertices.Length; i++) 
                { 
                    screenVertices[i].TextureCoordinate.X += 0.00001f; 
                    screenVertices[i].TextureCoordinate.Y += 0.00001f; 
                } 

    However that solution doesn't allow the use of LINEAR (which I don't need but would like a complete solution).
    Thus I tried offsetting it in my vertex shader, AND setting LINEAR filter sampling.  After a little work this is the solution I came up with.
    sampler TextureSampler = sampler_state { 
    texture = <frameTexture>; 
    magfilter = LINEAR; 
    minfilter = LINEAR; 
    AddressU = mirror; 
    AddressV = mirror; 
    }; 
     
    // ... 
     
    PositionColorTex VertShader(PositionColorTex input) 
        input.Tex.x += (1 / ViewportWidth) / 2; 
        input.Tex.y += (1 / ViewportHeight) / 2; 
        return input; 

    Thanks Phil! I just wish I had read your post before going crazy.
  • 3/7/2012 4:53 PM In reply to

    Re: [Solved] Preserved RenderTarget2D experiences "drift" or "spills paint" effect in pixel shader.

    Or you could use SpriteBatch, which handles this for you...
  • 3/7/2012 9:45 PM In reply to

    Re: [Solved] Preserved RenderTarget2D experiences "drift" or "spills paint" effect in pixel shader.

    Shawn Hargreaves:
    Or you could use SpriteBatch, which handles this for you...

    This is an interesting idea... I found your blog on the subject and am now contemplating this.  The biggest advantage is remove the 1080p to 1.0f conversion in my C# code.  Although because I still have to do unit conversions in my pixel shader, I'm kinda apprehensive to using SpriteBatch as I have all my conversion functions built and working.
Page 1 of 1 (6 posts) Previous Discussion Next Discussion