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:
I need to create a custom effect implementation.
I need a bitmap source for the effect.
I need a pixel shader that does as little as possible,
while still being an obvious change.
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:
TEXT 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():
TEXT 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.
TEXT 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.
TEXT 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().
TEXT 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.
TEXT 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.
TEXT 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.
TEXT 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.
TEXT 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.
TEXT 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:
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.
TEXT 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:
Set pbmSrc as the target for pdcDraw.
Clear pbmSrc to black.
Draw a green ball at a constant position in pbmSrc.
Set pbmImg as the target for pdcDraw.
Clear pbmImg to black.
Translate to the bouncing ball position.
Set pbmSrc as the input to pEffect.
Draw pbmSrc onto pbmImg using pEffect.
TEXT 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!
All Together
Here is the complete program in a single piece:
TEXT 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.
WebV7 (C)2018 nlited | Rendered by tikope in 41.606ms | 3.145.91.152