From e8e656487063e496f16827892fcc23dbc8c8c6fc Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Mar 2026 19:29:58 +0100 Subject: [PATCH 1/6] bugfix(smudge): Decouple smudge time step from render update --- Core/GameEngine/Include/GameClient/Smudge.h | 32 ++++++--- .../Source/GameClient/System/ParticleSys.cpp | 37 +++++++++++ .../Source/GameClient/System/Smudge.cpp | 65 +++++++++++++++---- .../Include/W3DDevice/GameClient/W3DSmudge.h | 2 +- .../Source/W3DDevice/GameClient/W3DSmudge.cpp | 21 ++++-- .../W3DDevice/GameClient/W3DParticleSys.cpp | 30 ++++----- 6 files changed, 139 insertions(+), 48 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/Smudge.h b/Core/GameEngine/Include/GameClient/Smudge.h index f9443ca41b1..a5951cffcad 100644 --- a/Core/GameEngine/Include/GameClient/Smudge.h +++ b/Core/GameEngine/Include/GameClient/Smudge.h @@ -28,15 +28,20 @@ struct Smudge : public DLNodeClass { + typedef void *Identifier; + W3DMPO_GLUE(Smudge) + Identifier m_identifier; //a number or pointer to identify this smudge Vector3 m_pos; //position of smudge center Vector2 m_offset; // difference in position between "texture" extraction and re-insertion for center vertex Real m_size; //size of smudge in world space. Real m_opacity; //alpha of center vertex, corners are assumed at 0 + Bool m_draw; //whether this smudge needs to be drawn struct smudgeVertex - { Vector3 pos; //world-space position of vertex + { + Vector3 pos; //world-space position of vertex Vector2 uv; //uv coordinates of vertex }; smudgeVertex m_verts[5]; //5 vertices of this smudge (in counter-clockwise order, starting at top-left, ending in center.) @@ -44,23 +49,28 @@ struct Smudge : public DLNodeClass struct SmudgeSet : public DLNodeClass { - W3DMPO_GLUE(SmudgeSet) - -public: - friend class SmudgeManager; + W3DMPO_GLUE(SmudgeSet) + SmudgeSet(); virtual ~SmudgeSet() override; + void reset(); + void resetDraw(); + + Smudge *addSmudgeToSet(Smudge::Identifier identifier); ///< add and return a smudge to the set with the given identifier + void removeSmudgeFromSet(Smudge *&smudge); ///< remove and invalidate the given smudge + Smudge *findSmudge(Smudge::Identifier identifier); ///< find the smudge that belongs to this identifier - Smudge *addSmudgeToSet(); - void removeSmudgeFromSet ( Smudge &mySmudge); DLListClass &getUsedSmudgeList() { return m_usedSmudgeList;} Int getUsedSmudgeCount() { return m_usedSmudgeCount; } /// SmudgeIdToPtrMap; + DLListClass m_usedSmudgeList; /// m_freeSmudgeList; ///getHardwareSupport() && TheGlobalData->m_useHeatEffects; + + if (drawSmudge) + { + // TheSuperHackers @bugfix The smudge time step is now decoupled from the render update. + // This clears all prior smudges and recreates them for all current smudge particles. + + TheSmudgeManager->reset(); + SmudgeSet *set = TheSmudgeManager->addSmudgeSet(); //global smudge set through which all smudges are rendered. + + for (ParticleSystemManager::ParticleSystemListIt it = m_allParticleSystemList.begin(); it != m_allParticleSystemList.end(); ++it) + { + ParticleSystem *sys = (*it); + if (!sys) + continue; + + // only look at particle/point style systems + if (sys->isUsingDrawables()) + continue; + + // temporary hack that checks if texture name starts with "SMUD" - if so, we can assume it's a smudge type + if (/*sys->isUsingSmudge()*/ *((DWORD *)sys->getParticleTypeName().str()) == 0x44554D53) + { + for (Particle *p = sys->getFirstParticle(); p; p = p->m_systemNext) + { + const Coord3D *pos = p->getPosition(); + Smudge *smudge = set->addSmudgeToSet(p); + smudge->m_pos.Set(pos->x, pos->y, pos->z); + smudge->m_offset.Set(GameClientRandomValueReal(-0.06f,0.06f), GameClientRandomValueReal(-0.03f,0.03f)); + smudge->m_size = p->getSize(); + smudge->m_opacity = p->getAlpha(); + } + } + } + } } // ------------------------------------------------------------------------------------------------ diff --git a/Core/GameEngine/Source/GameClient/System/Smudge.cpp b/Core/GameEngine/Source/GameClient/System/Smudge.cpp index 77b1b8a301a..57a532af2af 100644 --- a/Core/GameEngine/Source/GameClient/System/Smudge.cpp +++ b/Core/GameEngine/Source/GameClient/System/Smudge.cpp @@ -76,6 +76,14 @@ void SmudgeManager::reset() } } +void SmudgeManager::resetDraw() +{ + SmudgeSet* smudgeSet = m_usedSmudgeSetList.Head(); + for (; smudgeSet; smudgeSet = smudgeSet->Succ()) { + smudgeSet->resetDraw(); + } +} + SmudgeSet *SmudgeManager::addSmudgeSet() { SmudgeSet* set=m_freeSmudgeSetList.Head(); @@ -89,12 +97,25 @@ SmudgeSet *SmudgeManager::addSmudgeSet() return set; } -void SmudgeManager::removeSmudgeSet(SmudgeSet &mySmudge) +void SmudgeManager::removeSmudgeSet(SmudgeSet *&smudgeSet) { - mySmudge.Remove(); //remove from used list - m_freeSmudgeSetList.Add_Head(&mySmudge); //add to free list. + smudgeSet->Remove(); //remove from used list + m_freeSmudgeSetList.Add_Head(smudgeSet); //add to free list. + smudgeSet = nullptr; } +Smudge *SmudgeManager::findSmudge(Smudge::Identifier identifier) +{ + SmudgeSet *smudgeSet = m_usedSmudgeSetList.Head(); + for (; smudgeSet; smudgeSet = smudgeSet->Succ()) { + if (Smudge *smudge = smudgeSet->findSmudge(identifier)) { + return smudge; + } + } + return nullptr; +} + + SmudgeSet::SmudgeSet() { m_usedSmudgeCount=0; @@ -113,26 +134,46 @@ void SmudgeSet::reset() m_usedSmudgeList.Remove_Head (); m_freeSmudgeList.Add_Head(head); //add to free list } + + m_usedSmudgeMap.clear(); +} + +void SmudgeSet::resetDraw() +{ + Smudge* smudge = m_usedSmudgeList.Head(); + for (; smudge; smudge = smudge->Succ()) { + smudge->m_draw = false; + } } -Smudge *SmudgeSet::addSmudgeToSet() +Smudge *SmudgeSet::addSmudgeToSet(Smudge::Identifier identifier) { - Smudge* smudge=m_freeSmudgeList.Head(); + Smudge* smudge = m_freeSmudgeList.Head(); if (smudge) { smudge->Remove(); //remove from free list - m_usedSmudgeList.Add_Tail(smudge); //add to used list. - m_usedSmudgeCount++; - return smudge; + } else { + smudge = W3DNEW Smudge(); } - smudge=W3DNEW Smudge(); + smudge->m_identifier = identifier; m_usedSmudgeList.Add_Tail(smudge); //add to used list. + m_usedSmudgeMap[identifier] = smudge; m_usedSmudgeCount++; return smudge; } -void SmudgeSet::removeSmudgeFromSet(Smudge &mySmudge) +void SmudgeSet::removeSmudgeFromSet(Smudge *&smudge) { - mySmudge.Remove(); //remove from used list. - m_freeSmudgeList.Add_Head(&mySmudge); //add to free list + m_usedSmudgeMap.erase(smudge->m_identifier); + smudge->Remove(); //remove from used list. + m_freeSmudgeList.Add_Head(smudge); //add to free list + smudge = nullptr; m_usedSmudgeCount--; } + +Smudge *SmudgeSet::findSmudge(Smudge::Identifier identifier) +{ + SmudgeIdToPtrMap::const_iterator it = m_usedSmudgeMap.find(identifier); + if (it != m_usedSmudgeMap.end()) + return it->second; + return nullptr; +} diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DSmudge.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DSmudge.h index 080dec5759f..66ad505cefe 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DSmudge.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DSmudge.h @@ -32,7 +32,7 @@ class DX8IndexBufferClass; //#define USE_COPY_RECTS 1 //this was the old method that didn't render to texture. Just copied backbuffer into texture. Slow on Nvidia. -class W3DSmudgeManager : public SmudgeManager +class W3DSmudgeManager final : public SmudgeManager { public: W3DSmudgeManager(); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp index 576aba343a3..8f76eabb11d 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp @@ -366,7 +366,8 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) Int count = 0; if (set) - { //there are possibly some smudges to render, so make sure background particles have finished drawing. + { + //there are possibly some smudges to render, so make sure background particles have finished drawing. SortingRendererClass::Flush(); //draw sorted translucent polys like particles. } @@ -374,8 +375,11 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) { Smudge *smudge=set->getUsedSmudgeList().Head(); - while (smudge) + for (; smudge; smudge = smudge->Succ()) { + if (!smudge->m_draw) + continue; + //Get view-space center Matrix3D::Transform_Vector(view,smudge->m_pos,&vsVert); @@ -385,6 +389,8 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) //Do center vertex outside 'for' loop since it's different. verts[4].pos = vsVert; + Vector2 offset = smudge->m_offset; + for (Int i=0; i<4; i++) { verts[i].pos = vsVert + vertex_offsets[i] * smudge->m_size; @@ -399,26 +405,27 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) // Zero coordinates that fall outside valid texel bounds if (thisUV.X < 0 || thisUV.X > texClampX) - smudge->m_offset.X = 0; + offset.X = 0; if (thisUV.Y < 0 || thisUV.Y > texClampY) - smudge->m_offset.Y = 0; + offset.Y = 0; } //Finish center vertex //Ge uv coordinates by interpolating corner uv coordinates and applying desired offset. uvSpanX=verts[3].uv.X - verts[0].uv.X; uvSpanY=verts[1].uv.Y - verts[0].uv.Y; - verts[4].uv.X=verts[0].uv.X+uvSpanX*(0.5f+smudge->m_offset.X); - verts[4].uv.Y=verts[0].uv.Y+uvSpanY*(0.5f+smudge->m_offset.Y); + verts[4].uv.X=verts[0].uv.X+uvSpanX*(0.5f+offset.X); + verts[4].uv.Y=verts[0].uv.Y+uvSpanY*(0.5f+offset.Y); count++; //increment visible smudge count. - smudge=smudge->Succ(); } set=set->Succ(); //advance to next node. } + m_smudgeCountLastFrame = count; + if (!count) { REF_PTR_RELEASE(background); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp index 7f311c1aa39..b1b792b3ac9 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp @@ -115,10 +115,6 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) /// @todo lorenzen sez: this should be debug only: m_onScreenParticleCount = 0; - Int visibleSmudgeCount = 0; - if (TheSmudgeManager) - TheSmudgeManager->setSmudgeCountLastFrame(0); //keep track of visible smudges - const FrustumClass & frustum = rinfo.Camera.Get_Frustum(); AABoxClass bbox; @@ -141,9 +137,12 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) m_fieldParticleCount = 0; - SmudgeSet *set=nullptr; - if (TheSmudgeManager) - set=TheSmudgeManager->addSmudgeSet(); //global smudge set through which all smudges are rendered. + const Bool drawSmudge = TheSmudgeManager && TheSmudgeManager->getHardwareSupport() && TheGlobalData->m_useHeatEffects; + + if (drawSmudge) + { + TheSmudgeManager->resetDraw(); + } ParticleSystemManager::ParticleSystemList &particleSysList = TheParticleSystemManager->getAllParticleSystems(); for( ParticleSystemManager::ParticleSystemListIt it = particleSysList.begin(); it != particleSysList.end(); ++it) @@ -160,9 +159,8 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) //temporary hack that checks if texture name starts with "SMUD" - if so, we can assume it's a smudge type if (/*sys->isUsingSmudge()*/ *((DWORD *)sys->getParticleTypeName().str()) == 0x44554D53) { - if (TheSmudgeManager && ((W3DSmudgeManager*)TheSmudgeManager)->getHardwareSupport() && TheGlobalData->m_useHeatEffects) + if (drawSmudge) { - //set-up all the per-particle for (Particle *p = sys->getFirstParticle(); p; p = p->m_systemNext) { const Coord3D *pos = p->getPosition(); @@ -178,13 +176,11 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) if (WWMath::Fabs( pos->z - bcZ ) > ( beZ + psize ) ) continue; - Smudge *smudge = set->addSmudgeToSet(); - - smudge->m_pos.Set( pos->x, pos->y, pos->z ); - smudge->m_offset.Set( GameClientRandomValueReal(-0.06f,0.06f), GameClientRandomValueReal(-0.03f,0.03f) ); - smudge->m_size = psize; - smudge->m_opacity = p->getAlpha(); - visibleSmudgeCount++; + if (Smudge *smudge = TheSmudgeManager->findSmudge(p)) + { + // The particle is in view. Draw the smudge! + smudge->m_draw = true; + } } } continue; @@ -376,7 +372,5 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) if(TheSmudgeManager) { ((W3DSmudgeManager *)TheSmudgeManager)->render(rinfo); - TheSmudgeManager->reset(); //clear all the smudges after rendering since we fill again each frame. - TheSmudgeManager->setSmudgeCountLastFrame(visibleSmudgeCount); } } From 8f8b5dea487a0586e1858584654fe0d586d1366c Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Mar 2026 20:21:14 +0100 Subject: [PATCH 2/6] Fix logic in W3DSmudgeManager::render --- .../Source/W3DDevice/GameClient/W3DSmudge.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp index 8f76eabb11d..ff627c558f7 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp @@ -488,8 +488,11 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) { Smudge *smudge=remainingSmudgeStart; - while (smudge) + for (; smudge; smudge=smudge->Succ()) { + if (!smudge->m_draw) + continue; + Smudge::smudgeVertex *smVerts = smudge->m_verts; //Check if we exceeded maximum number of smudges allowed per draw call. @@ -520,7 +523,6 @@ void W3DSmudgeManager::render(RenderInfoClass &rinfo) } smudgesInRenderBatch++; - smudge=smudge->Succ(); } set=set->Succ(); //advance to next node. From 519a3867ef122fe687dc75cfb03c3295ab0e0802 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Mar 2026 20:21:25 +0100 Subject: [PATCH 3/6] Fix STLPort compile error --- Core/GameEngine/Include/GameClient/Smudge.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Core/GameEngine/Include/GameClient/Smudge.h b/Core/GameEngine/Include/GameClient/Smudge.h index a5951cffcad..f36e12c857d 100644 --- a/Core/GameEngine/Include/GameClient/Smudge.h +++ b/Core/GameEngine/Include/GameClient/Smudge.h @@ -47,6 +47,16 @@ struct Smudge : public DLNodeClass smudgeVertex m_verts[5]; //5 vertices of this smudge (in counter-clockwise order, starting at top-left, ending in center.) }; +#ifdef USING_STLPORT +namespace std +{ + template<> struct hash + { + size_t operator()(Smudge::Identifier id) const { return reinterpret_cast(id); } + }; +} +#endif // USING_STLPORT + struct SmudgeSet : public DLNodeClass { friend class SmudgeManager; From ac546750b342da458a0b6dc3b0a4f4291d51075a Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Mar 2026 20:40:54 +0100 Subject: [PATCH 4/6] Fix counter in SmudgeSet::reset --- Core/GameEngine/Source/GameClient/System/Smudge.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/GameEngine/Source/GameClient/System/Smudge.cpp b/Core/GameEngine/Source/GameClient/System/Smudge.cpp index 57a532af2af..3bc5b973e6f 100644 --- a/Core/GameEngine/Source/GameClient/System/Smudge.cpp +++ b/Core/GameEngine/Source/GameClient/System/Smudge.cpp @@ -136,6 +136,7 @@ void SmudgeSet::reset() } m_usedSmudgeMap.clear(); + m_usedSmudgeCount = 0; } void SmudgeSet::resetDraw() From eb5820137015d6235e80eeef1bda3a5222b0bbae Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 22 Mar 2026 23:28:07 +0100 Subject: [PATCH 5/6] Initialize m_draw --- Core/GameEngine/Source/GameClient/System/ParticleSys.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp index 28fc32cee0c..52161943965 100644 --- a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp +++ b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp @@ -3036,6 +3036,7 @@ void ParticleSystemManager::update() smudge->m_offset.Set(GameClientRandomValueReal(-0.06f,0.06f), GameClientRandomValueReal(-0.03f,0.03f)); smudge->m_size = p->getSize(); smudge->m_opacity = p->getAlpha(); + smudge->m_draw = false; } } } From 12d14aab5eab733156d6b9a0c378ca792c0829ac Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:07:49 +0100 Subject: [PATCH 6/6] Add assert to SmudgeSet::addSmudgeToSet --- Core/GameEngine/Source/GameClient/System/Smudge.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/GameEngine/Source/GameClient/System/Smudge.cpp b/Core/GameEngine/Source/GameClient/System/Smudge.cpp index 3bc5b973e6f..fcd99f7c62a 100644 --- a/Core/GameEngine/Source/GameClient/System/Smudge.cpp +++ b/Core/GameEngine/Source/GameClient/System/Smudge.cpp @@ -149,6 +149,9 @@ void SmudgeSet::resetDraw() Smudge *SmudgeSet::addSmudgeToSet(Smudge::Identifier identifier) { + DEBUG_ASSERTCRASH(m_usedSmudgeMap.find(identifier) == m_usedSmudgeMap.end(), + ("SmudgeSet::addSmudgeToSet: identifier already present")); + Smudge* smudge = m_freeSmudgeList.Head(); if (smudge) { smudge->Remove(); //remove from free list