dev.nlited.com

>>

Constants

<<<< prev
next >>>>

2022-12-15 18:58:15 chip Page 2466 📢 PUBLIC

December 15 2022

Next, I want to update the shader from the program by adjusting the tint color. This is done by defining a constant buffer in the shader and then transferring a copy from the CPU to the GPU inside the PrepareForRender() function.

The use of the word "constants" here is misleading -- the values are constant only from the perspective of the shader code. From the CPU perspective, the whole point of the constants buffer is to dynamically modify the behavior of the shader at runtime.

Add a CONSTANTS buffer to the Shader

My constant buffer will contain a single value, a COLOR that will be added to each pixel. Add this to the shader hlsl code:


Shader tint: #define D2D_INPUT_COUNT 1 // The pixel shader takes 1 input texture. #define D2D_INPUT0_COMPLEX // The first input is sampled in a complex manner: to calculate the output of a pixel, // the shader samples more than just the corresponding input coordinate. #define D2D_REQUIRES_SCENE_POSITION // The pixel shader requires the SCENE_POSITION input. // Note that the custom build step must provide the correct path to find d2d1effecthelpers.hlsli when calling fxc.exe. #include "d2d1effecthelpers.hlsli" cbuffer constants: register(b0) { float4 tint:COLOR; }; D2D_PS_ENTRY(main) { float2 toPixel = D2DGetScenePosition().xy; // Scale distance to account for screen DPI, and such that the ripple's displacement // decays to 0 at a hardcoded limit of 500 DIPs. float distance = length(toPixel / 500.0f); float2 direction = normalize(toPixel); // Resample the image based on the new coordinates. float4 color = D2DSampleInputAtOffset(0, direction); color+= tint; return color; } //EOF: FIRSTSHADER.HLSL

Constants cannot be initialized or updated in the hlsl, they must be updated from the CPU? What does "register(b0)" mean? What is register b0?

Microsoft: HLSL uses virtual registers that the code can use to provide optimization hints to the compiler. These registers are broken into four functional spaces:

Add the Constants to the Effect

I need to keep a mirror image of the constants buffer in the effect on the cpu. Add this to MyEffect:

Effect Constants: class MyEffect: public ID2D1EffectImpl, public ID2D1DrawTransform { public: IFACEMETHODIMP_(ULONG) AddRef(void) { return(++ctReference); }; IFACEMETHODIMP_(ULONG) Release(void); IFACEMETHODIMP QueryInterface(REFIID riid, void **ppInterface); static HRESULT Register(_In_ ID2D1Factory1 *pFactory); static HRESULT __stdcall CreateMyEffect(_Outptr_ IUnknown **ppEffect); IFACEMETHODIMP Initialize(_In_ ID2D1EffectContext *pCtx, _In_ ID2D1TransformGraph *pGraph); IFACEMETHODIMP PrepareForRender(D2D1_CHANGE_TYPE Type); IFACEMETHODIMP SetGraph(ID2D1TransformGraph *pGraph); IFACEMETHODIMP SetDrawInfo(ID2D1DrawInfo *pDraw); IFACEMETHODIMP MapOutputRectToInputRects(const D2D1_RECT_L *prOut, D2D1_RECT_L *prIn, UINT32 ctIn) const; IFACEMETHODIMP MapInputRectsToOutputRect(const D2D1_RECT_L *prIn, const D2D1_RECT_L *prInOpaque, UINT32 ctIn, D2D1_RECT_L *prOut, D2D1_RECT_L *prOutOpaque); IFACEMETHODIMP MapInvalidRect(UINT32 nIn, D2D1_RECT_L rInInvalid, D2D1_RECT_L *prOut) const; IFACEMETHODIMP_(UINT32) GetInputCount(void) const; private: MyEffect(void); ~MyEffect(void); // Data LONG ctReference; ID2D1EffectContext *pCtx; ID2D1DrawInfo *pDraw; D2D1_RECT_L rIn; struct ShaderConstant_s { float tint[4]; } Constants; };

Initialize the tint with 50% blue.

MyEffect::MyEffect(): MyEffect::MyEffect(void) { ctReference= 1; rIn= { 0,0,0,0 }; Zero(Constants); Constants.tint[2]= 0.50f; }

Finally, update the constants in PrepareForRender():

PrepareForRender(): HRESULT MyEffect::PrepareForRender(D2D1_CHANGE_TYPE Type) { HRESULT WinErr= S_OK; pDraw->SetPixelShaderConstantBuffer((BYTE*)&Constants,sizeof(Constants)); return(WinErr); }

I should now see a blue tint on the bouncing ball.

PxlShader

Changing the Constants

Now I need to update the tint color from the CPU. Since DirectX is managing the effect, I need to go through the ID2D1Effect interface. This adds some extra steps:

  1. Create GetTint() and SetTint() functions.
  2. Add the functions to the Bindings in RegisterEffectFromString()
  3. Update the Effect XML to describe the Tint property.

The GetTint() and SetTint() functions are straight-forward, except that I want to manage the tint color as a UINT32 on the CPU and as a float4 on the GPU. So I will need to convert between UINT32 and float4.

Get/Set: class MyEffect: public ID2D1EffectImpl, public ID2D1DrawTransform { public: IFACEMETHODIMP_(ULONG) AddRef(void) { return(++ctReference); }; IFACEMETHODIMP_(ULONG) Release(void); IFACEMETHODIMP QueryInterface(REFIID riid, void **ppInterface); static HRESULT Register(_In_ ID2D1Factory1 *pFactory); static HRESULT __stdcall CreateMyEffect(_Outptr_ IUnknown **ppEffect); IFACEMETHODIMP Initialize(_In_ ID2D1EffectContext *pCtx, _In_ ID2D1TransformGraph *pGraph); IFACEMETHODIMP PrepareForRender(D2D1_CHANGE_TYPE Type); IFACEMETHODIMP SetGraph(ID2D1TransformGraph *pGraph); IFACEMETHODIMP SetDrawInfo(ID2D1DrawInfo *pDraw); IFACEMETHODIMP MapOutputRectToInputRects(const D2D1_RECT_L *prOut, D2D1_RECT_L *prIn, UINT32 ctIn) const; IFACEMETHODIMP MapInputRectsToOutputRect(const D2D1_RECT_L *prIn, const D2D1_RECT_L *prInOpaque, UINT32 ctIn, D2D1_RECT_L *prOut, D2D1_RECT_L *prOutOpaque); IFACEMETHODIMP MapInvalidRect(UINT32 nIn, D2D1_RECT_L rInInvalid, D2D1_RECT_L *prOut) const; IFACEMETHODIMP_(UINT32) GetInputCount(void) const; HRESULT SetTint(UINT32 Clr); UINT32 GetTint(void) const; private: MyEffect(void); ~MyEffect(void); // Data LONG ctReference; ID2D1EffectContext *pCtx; ID2D1DrawInfo *pDraw; D2D1_RECT_L rIn; struct ShaderConstant_s { float tint[4]; } Constants; }; HRESULT MyEffect::SetTint(UINT32 Clr) { Constants.tint[0]= (float)((Clr>>16) & 0xFF)/255.0f; Constants.tint[1]= (float)((Clr>> 8) & 0xFF)/255.0f; Constants.tint[2]= (float)((Clr ) & 0xFF)/255.0f; Constants.tint[3]= (float)((Clr>>24) & 0xFF)/255.0f; return(S_OK); } UINT32 MyEffect::GetTint(void) const { UINT32 Clr= 0; Clr|= ((UINT32)(Constants.tint[0]*255.0f) & 0xFF)<<16; Clr|= ((UINT32)(Constants.tint[1]*255.0f) & 0xFF)<< 8; Clr|= ((UINT32)(Constants.tint[2]*255.0f) & 0xFF); Clr|= ((UINT32)(Constants.tint[3]*255.0f) & 0xFF)<<24; return(Clr); }

Update MyEffect::Register() to add the Tint property. The name in the Bindings and in the XML must match exactly.

Register: HRESULT MyEffect::Register(ID2D1Factory1 *pFactory) { HRESULT WinErr= S_OK; static const PCWSTR pszXml = L"<?xml version='1.0'?>\r\n" L"<Effect>\r\n" L" <!-- System Properties -->\r\n" L" <Property name='DisplayName' type='string' value='FirstShader1'/>\r\n" L" <Property name='Author' type='string' value='nlited systems'/>\r\n" L" <Property name='Category' type='string' value='Experimental'/>\r\n" L" <Property name='Description' type='string' value='My first effect.'/>\r\n" L" <Inputs>\r\n" // Source must be specified. L" <Input name='Source'/>\r\n" L" </Inputs>\r\n" L" <!-- Custom Properties go here. -->\r\n" L" <Property name='Tint' type='uint32'>\r\n" L" <Property name='DisplayName' type='string' value='Tint'/>\r\n" L" <Property name='Default' type='uint32' value='0'/>\r\n" L" </Property>\r\n" L"</Effect>\r\n" ; static const D2D1_PROPERTY_BINDING Bindings[]= { D2D1_VALUE_TYPE_BINDING(L"Tint",&SetTint,&GetTint) }; if(!SUCCEEDED(WinErr= pFactory->RegisterEffectFromString(CLSID_MyEffect,pszXml,Bindings,ARRAYSIZE(Bindings),CreateMyEffect))) { Error(ERR_DIRECTX,"MyEffect:Register: RegisterEffectFromString() failed. [%X]",WinErr); } return(WinErr); }

Finally, in my MsgTimer() function I will update the Tint to a random color every 30ms. Note that rand() returns a 16bit value, so I use two to form a 32bit RGBA color.

MsgTimer(): int PxlShader::MsgTimer(void) { ptBall.x+= BallVector.x; if(ptBall.x < rWnd.left || ptBall.x >= rWnd.right) { BallVector.x= -BallVector.x; ptBall.x+= BallVector.x*2; } ptBall.y+= BallVector.y; if(ptBall.y < rWnd.top || ptBall.y >= rWnd.bottom) { BallVector.y= -BallVector.y; ptBall.y+= BallVector.y*2; } UINT32 Tint= ((UINT32)rand()<<16)|rand(); pEffect->SetValueByName(L"Tint",Tint); InvalidateRect(hWnd,0,0); return(1); }

Now I should see the ball covered by a random, flickering tint.

Sharp eyes might notice something odd: I am setting Tint to a random 32bit value, which includes the alpha channel. I would expect the ball to flicker in brightness, tending toward very dim since most of the random alpha values would result in blending the black background. This is clearly not the case, the ball flickers in color but is vivid. Why?

My pixel shader is now running in place of the normal alpha blender, and I am ignoring the alpha value. If I wanted the alpha to be applied, I would need to perform the multiplication explicitly.

Add a Second Parameter

Bright will be a second parameter that will control the overall brightness (0.0 to 1.0) of all non-black pixels.

  1. Add float bright to the Constants struct.
  2. Add SetBright() and GetBright() to MyEffect.
  3. Add float bright to the shader cbuffer.
  4. Add the Bright property to the Register XML and Bindings array.
  5. Modify MsgTimer() to update the Bright parameter.
  6. Modify the shader code to use bright.

Add Bright to the Constants.

Bright MyEffect: class MyEffect: public ID2D1EffectImpl, public ID2D1DrawTransform { public: //Added HRESULT SetTint(UINT32 Clr); UINT32 GetTint(void) const; HRESULT SetBright(float bright) { Constants.bright= bright; return(S_OK); }; float GetBright(void) const { return(Constants.bright); }; private: // Data struct ShaderConstant_s { float tint[4]; float bright; } Constants; }; MyEffect::MyEffect(void) { Constants.bright= 1.0f; }

Add bright to the shader cbuffer:

bright cbuffer: cbuffer constants: register(b0) { float4 tint:COLOR; float bright; };

Add the Bright property to the Register XML and Bindings. Be careful here, as any typo in the XML will not be caught by the compiler but will cause the Register() to fail with "0x80070057: The parameter is incorrect."

bright Register(): HRESULT MyEffect::Register(ID2D1Factory1 *pFactory) { HRESULT WinErr= S_OK; static const PCWSTR pszXml = L"<?xml version='1.0'?>\r\n" L"<Effect>\r\n" ... L" <!-- Custom Properties go here. -->\r\n" L" <Property name='Tint' type='uint32'>\r\n" L" <Property name='DisplayName' type='string' value='Tint'/>\r\n" L" <Property name='Default' type='uint32' value='0'/>\r\n" L" </Property>\r\n" L" <Property name='Bright' type='float'>\r\n" L" <Property name='DisplayName' type='string' value='Bright'/>\r\n" L" <Property name='Default' type='float' value='1.0'/>\r\n" L" <Property name='Min' type='float' value='0'/>\r\n" L" <Property name='Max' type='float' value='1.0'/>\r\n" L" </Property>\r\n" L"</Effect>\r\n" ; static const D2D1_PROPERTY_BINDING Bindings[]= { D2D1_VALUE_TYPE_BINDING(L"Tint",&SetTint,&GetTint) ,D2D1_VALUE_TYPE_BINDING(L"Bright",&SetBright,&GetBright) }; if(!SUCCEEDED(WinErr= pFactory->RegisterEffectFromString(CLSID_MyEffect,pszXml,Bindings,ARRAYSIZE(Bindings),CreateMyEffect))) { Error(ERR_DIRECTX,"MyEffect:Register: RegisterEffectFromString() failed. [%X]",WinErr); } return(WinErr); }

Update Bright in MsgTimer(): I want the brightness to throb between 0.333 and 1.0 with a sine function over time, so this is a bit complicated.

bright MsgTimer(): class PxlShader { private: //Data UINT64 msPrev; // Previous timer tick float Time; // Accumulated game time (seconds) }; int PxlShader::MsgTimer(void) { UINT64 msTick= GetTickCount64(); UINT msElapsed= msPrev ? (UINT)(msTick-msPrev) : 0; msPrev= msTick; Time+= (float)msElapsed/1000.0f; ... if(pEffect) { UINT32 Tint= ((UINT32)rand()<<16)|rand(); pEffect->SetValueByName(L"Tint",Tint); pEffect->SetValueByName(L"Bright",(float)((cosf(Time*3.14f)+2)/3)); } InvalidateRect(hWnd,0,0); return(1); }

Modify the shader code to apply bright:

bright shader: D2D_PS_ENTRY(main) { float4 color= 0; ... color*= bright; return color; }

And now the merged image pulses in brightness.


Moderator: close comments Comments are closed.

Comments are moderated. Anonymous comments are not visible to others until moderated. Comments are owned by the author but may be removed or reused (but not modified) by this site at any time without notice.

HTML
  1. Moderator: [] approve delete HTML



WebV7 (C)2018 nlited | Rendered by tikope in 44.094ms | 18.116.89.8