1
\$\begingroup\$

I'm beginning with DirectX development and I'm quite confused by the documentation about how to do the following:

I have 1 image (Texture2D), I'd like to apply 2 independent HLSL, one after the other, and render it.

For instance, one shader makes the texture semi-transparent, the other turns it to black and white. Also, I'm computing everything off-screen, so I don't have a SwapChain.

So far the output texture I get has the 1st shader applied but not the second. If I switch the order where the shaders are applied, then I'm seeing the 1st shader applied (in the new order) but not the second. In other words, both shaders work separately but not together. I'm using SharpDX and C#.

Here is what I made

Setup

  1. Create D3D11 device.
  2. Set Blend state
            BlendStateDescription blendStateDescription = new BlendStateDescription
            {
                AlphaToCoverageEnable = false,
                IndependentBlendEnable = false
            };
            blendStateDescription.RenderTarget[0].IsBlendEnabled = true;
            blendStateDescription.RenderTarget[0].SourceBlend = BlendOption.SourceAlpha;
            blendStateDescription.RenderTarget[0].DestinationBlend = BlendOption.InverseSourceAlpha;
            blendStateDescription.RenderTarget[0].SourceAlphaBlend = BlendOption.One;
            blendStateDescription.RenderTarget[0].DestinationAlphaBlend = BlendOption.One;
            blendStateDescription.RenderTarget[0].BlendOperation = BlendOperation.Add;
            blendStateDescription.RenderTarget[0].AlphaBlendOperation = BlendOperation.Add;
            blendStateDescription.RenderTarget[0].RenderTargetWriteMask = ColorWriteMaskFlags.All;

            device.ImmediateContext.OutputMerger.SetBlendState(new BlendState(device, blendStateDescription));
  1. Set Depth stencil state
            var depthStencilState = new DepthStencilState(device, DepthStencilStateDescription.Default());
            device.ImmediateContext.OutputMerger.SetDepthStencilState(depthStencilState);
  1. Create a RenderTargetView and a texture
  2. Create DepthStencilView and a texture
  3. Set the RenderTargetView and DepthStencilView are targets of the output merger.

Run

  1. Load an image from the hard drive to a Texture2D
  2. Create a ShaderResourceView from this texture.
  3. For each effects I want to apply:
    • Load the shader from the bytecode into an Effect.
    • Create a vertice, then create a buffer from it.
    • Set this buffer as a vertex buffer in the Input Assembler: deviceContext.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(myBuffer, Marshal.SizeOf(typeof(VertexPositionTexture)), 0));
    • Set the input layout.
    • Get a technique from the shader and for each pass, I effectPass.Apply then do a deviceContext.Draw.
  4. deviceContext.Flush();
  5. Save the texture associated to the RenderTargetView as a PNG.

Question

Is my approach correct or am I doing something wrong in that method?

I wonder if the issue could be in the blend state or depth stencil. Let me know if you'd like me to share more code to see my implementation details.

Thank you in advance for the help. :)

[UPDATE 3/8/21]

Here is a simplified version of my code for insights:

var device = new Device(
                    DriverType.Hardware,
                    DeviceCreationFlags.BgraSupport | DeviceCreationFlags.VideoSupport,
                    FeatureLevel.Level_11_0);

            BlendStateDescription blendStateDescription = new BlendStateDescription
            {
                AlphaToCoverageEnable = false,
            };
            blendStateDescription.RenderTarget[0].IsBlendEnabled = true;
            blendStateDescription.RenderTarget[0].SourceBlend = BlendOption.SourceAlpha;
            blendStateDescription.RenderTarget[0].DestinationBlend = BlendOption.InverseSourceAlpha;
            blendStateDescription.RenderTarget[0].BlendOperation = BlendOperation.Add;
            blendStateDescription.RenderTarget[0].SourceAlphaBlend = BlendOption.Zero;
            blendStateDescription.RenderTarget[0].DestinationAlphaBlend = BlendOption.Zero;
            blendStateDescription.RenderTarget[0].AlphaBlendOperation = BlendOperation.Add;
            blendStateDescription.RenderTarget[0].RenderTargetWriteMask = ColorWriteMaskFlags.All;

            device.ImmediateContext.OutputMerger.SetBlendState(new BlendState(device, blendStateDescription));


            DepthStencilStateDescription description = DepthStencilStateDescription.Default();
            description.DepthComparison = Comparison.LessEqual;
            description.IsDepthEnabled = true;

            device.ImmediateContext.OutputMerger.SetDepthStencilState(new DepthStencilState(device, description));

// [...]

            Texture2DDescription depthDesc = new Texture2DDescription()
            {
                Width = _outputTextureSize.Width,
                Height = _outputTextureSize.Height,
                Format = Format.D24_UNorm_S8_UInt,
                OptionFlags = ResourceOptionFlags.None,
                ArraySize = 1,
                MipLevels = 1,
                SampleDescription =
                {
                    Count = 1,
                    Quality = 0
                },
                BindFlags = BindFlags.DepthStencil,
                CpuAccessFlags = CpuAccessFlags.None,
                Usage = ResourceUsage.Default
            };

            DepthStencilViewDescription depthViewDesc = new DepthStencilViewDescription()
            {
                Dimension = DepthStencilViewDimension.Texture2D,
                Format = Format.D24_UNorm_S8_UInt,
            };

            _depthStencilTexture = new Texture2D(device, depthDesc);
            _depthStencilView = new DepthStencilView(device, _depthStencilTexture, depthViewDesc);
            device.ImmediateContext.OutputMerger.SetDepthStencilState(
                new DepthStencilState(device,
                DepthStencilStateDescription.Default()));


// RENDERING

            context.ClearRenderTargetView(_renderTargetView, Color.CornflowerBlue);
            context.ClearDepthStencilView(_depthStencilView, DepthStencilClearFlags.Depth, 1f, 0);

            using ShaderResourceView shaderResourceView = new ShaderResourceView(device, inputTexture2D)

foreach (var effect in effectList)
{
    using ShaderBytecode shaderByteCode = ShaderBytecode.FromFile(effect.FilePath);
    var shaderEffect = new Effect(device, shaderByteCode.Data);

    using var verticesRectangle = new DataStream(
                Marshal.SizeOf(typeof(VertexPositionTexture)) * 4,
                true,
                true);

    // verticesRectangle.Write ...
    verticesRectangle.Position = 0;
    var buffer
                = new Buffer(
                    device,
                    verticesRectangle,
                    new BufferDescription
                    {
                        BindFlags = BindFlags.VertexBuffer,
                        CpuAccessFlags = CpuAccessFlags.None,
                        OptionFlags = ResourceOptionFlags.None,
                        SizeInBytes = Marshal.SizeOf(typeof(VertexPositionTexture)) * 4,
                        Usage = ResourceUsage.Default,
                        StructureByteStride = 0
                    });

    device.ImmediateContext.InputAssembler.InputLayout
                    = new InputLayout(
                        device,
                        shaderEffect.GetTechniqueByIndex(0).GetPassByIndex(0).Description.Signature,
                        elements: VertexPositionTexture.InputElements);

     device.ImmediateContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleStrip;
     device.ImmediateContext.InputAssembler.SetVertexBuffers(
                    0,
                    new VertexBufferBinding(
                        buffer,
                        Marshal.SizeOf(typeof(VertexPositionTexture)),
                        0));

    for (var i = 0; i < shaderEffect.GetTechniqueByIndex(0).Description.PassCount; ++i)
    {
                    using EffectPass effectPass = shaderEffect.GetTechniqueByIndex(0).GetPassByIndex(i);

                    if (effectPass.IsValid)
                    {
                        effectPass.Apply(deviceContext);
                    }

                    device.ImmediateContext.Draw(4, 0);
                }

                device.ImmediateContext.InputAssembler.InputLayout.Dispose();
                device.ImmediateContext.InputAssembler.InputLayout = null;
}

device.ImmediateContext.Flush();

My shader effects:

///
/// Shader effect that changes the opacity of a texture.
///

Texture2D Texture : register(t0);
SamplerState TextureSampler : register(s0);

float TargetOpacity;

struct ShaderParameters
{
    float4 Position : SV_Position; // vertex position input
    float2 TextureUV : TEXCOORD0; // first texture coordinate input
};

// Vertex Shader
ShaderParameters VS(ShaderParameters input)
{
    ShaderParameters output = (ShaderParameters) 0;

    output.Position = input.Position;
    output.TextureUV = input.TextureUV;

    return output; // Returns the position without any change to it.
}

// Pixel Shader
float4 PS(ShaderParameters input) : SV_Target
{
    float4 rgbaColor = Texture.Sample(TextureSampler, input.TextureUV);

    return float4(rgbaColor.rgb, rgbaColor.a * TargetOpacity);
}

technique10 OpacityTechnique
{
    pass P0
    {
        SetGeometryShader(0);
        SetVertexShader(CompileShader(vs_5_0, VS()));
        SetPixelShader(CompileShader(ps_5_0, PS()));
    }
}

And

///
/// Shader effect that converts a colorful texture to monochrome (gray).
///

Texture2D Texture : register(t0);
SamplerState TextureSampler : register(s0);

struct ShaderParameters
{
    float4 Position : SV_Position; // vertex position input
    float2 TextureUV : TEXCOORD0; // first texture coordinate input
};

// Vertex Shader
ShaderParameters VS(ShaderParameters input)
{
    ShaderParameters output = (ShaderParameters) 0;

    output.Position = input.Position;
    output.TextureUV = input.TextureUV;

    return output; // Returns the position without any change to it.
}

// Pixel Shader
float4 PS(ShaderParameters input) : SV_Target
{
    float4 rgbaColor = Texture.Sample(TextureSampler, input.TextureUV);
    float gray = (rgbaColor.r + rgbaColor.g + rgbaColor.b) / 3;
    
    return float4(gray, gray, gray, rgbaColor.a);
}

technique10 GrayscaleTechnique
{
    pass P0
    {
        SetGeometryShader(0);
        SetVertexShader(CompileShader(vs_5_0, VS()));
        SetPixelShader(CompileShader(ps_5_0, PS()));
    }
}

Thanks :)

\$\endgroup\$
13
  • \$\begingroup\$ Did you clear your depth buffer between passes, so the depth information from the previous pass doesn't prevent future pixels from drawing there? \$\endgroup\$ – DMGregory Mar 8 at 16:10
  • \$\begingroup\$ Thanks for your reply. No I didn't clear the depth buffer between passes. I just gave it a try by doing deviceContext.ClearDepthStencilView(_depthStencilView, DepthStencilClearFlags.Depth, 1f, 0);. I placed this list the deviceContext.Draw(verticeCount, 0). Now I'm seeing the opposite issue. The second effect is applied to the output texture but not the first one. I'd assume the first one got overwrote by the second one. I'm not sure to understand why. \$\endgroup\$ – Etienne Baudoux Mar 8 at 16:44
  • \$\begingroup\$ Are you taking the output colour buffer from your first pass and providing it as the input texture for your second pass, rendering to a fresh output colour buffer? If not, can you explain in more detail how you're passing information between these stages? \$\endgroup\$ – DMGregory Mar 8 at 16:47
  • \$\begingroup\$ I don't think I explicitly pass the texture again. I'm very beginner at it so it's very possible I'm missing a concept here. I update my original post with some code. \$\endgroup\$ – Etienne Baudoux Mar 8 at 17:01
  • \$\begingroup\$ Your shaders can only act on what they're given as input. If you want the second pass to modify the output of the first pass, it needs to receive the first pass's output as input. If the input you give it is the original texture, the output you'll get is the result of applying only that one effect to the original texture. \$\endgroup\$ – DMGregory Mar 8 at 17:03
0
\$\begingroup\$

I finally fixed the issue!

There were 3 problems to my initial code.

  1. The first effect I was applying was adding transparency to the image. My BlendState was configured in a way that the output texture would be completely transparent. Therefore, using a completely transparent texture for the next effect would just return something transparent too. I fixed the issue by making a blend state specific to each effect to apply.

  2. I wasn't clearing the DepthStencilView and RenderTargetView between each effect. I thought I had to do it between each frame, not each effect.

  3. As @DMGregory mentioned (and thanks to you :) ), I needed to make sure to reuse the output texture of the first shader as input for the next one.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.