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
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