dev.nlited.com

>>

My First Pixel Shader

<<<< prev
next >>>>

2022-12-16 19:00:09 chip Page 2472 📢 PUBLIC

KISS!

Most of the shader examples assume I want a full 3D rendering engine, and that the pixel shader is only needed to support the vertex shader. But 3D adds a tremendous amount of unnecessary complexity when I am really just interested in applying custom pixel effects to a 2D display. A lot of this complexity can be punted away by creating a Direct2D Effect that consists solely of a Pixel Shader. This project's objective is to create a working pixel shader with the fewest lines of code possible.

The end goal is to execute this single line of code:
pdcDraw->DrawImage(pEffect)
Where pEffect is the container for our pixel shader. My goal is to use a pixel shader to change the color of the bouncing ball.

Once again, I need to navigate the web of prerequisites:

Creating a Custom Effect

The custom effect is created by deriving from the ID2D1EffectImpl class and implementing my own code. I start by defining my new MyEffect class:


MyEffect: 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; };

I am taking a bit of a shortcut, following the RippleEffect example from the Microsoft Windows-Universal-Samples, by implementing both the ID2D1EffectImpl and ID2D1DrawTransform in the same class. This works if the effect consists of a single transform, which is true both now and 99% of the time.

First the easy stuff, the base constructor, destructor, and Release():

Constructor: MyEffect::MyEffect(void) { ctReference= 1; rIn= { 0,0,0,0 }; } MyEffect::~MyEffect(void) { } ULONG MyEffect::Release(void) { if(--ctReference > 0) return(ctReference); delete this; return(0); }

The QueryInterface() is a bit complicated because I am implementing both ID2D1EffectImpl and ID2D1DrawTransform in the same class. Be careful with reinterpret_cast vs static_cast! I spent a couple hours chasing down some extremely weird behavior because I used the wrong casts.

QueryInterface: HRESULT MyEffect::QueryInterface(REFIID riid, void **ppInterface) { HRESULT WinErr= S_OK; void *pInterface= 0; if(riid==__uuidof(ID2D1EffectImpl)) { pInterface= reinterpret_cast<ID2D1EffectImpl*>(this); } else if(riid==__uuidof(ID2D1DrawTransform)) { pInterface= static_cast<ID2D1DrawTransform*>(this); } else if(riid==__uuidof(ID2D1Transform)) { pInterface= static_cast<ID2D1Transform*>(this); } else if(riid==__uuidof(ID2D1TransformNode)) { pInterface= static_cast<ID2D1TransformNode*>(this); } else if(riid==__uuidof(IUnknown)) { pInterface= this; } else { WinErr= E_NOINTERFACE; } if(ppInterface) { *ppInterface= pInterface; if(pInterface) AddRef(); } return(WinErr); }

The Register() function tells the system I have a new Effect and how to use it. The number of Input clauses in Inputs must match the number of expected inputs, but the names are not important. The Property entries must be accurate and exactly match Bindings[]. For now, there will be no properties to get or set and no bindings.

I need to create two GUIDs to identify my effect and my shader.

Register(): // {D8255497-025E-4D3F-A4DF-5C25306F67AC} static const GUID CLSID_MyEffect = { 0xd8255497, 0x25e, 0x4d3f, { 0xa4, 0xdf, 0x5c, 0x25, 0x30, 0x6f, 0x67, 0xac } }; // {2BCB702A-4F42-44D0-B8ED-4E9F9FC7A905} static const GUID GUID_MyPixelShader = { 0x2bcb702a, 0x4f42, 0x44d0, { 0xb8, 0xed, 0x4e, 0x9f, 0x9f, 0xc7, 0xa9, 0x5 } }; 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='MyEffect'/>\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" L" <Input name='Source'/>\r\n" L" </Inputs> " L" <!-- Custom Properties go here. --> " L"</Effect>" ; if(!SUCCEEDED(WinErr= pFactory->RegisterEffectFromString(CLSID_MyEffect,pszXml,0,0,CreateMyEffect))) { Error(ERR_DIRECTX,"MyEffect:Register: RegisterEffectFromString() failed. [%X]",WinErr); } return(WinErr); } HRESULT __stdcall MyEffect::CreateMyEffect(IUnknown **ppEffect) { HRESULT WinErr= S_OK; *ppEffect= static_cast<ID2D1EffectImpl*>(new MyEffect); if(!*ppEffect) { WinErr= E_OUTOFMEMORY; } return(WinErr); }

There are a lot of hidden gotchas in the Register() XML text, be careful. The number of Inputs must match the number of expected inputs, in this case 1. I wasted almost two hours trying to figure out why SetSingleTransformNode() was failing with INVALID_PARAMETER. It was because I had omitted the Input line. The Property entries must match exactly what is specified in the Bindings array, if any.

Initialize() is where I load by shader code and patch myself into the TransformGraph. It seems that 90% of the problems are going to show up in the call to SetSingleTransformNode().

Initialize(): #include <d3dcompiler.h> HRESULT MyEffect::Initialize(ID2D1EffectContext *_pCtx, ID2D1TransformGraph *pGraph) { HRESULT WinErr= S_OK; ID3DBlob *pCode= 0; ID3DBlob *pError= 0; pCtx= _pCtx; ID2D1TransformNode *pNode= 0; QueryInterface(__uuidof(ID2D1TransformNode),(void**)&pNode); if(!SUCCEEDED(WinErr= D3DReadFileToBlob(L"S:\\Src\\HQ\\Dev\\SB\\Chip\\Bugs\\BugsV1\\Out\\Winx64Debug\\FirstShader.cso",&pCode))) { Warn(ERR_FILE_READ,"MyEffect:Initialize: Unable to read shader. [%X]",WinErr); } else if(!SUCCEEDED(WinErr= pCtx->LoadPixelShader(GUID_MyPixelShader,(BYTE*)pCode->GetBufferPointer(),(UINT32)pCode->GetBufferSize()))) { Warn(ERR_DIRECTX,"MyEffect:Initialize: Unable to create pixel shader. [%X]",WinErr); } else if(!SUCCEEDED(WinErr= pGraph->SetSingleTransformNode(this))) { Warn(ERR_DIRECTX,"MyEffect:Initialize: SetSingleTransformNode() failed. [%X]",WinErr); } SafeRelease(pCode); SafeRelease(pError); return(WinErr); }

There are a couple gotchas lurking here. I am reading the pre-compiled shader code from a file, which is written to the solution's output directory. This is not the current working directory, so I need to sort out the path. For expediency, I am hard-coding the full path and punting the problem of sorting it out until later.

PrepareForRender() is called just before my shader executes. This is when I should transfer any updated parameters from the CPU to the GPU. For now, there are no parameters so this does nothing.

PrepareForRender(): HRESULT MyEffect::PrepareForRender(D2D1_CHANGE_TYPE Type) { HRESULT WinErr= S_OK; return(WinErr); }

SetGraph() is called when a new node is added to the TransformGraph. MyEffect supports only a single transform, so this should never be called.

SetGraph(): // This is a single-input graph, SetGraph() should never be called. HRESULT MyEffect::SetGraph(ID2D1TransformGraph *pGraph) { Warn(ERR_DIRECTX,"MyEffect:SetGraph: Should not be called."); return(E_NOTIMPL); }

SetDrawInfo() is my opportunity to assign my shader to the Draw operations.

SetDrawInfo(): HRESULT MyEffect::SetDrawInfo(ID2D1DrawInfo *_pDraw) { HRESULT WinErr= S_OK; pDraw= _pDraw; if(!SUCCEEDED(WinErr= pDraw->SetPixelShader(GUID_MyPixelShader))) { Warn(ERR_DIRECTX,"MyEffect:SetDrawInfo: SetPixelShader() failed. [%X]",WinErr); } return(WinErr); }

MapOutputRectToInputRects(), MapInputRectsToOutputRect(), and MapInvalidRect() let me notify the system if the input and output rectangles are different, such as when a Gaussian blur bleeds over the edges. In this case, no pixels outside the input are affected so the rectangles are identical. GetInputCount() needs to return the number of expected inputs to my transform, which will be 1.

MapRects: HRESULT MyEffect::MapOutputRectToInputRects(const D2D1_RECT_L *prOut, _Out_writes_(ctIn) D2D1_RECT_L *prIn, UINT32 ctIn) const { HRESULT WinErr= S_OK; if(ctIn!=1) { Warn(ERR_DIRECTX,"MyEffect:MapOutputRectToInputRects: Only 1 input is supported. [%d]",ctIn); WinErr= E_INVALIDARG; } else { prIn[0]= *prOut; } return(WinErr); } IFACEMETHODIMP MyEffect::MapInputRectsToOutputRect(const D2D1_RECT_L *prIn, const D2D1_RECT_L *prInOpaque, UINT32 ctIn, D2D1_RECT_L *prOut, D2D1_RECT_L *prOutOpaque) { HRESULT WinErr= S_OK; if(ctIn!=1) { Warn(ERR_DIRECTX,"MyEffect:MapInputRectsToOutputRect: Only 1 input is supported. [%d]",ctIn); WinErr= E_INVALIDARG; } else { rIn= *prOut= prIn[0]; Zero(*prOutOpaque); } return(WinErr); } IFACEMETHODIMP MyEffect::MapInvalidRect(UINT32 nIn, D2D1_RECT_L rInInvalid, D2D1_RECT_L *prOutInvalid) const { HRESULT WinErr= S_OK; // Set entire output to invalid *prOutInvalid= rIn; return(WinErr); } IFACEMETHODIMP_(UINT32) MyEffect::GetInputCount(void) const { return(1); // Always 1 }

My First Shader

The final step is to write the pixel shader. Create a project folder called "Shaders" and add a new item named "FirstShader.hlsl". DO NOT SELECT HLSL! Visual Studio does not provide Pixel Shader as an HLSL type, and creating it as a vertex or compute shader will create a pile of problems. Select "Utility / Text" instead, but name it "FirstShader.hlsl". This will create an empty file that VS will recognize and treat as a shader.

This shader will simply bump up the red channel for all pixels. I started with the shader from the RippleEffect project and pruned it down to almost nothing.

FirstShader.hlsl: #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" 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.r+= 0.25f; return color; } //EOF: FIRSTSHADER.HLSL

Update the HLSL compiler options:
PxlShader properties

Include directories:$(WindowsSDK_IncludePath)
Entrypoint:main
Shader type:Pixel Shader (/ps)
Shader model:Shader Model 4 Level 9_3 (/4_0_level_9_3)
Compile a Direct2D custom pixel shader effect:Yes

I should now be able to compile the shader. Verify that FirstShader.cso exists in the output directory. The HLSL compiler can fail with some extremely cryptic and unhelpful error messages. Be very careful when making changes to the HLSL code. Run-time errors in the shader will typically result in silently failing to draw anything. Try to keep the shader code as simple as possible.

Invoking the Effect

Now I need to create the effect and an intermediate bitmap to use as the source to the effect. I will draw my ball to pbmSrc, then use the Effect to draw and transform the source into pbmImg where it will become visible when SwapChain presents the buffer.

Effect: ID2D1Bitmap1 *pbmSrc; ID2D1Effect *pEffect; int PxlShader::DrawCreate(void) { int Err= ERR_OK; HRESULT WinErr; D2D1_SIZE_U szWnd= { (UINT32)RWID(rWnd), (UINT32)RHGT(rWnd) }; D2D1_DEVICE_CONTEXT_OPTIONS DCOptions= D2D1_DEVICE_CONTEXT_OPTIONS_NONE; if(!pDXGIDevice && IsErr(DrawCreateDX())) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to create DXGI device."); } else if(!pSwapChain && IsErr(Err= DrawCreateSwapChain())) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to create SwapChain."); } else if(!pDXGISurface && !SUCCEEDED(WinErr= pSwapChain->GetBuffer(0,IID_PPV_ARGS(&pDXGISurface)))) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to retrieve DXGI surface. [%X]",WinErr); } else if(!pD2Factory && IsErr(Err= DrawCreateD2Factory())) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to create Direct2D factory."); } else if(!pD2Device && !SUCCEEDED(WinErr= pD2Factory->CreateDevice(pDXGIDevice,&pD2Device))) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to create Direct2D device."); } else if(!pdcDraw && !SUCCEEDED(WinErr= pD2Device->CreateDeviceContext(DCOptions,&pdcDraw))) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to create Draw context."); } else if(!pbmImg && IsErr(Err= CreateBitmapBase(pdcDraw,szWnd,pbmImg))) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to create base bitmap."); } else if(!pbmSrc && IsErr(Err= CreateBitmapComposite(pdcDraw,SizeU(100,100),pbmSrc))) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to create source bitmap."); } else if(!pEffect && IsErr(Err= CreateEffect())) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to create MyEffect."); } return(Err); }

My DrawUpdate() becomes a bit more complex:

  1. Set pbmSrc as the target for pdcDraw.
  2. Clear pbmSrc to black.
  3. Draw a green ball at a constant position in pbmSrc.
  4. Set pbmImg as the target for pdcDraw.
  5. Clear pbmImg to black.
  6. Translate to the bouncing ball position.
  7. Set pbmSrc as the input to pEffect.
  8. Draw pbmSrc onto pbmImg using pEffect.
DrawUpdate()2: int PxlShader::DrawUpdate(void) { int Err= ERR_OK; HRESULT WinErr; pdcDraw->BeginDraw(); pdcDraw->SetTransform(Matrix3x2F::Identity()); pdcDraw->SetTarget(pbmSrc); pdcDraw->Clear(ColorF(ColorF::Black,0)); D2D1_ELLIPSE dot= { {50,50}, 50,50 }; pdcDraw->FillEllipse(&dot,D2Brush(pdcDraw,ColorF(ColorF::ForestGreen))); pdcDraw->SetTarget(pbmImg); pdcDraw->Clear(ColorF(ColorF::Black,0)); pdcDraw->SetTransform(Matrix3x2F::Translation(SizeF(ptBall.x,ptBall.y))); pEffect->SetInput(0,pbmSrc); pdcDraw->DrawImage(pEffect); if(!SUCCEEDED(WinErr= pdcDraw->EndDraw())) { Err= Error(ERR_DIRECTX,"PxlShader:DrawUpdate: EndDraw() failed. [%X]",WinErr); ReleaseEverything(); } return(Err); }

If everthing works correctly, I should see the green bouncing ball with a red tint applied to it. The red tint will appear as a square since I am blindly applying the tint to every pixel in the source bitmap, which is a square.

Two hours spent scratching my head, because SetSingleTransformNode() was returning INVALID_PARAMETER. I finally tracked this down to the Register() XML, which was missing the "Input" line.

Success! A red-tinted bouncing ball!

PxlShader

All Together

Here is the complete program in a single piece:

PxlShader.cpp: /*************************************************************************/ /** PxlShader.cpp: Minimal code to run a pixel shader. **/ /** (C)2022 nlited systems, cmd **/ /*************************************************************************/ #include <Windows.h> #include <d3d11_1.h> #include <d2d1.h> #include <d2d1_1.h> #include <d2d1helper.h> #include <d2d1effectauthor.h> #include <d2d1effecthelpers.h> #include <d3dcompiler.h> #include "Handles.h" #include "ChipLib.h" #pragma comment(lib,"D3D11.lib") #pragma comment(lib,"D2D1.lib") #pragma comment(lib,"DXGI.lib") #pragma comment(lib,"d3dcompiler.lib") #pragma message(__FILE__": Optimizer disabled.") #pragma optimize("",off) #define SIGNATURE_PXLSHADER 0xCD190001 using namespace D2D1; typedef D2D1_POINT_2F POINT2D; HINSTANCE ghInst; DWORD DbgFilter; static const WCHAR ClassName[]= { L"PxlShader" }; // {D8255497-025E-4D3F-A4DF-5C25306F67AC} static const GUID CLSID_MyEffect = { 0xd8255497, 0x25e, 0x4d3f, { 0xa4, 0xdf, 0x5c, 0x25, 0x30, 0x6f, 0x67, 0xac } }; // {2BCB702A-4F42-44D0-B8ED-4E9F9FC7A905} static const GUID GUID_MyPixelShader = { 0x2bcb702a, 0x4f42, 0x44d0, { 0xb8, 0xed, 0x4e, 0x9f, 0x9f, 0xc7, 0xa9, 0x5 } }; #define SafeRelease(pInterface) { if(pInterface && pInterface->Release()==0) pInterface= 0; } //Auto-destruct solid brush. class D2Brush { public: D2Brush(ID2D1RenderTarget *pRT, const D2D1_COLOR_F &clr) { pRT->CreateSolidColorBrush(clr,&pbrBrush); }; ~D2Brush(void) { pbrBrush->Release(); } operator ID2D1Brush *() { return(static_cast<ID2D1Brush*>(pbrBrush)); }; // This lets me use the object as the parameter to the various RenderTarget functions. ID2D1SolidColorBrush *pbrBrush; }; 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); HRESULT UpdateConstants(void); // Data LONG ctReference; ID2D1EffectContext *pCtx; ID2D1DrawInfo *pDraw; D2D1_RECT_L rIn; }; class PxlShader { public: static PxlShader *Ptr(void *pObj); static PxlShader *Ptr(HWND hWnd); static int Create(HWND &hWnd); private: PxlShader(void); ~PxlShader(void); int Create2(void); static INT_PTR CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParm, LPARAM lParm); LRESULT WndProc2(HWND hWnd, UINT Msg, WPARAM wParm, LPARAM lParm); int MsgCreate(HWND hWnd); int MsgDestroy(void); int MsgClose(void); int MsgTimer(void); int MsgMoved(void); int MsgPaint(void); int DrawCreate(void); int DrawCreateDX(void); int DrawCreateSwapChain(void); int DrawCreateD2Factory(void); int CreateBitmapBase(ID2D1DeviceContext *pdcDst, D2D1_SIZE_U szBitmap, ID2D1Bitmap1 *&pbmBase); int CreateBitmapComposite(ID2D1DeviceContext *pdcDst, D2D1_SIZE_U szBitmap, ID2D1Bitmap1 *&pbmComposite); int CreateEffect(void); int DrawUpdate(void); int DrawShow(void); void ReleaseEverything(void); //Data DWORD Signature; HWND hWnd; RECT rWnd; // Window client area POINT2D ptBall; // Bouncing ball position POINT2D BallVector; // Bouncing ball vector D3D_FEATURE_LEVEL DxFeatures; // Supported feature set IDXGIFactory2 *pDXGIFactory; IDXGISwapChain1 *pSwapChain; IDXGISurface1 *pDXGISurface; IDXGIDevice *pDXGIDevice; ID2D1Factory1 *pD2Factory; ID2D1Device *pD2Device; ID2D1DeviceContext *pdcDraw; ID2D1Bitmap1 *pbmImg; ID2D1Bitmap1 *pbmSrc; ID2D1Effect *pEffect; }; /*************************************************************************/ /** Window entry point **/ /*************************************************************************/ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR pArgs, int nShow) { int Err= ERR_OK; HWND hWnd= 0; ghInst= hInst; MemCreate(); TmpBufCreate(16*1024*1024); if(IsErr(Err= PxlShader::Create(hWnd))) { Err= Error(Err,"WinMain: Unable to create the main window."); } else { while(hWnd && IsWindow(hWnd)) { MSG Msg; GetMessage(&Msg,hWnd,0,0); TranslateMessage(&Msg); DispatchMessage(&Msg); } } TmpBufDestroy(); MemDestroy(); if(IsErr(MemReport())) MessageBox(0,L"PxlShader: Memory error.",L"PxlShader",MB_OK|MB_SETFOREGROUND); return(Err); } void ConsolePrint(DWORD Type, const WCHAR *Text) { TXT Out(0,0,"%s\r\n",Text); OutputDebugString(Out); } /*************************************************************************/ /** PxlShader class **/ /*************************************************************************/ PxlShader::PxlShader(void) { Signature= SIGNATURE_PXLSHADER; ptBall= { 10,20 }; BallVector= { 1,1 }; } PxlShader::~PxlShader(void) { Signature|= SIGNATURE_INVALID; ReleaseEverything(); } PxlShader *PxlShader::Ptr(void *pObj) { PxlShader *pPxl= (PxlShader*)pObj; if(!pObj || IsBadPtr(pObj,sizeof(*pPxl),BADPTR_RW) || pPxl->Signature!=SIGNATURE_PXLSHADER) pPxl= 0; return(pPxl); } PxlShader *PxlShader::Ptr(HWND hWnd) { if(!hWnd || !IsWindow(hWnd)) return(0); return(Ptr((void*)GetWindowLongPtr(hWnd,GWLP_USERDATA))); } int PxlShader::Create(HWND &hWnd) { int Err= ERR_OK; PxlShader *pPxl= new PxlShader; if(!pPxl) { Err= Error(ERR_NO_MEM,"PxlShader:Create: NoMem"); } else if(IsErr(Err= pPxl->Create2())) { delete pPxl; } else { hWnd= pPxl->hWnd; } return(Err); } int PxlShader::Create2(void) { int Err= ERR_OK; ATOM hWndClass; WNDCLASS WndClass; RECT R= { 100,100,500,300 }; Zero(WndClass); WndClass.style= CS_DBLCLKS|CS_HREDRAW|CS_VREDRAW; WndClass.lpfnWndProc= WndProc; WndClass.hInstance= ghInst; WndClass.lpszClassName= ClassName; DWORD style= WS_OVERLAPPEDWINDOW|WS_VISIBLE; if(!(hWndClass= RegisterClass(&WndClass))) { Err= Error(ERR_SYSCREATE,"PxlShader:Create2: Unable to register the main window class."); } else if(!(hWnd= CreateWindow(ClassName,L"PxlShader",style,R.left,R.top,RWID(R),RHGT(R),0,0,ghInst,(LPVOID)this))) { Err= Error(ERR_SYSCREATE,"PxlShader:Create2: Unable to create the main window."); } return(Err); } /*************************************************************************/ /** Window message handler **/ /*************************************************************************/ INT_PTR CALLBACK PxlShader::WndProc(HWND hWnd, UINT Msg, WPARAM wParm, LPARAM lParm) { INT_PTR Result= 0; PxlShader *pPxl= 0; if(Msg==WM_CREATE) { CREATESTRUCT *pCreate= (CREATESTRUCT*)lParm; pPxl= Ptr(pCreate->lpCreateParams); } else { pPxl= Ptr(hWnd); } if(!pPxl) { Result= DefWindowProc(hWnd,Msg,wParm,lParm); } else { Result= pPxl->WndProc2(hWnd,Msg,wParm,lParm); } return(Result); } LRESULT PxlShader::WndProc2(HWND hWnd, UINT Msg, WPARAM wParm, LPARAM lParm) { LRESULT Result= 0; switch(Msg) { case WM_CREATE: Result= MsgCreate(hWnd); break; case WM_DESTROY: Result= MsgDestroy(); break; case WM_CLOSE: Result= MsgClose(); break; case WM_TIMER: Result= MsgTimer(); break; case WM_WINDOWPOSCHANGED: Result= MsgMoved(); break; case WM_PAINT: Result= MsgPaint(); break; default: Result= DefWindowProc(hWnd,Msg,wParm,lParm); break; } return(Result); } int PxlShader::MsgCreate(HWND _hWnd) { hWnd= _hWnd; SetWindowLongPtr(hWnd,GWLP_USERDATA,(LONG_PTR)this); SetTimer(hWnd,1,30,0); return(1); } int PxlShader::MsgDestroy(void) { SetWindowLongPtr(hWnd,GWLP_USERDATA,0); delete this; return(1); } int PxlShader::MsgClose(void) { DestroyWindow(hWnd); return(1); } 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; } InvalidateRect(hWnd,0,0); return(1); } int PxlShader::MsgMoved(void) { RECT rNew; GetClientRect(hWnd,&rNew); if(RWID(rNew)!=RWID(rWnd) || RHGT(rNew)!=RHGT(rWnd)) { rWnd= rNew; ReleaseEverything(); } return(1); } int PxlShader::MsgPaint(void) { int Err= ERR_OK; PAINTSTRUCT Pnt; GetClientRect(hWnd,&rWnd); if(BeginPaint(hWnd,&Pnt)) { if(IsErr(Err= DrawCreate())) { Err= Warn(Err,"PxlShader:MsgPaint: DrawCreate() failed."); } else if(IsErr(Err= DrawUpdate())) { Err= Warn(Err,"PxlShader:MsgPaint: DrawUdpate() failed."); } else if(IsErr(Err= DrawShow())) { Err= Warn(Err,"PxlShader:MsgPaint: DrawPaint() failed."); } if(IsErr(Err)) { // Fall back to GDI paint. HBRUSH hbrFill= CreateSolidBrush(RGB(20,30,40)); FillRect(Pnt.hdc,&Pnt.rcPaint,hbrFill); DeleteObject(hbrFill); } EndPaint(hWnd,&Pnt); } ValidateRect(hWnd,0); return(1); } /*************************************************************************/ /** DirectX **/ /*************************************************************************/ int PxlShader::DrawCreate(void) { int Err= ERR_OK; HRESULT WinErr; D2D1_SIZE_U szWnd= { (UINT32)RWID(rWnd), (UINT32)RHGT(rWnd) }; D2D1_DEVICE_CONTEXT_OPTIONS DCOptions= D2D1_DEVICE_CONTEXT_OPTIONS_NONE; if(!pDXGIDevice && IsErr(DrawCreateDX())) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to create DXGI device."); } else if(!pSwapChain && IsErr(Err= DrawCreateSwapChain())) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to create SwapChain."); } else if(!pDXGISurface && !SUCCEEDED(WinErr= pSwapChain->GetBuffer(0,IID_PPV_ARGS(&pDXGISurface)))) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to retrieve DXGI surface. [%X]",WinErr); } else if(!pD2Factory && IsErr(Err= DrawCreateD2Factory())) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to create Direct2D factory."); } else if(!pD2Device && !SUCCEEDED(WinErr= pD2Factory->CreateDevice(pDXGIDevice,&pD2Device))) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to create Direct2D device."); } else if(!pdcDraw && !SUCCEEDED(WinErr= pD2Device->CreateDeviceContext(DCOptions,&pdcDraw))) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to create Draw context."); } else if(!pbmImg && IsErr(Err= CreateBitmapBase(pdcDraw,szWnd,pbmImg))) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to create base bitmap."); } else if(!pEffect && IsErr(Err= CreateEffect())) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to create MyEffect."); } else if(!pbmSrc && IsErr(Err= CreateBitmapComposite(pdcDraw,SizeU(100,100),pbmSrc))) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreate: Unable to create source bitmap."); } return(Err); } int PxlShader::DrawCreateDX(void) { int Err= ERR_OK; HRESULT WinErr; UINT Flags= D3D11_CREATE_DEVICE_BGRA_SUPPORT; static const D3D_FEATURE_LEVEL Levels[]= { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, D3D_FEATURE_LEVEL_9_1 }; D3D_DRIVER_TYPE Type= D3D_DRIVER_TYPE_HARDWARE; UINT ctLevels= ARRAYSIZE(Levels); UINT Version= D3D11_SDK_VERSION; ID3D11Device *pD3D11Device= 0; ID3D11DeviceContext *pD3D11DC= 0; IDXGIAdapter *pDXGIAdapter= 0; if(!SUCCEEDED(WinErr= D3D11CreateDevice(0,Type,0,Flags,Levels,ctLevels,Version,&pD3D11Device,&DxFeatures,&pD3D11DC))) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreateDX: D3D11CreateDevice() failed."); } else if(!SUCCEEDED(WinErr= pD3D11Device->QueryInterface(&pDXGIDevice))) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreateDX: Unable to retrieve DXGI device. [%X]",WinErr); } else if(!SUCCEEDED(WinErr= pDXGIDevice->GetAdapter(&pDXGIAdapter))) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreateDX: Unable to retrieve DXGI adapter. [%X]",WinErr); } else if(!SUCCEEDED(WinErr= pDXGIAdapter->GetParent(IID_PPV_ARGS(&pDXGIFactory)))) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreateDX: Unable to retrieve DXGI factory. [%X]",WinErr); } SafeRelease(pDXGIAdapter); SafeRelease(pD3D11Device); SafeRelease(pD3D11DC); return(Err); } int PxlShader::DrawCreateSwapChain(void) { int Err= ERR_OK; HRESULT WinErr; DXGI_SWAP_CHAIN_DESC1 SwapDesc; Zero(SwapDesc); SwapDesc.Format= DXGI_FORMAT_B8G8R8A8_UNORM; SwapDesc.SampleDesc.Count= 1; SwapDesc.BufferUsage= DXGI_USAGE_RENDER_TARGET_OUTPUT; SwapDesc.BufferCount= 2; SwapDesc.Scaling= DXGI_SCALING_STRETCH; SwapDesc.SwapEffect= DXGI_SWAP_EFFECT_DISCARD; if(!SUCCEEDED(WinErr= pDXGIFactory->CreateSwapChainForHwnd(pDXGIDevice,hWnd,&SwapDesc,0,0,&pSwapChain))) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawCreateSwapChain: Unable to create SwapChain. [%X]",WinErr); } return(Err); } int PxlShader::DrawCreateD2Factory(void) { int Err= ERR_OK; HRESULT WinErr; REFIID guid= __uuidof(ID2D1Factory1); D2D1_FACTORY_OPTIONS options; options.debugLevel= D2D1_DEBUG_LEVEL_INFORMATION; // DEBUG_LEVEL will trigger exceptions if EndDraw() fails or the factory is released with // outstanding (unreleased) objects. if(!SUCCEEDED(WinErr= D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED,guid,&options,(void**)&pD2Factory))) { Err= Error(ERR_DIRECTX,"PxlShader:DrawCreateD2Factory: Unable to create D2D factory. [%X]",WinErr); } return(Err); } // Create a targetable bitmap from a DXGI surface. // This serves as the final rendering destination bitmap. int PxlShader::CreateBitmapBase(ID2D1DeviceContext *pdcDst, D2D1_SIZE_U szBitmap, ID2D1Bitmap1 *&pbmBase) { int Err= ERR_OK; HRESULT WinErr; IDXGISurface *pSurface= 0; D2D1_BITMAP_PROPERTIES1 bmProp; Zero(bmProp); bmProp.bitmapOptions= D2D1_BITMAP_OPTIONS_TARGET|D2D1_BITMAP_OPTIONS_CANNOT_DRAW; bmProp.pixelFormat= PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,D2D1_ALPHA_MODE_IGNORE); pdcDst->GetDpi(&bmProp.dpiX,&bmProp.dpiY); if(!SUCCEEDED(WinErr= pdcDst->CreateBitmapFromDxgiSurface(pDXGISurface,bmProp,&pbmBase))) { Err= Warn(ERR_DIRECTX,"PxlShader:CreateBitmapBase: Unable to create %ux%u bitmap.",szBitmap.width,szBitmap.height); } else { pdcDst->SetTarget(pbmBase); } return(Err); } // Create a targetable bitmap that can be used for compositing. int PxlShader::CreateBitmapComposite(ID2D1DeviceContext *pdcDst, D2D1_SIZE_U szBitmap, ID2D1Bitmap1 *&pbmComposite) { int Err= ERR_OK; HRESULT WinErr; D2D1_BITMAP_PROPERTIES1 bmProp; Zero(bmProp); bmProp.bitmapOptions= D2D1_BITMAP_OPTIONS_TARGET; bmProp.pixelFormat= PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,D2D1_ALPHA_MODE_PREMULTIPLIED); pdcDst->GetDpi(&bmProp.dpiX,&bmProp.dpiY); if(!SUCCEEDED(WinErr= pdcDst->CreateBitmap(szBitmap,0,0,bmProp,&pbmComposite))) { Err= Warn(ERR_DIRECTX,"PxlShader:CreateBitmapComposite: Unable to create %ux%u bitmap.",szBitmap.width,szBitmap.height); } else { pdcDst->SetTarget(pbmComposite); } return(Err); } int PxlShader::CreateEffect(void) { int Err= ERR_OK; HRESULT WinErr; if(!SUCCEEDED(WinErr= MyEffect::Register(pD2Factory))) { Err= Warn(ERR_DIRECTX,"PxlShader:CreateEffect: Unable to register MyEffect. [%X]",WinErr); } else if(!SUCCEEDED(WinErr= pdcDraw->CreateEffect(CLSID_MyEffect,&pEffect))) { Err= Warn(ERR_DIRECTX,"PxlShader:CreateEffect: Unable to create MyEffect. [%X]",WinErr); } else { Print(PRINT_INFO,"PxlShader:CreateEffect: OK"); } return(Err); } void PxlShader::ReleaseEverything(void) { SafeRelease(pEffect); SafeRelease(pbmSrc); SafeRelease(pbmImg); SafeRelease(pdcDraw); SafeRelease(pD2Device); SafeRelease(pD2Factory); SafeRelease(pDXGIDevice); SafeRelease(pDXGISurface); SafeRelease(pSwapChain); SafeRelease(pDXGIFactory); } /*************************************************************************/ /** Draw **/ /*************************************************************************/ int PxlShader::DrawUpdate(void) { int Err= ERR_OK; HRESULT WinErr; pdcDraw->BeginDraw(); pdcDraw->SetTransform(Matrix3x2F::Identity()); pdcDraw->SetTarget(pbmSrc); pdcDraw->Clear(ColorF(ColorF::Black,0)); D2D1_ELLIPSE dot= { {50,50}, 50,50 }; pdcDraw->FillEllipse(&dot,D2Brush(pdcDraw,ColorF(ColorF::ForestGreen))); pdcDraw->SetTarget(pbmImg); pdcDraw->Clear(ColorF(ColorF::Black,0)); pdcDraw->SetTransform(Matrix3x2F::Translation(SizeF(ptBall.x,ptBall.y))); pEffect->SetInput(0,pbmSrc); pdcDraw->DrawImage(pEffect); if(!SUCCEEDED(WinErr= pdcDraw->EndDraw())) { Err= Error(ERR_DIRECTX,"PxlShader:DrawUpdate: EndDraw() failed. [%X]",WinErr); ReleaseEverything(); } return(Err); } int PxlShader::DrawShow(void) { int Err= ERR_OK; HRESULT WinErr; DXGI_PRESENT_PARAMETERS PresentParm; Zero(PresentParm); if(!SUCCEEDED(WinErr= pSwapChain->Present1(1,0,&PresentParm))) { Err= Warn(ERR_DIRECTX,"PxlShader:DrawShow: Present() failed. [%X]",WinErr); } return(Err); } /*************************************************************************/ /** MyEffect **/ /*************************************************************************/ MyEffect::MyEffect(void) { ctReference= 1; rIn= { 0,0,0,0 }; } MyEffect::~MyEffect(void) { } ULONG MyEffect::Release(void) { if(--ctReference > 0) return(ctReference); delete this; return(0); } HRESULT MyEffect::QueryInterface(REFIID riid, void **ppInterface) { HRESULT WinErr= S_OK; void *pInterface= 0; if(riid==__uuidof(ID2D1EffectImpl)) { pInterface= reinterpret_cast<ID2D1EffectImpl*>(this); } else if(riid==__uuidof(ID2D1DrawTransform)) { pInterface= static_cast<ID2D1DrawTransform*>(this); } else if(riid==__uuidof(ID2D1Transform)) { pInterface= static_cast<ID2D1Transform*>(this); } else if(riid==__uuidof(ID2D1TransformNode)) { pInterface= static_cast<ID2D1TransformNode*>(this); } else if(riid==__uuidof(IUnknown)) { pInterface= this; } else { WinErr= E_NOINTERFACE; } if(ppInterface) { *ppInterface= pInterface; if(pInterface) AddRef(); } return(WinErr); } HRESULT MyEffect::Register(ID2D1Factory1 *pFactory) { HRESULT WinErr= S_OK; static const PCWSTR pszXml = L"<?xml version='1.0'?> " L"<Effect> " L" <!-- System Properties --> " L" <Property name='DisplayName' type='string' value='FirstShader1'/> " L" <Property name='Author' type='string' value='nlited systems'/> " L" <Property name='Category' type='string' value='Experimental'/> " L" <Property name='Description' type='string' value='My first effect.'/> " L" <Inputs> " // Source must be specified. L" <Input name='Source'/>\r\n" L" </Inputs> " L" <!-- Custom Properties go here. --> " L"</Effect>" ; if(!SUCCEEDED(WinErr= pFactory->RegisterEffectFromString(CLSID_MyEffect,pszXml,0,0,CreateMyEffect))) { Error(ERR_DIRECTX,"MyEffect:Register: RegisterEffectFromString() failed. [%X]",WinErr); } return(WinErr); } HRESULT __stdcall MyEffect::CreateMyEffect(IUnknown **ppEffect) { HRESULT WinErr= S_OK; *ppEffect= static_cast<ID2D1EffectImpl*>(new MyEffect); if(!*ppEffect) { WinErr= E_OUTOFMEMORY; } return(WinErr); } HRESULT MyEffect::Initialize(ID2D1EffectContext *_pCtx, ID2D1TransformGraph *pGraph) { HRESULT WinErr= S_OK; ID3DBlob *pCode= 0; ID3DBlob *pError= 0; pCtx= _pCtx; if(!SUCCEEDED(WinErr= D3DReadFileToBlob(L"S:\\Src\\HQ\\Dev\\SB\\Chip\\Bugs\\BugsV1\\Out\\Winx64Debug\\FirstShader.cso",&pCode))) { Warn(ERR_FILE_READ,"MyEffect:Initialize: Unable to read shader. [%X]",WinErr); } else if(!SUCCEEDED(WinErr= pCtx->LoadPixelShader(GUID_MyPixelShader,(BYTE*)pCode->GetBufferPointer(),(UINT32)pCode->GetBufferSize()))) { Warn(ERR_DIRECTX,"MyEffect:Initialize: Unable to create pixel shader. [%X]",WinErr); } else if(!SUCCEEDED(WinErr= pGraph->SetSingleTransformNode(this))) { Warn(ERR_DIRECTX,"MyEffect:Initialize: SetSingleTransformNode() failed. [%X]",WinErr); } SafeRelease(pCode); SafeRelease(pError); return(WinErr); } HRESULT MyEffect::PrepareForRender(D2D1_CHANGE_TYPE Type) { HRESULT WinErr= S_OK; return(WinErr); } // This is a single-input graph, SetGraph() should never be called. HRESULT MyEffect::SetGraph(ID2D1TransformGraph *pGraph) { Warn(ERR_DIRECTX,"MyEffect:SetGraph: Should not be called."); return(E_NOTIMPL); } HRESULT MyEffect::SetDrawInfo(ID2D1DrawInfo *_pDraw) { HRESULT WinErr= S_OK; pDraw= _pDraw; if(!SUCCEEDED(WinErr= pDraw->SetPixelShader(GUID_MyPixelShader))) { Warn(ERR_DIRECTX,"MyEffect:SetDrawInfo: SetPixelShader() failed. [%X]",WinErr); } return(WinErr); } HRESULT MyEffect::MapOutputRectToInputRects(const D2D1_RECT_L *prOut, _Out_writes_(ctIn) D2D1_RECT_L *prIn, UINT32 ctIn) const { HRESULT WinErr= S_OK; if(ctIn!=1) { Warn(ERR_DIRECTX,"MyEffect:MapOutputRectToInputRects: Only 1 input is supported. [%d]",ctIn); WinErr= E_INVALIDARG; } else { prIn[0]= *prOut; } return(WinErr); } IFACEMETHODIMP MyEffect::MapInputRectsToOutputRect(const D2D1_RECT_L *prIn, const D2D1_RECT_L *prInOpaque, UINT32 ctIn, D2D1_RECT_L *prOut, D2D1_RECT_L *prOutOpaque) { HRESULT WinErr= S_OK; if(ctIn!=1) { Warn(ERR_DIRECTX,"MyEffect:MapInputRectsToOutputRect: Only 1 input is supported. [%d]",ctIn); WinErr= E_INVALIDARG; } else { rIn= *prOut= prIn[0]; Zero(*prOutOpaque); } return(WinErr); } IFACEMETHODIMP MyEffect::MapInvalidRect(UINT32 nIn, D2D1_RECT_L rInInvalid, D2D1_RECT_L *prOutInvalid) const { HRESULT WinErr= S_OK; // Set entire output to invalid *prOutInvalid= rIn; return(WinErr); } IFACEMETHODIMP_(UINT32) MyEffect::GetInputCount(void) const { return(1); // Always 1 } //EOF: PXLSHADER.CPP

I started with an empty project this morning, and twelve hours later I have figured out how to write a custom IEffect and a pixel shader. This included all the time spent documenting and creating a clean example project. Not bad. Now I have a solid starting point to begin experimenting with the shader code.


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 41.606ms | 3.145.91.152