dev.nlited.com

>>

FoW Redux

<<<< prev
next >>>>

2022-12-21 16:36:06 chip Page 2477 📢 PUBLIC

December 20 2022

PxlShader Fog of War

The FoW PoC is working. Now it needs to be reorganized into a form that I can use in my game project. This will introduce a lot of external dependencies, so the PxlShader project will begin to diverge from being a stand-alone experiment and become more enmeshed in the Bugs project. That's called progress.

The primary objective is to isolate all the code that is specific to the FoW effect into an opaque class that can be easily integrated into the other game projects. This includes migrating all the cpu code from PxlShader into FogEffect.cpp and creating a formal HFOG interface to it.

While working on this, I bumped into a mismatch between the pixel format I was using in PxlShader versus the pixel format used in Bugs. DirectX lets me specify the pixel format I want to use, but it is important that I be consistent. Otherwise the various draw operations will fail with "Invalid parameter". I decided to change PxlShader to match Bugs, which uses RGBA (R8G8B8A8_UNORM, ALPHA_MODE_PREMULTIPLIED). I'm not sure if this is really the pixel I want (Red is the least significant byte -- usually blue is LSB) but at least I am consistent.

Initially, I thought the fog clearing radius had to be fixed. Then I added support for multiple fixed sizes. Then I realized there was no real overhead to creating a reference circle for every integer radius. The time cost is incurred only once, the first time a unique radius is requested, and the overhead is purely memory and not cpu cycles. The performance hit is almost entirely in DrawFog() when pFogPixels is transferred to the gpu, and this is strictly determined by the window size. The number and size of the fog bubbles has some effect but much less.

I tried to figure out a way to avoid creating and destroying pbmFog during each call to DrawFog(). Ideally, I would use the gpu to clear the bitmap and map it to the cpu during the updates. This is not how it works. pFogPixels needs to live in main memory where the cpu can access it. pbmFog needs to live in gpu memory where the pixel shader can access it and the display adapter can read it. The cpu cannot read gpu memory and the gpu cannot read cpu memory, they can only copy data between them. CreateBitmap() is actually copying the source pixels to the gpu where it is used as a reference texture to create the bitmap in gpu memory. This transfer would need to happen during DrawFog() even if it were possible to keep pbmFog persistent between cycles, so there would be no performance benefit anyway.

Return of the GPU

PxlShader Fog of War

The alternative approach would be to do everything on the gpu. This would require a complete BeginDraw() / EndDraw() sequence for every call to ClearFog(), plus the final DrawFog() to blend pbmSrc and pbmFog. But it would avoid doing anything on the cpu and copying pixels to the gpu. It is worth investigating.

Now that FogEffect is completely encapsulated behind an API, I can fiddle around with the internal code without disturbing the app code. This sort of investigation can be pursued later, after FogEffect has been integrated into Game.

UPDATE: I tried this approach and it worked! After all that trouble, I discovered the dark overlapping rings were caused by a badly configured gradient. Doh! I don't know whether to feel clever or stupid. The good news: FoW is now running 100% on the gpu. VMware is not handling the many EndDraw() calls very well, occasionally stuttering quite visibly, but that is a known issue. I don't see any of that when running natively.

Then I noticed that my gradient ran from Red:1 to Black:0, when it should have been Red:1 to Red:0. Fading to black caused the red channel to fall off faster than the alpha, which was causing the bubbles to be smaller than expected and the weird dark rings when two bubbles intersected.

Then I wondered if this goof in the gradient was the root of all my problems... and it was. I can now draw all the radial gradients inside a single BeginDraw() / EndDraw(). So all that blather about "accumulating" was completely wrong. The problem was that my gradient was drawing opaque black when it should have been drawing transparent red. Double-Doh! Now there is no stuttering in the VM.

Surprisingly, the cpu and gpu usage reported by ProcExplorer is roughly the same between the cpu version and the new gpu version. I am sure the gpu version will scale up better. Removing the extra EndDraw() calls improved cpu performance, down to 2% when running full-screen.

Hmmm.... Now that I have fixed the dark rings, I miss them. They added a weird sort of interaction between the glows. With the mystery solved, the black rings are no longer annoying and make the interaction more interesting.


HFOG


FogEffect.cpp: /*************************************************************************/ /** FogEffect.cpp: Fog of War Effect **/ /** (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 "ChipLib.h" #include "List.h" #include "Bugs.h" #include "Profiler.h" #include "ProfilerChannels.h" #include "Globals.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) //NOTE: FogEffect assumes all bitmaps are RGBA (Red in the first, least significant position) // {0831F65D-D59B-41BB-B072-43E5355DE603} static const GUID CLSID_FogEffect= { 0x831f65d, 0xd59b, 0x41bb, { 0xb0, 0x72, 0x43, 0xe5, 0x35, 0x5d, 0xe6, 0x3 } }; // {F7AB0F51-CD65-4515-AB20-6C2F8BE687BA} static const GUID GUID_FogShader= { 0xf7ab0f51, 0xcd65, 0x4515, { 0xab, 0x20, 0x6c, 0x2f, 0x8b, 0xe6, 0x87, 0xba } }; #define RADIUS_MAX 200 #define INPUT_MAX 2 class FogBlendEffect: public ID2D1EffectImpl, public ID2D1DrawTransform { public: //EffectImpl static HRESULT Register(_In_ ID2D1Factory1 *pFactory); static HRESULT __stdcall CreateD2Effect(_Outptr_ IUnknown **ppEffect); IFACEMETHODIMP QueryInterface(REFIID riid, void **ppInterface); IFACEMETHODIMP_(ULONG) AddRef(void) { return(++ctReference); }; IFACEMETHODIMP_(ULONG) Release(void); IFACEMETHODIMP Initialize(_In_ ID2D1EffectContext *pCtx, _In_ ID2D1TransformGraph *pGraph); IFACEMETHODIMP PrepareForRender(D2D1_CHANGE_TYPE Type); IFACEMETHODIMP SetGraph(ID2D1TransformGraph *pGraph); //DrawTransform 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; //Added HRESULT SetFogColor(UINT32 clr); UINT32 GetFogColor(void) const; private: FogBlendEffect(void); ~FogBlendEffect(void); // Data DWORD Signature; // Must be SIGNATURE_FOGBLEND LONG ctReference; // ID2D1EffectImpl reference count ID2D1EffectContext *pCtx; // ID2D1EffectImpl context ID2D1DrawInfo *pDraw; // BeginDraw context UINT ctInput; // Number of inputs to the IEffect. D2D1_RECT_L rIn[INPUT_MAX]; // Input rectangles D2D1_RECT_L rOut; // Output rectangle struct Constants_s { float clrFog[4]; // Fog color } Constants; }; class FogEffect { public: static FogEffect *Ptr(HFOG hFog); static int Create(HFOG &hFog, ID2D1Factory1 *pD2Factory, ID2D1Device *pDevice, ID2D1DeviceContext *pdcDst, D2D1_SIZE_U szFog, UINT Radius); int Destroy(void); int Reset(UINT32 clrFog); int ClearFog(POINT2D ptCenter, UINT Radius); int ClearStencil(ID2D1DeviceContext *pdcSrc, ID2D1Bitmap1 *pbmStencil, POINT2D *prLT); int DrawFog(ID2D1DeviceContext *pdcDst, ID2D1Bitmap1 *pbmSrc, ID2D1Bitmap1 *pbmDst); private: FogEffect(void); ~FogEffect(void); int Create2(ID2D1Factory1 *pD2Factory, ID2D1Device *pDevice, ID2D1DeviceContext *pdcDst, D2D1_SIZE_U szFog, UINT Radius); int CreateFogBitmap(void); int CreateEffect(ID2D1DeviceContext *pdcDst); //Data DWORD Signature; // Must be SIGNATURE_FOGEFFECT SIZE szFog; // Size (pixels) of Fog bitmap UINT DfltRadius; // Default radius ID2D1DeviceContext *pdcFog; // Used to create reference circles ID2D1Bitmap1 *pbmFog; ID2D1Effect *pEffect; // ID2D1Effect }; /*************************************************************************/ /** Public interface **/ /*************************************************************************/ int FogCreate(HFOG &hFog, ID2D1Factory1 *pD2Factory, ID2D1Device *pDevice, ID2D1DeviceContext *pdcDst, D2D1_SIZE_U szFog, UINT Radius) { return(FogEffect::Create(hFog,pD2Factory,pDevice,pdcDst,szFog,Radius)); } int FogDestroy(HFOG &hFog) { int Err= ERR_OK; if(hFog) { FogEffect *pFog= FogEffect::Ptr(hFog); if(!pFog || !IsErr(Err= pFog->Destroy())) hFog= 0; } return(Err); } int FogReset(HFOG hFog, UINT32 clrFog) { FogEffect *pFog= FogEffect::Ptr(hFog); return(pFog ? pFog->Reset(clrFog) : ERR_BAD_HANDLE); } int FogClear(HFOG hFog, POINT2D ptCenter, UINT Radius) { FogEffect *pFog= FogEffect::Ptr(hFog); return(pFog ? pFog->ClearFog(ptCenter,Radius) : ERR_BAD_HANDLE); } int FogClearStencil(HFOG hFog, ID2D1DeviceContext *pdcSrc, ID2D1Bitmap1 *pbmStencil, POINT2D *prLT) { FogEffect *pFog= FogEffect::Ptr(hFog); return(pFog ? pFog->ClearStencil(pdcSrc,pbmStencil,prLT) : ERR_BAD_HANDLE); } int FogDraw(HFOG hFog, ID2D1DeviceContext *pdcDst, ID2D1Bitmap1 *pbmSrc, ID2D1Bitmap1 *pbmDst) { FogEffect *pFog= FogEffect::Ptr(hFog); return(pFog ? pFog->DrawFog(pdcDst,pbmSrc,pbmDst) : ERR_BAD_HANDLE); } /*************************************************************************/ /** Private code **/ /*************************************************************************/ FogEffect *FogEffect::Ptr(HFOG hFog) { FogEffect *pFog= (FogEffect*)hFog; if(!hFog || IsBadPtr(pFog,sizeof(*pFog),BADPTR_RW) || pFog->Signature!=SIGNATURE_FOGEFFECT) pFog= 0; return(pFog); } FogEffect::FogEffect(void) { Signature= SIGNATURE_FOGEFFECT; DfltRadius= 60; } FogEffect::~FogEffect(void) { Signature|= SIGNATURE_INVALID; SafeRelease(pbmFog); SafeRelease(pdcFog); SafeRelease(pEffect); } int FogEffect::Create(HFOG &hFog, ID2D1Factory1 *pD2Factory, ID2D1Device *pDevice, ID2D1DeviceContext *pdcDst, D2D1_SIZE_U szFog, UINT DfltRadius) { int Err= ERR_OK; FogEffect *pFog= new FogEffect; if(!pFog) { Err= Warn(ERR_NO_MEM,"FogEffect:Create: NoMem."); } else if(IsErr(Err= pFog->Create2(pD2Factory,pDevice,pdcDst,szFog,DfltRadius))) { delete pFog; } else { hFog= (HFOG)pFog; } return(Err); } int FogEffect::Create2(ID2D1Factory1 *pD2Factory, ID2D1Device *pDevice, ID2D1DeviceContext *pdcDst, D2D1_SIZE_U _szFog, UINT _DfltRadius) { int Err= ERR_OK; HRESULT WinErr; DfltRadius= _DfltRadius; szFog= { (LONG)_szFog.width, (LONG)_szFog.height }; D2D1_DEVICE_CONTEXT_OPTIONS Opt= D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS; if(!SUCCEEDED(WinErr= pDevice->CreateDeviceContext(Opt,&pdcFog))) { Err= Warn(ERR_DIRECTX,"FogEffect:Create2: Unable to create pdcFog. [%X]",WinErr); } else if(IsErr(Err= CreateFogBitmap())) { Err= Warn(Err,"FogEffect:Create2: Unable to create pbmFog."); } else if(!SUCCEEDED(WinErr= FogBlendEffect::Register(pD2Factory))) { Err= Warn(ERR_DIRECTX,"FogEffect:Create2: Unable to register ID2D1Effect. [%X]",WinErr); } else if(IsErr(Err= CreateEffect(pdcDst))) { Err= Warn(Err,"FogEffect:Create2: Unable to create ID2D1Effect."); } return(Err); } int FogEffect::Destroy(void) { int Err= ERR_OK; delete this; return(Err); } int FogEffect::CreateFogBitmap(void) { int Err= ERR_OK; HRESULT WinErr; D2D1_SIZE_U szBitmap= { (UINT32)szFog.cx, (UINT32)szFog.cy }; D2D1_BITMAP_PROPERTIES1 bmProp; Zero(bmProp); bmProp.bitmapOptions= D2D1_BITMAP_OPTIONS_TARGET; bmProp.pixelFormat= PixelFormat(DXGI_FORMAT_R8G8B8A8_UNORM,D2D1_ALPHA_MODE_PREMULTIPLIED); pdcFog->GetDpi(&bmProp.dpiX,&bmProp.dpiY); if(!SUCCEEDED(WinErr= pdcFog->CreateBitmap(szBitmap,0,0,bmProp,&pbmFog))) { Err= Warn(ERR_DIRECTX,"FogEffect:CreateFogBitmap: Unable to create %ux%u bitmap. [%X]",szFog.cx,szFog.cy,WinErr); } return(Err); } // Create the FogBlendEffect, which loads the pixel shader. int FogEffect::CreateEffect(ID2D1DeviceContext *pdcDst) { int Err= ERR_OK; HRESULT WinErr; if(!SUCCEEDED(WinErr= pdcDst->CreateEffect(CLSID_FogEffect,&pEffect))) { Err= Warn(ERR_DIRECTX,"FogEffect:CreateEffect: Failed. [%X]",WinErr); } else { Print(PRINT_DEBUG,"FogEffect:CreateEffect: OK."); } return(Err); } // Reset pFogPixels to 0, update the fog color. int FogEffect::Reset(UINT32 clrFog) { int Err= ERR_OK; if(pdcFog && pbmFog) { pdcFog->BeginDraw(); pdcFog->SetTarget(pbmFog); pdcFog->Clear(0); } if(pEffect) pEffect->SetValueByName(L"clrFog",clrFog); return(Err); } int FogEffect::ClearFog(POINT2D ptCenter, UINT Radius) { int Err= ERR_OK; if(!pdcFog || !pbmFog) { Err= Warn(ERR_NOT_CREATED,"FogEffect:ClearFog: No pdcFog or pbmFog."); } else { FLOAT radius= (FLOAT)Radius; ID2D1RadialGradientBrush *brFill= 0; ID2D1GradientStopCollection *pStops= 0; D2D1_GRADIENT_STOP Stops[2]= { { 0,ColorF(ColorF::Red,1.0f) },{ 1.0f,ColorF(ColorF::Red,0.0f) } }; pdcFog->SetTarget(pbmFog); pdcFog->CreateGradientStopCollection(Stops,2,D2D1_GAMMA_2_2,D2D1_EXTEND_MODE_CLAMP,&pStops); pdcFog->CreateRadialGradientBrush(RadialGradientBrushProperties(ptCenter,Point2F(0,0),radius,radius),pStops,&brFill); pdcFog->FillEllipse(Ellipse(ptCenter,radius,radius),brFill); brFill->Release(); pStops->Release(); } return(Err); } int FogEffect::ClearStencil(ID2D1DeviceContext *pdcSrc, ID2D1Bitmap1 *pbmStencil, POINT2D *prLT) { int Err= ERR_OK; return(Err); } int FogEffect::DrawFog(ID2D1DeviceContext *pdcDst, ID2D1Bitmap1 *pbmSrc, ID2D1Bitmap1 *pbmDst) { ProfilerAuto prf(PROF_FOG_DRAW); int Err= ERR_OK; if(!pEffect) { Err= Warn(ERR_NOT_CREATED,"FogEffect:DrawFog: ID2D1Effect has not been created."); } else if(!pdcFog || !pbmFog) { Err= Warn(ERR_NOT_CREATED,"FogEffect:DrawFog: No FogPixels."); } else { pdcFog->EndDraw(); pEffect->SetInput(0,pbmFog); pEffect->SetInput(1,pbmSrc); pdcDst->SetTarget(pbmDst); pdcDst->DrawImage(pEffect); } return(Err); } /*************************************************************************/ /** FogBlendEffect **/ /************************************************************************* FogBlendEffect uses the FogShader pixel shader to blend a fog and source bitmap into the final output bitmap. The shader uses the RED channel of the fog bitmap as the alpha multiplier to mix between the fog color and the source pixels. TODO: Add a swirling effect to the fog. *************************************************************************/ FogBlendEffect::FogBlendEffect(void) { Signature= SIGNATURE_FOGBLEND; ctReference= 1; ctInput= 0; Zero(Constants.clrFog); } FogBlendEffect::~FogBlendEffect(void) { Signature|= SIGNATURE_INVALID; } ULONG FogBlendEffect::Release(void) { if(--ctReference > 0) return(ctReference); delete this; return(0); } HRESULT FogBlendEffect::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(ID2D1ComputeTransform)) { Print(PRINT_DEBUG,"FogBlendEffect:QueryInterface: I am not a compute transform."); WinErr= E_NOINTERFACE; } else if(riid==__uuidof(ID2D1SourceTransform)) { Print(PRINT_DEBUG,"FogBlendEffect:QueryInterface: I am not a source transform."); WinErr= E_NOINTERFACE; } else if(riid==__uuidof(IUnknown)) { pInterface= this; } else { WinErr= E_NOINTERFACE; } if(ppInterface) { *ppInterface= pInterface; if(pInterface) AddRef(); } return(WinErr); } HRESULT FogBlendEffect::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='FogOfWar'/>\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='Obscuring fog'/>\r\n" L" <Inputs minimum='0' maximum='2'>\r\n" // Source must be specified. L" <Input name='Source1'/>\r\n" L" <Input name='Source2'/>\r\n" L" </Inputs>\r\n" L" <!-- Custom Properties go here. -->\r\n" L" <Property name='clrFog' type='uint32'>\r\n" L" <Property name='DisplayName' type='string' value='clrFog'/>\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"clrFog",&SetFogColor,&GetFogColor) }; if(!SUCCEEDED(WinErr= pFactory->RegisterEffectFromString(CLSID_FogEffect,pszXml,Bindings,ARRAYSIZE(Bindings),CreateD2Effect))) { Error(ERR_DIRECTX,"FogBlendEffect:Register: RegisterEffectFromString() failed. [%X]",WinErr); } return(WinErr); } HRESULT FogBlendEffect::SetFogColor(UINT32 clr) { Constants.clrFog[0]= (float)((clr>>16) & 0xFF)/255.0f; // blue Constants.clrFog[1]= (float)((clr>> 8) & 0xFF)/255.0f; // green Constants.clrFog[2]= (float)((clr ) & 0xFF)/255.0f; // red Constants.clrFog[3]= 1.0f; // alpha return(S_OK); } UINT32 FogBlendEffect::GetFogColor(void) const { UINT32 clr= 0; clr|= (UINT32)(Constants.clrFog[0]*255.0f)<<16; clr|= (UINT32)(Constants.clrFog[1]*255.0f)<<8; clr|= (UINT32)(Constants.clrFog[2]*255.0f); clr|= 0xFF000000; return(clr); }; // This is called by Direct2D in response to ID2D1DeviceContext::CreateEffect() -- Do not call directly. HRESULT __stdcall FogBlendEffect::CreateD2Effect(IUnknown **ppEffect) { HRESULT WinErr= S_OK; *ppEffect= static_cast<ID2D1EffectImpl*>(new FogBlendEffect); if(!*ppEffect) { WinErr= E_OUTOFMEMORY; } return(WinErr); } HRESULT FogBlendEffect::Initialize(ID2D1EffectContext *_pCtx, ID2D1TransformGraph *pGraph) { HRESULT WinErr= S_OK; ID3DBlob *pCode= 0; ID3DBlob *pError= 0; pCtx= _pCtx; if(!SUCCEEDED(WinErr= D3DReadFileToBlob(L"FogShader.cso",&pCode))) { Warn(ERR_FILE_READ,"FogBlendEffect:Initialize: Unable to read shader. [%X]",WinErr); } else if(!SUCCEEDED(WinErr= pCtx->LoadPixelShader(GUID_FogShader,(BYTE*)pCode->GetBufferPointer(),(UINT32)pCode->GetBufferSize()))) { Warn(ERR_DIRECTX,"FogBlendEffect:Initialize: Unable to create pixel shader. [%X]",WinErr); } else if(!SUCCEEDED(WinErr= pGraph->AddNode(this))) { Warn(ERR_DIRECTX,"FogBlendEffect:Initialize: Unable to add Node. [%X]",WinErr); } else if(!SUCCEEDED(WinErr= pGraph->SetOutputNode(this))) { Warn(ERR_DIRECTX,"FogBlendEffect:Initialize: Unable to set output node. [%X]",WinErr); } else if(!SUCCEEDED(WinErr= pGraph->ConnectToEffectInput(0,this,0))) { Warn(ERR_DIRECTX,"FogBlendEffect:Initialize: Unable to connect input 0. [%X]",WinErr); } else if(!SUCCEEDED(WinErr= pGraph->ConnectToEffectInput(1,this,1))) { Warn(ERR_DIRECTX,"FogBlendEffect:Initialize: Unable to connect input 1. [%X]",WinErr); } else { ctInput= 2; Print(PRINT_INFO,"FogBlendEffect:Initialize: OK."); } SafeRelease(pCode); SafeRelease(pError); return(WinErr); } // This is a single-transform, single-node graph, SetGraph() should never be called. HRESULT FogBlendEffect::SetGraph(ID2D1TransformGraph *pGraph) { Warn(ERR_DIRECTX,"FogBlendEffect:SetGraph: Should not be called."); return(E_NOTIMPL); } HRESULT FogBlendEffect::PrepareForRender(D2D1_CHANGE_TYPE Type) { HRESULT WinErr= S_OK; pDraw->SetPixelShaderConstantBuffer((BYTE*)&Constants,sizeof(Constants)); return(WinErr); } // ID2D1DrawTransform HRESULT FogBlendEffect::SetDrawInfo(ID2D1DrawInfo *_pDraw) { HRESULT WinErr= S_OK; pDraw= _pDraw; if(!SUCCEEDED(WinErr= pDraw->SetPixelShader(GUID_FogShader))) { Warn(ERR_DIRECTX,"FogBlendEffect:SetDrawInfo: SetPixelShader() failed. [%X]",WinErr); } return(WinErr); } IFACEMETHODIMP FogBlendEffect::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; //Print(PRINT_DEBUG,"MyEffect:MapInputRectsToOutputRect: ctIn=%u",ctIn); if(ctIn==0) { Warn(ERR_DIRECTX,"FogBlendEffect:MapInputRectsToOutputRect: ctIn is zero?"); } else if(ctIn>INPUT_MAX) { Warn(ERR_DIRECTX,"FogBlendEffect:MapInputRectsToOutputRect: Only %u inputs defined. [%d]",ctInput,ctIn); WinErr= E_INVALIDARG; } else if(ctIn>0) { for(UINT n1=0;n1<ctIn;n1++) { rIn[n1]= prIn[n1]; //Print(PRINT_DEBUG,"MyEffect:MapInToOut: %u (%d,%d) %dx%d",n1,prIn[n1].left,prIn[n1].top,RWID(prIn[n1]),RHGT(prIn[n1])); if(n1==0) { rOut= prIn[n1]; } else { // Expand prOut to the union of all prIn. rOut.left= min(rOut.left,prIn[n1].left); rOut.top= min(rOut.top,prIn[n1].top); rOut.right= max(rOut.right,prIn[n1].right); rOut.bottom= max(rOut.bottom,prIn[n1].bottom); } } *prOut= rOut; //Print(PRINT_DEBUG,"MyEffect:MapInToOut: rOut (%d,%d) %dx%d",rOut.left,rOut.top,RWID(rOut),RHGT(rOut)); Zero(*prOutOpaque); } return(WinErr); } HRESULT FogBlendEffect::MapOutputRectToInputRects(const D2D1_RECT_L *prOut, _Out_writes_(ctIn) D2D1_RECT_L *prIn, UINT32 ctIn) const { HRESULT WinErr= S_OK; //Print(PRINT_DEBUG,"MyEffect:MapOutputRectToInputRects: ctIn=%u",ctIn); if(ctIn==0) { Warn(ERR_DIRECTX,"FogBlendEffect:MapOutputRectToInputRects: ctIn is zero?"); } else if(ctIn>INPUT_MAX) { Warn(ERR_DIRECTX,"FogBlendEffect:MapOutputRectToInputRects: Only %u inputs defined. [%d]",ctInput,ctIn); WinErr= E_INVALIDARG; } else if(ctIn>0) { //Print(PRINT_DEBUG,"MyEffect:MapOutToIn: rOut (%d,%d) %dx%d",prOut->left,prOut->top,RWID(*prOut),RHGT(*prOut)); for(UINT n1=0;n1<ctIn;n1++) { prIn[n1]= *prOut; //Print(PRINT_DEBUG,"MyEffect:MapOutToIn: rIn[%u] (%d,%d) %dx%d",n1,prIn[n1].left,prIn[n1].top,RWID(prIn[n1]),RHGT(prIn[n1])); } } return(WinErr); } IFACEMETHODIMP FogBlendEffect::MapInvalidRect(UINT32 nIn, D2D1_RECT_L rInInvalid, D2D1_RECT_L *prOutInvalid) const { HRESULT WinErr= S_OK; //Print(PRINT_DEBUG,"MyEffect:MapInvalidRect:"); // Set entire output to invalid *prOutInvalid= rOut; return(WinErr); } IFACEMETHODIMP_(UINT32) FogBlendEffect::GetInputCount(void) const { return(INPUT_MAX); } //EOF: FOGEFFECT.CPP

The FogShader pixel shader takes two inputs and a constants parameter:

cbuffer constants { float4 clrFog:COLOR; //The color of the fog. }; Input0: The RED channel is used to blend fog and pbmSrc. RED=0 is pure fog, RED=1 is pure pbmSrc. Input1: The source bitmap pbmSrc.

FogShader.hlsl: /*************************************************************************/ /** FogShader.hlsl: Fog of War **/ /** (C)2022 nlited systems, cmd **/ /*************************************************************************/ #define D2D_INPUT_COUNT 2 #include "d2d1effecthelpers.hlsli" // This shader assumes RGBA (Red in the first, least-significant position) pixels. cbuffer constants: register(b0) { float4 clrFog:COLOR; }; // Input0 is an alpha-map, RED is the alpha blend channel. // Input1 is the field image, copied to the output depending on the Input0.blue value. D2D_PS_ENTRY(main) { float4 AlphaMap= D2DGetInput(0); float4 Pixel= D2DGetInput(1); float4 OutPxl; if(AlphaMap.g) { // Any g means b overflowed. Pixel.a= 1.0; // Alpha is saturated, rgb from Input1. } else if(AlphaMap.r) { Pixel.a= AlphaMap.r; // Copy alpha from Input0, rgb from Input1. } else { Pixel.a= 0; // Alpha blend is 0 } // Use the alpha channel to blend Pixel/Fog. OutPxl.a= 1.0; OutPxl.rgb= Pixel.a*Pixel.rgb + (1.0-Pixel.a)*clrFog.rgb; return(OutPxl); } //EOF: FOGSHADER.HLSL


WebV7 (C)2018 nlited | Rendered by tikope in 51.735ms | 3.12.123.41