Need help with rendertargets, multipass, HLSL

Started by
7 comments, last by MJP 16 years, 1 month ago
Could somebody please explain to me in simple terms or point me to an explanation of how rendertargets, buffers, textures and those ": COLOR0" semantics all fit together, and what exactly they are (I know what textures are)? Despite spending much time searching, reading and tinkering I haven't achieved that eureka moment where everything comes together and makes sense. Also the relevant methods and such to use in XNA and how they manipulate those buffers and things would help. Perhaps an explanation of what I'm trying to do would help, but I don't want to scare people away with a long post so I will provide a longer story later if needed.
Advertisement
Hey Lomacar

I'll try to give you a brief walkthrough on how stuff are connected.

A texture is a piece of data which contains say a brickwall etc. They can exist on disc or in memory. A texture is used as input when rendering triangles - Either via the fixed-function pipeline or by a shaderbased texture lookup. Texture lookup can only be done in pixelshader (fragmentshaders). They can also be done by vertexshaders on hardware supporting Shadermodel 3.0 and up.

A rendertarget / buffer in this context is very much the same thing I would say. A buffer is normally a piece of memory in which in write data of some sort. A rendertarget is also just a piece of memory configured to holding data of a specific sort - A buffer so to speak. When rendering triangles you always render to some rendertarget (correct me if I'm wrong) - Normally this rendertarget is the backbuffer (which btw is also a rendertarget). If you want to, say, render an amount of particle sprites into an offscreen texture - You would configure a rendertarget to eg. contain RGBA data (there's a bunch of different formats). Then before rendering the particles you would set the particle rendertarget as your main target instead of you normal backbuffer. Then render the partices normally. When done you would resolve the particle rendertarget into a texture and set the normal backbuffer back as main rendertarget. The same holds true for the stencil / depth buffer. This is a very rough walkthrough so it might be wise to lookup more info about these topics.

The COLOR0 semantics are very closely connected to the rendertarget issue. Just a the TEXCORD semantics is able to index different texture pipeline so are the COLOR semantics able to index different color buffers. Say you configure your device to have two active rendertargets (yes you can up to four active - also refered to as MRT (Multiple RenderTargets)) the COLOR0 output semantic would output to index 0 and the COLOR1 would output to index 1 and so on.

Hopefully this cleared it up a bit. Oh and please correct me if I'm wrong about anything :). Best of luck.
Thanks for taking the time to explain that stuff to me. I actually managed to get my program working and I was already on the right track, mostly just stupid mistakes.

But I could still use some clarification. Are the COLOR0, COLOR1 etc semantics only used when you want to simultaneously produce two outputs? Because if I set a shader function to just use COLOR1 it gives an error about needing contiguous outputs up to COLORn.

Also, in my situation I don't just need to use multiple rendertargets for multiple passes, I also need to save the output for reprocessing on the next frame (I'm making a cellular automata). Is it really necessary to read the rendertargets and save them to textures and then re-input them to the texture samplers? It seems like if it is already on the card you should be able to just refer to the place that you just wrote the results to from the last frame. So it would be nice to have two textures/targets/buffers on the card and alternately read from one and then draw to the other and then reverse the process on the next frame.
Quote:Original post by Lomacar

But I could still use some clarification. Are the COLOR0, COLOR1 etc semantics only used when you want to simultaneously produce two outputs? Because if I set a shader function to just use COLOR1 it gives an error about needing contiguous outputs up to COLORn.



Yes sir, those are only for multiple render targets.

Quote:Original post by Lomacar
Also, in my situation I don't just need to use multiple rendertargets for multiple passes, I also need to save the output for reprocessing on the next frame (I'm making a cellular automata). Is it really necessary to read the rendertargets and save them to textures and then re-input them to the texture samplers? It seems like if it is already on the card you should be able to just refer to the place that you just wrote the results to from the last frame. So it would be nice to have two textures/targets/buffers on the card and alternately read from one and then draw to the other and then reverse the process on the next frame.


You can absolutely do what you're talking about. It's a common technique, usually known as "ping-ponging". You don't have to copy anything once you've rendered to a texture, that texture contains the data you rendered to the surface and is immediately available for use as an input to another shader. *The only exception to this case is when you want to render to a multi-sampled render target. In this case, you can't create a multi-sampled texture so what you have to do is create a multi-sampled render target surface and when you're done resolve the sub-samples so that you have a normal texture.

*NOTE: This applies to regular D3D9. I don't really know much about XNA, but from what I gather it's just D3D9 under the hood so probably the same applies.
Hmm, OK. So I don't know if I am already doing that ping-ponging you speak of. Here is the code:
Quote:
if (generation==0)
{
device.Textures[0] = startingTex;
effect.CurrentTechnique = effect.Techniques["PassThrough"];
}
else
{

device.Textures[0] = renderTarget[0].GetTexture();
if (generation > 1)
{
device.Textures[1] = renderTarget[1].GetTexture();
}

effect.CurrentTechnique = effect.Techniques["ApplyRules"];
}

effect.Begin();
int targ_index = 0;
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
device.SetRenderTarget(0, renderTarget[targ_index]);
pass.Begin();
device.VertexDeclaration = declaration;
device.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleStrip, verts, 0, 2);
pass.End();

targ_index++;
}
device.SetRenderTarget(0, null);
effect.End();

effect.CurrentTechnique = effect.Techniques["Composite"];
effect.Begin();
effect.CurrentTechnique.Passes[0].Begin();
device.VertexDeclaration = declaration;
device.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleStrip, verts, 0, 2);
effect.CurrentTechnique.Passes[0].End();
effect.End();


Is that the best way to do it? Do all the calculations stay on the video card or are the textures being shuffled out to system memory and back?

Also, I find it very strange that if I leave out the if statement around this part
Quote:
if (generation > 1)
{
device.Textures[1] = renderTarget[1].GetTexture();
}

It gives an exception that "The render target must not be set on the device when calling GetTexture." I realize it should cause in error because on frame 0 there is only one pass which simply displays the starting texture so renderTarget[1] hasn't been used yet on frame 1. So the error I would expect is something like "The render target MUST be set on the device when calling GetTexture", the opposite of what it says.
Well I can't tell you anything for sure (because like I said, I don't know XNA) but I can tell you what happens at a lower level. In D3D9, any texture you create is put into one of the resource pools made available. Any texture you create as a render target must be placed into D3DPOOL_DEFAULT, which generally translates to that texture being placed into GPU memory and never taken out. This means that as long as you only use those textures declared as render targets, everything would stay on the GPU. I would assume that the XNA framework just creates two textures in D3DPOOL_DEFAULT when you create those RenderTarget's, since doing anything else would make absolutely zero sense. However I can't guarantee that, which is why I'm giving this whole spiel in the first place. But everything definitely looks okay based on what I can interpret from your code.

As for your exception, it sounds to me like you're trying to apply a texture as an input that's also bound as a render target (which you can't do, for obvious reasons). From your code it looks like this will happen, since you bind both textures as inputs and then proceed to render to both textures as render targets in your for loop. For ping-ponging to work, you need to have only one texture set as an input and one as an output. If you need more than one input, you will need to add a third texture that contains the results from using two inputs. Also you may need to set a device's input texture back to NULL if you're not replacing it with another texture.

Oh and btw...if you need to post code you can use either the "code" tags (for short bits of code) or the "source" tags (for longer bits of code with syntax highlighting).
Quote:Original post by MJP
Oh and btw...if you need to post code you can use either the "code" tags (for short bits of code) or the "source" tags (for longer bits of code with syntax highlighting).


Oh thanks, I tried "code" but it didn't do anything. Why doesn't this forum have buttons to show what you can do?

So, is it any better to say
device.Textures[0] = renderTarget[0].GetTexture()device.SetRenderTarget(0, renderTarget[0]);

than to alternate between
device.Textures[0] = renderTarget[1].GetTexture()device.SetRenderTarget(0, renderTarget[0]);//anddevice.Textures[1] = renderTarget[0].GetTexture()device.SetRenderTarget(0, renderTarget[1]);

Is that ping-ponging? If so it doesn't seem any more efficient.
Your first bit has the same problem I just mentioned, you're setting a texture to be both an output and an input at the same time. If you've bound something as a texture for reading, you can't set it as a render target immediately after because you'd by reading and writing to the same surface.

You're second bit is fine, although if you don't need two textures in your second pass you should always bind to device.Textures[0]. Otherwise renderTarget[1] will still be bound to device.Textures[0] as an input, and you would have to set device.Textures[0] to NULL before setting it as a render target.

Quote:Original post by Lomacar
If so it doesn't seem any more efficient.


What exactly do you mean by that? Do you mean it doesn't seem more efficient when you look at the code, or that it doesn't seem to run any better when you use your app?
I meant it doesn't seem more efficient when I look at the code. But either I'm not getting how this all works still or you are misinterpreting the XNA stuff.

You are saying my first code example won't work, but if you look at the bunch of code I posted earlier which is from my app, which works, it is essentially doing the same thing. So...what's going on?

So you are saying I can do the following in my draw loop and it will be ping-ponging?
target = generation % 2;//as generation counts up each frame target alternates between 0 and 1device.Textures[0] = renderTarget[target].GetTexture();device.SetRenderTarget(0, renderTarget[-1*(target-1))];//the opposite of targetgeneration++
Okay I read some of the XNA documentation and it confirms what I was saying: you can't have a texture simultaneously bound as both input and output to a shader. The documentation for the Textures property of GraphicsDevice says it in plain language:

Quote:
At draw time, a texture cannot be simultaneously set as a render target and a texture at a stage.


So in other words, you can't do this:

device.Textures[0] = renderTarget.GetTexture()device.SetRenderTarget(0, renderTarget);// Render whatever


When you do this you're essentially saying "I want to sample from this texture, but at the same time I want to write to it." In low-level D3D9 this actually produced undefined behavior, the runtime will spit out a warning message in debug mode but won't actually stop you or anything. At that point, the driver may or may not do unpredictable things. I don't know what the XNA Framework does in this case, from what you've said it sounds like it will throw an exception but it may not do that every time (maybe it checks the Effect to see if it actually samples from that texture, I have no idea). Either way the documentation makes it pretty clear that it's not something you should do.

And that last bit you posted looks completely fine. Sorry if I wasn't clear while explaining all of this, however it looks like you're definitely on the right track.

This topic is closed to new replies.

Advertisement