Sign in to follow this  
SaraJ

How to retrieve pass or fail info from HLSL shader

Recommended Posts

Hi, I hope this is the right place for posting a question on HLSL (using DirectX/Direct3D and C#). I am doing some image processing using HLSL and at one point I do "test" in a pixel shader and want to return information to my C# code if the test passed or not. The test being to subtract one texture from another, and passing the test means that all pixels in texture 2 is larger or equal to corresponding pixels in texture1. That is, if texture1-texture2>0, the test failed. Reading texture values from the textures and subtracting them is no problem, but I want to know if anyone knows of an efficient way of returning if the test passed or not to the c#-code. Of course I can return the subtracted value for each pixel (rendering to a texture), and then read all texture values in the c# code using SurfaceLoader.FromSurface to copy the rendersurface and then locking it. However, the texture copying is far to slow for this being an realistic option (the test might be run hundreds of times). So, does anyone know another way of doing it? The optimum thing would be to send some sort of "break" message to the c# code as soon as the subtracted value is above zero, telling the test has failed, but I don't know how to do this. I know there is a discard option in hlsl, but I don't know of any way of sending info to the c# code that the shader has been discarded. This is what my shader do so far (I use shader version 3.0):
PixelToFrame FirstPixelShader(VertexToPixel PSIn) 
{ 
    PixelToFrame Output = (PixelToFrame)0; 
 
    float tex1 = tex2D(TextureSampler1, PSIn.TexCoords).r; 
    float tex2 = tex2D(TextureSampler2, PSIn.TexCoords).r; 
        
    if((tex1-tex2)>0) 
    { 
        Output.Color.r=0; 
    } 
    else 
    { 
        Output.Color.r=1; 
    } 
    return Output; 
} 


Any help is very appreciated, and from experience I know people here are usually very helpful. Thanks!

Share this post


Link to post
Share on other sites
I don't see the reason why you should do this test in pixel shader and then read values in render target. If both textures have the same dimensions, you might just lock their surfaces and do this test in one loop directly in C# code.
But if you want to use pixel shader, I would create some global variable in effect file and set it to true before testing and if tex1 < tex2 then set it to false. And then you would just read it back in your C# code, using for ex. GetParameterByName and GetBool (if it's that, I do D3D in C++, but those function should be there too).

Share this post


Link to post
Share on other sites
I have an extremely limited knowledge of HLSL, so this might be a long shot.

Could you not declare a global in your shader, and then set it where you do your test;


uniform extern bool g_Test;
//...
if((tex1-tex2)>0)
{
Output.Color.r=0;
g_Test = true;
}
else
{
Output.Color.r=1;
g_Test = false;
}

Then in code obtain a handle to the global, and use that to test what it is and act accordingly?

LPD3DXEFFECT fx;
D3DXHANDLE handle = fx->GetParameterByName(0, "g_Test");
bool test;
if( fx->GetBool( handle, &test ) )
{
//Do Something
}


Its a wild stab in the dark but it might help.

Share this post


Link to post
Share on other sites
Quote:
Original post by 0rcus
I don't see the reason why you should do this test in pixel shader and then read values in render target. If both textures have the same dimensions, you might just lock their surfaces and do this test in one loop directly in C# code.
But if you want to use pixel shader, I would create some global variable in effect file and set it to true before testing and if tex1 < tex2 then set it to false. And then you would just read it back in your C# code, using for ex. GetParameterByName and GetBool (if it's that, I do D3D in C++, but those function should be there too).


Thanks for the tip! There is a GetParameter function in c#, so I will look into that. I will test doing it on the CPU as well, but the textures to be subtracted are render target textures (hence, need to be copied to another texture prior to reading/subtacting values) and quite large, so this will probably be much to slow.

Share this post


Link to post
Share on other sites
GetParameter() isn't going to work - it will only return the value you set yourself via SetParameter().

Here's some options that should work:

1. As mentioned before do the check on the CPU side. Unless you're comparing lots of huge textures it should be quicker and it's much easier to do.

2. After subtracting the textures, downsize the result by summing the pixel values. Once you get to a 1x1 texture, lock that on the CPU. If that pixel is black then they were equal.

3. Use a hardware occlusion query. To use that you'll need the pixel shader to output a different z value depending on the result of your comparison.

Share this post


Link to post
Share on other sites
Quote:
Original post by Adam_42
GetParameter() isn't going to work - it will only return the value you set yourself via SetParameter().


Thanks for that info, I was just trying to use that one but could (obviously) not get it to work properly. You saved me a lot of time :)

Quote:
Original post by Adam_42
1. As mentioned before do the check on the CPU side. Unless you're comparing lots of huge textures it should be quicker and it's much easier to do.


This is of course the most straight forward method. However, as I mentioned previousley, both textures are render targets (and have to be that) and as far as I have understood you can't read any values from a render target texture (at least not using DirectX+C#). So the only way I can see to compare the two textures on the CPU would be to copy them to new (non render target) surfaces, lock them and then do the comparison. I would need to do this about some hundred or actually probably some thousand times, and the copying is very slow... Do you know of any other way it would be possible to read the texture values?

Thanks!

Share this post


Link to post
Share on other sites
Quote:
Original post by Adam_42
GetParameter() isn't going to work - it will only return the value you set yourself via SetParameter().


I am not sure how about C#, but GetParameterXXX functions in C++ return handle of effect parameter which is used to set/get value of that parameter. And if you change this value in shader, it will persist changed, so you can set it in shader and then read it in C#. For ex. in C++ I would do:

D3DXHANDLE hPassed = pEffect->GetParameterByName(NULL, TEXT("bPassed"));
pEffect->SetBool(hPassed, TRUE);
//execute shader here
BOOL bPassed;
pEffect->GetBool(hPassed, &bPassed);
if (bPassed)
{
//do something if test passed
}

Assuming bPassed is global boolean variable declared in effect (extern and uniform storage classes are default for global variables)

Share this post


Link to post
Share on other sites
Quote:
Original post by 0rcus
I am not sure how about C#, but GetParameterXXX functions in C++ return handle of effect parameter which is used to set/get value of that parameter. And if you change this value in shader, it will persist changed

No, it won't. These are shader constants. The shader can't change them. Suppose it could, which of the shader runs (the shader would run for each pixel) would store the value there? See, it doesn't make sense.

Adam_42 suggested two very reasonable options (2 and 3) for doing this on the GPU without reading everything back.

Share this post


Link to post
Share on other sites
Quote:
Original post by Adam_42
2. After subtracting the textures, downsize the result by summing the pixel values. Once you get to a 1x1 texture, lock that on the CPU. If that pixel is black then they were equal.

3. Use a hardware occlusion query. To use that you'll need the pixel shader to output a different z value depending on the result of your comparison.


Seems like I have to look into the above suggestions. Did some tests on doing the comparison on the CPU (copying rendertarget textures), and it was way to slow. It is to be used in an interactive tool, and the comparison step took several minutes, which is not an acceptable time.

Share this post


Link to post
Share on other sites
Thanks everyone for the tip on using occlusion queries, it seems to solve my problem perfectly!

If someone else have a similar problem, this is what I do:

- create an occlusion query and use it while running the shader
- in the shader, only render pixel if its value in texture2 is higher than or equal to value in texture1
- when the shader has finished, get the number of pixels rendered, and hence number of pixels passing the test

Since it was a bit hard finding c# examples for this, here is my code for using a query:


Query _query = new Query(_device, QueryType.Occlusion);

//start query
_query.Issue(IssueFlags.Begin);

//Begin using effect
_effect.Begin(0);

_effect.BeginPass(0);
_device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2);
_effect.EndPass();
_effect.End();

//end query
_query.Issue(IssueFlags.End);

bool dataReturned = false;
int numPixels=-1;

while (!dataReturned)
{
numPixels = (int)_query.GetData(typeof(int), true, out dataReturned);

}

if ((_textureWidth*_textureHeight - numPixels) == 0)
{
//All pixels has higher value in texture2 than in texture1
}





And the pixel shader code looks something like this:


PixelToFrame FirstPixelShader(VertexToPixel PSIn)
{
PixelToFrame Output = (PixelToFrame)0;

float tex1 = tex2D(Texture1Sampler, PSIn.TexCoords).b;
float tex2 = tex2D(Texture2Sampler, PSIn.TexCoords).b;

clip(tex2 - tex1);

return Output;
}


Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this