From 840395c7f4e79f0443db11747a721debddfdf220 Mon Sep 17 00:00:00 2001 From: Ivan Ushakov Date: Sat, 25 Jul 2026 22:27:55 +0300 Subject: [PATCH 1/2] Render: drop the stub bodies nothing references any more RenderStub.cpp carried link-only bodies for symbols the retired D3D9 backend used to define. Some are still demanded -- by live code, at that: GrassMap:: BuildGrass wants ColorByNormalRGBA, cTexture::CalcTextureSize wants GetTextureFormatSize, LensFlareRenderer and cOcclusionSilouette construct a cOcclusionQuery, GameShell owns an sVideoWrite. Those stay. These do not. Verified by deleting them and relinking (Debug and RelWithDebInfo, both clean): DrawStrip::Begin/End PoolManager, Pool, VertexPool, IndexPool (the D3D9 tilemap page allocator) DrawType::BeginDraw/SetTileColor cTileMapRender (7 members) cD3DRender::LockTexture x2, UnlockTexture, DrawQuad, createRenderTargets, deleteRenderTargets, CreateFloatTexture, CreateMirageMap, SetAdvance, SetBlendState, SetRenderTarget1 DrawStrip is the one worth a note rather than a silent delete: its Set() is an inline that writes into a locked cVertexBuffer we do not have, so a body would leave the pointer garbage. Without one, a new caller now fails at link time instead of corrupting the heap at run time -- strictly better than the assert that was there. The comment says so, and says where callers should go instead. D3DRenderTilemap.h goes with cTileMapRender; nothing else in the file needed it. Co-Authored-By: Claude Opus 5 --- Render/RenderStub.cpp | 85 +++++++++---------------------------------- 1 file changed, 17 insertions(+), 68 deletions(-) diff --git a/Render/RenderStub.cpp b/Render/RenderStub.cpp index 55c2dff1..9ae26a4a 100644 --- a/Render/RenderStub.cpp +++ b/Render/RenderStub.cpp @@ -14,7 +14,6 @@ #include "VisGeneric.h" #include "SDLRenderDevice.h" #include "D3DRender.h" -#include "D3DRenderTilemap.h" #include "src/WinVideo.h" // --------------------------------------------------------------------------- @@ -146,43 +145,19 @@ void cD3DRender::RegisterVertexDeclaration(LPDIRECT3DVERTEXDECLARATION9& declara } // --------------------------------------------------------------------------- -// DrawStrip / PoolManager -// --------------------------------------------------------------------------- -// DrawStrip has no off-Windows implementation: its Set() is an inline in +// DrawStrip has no implementation, deliberately. Its Set() is an inline in // Render/D3D/VertexBuffer.h that writes straight into a locked cVertexBuffer, and there is -// no such buffer here. These bodies exist only so the class still links -- and they leave -// `buf` null and `pointer` uninitialised, so the first Set() writes through a garbage -// pointer. Every off-Windows caller therefore takes SDLWorldQuadRenderer's triangle route -// instead (cUnkLight::Draw, CircleManager::Layer::drawSpline, Lighting::OneLight::Draw); -// the assert is here to catch a new one before it corrupts the heap. -void DrawStrip::Begin() { xassert(0 && "DrawStrip is Windows-only: use SDLWorldQuadRenderer"); } -void DrawStrip::End() {} - -PoolManager::PoolManager() {} -PoolManager::~PoolManager() {} - -// Pool hierarchy (D3D/PoolManager.cpp): defining Pool's virtual dtor (key -// function) emits its vtable+typeinfo; VertexPool/IndexPool define their full -// override set so their vtables resolve when constructed. -Pool::Pool() : total_pages(0), free_pages(0), free_pages_list(0), parameter(0) {} -Pool::~Pool() {} - -VertexPool::VertexPool() : vb(0), page_size(0), vertex_declaration(0), vertex_size(0) {} -VertexPool::~VertexPool() {} -void VertexPool::Create(const PoolParameter*) {} -void VertexPool::Select(int) {} -void* VertexPool::LockPage(int) { return 0; } -void VertexPool::UnlockPage(int) {} -void VertexPool::GetUsedMemory(int& total, int& free) { total = 0; free = 0; } -void* VertexPool::InternalLockPage(int) { return 0; } - -IndexPool::IndexPool() : ib(0), page_size(0) {} -IndexPool::~IndexPool() {} -void IndexPool::Create(const PoolParameter*) {} -void IndexPool::Select(int) {} -void* IndexPool::LockPage(int) { return 0; } -void IndexPool::UnlockPage(int) {} -void IndexPool::GetUsedMemory(int& total, int& free) { total = 0; free = 0; } +// no such buffer here: a body would leave `buf` null and `pointer` uninitialised, so the +// first Set() would write through a garbage pointer. Every caller takes +// SDLWorldQuadRenderer's triangle route instead (cUnkLight::Draw, +// CircleManager::Layer::drawSpline, Lighting::OneLight::Draw), and a new one now fails at +// link time rather than corrupting the heap at run time. +// +// PoolManager / Pool / VertexPool / IndexPool (the D3D9 tilemap's page allocator), DrawType +// and cTileMapRender were stubbed here for the same reason and are not stubbed any more: +// nothing left in the build references them. They live on in Render/D3D/, which is compiled +// nowhere. +// --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // Render free functions (D3D/*.cpp): logging, format/size queries, debug stats. @@ -235,44 +210,18 @@ void cOcclusionQuery::Begin() {} void cOcclusionQuery::End() {} // --------------------------------------------------------------------------- -// DrawType (abstract; only these non-virtual members are referenced — DrawType -// itself is never constructed off-Windows, so no vtable is needed). -// --------------------------------------------------------------------------- -void DrawType::BeginDraw() {} -void DrawType::SetTileColor(Color4f /*color*/) {} - -// --------------------------------------------------------------------------- -// cTileMapRender (polymorphic via ManagedResource; defining the destructor -// emits its vtable + typeinfo, so the pure-virtual overrides are defined too). -// --------------------------------------------------------------------------- -cTileMapRender::cTileMapRender(cTileMap* /*pTileMap*/) {} -cTileMapRender::~cTileMapRender() {} -void cTileMapRender::deleteManagedResource() {} -void cTileMapRender::restoreManagedResource() {} -void cTileMapRender::dumpManagedResource(XBuffer& /*buffer*/) {} -void cTileMapRender::PreDraw(Camera* /*camera*/) {} -void cTileMapRender::DrawBump(Camera* /*camera*/, eBlendMode /*MatMode*/, bool /*shadow*/, bool /*zbuffer*/) {} - -// --------------------------------------------------------------------------- -// cD3DRender — the helper (non-virtual) methods called from compiled code. +// cD3DRender — the helper (non-virtual) methods still named by compiled code. +// Every call reaches them through the permanently null gb_RenderDevice3D, so none of +// these bodies ever runs; they exist so those call sites link. Only the methods the +// linker actually asks for are here — the rest of cD3DRender's helpers went with the +// pools above. // --------------------------------------------------------------------------- -void* cD3DRender::LockTexture(cTexture* /*Texture*/, int& /*Pitch*/) { return 0; } -void* cD3DRender::LockTexture(cTexture* /*Texture*/, int& /*Pitch*/, Vect2i /*lock_min*/, Vect2i /*lock_size*/) { return 0; } -void cD3DRender::UnlockTexture(cTexture* /*Texture*/) {} -void cD3DRender::DrawQuad(float, float, float, float, float, float, float, float, Color4c) {} Mat4f cD3DRender::shadowMatBias() const { return Mat4f(); } -bool cD3DRender::createRenderTargets(int /*xysize*/) { return false; } -void cD3DRender::deleteRenderTargets() {} -bool cD3DRender::CreateFloatTexture(int /*width*/, int /*height*/) { return false; } -void cD3DRender::CreateMirageMap(int /*x*/, int /*y*/, bool /*recreate*/) {} -void cD3DRender::SetAdvance(bool /*is_shadow*/) {} void cD3DRender::SetAnisotropic(int /*level*/) {} int cD3DRender::GetAnisotropic() { return 0; } int cD3DRender::GetMaxAnisotropicLevels() { return 0; } -void cD3DRender::SetBlendState(eBlendMode /*blend*/) {} void cD3DRender::SetBlendStateAlphaRef(eBlendMode /*blend*/) {} void cD3DRender::SetRenderTarget(cTexture* /*target*/, IDirect3DSurface9* /*pZBuffer*/) {} -void cD3DRender::SetRenderTarget1(cTexture* /*target1*/) {} void cD3DRender::RestoreRenderTarget() {} void cD3DRender::FlushPrimitive3DWorld() {} bool cD3DRender::ReinitOcclusion() { return false; } From 9aec189b84ab3d0815b581f69847f6a9caa22c76 Mon Sep 17 00:00:00 2001 From: Ivan Ushakov Date: Sat, 25 Jul 2026 22:33:22 +0300 Subject: [PATCH 2/2] Render: say what RenderStub.cpp is now, not what it was The header still described the D3D9 backend as "the real renderer", the SDL one as "Track B", and this file as no-op definitions for a silent renderer on the platforms D3D could not reach. All three stopped being true when D3D9 was retired -- which is why the file reads as disposable when it holds the only CreateIRenderDevice the engine has. Rewritten to describe the two things actually in it: the renderer's entry point and backend globals, and the seam retirement could not reach -- portable engine code that still speaks D3D9 nouns (GetTextureFormatSize, ColorByNormalRGBA, IDirect3DSurface9* parameters) and still owns classes whose only implementation was D3D9 (cOcclusionQuery, sVideoWrite, the dynamic buffer family). It also draws a distinction the old header did not, and that cost time to rediscover: the cD3DRender:: helpers are unreachable, every call arriving through the null gb_RenderDevice3D -- but the other bodies DO run and their returns are consumed. GrassMap::BuildGrass writes ColorByNormalRGBA's result into every bush, cOcclusionQuery answers IsVisible() = true, GetTextureFormatSize reports 0 bpp. Answers the game acts on, not silence. Same pass over the section comments, which framed everything as Windows vs off-Windows: the samplers, CreateIRenderDevice, RegisterVertexDeclaration, cSkinVertex, the sPtr bodies. Comments only. Co-Authored-By: Claude Opus 5 --- Render/RenderStub.cpp | 70 ++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/Render/RenderStub.cpp b/Render/RenderStub.cpp index 9ae26a4a..e8361f8c 100644 --- a/Render/RenderStub.cpp +++ b/Render/RenderStub.cpp @@ -1,15 +1,30 @@ -// Render-backend stub for the cross-platform build. +// Where the renderer is created, and the seam the D3D9 retirement left behind. // -// The real renderer is the D3D9 backend (Render/D3D/*.cpp), which drives -// IDirect3DDevice9 / IDirect3D9 directly and is therefore Windows-only (gated in -// Render/CMakeLists.txt). Off-Windows the fake d3d9.h only makes the render -// headers parse, not the device-creation call sites, so we provide no-op -// definitions for the render-backend symbols the rest of the engine references. -// The real cross-platform renderer is the SDL GPU backend (Track B); until then -// the game links and runs with a silent renderer. +// CreateIRenderDevice() below is the engine's one entry point into the renderer: it builds +// gb_VisGeneric and the SDL GPU device every platform now draws through. The D3D9 backend it +// replaced is retired -- Render/D3D/*.cpp is compiled nowhere, nothing assigns +// gb_RenderDevice3D, so that pointer is null for the life of the process, on Windows too. +// Every D3D name below resolves through the fake d3d9.h; there is no Direct3D in this build. // -// Only the symbols actually referenced by the compiled (non-WIN32) sources are -// stubbed here — see the undefined-symbol set at the final Game link. +// The rest of the file is what retirement could not reach. Portable engine code still speaks +// D3D9 nouns -- cTexture::CalcTextureSize asks GetTextureFormatSize(D3DFORMAT), +// GrassMap::BuildGrass packs normals with ColorByNormalRGBA, Camera::SetRenderTarget takes an +// IDirect3DSurface9* -- and it still owns classes whose only implementation was D3D9: +// cOcclusionQuery (cObject3dx's silhouette test, the lens flare), sVideoWrite (GameShell's +// movie recorder), cVertexBufferInternal / cQuadBufferInternal (the dynamic buffer family, +// Documents/Render-PORTING.md #15). The call sites are real and have to link; these bodies are +// what they land on. +// +// Two different kinds of body, and the difference matters. The cD3DRender:: helpers are +// unreachable -- every call goes through the null gb_RenderDevice3D, so nothing runs them. +// The rest DO run, and their return values are consumed: GrassMap::BuildGrass writes +// ColorByNormalRGBA's result into every bush (so each one gets a white normal), cOcclusionQuery +// answers IsVisible() = true, GetTextureFormatSize reports 0 bits per pixel. Those are answers +// the game acts on, not silence. Before treating one as inert, check what reads it. +// +// Only the symbols the linker actually asks for are defined here. When a feature above is +// ported -- or the dead D3D9 reference sources go -- delete what goes with it; the way to find +// out what is still owed is to remove a body and relink. #include "StdAfxRD.h" #include "VisGeneric.h" #include "SDLRenderDevice.h" @@ -17,7 +32,8 @@ #include "src/WinVideo.h" // --------------------------------------------------------------------------- -// Globals (defined in D3DRender.cpp / RenderDevice.cpp on Windows). +// Globals. cD3DRender's constructor used to define and own these; it no longer runs +// anywhere, so gb_RenderDevice3D keeps the null it is initialised with here. // --------------------------------------------------------------------------- RENDER_API cInterfaceRenderDevice* gb_RenderDevice = 0; RENDER_API cD3DRender* gb_RenderDevice3D = 0; @@ -30,8 +46,8 @@ RENDER_API SAMPLER_DATA sampler_clamp_linear; RENDER_API SAMPLER_DATA sampler_clamp_anisotropic; // The six samplers the engine names when it calls SetSamplerData/SetSamplerDataVirtual. -// cD3DRender::InitSamplerConstants fills them on Windows; without this they stay zeroed -// off it, and a caller asking for sampler_wrap_linear (the selection frame's tiled centre) +// cD3DRender::InitSamplerConstants used to fill them at device init; without this they stay +// zeroed, and a caller asking for sampler_wrap_linear (the selection frame's tiled centre) // would be asking for address mode 0, which is not a mode at all. static void initSamplerConstants() { @@ -59,21 +75,19 @@ static void initSamplerConstants() sampler_clamp_point.addressv = DX_TADDRESS_CLAMP; sampler_clamp_point.addressw = DX_TADDRESS_CLAMP; - // cD3DRender rounds these to linear until SetAnisotropic(n) raises them, and nothing - // off-Windows raises them yet. + // cD3DRender rounded these to linear until SetAnisotropic(n) raised them, and nothing + // raises them now: cVisGeneric::SetAnisotropic forwards to the null gb_RenderDevice3D. sampler_wrap_anisotropic = sampler_wrap_linear; sampler_clamp_anisotropic = sampler_clamp_linear; } RENDER_API cInterfaceRenderDevice* CreateIRenderDevice(bool multiThread) { - // Half 1 of the real CreateIRenderDevice (RenderDevice.cpp): construct the - // backend-agnostic cVisGeneric so gb_VisGeneric is non-null. Its constructor - // is pure file-IO/config (no GPU device), and VisGeneric.cpp is compiled into - // this library off-Windows, so this links and runs today. + // Half 1, unchanged from the original (RenderDevice.cpp): construct the backend-agnostic + // cVisGeneric so gb_VisGeneric is non-null. Its constructor is pure file-IO/config and + // touches no GPU device. // - // Half 2: the cross-platform render device is the SDL GPU backend - // (cSDLRenderDevice), replacing the Windows-only cD3DRender. + // Half 2 is where the backends part: cSDLRenderDevice, not cD3DRender. initSamplerConstants(); gb_VisGeneric = new cVisGeneric(multiThread); return gb_RenderDevice = new cSDLRenderDevice; @@ -88,7 +102,7 @@ ManagedResource::~ManagedResource() {} // --------------------------------------------------------------------------- // sPtrVertexBuffer / sPtrIndexBuffer — lifetime routes through the interface -// (mirrors the Windows D3DRender.cpp bodies), so the SDL backend owns the GPU +// (the shape the D3DRender.cpp bodies had), so the SDL backend owns the GPU // buffers behind the slot and releases them on Destroy/dtor. // // The gb_RenderDevice guard matters for handles with *static storage duration*: @@ -127,10 +141,10 @@ void sPtrIndexBuffer::CopyAddRef(const sPtrIndexBuffer& from) } // --------------------------------------------------------------------------- -// Vertex-format declaration registration. On Windows this queues (decl,elements) -// pairs for CreateVertexDeclaration at device init; off-Windows there is no D3D -// object, so we point the declaration at its immortal static element table (from -// the BEGIN_VERTEX_DECLARATION macro) so the SDL backend can read the layout. +// Vertex-format declaration registration. The D3D9 original queued (decl,elements) +// pairs for CreateVertexDeclaration at device init; there is no D3D object to create +// now, so the declaration points at its immortal static element table (from the +// BEGIN_VERTEX_DECLARATION macro) and the SDL backend reads the layout straight off it. // The vertex::declaration statics themselves are defined by VertexDeclaration.cpp. // --------------------------------------------------------------------------- void cD3DRender::RegisterVertexDeclaration(LPDIRECT3DVERTEXDECLARATION9& declaration, @@ -192,8 +206,8 @@ void cQuadBufferInternal::EndDraw() {} void* cQuadBufferInternal::Get() { return 0; } void cQuadBufferInternal::SetMatrix(const MatXf& /*m*/) {} -// cSkinVertex (ctor / GetWeight / GetDeclaration / declaration[] / Register) is -// defined by VertexDeclaration.cpp, which is now compiled off-Windows too. +// cSkinVertex (ctor / GetWeight / GetDeclaration / declaration[] / Register) is not stubbed: +// VertexDeclaration.cpp is real, portable, and in the build. // --------------------------------------------------------------------------- // cOcclusionQuery