diff --git a/audit/history.jsonl b/audit/history.jsonl index cb83ccec..0fe1b257 100644 --- a/audit/history.jsonl +++ b/audit/history.jsonl @@ -563,3 +563,4 @@ {"date": "2026-09-28T04:05:55+00:00", "unit": "EterLib/GrpShadowTexture.cpp + character shadow render target (RecordingDevice)", "action": "unit C part 2 / unit D: GrpShadowTexture stub replaced with verbatim 40250 copy (dead in 40250); CPU shadow-target rasterizer kept (flat TFACTOR pass, output-equivalent) and switched to the D3D top-left fill rule; Vulkan offscreen targets not needed since snow blur is hard-disabled in 40250", "evidence": "ctest 35/35 (login_live excluded); fake-server frame 900 vs previous mean abs diff 0.013/255 (frame noise); shadow parity NEEDS_LIVE"} {"date": "2026-09-28T04:31:20+00:00", "unit": "SpeedTreeLib/SpeedTreeWrapper.cpp + SpeedTreeForest.cpp + SpeedTreeForestDirectX8.cpp (+ CSpeedTreeRT stand-in)", "action": "unit E: platform proxy tree renderer deleted; 40250 wrapper/forest/DirectX8 copied verbatim (with SpeedTreeConfig.h, VertexShaders.h); SDK SpeedTreeRT.h public API as shim, closed CSpeedTreeRT reimplemented in platform/SpeedTreeLib/SpeedTreeRT.cpp (procedural geometry kept, DIVERGENT); D3D8 vertex-shader decl tokens, D3D_OK, CreateVertexShader/DeleteVertexShader, ID3DXBuffer/D3DXAssembleShader, MessageBox, __min/__max added to shims", "evidence": "ctest 35/35 (login_live excluded); fake-server frame 900 at spawn vs unit D mean abs diff ~3.2/255 (area-name fade + anim phase, no trees in view); spawn moved next to a tree (temporary) shows trunk/branches/leaf cards drawn by the wrapper; tree visuals NEEDS_LIVE"} {"date": "2026-09-28T04:34:02+00:00", "unit": "EterLib/GrpMarkInstance.cpp", "action": "unit F: platform_stub skeleton deleted (every member was MT_PLATFORM_STUB), 40250 source copied verbatim; guild marks (PythonTextTail, PythonWindow CMarkBox) now load and draw", "evidence": "ctest 35/35 (login_live excluded); mark rendering NEEDS_LIVE"} +{"date": "2026-09-28T04:53:55+00:00", "unit": "GameLib/MapOutdoorRenderSTP.cpp + UserInterface/PythonApplication.cpp Create (tiling) + RecordingDevice caps", "action": "unit G: STP file replaced by the full 40250 source (native-terrain gate only); Create gains the 40250 SOFTWARE_TILING/auto-tiling block and MinLOD for !IsFastTNL; RecordingDevice GetDeviceCaps reports VertexShaderVersion 1.1 + D3DPMISCCAPS_CLIPTLVERTS (40250 on a fast T&L card = HTP); native_render XYZRHW fixes: screen→NDC Y was inverted (drawn upside down and back-face culled), negative rhw forced w=1 (spikes), texture transform no longer applied to TL vertices, specular-alpha vertex fog plumbed (Render3DDraw.vertex_fog, capture v9)", "evidence": "ctest 35/35 (login_live excluded); default HTP frame 900 vs previous baseline mean abs diff ~0.6/255; STP forced (temporary edit, reverted) renders the spawn plaza/grass matching HTP layout, STP pixels NEEDS_LIVE"} diff --git a/audit/port-map/GameLib/MapOutdoorRenderSTP.cpp.json b/audit/port-map/GameLib/MapOutdoorRenderSTP.cpp.json index c8323c88..344b5412 100644 --- a/audit/port-map/GameLib/MapOutdoorRenderSTP.cpp.json +++ b/audit/port-map/GameLib/MapOutdoorRenderSTP.cpp.json @@ -5,81 +5,97 @@ "contracts": [], "functions": { "CMapOutdoor::__RenderTerrain_RenderSoftwareTransformPatch": { - "status": "DIVERGENT", + "status": "NEEDS_LIVE", "impl": [ "extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp:CMapOutdoor::__RenderTerrain_RenderSoftwareTransformPatch" ], - "note": "No-op: the Godot terrain adapter draws the height-field (PORT-PLAN §3)." + "note": "Verbatim 40250 body plus an early return when IsNativeTerrainRenderEnabled() is false (Godot terrain adapter path, PORT-PLAN §3), same gate as HTP. Reached only with SOFTWARE_TILING 1 or a non-fast-T&L device. Fake-server STP frame matches HTP layout; pixel parity vs a 40250 STP frame not verified." }, "CMapOutdoor::__SoftwareTransformPatch_RenderPatchSplat": { - "status": "DIVERGENT", - "impl": [], - "note": "Not built: only called from __RenderTerrain_RenderSoftwareTransformPatch, which is a no-op (Godot draws the terrain, PORT-PLAN §3)." + "status": "NEEDS_LIVE", + "impl": [ + "extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp:CMapOutdoor::__SoftwareTransformPatch_RenderPatchSplat" + ], + "note": "Drawn via XYZRHW through RecordingDevice/native_render (fixed 2026-09-28: pretransformed Y mapping, negative-rhw clip, specular-alpha vertex fog); fake-server frame with SOFTWARE_TILING forced matches the HTP layout; pixel parity vs a 40250 STP frame not verified." }, "CMapOutdoor::__SoftwareTransformPatch_RenderPatchNone": { - "status": "DIVERGENT", - "impl": [], - "note": "Not built: only called from __RenderTerrain_RenderSoftwareTransformPatch, which is a no-op (Godot draws the terrain, PORT-PLAN §3)." + "status": "NEEDS_LIVE", + "impl": [ + "extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp:CMapOutdoor::__SoftwareTransformPatch_RenderPatchNone" + ], + "note": "Drawn via XYZRHW through RecordingDevice/native_render (fixed 2026-09-28: pretransformed Y mapping, negative-rhw clip, specular-alpha vertex fog); fake-server frame with SOFTWARE_TILING forced matches the HTP layout; pixel parity vs a 40250 STP frame not verified." }, "CMapOutdoor::__SoftwareTransformPatch_ApplyStaticShadowRenderState": { - "status": "DIVERGENT", - "impl": [], - "note": "Not built: only called from __RenderTerrain_RenderSoftwareTransformPatch, which is a no-op (Godot draws the terrain, PORT-PLAN §3)." + "status": "PORTED", + "impl": [ + "extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp:CMapOutdoor::__SoftwareTransformPatch_ApplyStaticShadowRenderState" + ] }, "CMapOutdoor::__SoftwareTransformPatch_ApplyDynamicShadowRenderState": { - "status": "DIVERGENT", - "impl": [], - "note": "Not built: only called from __RenderTerrain_RenderSoftwareTransformPatch, which is a no-op (Godot draws the terrain, PORT-PLAN §3)." + "status": "PORTED", + "impl": [ + "extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp:CMapOutdoor::__SoftwareTransformPatch_ApplyDynamicShadowRenderState" + ] }, "CMapOutdoor::__SoftwareTransformPatch_ApplyFogShadowRenderState": { - "status": "DIVERGENT", - "impl": [], - "note": "Not built: only called from __RenderTerrain_RenderSoftwareTransformPatch, which is a no-op (Godot draws the terrain, PORT-PLAN §3)." + "status": "PORTED", + "impl": [ + "extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp:CMapOutdoor::__SoftwareTransformPatch_ApplyFogShadowRenderState" + ] }, "CMapOutdoor::__SoftwareTransformPatch_RestoreStaticShadowRenderState": { - "status": "DIVERGENT", - "impl": [], - "note": "Not built: only called from __RenderTerrain_RenderSoftwareTransformPatch, which is a no-op (Godot draws the terrain, PORT-PLAN §3)." + "status": "PORTED", + "impl": [ + "extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp:CMapOutdoor::__SoftwareTransformPatch_RestoreStaticShadowRenderState" + ] }, "CMapOutdoor::__SoftwareTransformPatch_RestoreDynamicShadowRenderState": { - "status": "DIVERGENT", - "impl": [], - "note": "Not built: only called from __RenderTerrain_RenderSoftwareTransformPatch, which is a no-op (Godot draws the terrain, PORT-PLAN §3)." + "status": "PORTED", + "impl": [ + "extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp:CMapOutdoor::__SoftwareTransformPatch_RestoreDynamicShadowRenderState" + ] }, "CMapOutdoor::__SoftwareTransformPatch_RestoreFogShadowRenderState": { - "status": "DIVERGENT", - "impl": [], - "note": "Not built: only called from __RenderTerrain_RenderSoftwareTransformPatch, which is a no-op (Godot draws the terrain, PORT-PLAN §3)." + "status": "PORTED", + "impl": [ + "extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp:CMapOutdoor::__SoftwareTransformPatch_RestoreFogShadowRenderState" + ] }, "CMapOutdoor::__SoftwareTransformPatch_ApplyRenderState": { - "status": "DIVERGENT", - "impl": [], - "note": "Not built: only called from __RenderTerrain_RenderSoftwareTransformPatch, which is a no-op (Godot draws the terrain, PORT-PLAN §3)." + "status": "PORTED", + "impl": [ + "extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp:CMapOutdoor::__SoftwareTransformPatch_ApplyRenderState" + ] }, "CMapOutdoor::__SoftwareTransformPatch_RestoreRenderState": { - "status": "DIVERGENT", - "impl": [], - "note": "Not built: only called from __RenderTerrain_RenderSoftwareTransformPatch, which is a no-op (Godot draws the terrain, PORT-PLAN §3)." + "status": "PORTED", + "impl": [ + "extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp:CMapOutdoor::__SoftwareTransformPatch_RestoreRenderState" + ] }, "CMapOutdoor::__SoftwareTransformPatch_BuildPipeline": { - "status": "DIVERGENT", - "impl": [], - "note": "Not built: only called from __RenderTerrain_RenderSoftwareTransformPatch, which is a no-op (Godot draws the terrain, PORT-PLAN §3)." + "status": "PORTED", + "impl": [ + "extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp:CMapOutdoor::__SoftwareTransformPatch_BuildPipeline" + ] }, "CMapOutdoor::__SoftwareTransformPatch_SetTransform": { - "status": "DIVERGENT", - "impl": [], - "note": "Not built: only called from __RenderTerrain_RenderSoftwareTransformPatch, which is a no-op (Godot draws the terrain, PORT-PLAN §3)." + "status": "PORTED", + "impl": [ + "extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp:CMapOutdoor::__SoftwareTransformPatch_SetTransform" + ] }, "CMapOutdoor::__SoftwareTransformPatch_SetSplatStream": { - "status": "DIVERGENT", - "impl": [], - "note": "Not built: only called from __RenderTerrain_RenderSoftwareTransformPatch, which is a no-op (Godot draws the terrain, PORT-PLAN §3)." + "status": "PORTED", + "impl": [ + "extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp:CMapOutdoor::__SoftwareTransformPatch_SetSplatStream" + ] }, "CMapOutdoor::__SoftwareTransformPatch_SetShadowStream": { - "status": "DIVERGENT", - "impl": [], - "note": "Not built: only called from __RenderTerrain_RenderSoftwareTransformPatch, which is a no-op (Godot draws the terrain, PORT-PLAN §3)." + "status": "PORTED", + "impl": [ + "extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp:CMapOutdoor::__SoftwareTransformPatch_SetShadowStream" + ] }, "CMapOutdoor::__SoftwareTransformPatch_Initialize": { "status": "PORTED", diff --git a/audit/port-map/UserInterface/PythonApplication.cpp.json b/audit/port-map/UserInterface/PythonApplication.cpp.json index 5f9a66a4..9595025e 100644 --- a/audit/port-map/UserInterface/PythonApplication.cpp.json +++ b/audit/port-map/UserInterface/PythonApplication.cpp.json @@ -157,7 +157,7 @@ "impl": [ "extension/src/platform/UserInterface/PythonApplication.cpp:CPythonApplication::Create" ], - "note": "40250 CPythonApplication lifecycle and state adapted for platform host and singletons; m_LightManager.Initialize() (40250 :1263) now called; Destroy() still empty (m_LightManager.Destroy at 40250 :1418 not yet mirrored)", + "note": "40250 CPythonApplication lifecycle and state adapted for platform host and singletons; m_LightManager.Initialize() (40250 :1263) now called; Destroy() still empty (m_LightManager.Destroy at 40250 :1418 not yet mirrored); 40250 :1196-1251 tiling selection mirrored (SOFTWARE_TILING cfg → GRAPHICS_CAPS_SOFTWARE_TILING before device create, auto tiling IsFastTNL ? HTP : STP via ReserveSoftwareTilingEnable, !IsFastTNL → CGrannyLODController::SetMinLODMode(true)); RecordingDevice caps report VS 1.1 + CLIPTLVERTS so auto picks HTP", "test": "extension/tests/port_login_flow_test.cpp" }, "CPythonApplication::SetGlobalCenterPosition": { diff --git a/extension/src/platform/EterLib/RecordingDevice.cpp b/extension/src/platform/EterLib/RecordingDevice.cpp index db377b18..7f3b2ebc 100644 --- a/extension/src/platform/EterLib/RecordingDevice.cpp +++ b/extension/src/platform/EterLib/RecordingDevice.cpp @@ -20,7 +20,7 @@ int g_native_terrain_override = -1; struct VertexLayout { unsigned stride = 0; bool rhw = false; - int normal = -1, diffuse = -1, uv0 = -1, uv1 = -1; + int normal = -1, diffuse = -1, specular = -1, uv0 = -1, uv1 = -1; }; // The element offsets of a fixed-function FVF (D3DXGetFVFVertexSize's order). @@ -41,7 +41,7 @@ VertexLayout fvf_layout(DWORD fvf) if (fvf & D3DFVF_NORMAL) { layout.normal = offset; offset += 12; } if (fvf & D3DFVF_PSIZE) offset += 4; if (fvf & D3DFVF_DIFFUSE) { layout.diffuse = offset; offset += 4; } - if (fvf & D3DFVF_SPECULAR) offset += 4; + if (fvf & D3DFVF_SPECULAR) { layout.specular = offset; offset += 4; } const unsigned texCount = (fvf & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT; for (unsigned i = 0; i < texCount; ++i) { @@ -281,11 +281,15 @@ public: return refs; } - // PORT: a D3D8 HAL on a T&L card (D3DDEVCAPS_HWTRANSFORMANDLIGHT), 8 lights, 4 texture stages. + // PORT: a D3D8 HAL on a T&L card (D3DDEVCAPS_HWTRANSFORMANDLIGHT), 8 lights, 4 texture stages, + // vertex shader 1.1 and T&L vertex clipping, as the D3D8 runtime reports for current GPUs; with + // these CGraphicBase::IsFastTNL() is true, so auto tiling picks the hardware-transform terrain. HRESULT GetDeviceCaps(D3DCAPS8* caps) override { std::memset(caps, 0, sizeof(*caps)); caps->DevCaps = D3DDEVCAPS_HWTRANSFORMANDLIGHT; + caps->VertexShaderVersion = D3DVS_VERSION(1, 1); + caps->PrimitiveMiscCaps = D3DPMISCCAPS_CLIPTLVERTS; caps->MaxActiveLights = 8; caps->MaxTextureBlendStages = 4; caps->MaxSimultaneousTextures = 4; @@ -785,6 +789,7 @@ private: // Elements past the stream's stride live in another stream (CreatePTStreamVertexShader). if (layout.normal + 12 > int(stride)) layout.normal = -1; if (layout.diffuse + 4 > int(stride)) layout.diffuse = -1; + if (layout.specular + 4 > int(stride)) layout.specular = -1; if (layout.uv0 + 8 > int(stride)) layout.uv0 = -1; if (layout.uv1 + 8 > int(stride)) layout.uv1 = -1; @@ -873,6 +878,7 @@ private: const Render3DDraw& prior = cached->second.geometry; draw.positions = prior.positions; draw.rhw = prior.rhw; + draw.vertex_fog = prior.vertex_fog; draw.normals = prior.normals; draw.uv0 = prior.uv0; draw.uv1 = prior.uv1; @@ -885,6 +891,8 @@ private: { draw.positions.resize(count * 3); if (layout.rhw) draw.rhw.resize(count); + const bool tl_fog = layout.rhw && layout.specular >= 0; + if (tl_fog) draw.vertex_fog.resize(count); if (layout.normal >= 0) draw.normals.resize(count * 3); if (layout.uv0 >= 0) draw.uv0.resize(count * 2); if (layout.uv1 >= 0) draw.uv1.resize(count * 2); @@ -894,6 +902,7 @@ private: const uint8_t* v = vertices + (lo + i) * stride; std::memcpy(&draw.positions[i * 3], v, 12); if (layout.rhw) std::memcpy(&draw.rhw[i], v + 12, 4); + if (tl_fog) draw.vertex_fog[i] = float(v[layout.specular + 3]) / 255.0f; // specular alpha if (layout.normal >= 0) std::memcpy(&draw.normals[i * 3], v + layout.normal, 12); if (layout.uv0 >= 0) std::memcpy(&draw.uv0[i * 2], v + layout.uv0, 8); if (layout.uv1 >= 0) std::memcpy(&draw.uv1[i * 2], v + layout.uv1, 8); diff --git a/extension/src/platform/EterLib/RenderCommands3D.h b/extension/src/platform/EterLib/RenderCommands3D.h index 57b73a59..ab3ddf96 100644 --- a/extension/src/platform/EterLib/RenderCommands3D.h +++ b/extension/src/platform/EterLib/RenderCommands3D.h @@ -37,6 +37,8 @@ struct Render3DDraw { bool pretransformed = false; std::vector positions; // x, y, z std::vector rhw; + // D3D8 vertex fog of an XYZRHW draw: the D3DFVF_SPECULAR alpha (0..1); empty otherwise. + std::vector vertex_fog; std::vector normals; // x, y, z std::vector uv0; // u, v std::vector uv1; // u, v diff --git a/extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp b/extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp index 5a9e9d0e..eba239a5 100644 --- a/extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp +++ b/extension/src/platform/GameLib/MapOutdoorRenderSTP.cpp @@ -1,10 +1,15 @@ -// Platform implementation of 40250 GameLib/MapOutdoorRenderSTP.cpp (software-transform terrain patches). -// The vertex-buffer lifecycle is the 40250 code as is; the patch drawing is left to the Godot terrain -// (see MapOutdoorRenderHTP.cpp). +// Platform copy of 40250 GameLib/MapOutdoorRenderSTP.cpp (software-transform terrain patches), as is +// apart from the includes and the native-terrain gate: the Godot Metin2World adapter draws the terrain +// unless IsNativeTerrainRenderEnabled() (see MapOutdoorRenderHTP.cpp). 40250 takes this path when +// SOFTWARE_TILING is 1 in metin2.cfg, or with auto tiling on a device without fast T&L. #include "GameLib/StdAfx.h" -#include "EterLib/StateManager.h" #include "GameLib/MapOutdoor.h" #include "GameLib/TerrainPatch.h" +#include "GameLib/TerrainQuadtree.h" + +#include "EterLib/Camera.h" +#include "EterLib/StateManager.h" +#include "../EterLib/RenderCommands3D.h" struct SoftwareTransformPatch_SSplatVertex { @@ -15,9 +20,724 @@ struct SoftwareTransformPatch_SSplatVertex D3DXVECTOR2 kTex2; }; -// PORT: Godot draws the terrain (PORT-PLAN §3). + + void CMapOutdoor::__RenderTerrain_RenderSoftwareTransformPatch() +{ + // PORT: see the file comment. + if (!IsNativeTerrainRenderEnabled()) + return; + + SoftwareTransformPatch_SRenderState kTPRS; + + DWORD dwFogEnable = STATEMANAGER.GetRenderState(D3DRS_FOGENABLE); + + __SoftwareTransformPatch_ApplyRenderState(); + + __SoftwareTransformPatch_BuildPipeline(kTPRS); + + std::pair fog_far(kTPRS.m_fFogFarDistance+800.0f, 0); + std::pair fog_near(kTPRS.m_fFogNearDistance-3200.0f, 0); + + std::vector >::iterator far_it = std::upper_bound(m_PatchVector.begin(),m_PatchVector.end(),fog_far); + std::vector >::iterator near_it = std::upper_bound(m_PatchVector.begin(),m_PatchVector.end(),fog_near); + + WORD wPrimitiveCount; + D3DPRIMITIVETYPE ePrimitiveType; + + BYTE byCUrrentLODLevel = 0; + + float fLODLevel1Distance = __GetNoFogDistance(); + float fLODLevel2Distance = __GetFogDistance(); + + SelectIndexBuffer(0, &wPrimitiveCount, &ePrimitiveType); + + STATEMANAGER.SetVertexShader(D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_SPECULAR|D3DFVF_TEX2); + + std::vector >::iterator it = m_PatchVector.begin(); + + for( ; it != near_it; ++it) + { + if (byCUrrentLODLevel == 0 && fLODLevel1Distance <= it->first) + { + byCUrrentLODLevel = 1; + SelectIndexBuffer(1, &wPrimitiveCount, &ePrimitiveType); + } + else if (byCUrrentLODLevel == 1 && fLODLevel2Distance <= it->first) + { + byCUrrentLODLevel = 2; + SelectIndexBuffer(2, &wPrimitiveCount, &ePrimitiveType); + } + + __SoftwareTransformPatch_RenderPatchSplat(kTPRS, it->second, wPrimitiveCount, ePrimitiveType, false); + if (m_iRenderedSplatNum >= m_iSplatLimit) + break; + + } + + if (m_iRenderedSplatNum < m_iSplatLimit) + { + for(it = near_it; it != far_it; ++it) + { + if (byCUrrentLODLevel == 0 && fLODLevel1Distance <= it->first) + { + byCUrrentLODLevel = 1; + SelectIndexBuffer(1, &wPrimitiveCount, &ePrimitiveType); + } + else if (byCUrrentLODLevel == 1 && fLODLevel2Distance <= it->first) + { + byCUrrentLODLevel = 2; + SelectIndexBuffer(2, &wPrimitiveCount, &ePrimitiveType); + } + + __SoftwareTransformPatch_RenderPatchSplat(kTPRS, it->second, wPrimitiveCount, ePrimitiveType, true); + + if (m_iRenderedSplatNum >= m_iSplatLimit) + break; + + } + } + + + STATEMANAGER.SetTexture(0, NULL); + STATEMANAGER.SetTexture(1, NULL); + + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TFACTOR); + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + + STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + + STATEMANAGER.SetVertexShader(D3DFVF_XYZRHW); + + if (IsFastTNL()) + { + if (byCUrrentLODLevel != 2) + { + byCUrrentLODLevel = 2; + SelectIndexBuffer(2, &wPrimitiveCount, &ePrimitiveType); + } + + if (m_iRenderedSplatNum < m_iSplatLimit) + { + for(it = far_it; it != m_PatchVector.end(); ++it) + { + __SoftwareTransformPatch_RenderPatchNone(kTPRS, it->second, wPrimitiveCount, ePrimitiveType); + + if (m_iRenderedSplatNum >= m_iSplatLimit) + break; + + } + } + } + + + ////////////////////////////////////////////////////////////////////////// + // Render State & TextureStageState + __SoftwareTransformPatch_RestoreRenderState(dwFogEnable); +} + +void CMapOutdoor::__SoftwareTransformPatch_RenderPatchSplat(SoftwareTransformPatch_SRenderState& rkTPRS, long patchnum, WORD wPrimitiveCount, D3DPRIMITIVETYPE ePrimitiveType, bool isFogEnable) { + assert(NULL!=m_pTerrainPatchProxyList && "CMapOutdoor::__SoftwareTransformPatch_RenderPatchSplat"); + + CTerrainPatchProxy * pTerrainPatchProxy = &m_pTerrainPatchProxyList[patchnum]; + + if (!pTerrainPatchProxy->isUsed()) + return; + + bool isDynamicShadow = pTerrainPatchProxy->IsIn(rkTPRS.m_v3Player, 3000.0f); + + if (!m_bDrawChrShadow) + isDynamicShadow = false; + + long sPatchNum = pTerrainPatchProxy->GetPatchNum(); + + if (sPatchNum < 0) + return; + + BYTE ucTerrainNum = pTerrainPatchProxy->GetTerrainNum(); + + if (0xFF == ucTerrainNum) + return; + + CTerrain * pTerrain; + if (!GetTerrainPointer(ucTerrainNum, &pTerrain)) + return; + + WORD wCoordX, wCoordY; + pTerrain->GetCoordinate(&wCoordX, &wCoordY); + + TTerrainSplatPatch & rTerrainSplatPatch = pTerrain->GetTerrainSplatPatch(); + + SoftwareTransformPatch_STLVertex akTransVertex[CTerrainPatch::TERRAIN_VERTEX_COUNT]; + if (!__SoftwareTransformPatch_SetTransform(rkTPRS, akTransVertex, *pTerrainPatchProxy, wCoordX, wCoordY, isFogEnable, isDynamicShadow)) + return; + + if (!__SoftwareTransformPatch_SetSplatStream(akTransVertex)) + return; + + if (isFogEnable) + { + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR); + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_BLENDDIFFUSEALPHA); + } + else + { + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + } + + int iPrevRenderedSplatNum=m_iRenderedSplatNum; + + bool isFirst=true; + for (DWORD j = 1; j < pTerrain->GetNumTextures(); ++j) + { + TTerainSplat & rSplat = rTerrainSplatPatch.Splats[j]; + + if (!rSplat.Active) + continue; + + if (rTerrainSplatPatch.PatchTileCount[sPatchNum][j] == 0) + continue; + + const TTerrainTexture & rTexture = m_TextureSet.GetTexture(j); + + if (isFirst) + { + STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2); + STATEMANAGER.SetTexture(0, rTexture.pd3dTexture); + STATEMANAGER.SetTexture(1, rSplat.pd3dTexture); + STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + isFirst=false; + } + else + { + STATEMANAGER.SetTexture(0, rTexture.pd3dTexture); + STATEMANAGER.SetTexture(1, rSplat.pd3dTexture); + STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount); + } + + std::vector::iterator aIterator = std::find(m_RenderedTextureNumVector.begin(), m_RenderedTextureNumVector.end(), (int)j); + if (aIterator == m_RenderedTextureNumVector.end()) + m_RenderedTextureNumVector.push_back(j); + ++m_iRenderedSplatNum; + if (m_iRenderedSplatNum >= m_iSplatLimit) + break; + } + + // 그림자 + if (m_bDrawShadow) + { + __SoftwareTransformPatch_SetShadowStream(akTransVertex); + __SoftwareTransformPatch_ApplyStaticShadowRenderState(); + + if (isDynamicShadow) + __SoftwareTransformPatch_ApplyDynamicShadowRenderState(); + else + __SoftwareTransformPatch_ApplyFogShadowRenderState(); + + if (isFogEnable) + { + STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, TRUE); + STATEMANAGER.SetRenderState(D3DRS_FOGCOLOR, 0xFFFFFFFF); + STATEMANAGER.SetTexture(0, pTerrain->GetShadowTexture()); + STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount); + STATEMANAGER.SetRenderState(D3DRS_FOGCOLOR, rkTPRS.m_dwFogColor); + STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, FALSE); + } + else + { + STATEMANAGER.SetTexture(0, pTerrain->GetShadowTexture()); + STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount); + } + + if (isDynamicShadow) + __SoftwareTransformPatch_RestoreDynamicShadowRenderState(); + else + __SoftwareTransformPatch_RestoreFogShadowRenderState(); + + ms_faceCount += wPrimitiveCount; + ++m_iRenderedSplatNum; + + + + __SoftwareTransformPatch_RestoreStaticShadowRenderState(); + } + + ++m_iRenderedPatchNum; + + int iCurRenderedSplatNum=m_iRenderedSplatNum-iPrevRenderedSplatNum; + + m_iRenderedSplatNumSqSum+=iCurRenderedSplatNum*iCurRenderedSplatNum; +} + +void CMapOutdoor::__SoftwareTransformPatch_RenderPatchNone(SoftwareTransformPatch_SRenderState& rkTPRS, long patchnum, WORD wPrimitiveCount, D3DPRIMITIVETYPE ePrimitiveType) +{ + assert(NULL!=m_pTerrainPatchProxyList && "CMapOutdoor::__SoftwareTransformPatch_RenderPatchNone"); + + CTerrainPatchProxy * pTerrainPatchProxy = &m_pTerrainPatchProxyList[patchnum]; + + if (!pTerrainPatchProxy->isUsed()) + return; + + long sPatchNum = pTerrainPatchProxy->GetPatchNum(); + if (sPatchNum < 0) + return; + + BYTE ucTerrainNum = pTerrainPatchProxy->GetTerrainNum(); + if (0xFF == ucTerrainNum) + return; + + CTerrain * pTerrain; + if (!GetTerrainPointer(ucTerrainNum, &pTerrain)) + return; + + WORD wCoordX, wCoordY; + pTerrain->GetCoordinate(&wCoordX, &wCoordY); + + SoftwareTransformPatch_SSourceVertex* akSrcVertex=pTerrainPatchProxy->SoftwareTransformPatch_GetTerrainVertexDataPtr(); + if (!akSrcVertex) + return; + + float fScreenHalfWidth=rkTPRS.m_fScreenHalfWidth; + float fScreenHalfHeight=rkTPRS.m_fScreenHalfHeight; + + D3DXMATRIX m4Frustum=rkTPRS.m_m4Frustum; + + SoftwareTransformPatch_STVertex akTransVertex[CTerrainPatch::TERRAIN_VERTEX_COUNT]; + + D3DXVECTOR4* akPosition=(D3DXVECTOR4*)akTransVertex; + D3DXVECTOR4* pkPosition; + for (UINT uIndex=0; uIndex!=CTerrainPatch::TERRAIN_VERTEX_COUNT; ++uIndex) + { + pkPosition=akPosition+uIndex; + D3DXVec3Transform(pkPosition, &akSrcVertex[uIndex].kPosition, &m4Frustum); + pkPosition->w=1.0f/pkPosition->w; + pkPosition->z*=pkPosition->w; + pkPosition->y=(pkPosition->y*pkPosition->w-1.0f)*fScreenHalfHeight; + pkPosition->x=(pkPosition->x*pkPosition->w+1.0f)*fScreenHalfWidth; + } + + + IDirect3DVertexBuffer8* pkVB=m_kSTPD.m_pkVBNone[m_kSTPD.m_dwNonePos++]; + m_kSTPD.m_dwNonePos%=SoftwareTransformPatch_SData::NONE_VB_NUM; + if (!pkVB) + return; + + DWORD dwVBSize=sizeof(SoftwareTransformPatch_STVertex)*CTerrainPatch::TERRAIN_VERTEX_COUNT; + SoftwareTransformPatch_STVertex* akDstVertex; + if (FAILED( + pkVB->Lock(0, dwVBSize, (BYTE**)&akDstVertex, D3DLOCK_DISCARD) + )) return; + + memcpy(akDstVertex, akTransVertex, dwVBSize); + + pkVB->Unlock(); + + STATEMANAGER.SetStreamSource(0, pkVB, sizeof(SoftwareTransformPatch_STVertex)); + STATEMANAGER.DrawIndexedPrimitive(ePrimitiveType, 0, m_iPatchTerrainVertexCount, 0, wPrimitiveCount); + ms_faceCount += wPrimitiveCount; +} + +void CMapOutdoor::__SoftwareTransformPatch_ApplyStaticShadowRenderState() +{ + STATEMANAGER.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO); + STATEMANAGER.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR); + + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); + +} + +void CMapOutdoor::__SoftwareTransformPatch_ApplyDynamicShadowRenderState() +{ + STATEMANAGER.SetTexture(1, m_lpCharacterShadowMapTexture); + STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT); + STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_CURRENT); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + + STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); +} + +void CMapOutdoor::__SoftwareTransformPatch_ApplyFogShadowRenderState() +{ + STATEMANAGER.SetTexture(1, NULL); + STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT); + STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_CURRENT); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); +} +void CMapOutdoor::__SoftwareTransformPatch_RestoreStaticShadowRenderState() +{ + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); + + STATEMANAGER.SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + STATEMANAGER.SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); +} + + + +void CMapOutdoor::__SoftwareTransformPatch_RestoreDynamicShadowRenderState() +{ + STATEMANAGER.SetTexture(1, NULL); + STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT); + STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + + STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); +} + + + +void CMapOutdoor::__SoftwareTransformPatch_RestoreFogShadowRenderState() +{ + STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, FALSE); + + STATEMANAGER.SetTexture(1, NULL); + STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT); + STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); +} + +void CMapOutdoor::__SoftwareTransformPatch_ApplyRenderState() +{ + DWORD dwFogColor=0xffffffff; + if (mc_pEnvironmentData) + dwFogColor=mc_pEnvironmentData->FogColor; + + BOOL isSoftwareVertexClipping=FALSE; + if (!IsTLVertexClipping()) + isSoftwareVertexClipping=TRUE; + + STATEMANAGER.SaveRenderState(D3DRS_SOFTWAREVERTEXPROCESSING, isSoftwareVertexClipping); + + STATEMANAGER.SaveRenderState(D3DRS_ALPHABLENDENABLE, TRUE); + STATEMANAGER.SaveRenderState(D3DRS_ALPHATESTENABLE, TRUE); + STATEMANAGER.SaveRenderState(D3DRS_ALPHAREF, 0x00000000); + STATEMANAGER.SaveRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER); + + STATEMANAGER.SaveRenderState(D3DRS_TEXTUREFACTOR, dwFogColor); + STATEMANAGER.SetRenderState(D3DRS_LIGHTING, FALSE); + STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, FALSE); + + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP); + STATEMANAGER.SetBestFiltering(0); + + STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT); + STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_CURRENT); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); + STATEMANAGER.SetBestFiltering(1); + + CSpeedTreeWrapper::ms_bSelfShadowOn = true; + + // Render State & TextureStageState + ////////////////////////////////////////////////////////////////////////// + + m_iRenderedSplatNumSqSum = 0; + m_iRenderedPatchNum = 0; + m_iRenderedSplatNum = 0; + m_RenderedTextureNumVector.clear(); + + m_matWorldForCommonUse._41 = 0.0f; + m_matWorldForCommonUse._42 = 0.0f; + STATEMANAGER.SetTransform(D3DTS_WORLD, &m_matWorldForCommonUse); +} + +void CMapOutdoor::__SoftwareTransformPatch_RestoreRenderState(DWORD dwFogEnable) +{ + STATEMANAGER.SetRenderState(D3DRS_LIGHTING, TRUE); + + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_CURRENT); + STATEMANAGER.SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + + STATEMANAGER.SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT); + STATEMANAGER.SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_SELECTARG1); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + STATEMANAGER.SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + + STATEMANAGER.SetRenderState(D3DRS_FOGENABLE, dwFogEnable); + + std::sort(m_RenderedTextureNumVector.begin(),m_RenderedTextureNumVector.end()); + + STATEMANAGER.RestoreRenderState(D3DRS_TEXTUREFACTOR); + + STATEMANAGER.RestoreRenderState(D3DRS_ALPHABLENDENABLE); + STATEMANAGER.RestoreRenderState(D3DRS_ALPHATESTENABLE); + STATEMANAGER.RestoreRenderState(D3DRS_ALPHAREF); + STATEMANAGER.RestoreRenderState(D3DRS_ALPHAFUNC); + + STATEMANAGER.RestoreRenderState(D3DRS_SOFTWAREVERTEXPROCESSING); + + // Render State & TextureStageState + ////////////////////////////////////////////////////////////////////////// +} + +void CMapOutdoor::__SoftwareTransformPatch_BuildPipeline(SoftwareTransformPatch_SRenderState& rkTPRS) +{ + memset(&rkTPRS, 0, sizeof(rkTPRS)); + + if (mc_pEnvironmentData) + { + rkTPRS.m_dwFogColor = mc_pEnvironmentData->FogColor; + rkTPRS.m_fFogNearDistance = mc_pEnvironmentData->GetFogNearDistance(); + rkTPRS.m_fFogFarDistance = mc_pEnvironmentData->GetFogFarDistance(); + } + else + { + rkTPRS.m_dwFogColor = 0xffffffff; + rkTPRS.m_fFogNearDistance = 5000.0f; + rkTPRS.m_fFogFarDistance = 10000.0f; + } + + UINT uScreenWidth; + UINT uScreenHeight; + CScreen::GetBackBufferSize(&uScreenWidth, &uScreenHeight); + + rkTPRS.m_fScreenHalfWidth = +float(uScreenWidth) / 2.0f; + rkTPRS.m_fScreenHalfHeight = -float(uScreenHeight) / 2.0f; + + STATEMANAGER.GetLight(0, &rkTPRS.m_kLight); + STATEMANAGER.GetMaterial(&rkTPRS.m_kMtrl); + + D3DXMATRIX m4View;STATEMANAGER.GetTransform(D3DTS_VIEW, &m4View); + D3DXMATRIX m4Proj;STATEMANAGER.GetTransform(D3DTS_PROJECTION, &m4Proj); + + D3DXMatrixMultiply(&rkTPRS.m_m4Frustum, &m4View, &m4Proj); + + rkTPRS.m_v3Player.x = +m_v3Player.x; + rkTPRS.m_v3Player.y = -m_v3Player.y; + rkTPRS.m_v3Player.z = +m_v3Player.z; + + rkTPRS.m_m4Proj = m4Proj; + rkTPRS.m_m4DynamicShadow = m_matLightView * m_matDynamicShadowScale; + + D3DXVECTOR3 kFogNearVector; + const auto farvv = D3DXVECTOR3(0.0f, 0.0f, -rkTPRS.m_fFogNearDistance); + D3DXVec3TransformCoord(&kFogNearVector, &farvv, &rkTPRS.m_m4Proj); + + D3DXVECTOR3 kFogFarVector; + const auto nearvv = D3DXVECTOR3(0.0f, 0.0f, -rkTPRS.m_fFogFarDistance); + D3DXVec3TransformCoord(&kFogFarVector, &nearvv, &rkTPRS.m_m4Proj); + + float fFogNear = kFogNearVector.z; + float fFogFar = kFogFarVector.z; + float fFogLenInv = 1.0f / (fFogFar-fFogNear); + + rkTPRS.m_fFogNearTransZ = fFogNear; + rkTPRS.m_fFogFarTransZ = fFogFar; + rkTPRS.m_fFogLenInv = fFogLenInv; + +} + +bool CMapOutdoor::__SoftwareTransformPatch_SetTransform(SoftwareTransformPatch_SRenderState& rkTPRS, SoftwareTransformPatch_STLVertex* akTransVertex, CTerrainPatchProxy& rkTerrainPatchProxy, UINT uTerrainX, UINT uTerrainY, bool isFogEnable, bool isDynamicShadow) +{ + SoftwareTransformPatch_SSourceVertex* akSrcVertex=rkTerrainPatchProxy.SoftwareTransformPatch_GetTerrainVertexDataPtr(); + if (!akSrcVertex) + return false; + + rkTerrainPatchProxy.SoftwareTransformPatch_UpdateTerrainLighting( + m_kSTPD.m_dwLightVersion, + rkTPRS.m_kLight, rkTPRS.m_kMtrl); + + D3DXVECTOR3* pkSrcPosition; + + float fTilePatternX=+1/640.0f; + float fTilePatternY=-1/640.0f; + + float fTerrainBaseX=-(float) (uTerrainX * CTerrainImpl::TERRAIN_XSIZE)+m_fTerrainTexCoordBase * 12.30769f; + float fTerrainBaseY=+(float) (uTerrainY * CTerrainImpl::TERRAIN_YSIZE)+m_fTerrainTexCoordBase * 12.30769f; + + float fScreenHalfWidth=rkTPRS.m_fScreenHalfWidth; + float fScreenHalfHeight=rkTPRS.m_fScreenHalfHeight; + + float fAlphaPatternX=m_matSplatAlpha._11; + float fAlphaPatternY=m_matSplatAlpha._22; + float fAlphaBiasX=m_matSplatAlpha._41; + float fAlphaBiasY=m_matSplatAlpha._42; + float fShadowPatternX=+m_fTerrainTexCoordBase * ((float) CTerrainImpl::PATCH_XSIZE / static_cast(CTerrainImpl::XSIZE)); + float fShadowPatternY=-m_fTerrainTexCoordBase * ((float) CTerrainImpl::PATCH_YSIZE / static_cast(CTerrainImpl::YSIZE)); + + D3DXMATRIX m4Frustum=rkTPRS.m_m4Frustum; + + if (isFogEnable) + { + float fFogCur; + float fFogFar=rkTPRS.m_fFogFarTransZ; + float fFogLenInv=rkTPRS.m_fFogLenInv; + + float fLocalX; + float fLocalY; + + SoftwareTransformPatch_STLVertex kWorkVertex; + for (UINT uIndex=0; uIndex!=CTerrainPatch::TERRAIN_VERTEX_COUNT; ++uIndex) + { + pkSrcPosition=&akSrcVertex[uIndex].kPosition; + D3DXVec3Transform(&kWorkVertex.kPosition, pkSrcPosition, &m4Frustum); + fLocalX=pkSrcPosition->x+fTerrainBaseX; + fLocalY=pkSrcPosition->y+fTerrainBaseY; + kWorkVertex.kPosition.w=1.0f/kWorkVertex.kPosition.w; + kWorkVertex.kPosition.x*=kWorkVertex.kPosition.w; + kWorkVertex.kPosition.y*=kWorkVertex.kPosition.w; + kWorkVertex.kPosition.z*=kWorkVertex.kPosition.w; + kWorkVertex.kPosition.x=(kWorkVertex.kPosition.x+1.0f)*fScreenHalfWidth; + kWorkVertex.kPosition.y=(kWorkVertex.kPosition.y-1.0f)*fScreenHalfHeight; + kWorkVertex.dwDiffuse=akSrcVertex[uIndex].dwDiffuse; + kWorkVertex.kTexTile.x=pkSrcPosition->x*fTilePatternX; + kWorkVertex.kTexTile.y=pkSrcPosition->y*fTilePatternY; + kWorkVertex.kTexAlpha.x=fLocalX*fAlphaPatternX+fAlphaBiasX; + kWorkVertex.kTexAlpha.y=fLocalY*fAlphaPatternY+fAlphaBiasY; + kWorkVertex.kTexStaticShadow.x=fLocalX*fShadowPatternX; + kWorkVertex.kTexStaticShadow.y=fLocalY*fShadowPatternY; + kWorkVertex.kTexDynamicShadow.x=0.0f; + kWorkVertex.kTexDynamicShadow.y=0.0f; + + fFogCur=(fFogFar-kWorkVertex.kPosition.z)*fFogLenInv; + if (fFogCur<0.0f) + kWorkVertex.dwFog=kWorkVertex.dwDiffuse=0x0000000|(kWorkVertex.dwDiffuse&0xffffff); + else if (fFogCur>1.0f) + kWorkVertex.dwFog=kWorkVertex.dwDiffuse=0xFF000000|(kWorkVertex.dwDiffuse&0xffffff); + else + kWorkVertex.dwFog=kWorkVertex.dwDiffuse=BYTE(255.0f*fFogCur)<<24|(kWorkVertex.dwDiffuse&0xffffff); + + *(akTransVertex+uIndex)=kWorkVertex; + } + } + else + { + float fLocalX; + float fLocalY; + + SoftwareTransformPatch_STLVertex kWorkVertex; + for (UINT uIndex=0; uIndex!=CTerrainPatch::TERRAIN_VERTEX_COUNT; ++uIndex) + { + pkSrcPosition=&akSrcVertex[uIndex].kPosition; + D3DXVec3Transform(&kWorkVertex.kPosition, pkSrcPosition, &m4Frustum); + fLocalX=pkSrcPosition->x+fTerrainBaseX; + fLocalY=pkSrcPosition->y+fTerrainBaseY; + kWorkVertex.kPosition.w=1.0f/kWorkVertex.kPosition.w; + kWorkVertex.kPosition.x*=kWorkVertex.kPosition.w; + kWorkVertex.kPosition.y*=kWorkVertex.kPosition.w; + kWorkVertex.kPosition.z*=kWorkVertex.kPosition.w; + kWorkVertex.kPosition.x=(kWorkVertex.kPosition.x+1.0f)*fScreenHalfWidth; + kWorkVertex.kPosition.y=(kWorkVertex.kPosition.y-1.0f)*fScreenHalfHeight; + kWorkVertex.dwDiffuse=akSrcVertex[uIndex].dwDiffuse; + kWorkVertex.dwFog=0xffffffff; + kWorkVertex.kTexTile.x=pkSrcPosition->x*fTilePatternX; + kWorkVertex.kTexTile.y=pkSrcPosition->y*fTilePatternY; + kWorkVertex.kTexAlpha.x=fLocalX*fAlphaPatternX+fAlphaBiasX; + kWorkVertex.kTexAlpha.y=fLocalY*fAlphaPatternY+fAlphaBiasY; + kWorkVertex.kTexStaticShadow.x=fLocalX*fShadowPatternX; + kWorkVertex.kTexStaticShadow.y=fLocalY*fShadowPatternY; + kWorkVertex.kTexDynamicShadow.x=0.0f; + kWorkVertex.kTexDynamicShadow.y=0.0f; + + *(akTransVertex+uIndex)=kWorkVertex; + } + } + + if (isDynamicShadow) + { + D3DXMATRIX m4DynamicShadow=rkTPRS.m_m4DynamicShadow; + + D3DXVECTOR3 v3Shadow; + for (UINT uIndex=0; uIndex!=CTerrainPatch::TERRAIN_VERTEX_COUNT; ++uIndex) + { + D3DXVec3TransformCoord(&v3Shadow, &akSrcVertex[uIndex].kPosition, &m4DynamicShadow); + akTransVertex[uIndex].kTexDynamicShadow.x=v3Shadow.x; + akTransVertex[uIndex].kTexDynamicShadow.y=v3Shadow.y; + } + } + + return true; +} + +bool CMapOutdoor::__SoftwareTransformPatch_SetSplatStream(SoftwareTransformPatch_STLVertex* akSrcVertex) +{ + IDirect3DVertexBuffer8* pkVB=m_kSTPD.m_pkVBSplat[m_kSTPD.m_dwSplatPos++]; + m_kSTPD.m_dwSplatPos%=SoftwareTransformPatch_SData::SPLAT_VB_NUM; + if (!pkVB) + return false; + + DWORD dwVBSize=sizeof(SoftwareTransformPatch_SSplatVertex)*CTerrainPatch::TERRAIN_VERTEX_COUNT; + SoftwareTransformPatch_SSplatVertex* akDstVertex; + if (FAILED( + pkVB->Lock(0, dwVBSize, (BYTE**)&akDstVertex, 0)//D3DLOCK_DISCARD) + )) return false; + + for (UINT uIndex=0; uIndex!=CTerrainPatch::TERRAIN_VERTEX_COUNT; ++uIndex) + *(akDstVertex+uIndex)=*((SoftwareTransformPatch_SSplatVertex*)(akSrcVertex+uIndex)); + + pkVB->Unlock(); + + STATEMANAGER.SetStreamSource(0, pkVB, sizeof(SoftwareTransformPatch_SSplatVertex)); + return true; +} + +bool CMapOutdoor::__SoftwareTransformPatch_SetShadowStream(SoftwareTransformPatch_STLVertex* akSrcVertex) +{ + IDirect3DVertexBuffer8* pkVB=m_kSTPD.m_pkVBSplat[m_kSTPD.m_dwSplatPos++]; + m_kSTPD.m_dwSplatPos%=SoftwareTransformPatch_SData::SPLAT_VB_NUM; + if (!pkVB) + return false; + + DWORD dwVBSize=sizeof(SoftwareTransformPatch_SSplatVertex)*CTerrainPatch::TERRAIN_VERTEX_COUNT; + SoftwareTransformPatch_SSplatVertex* akDstVertex; + if (FAILED( + pkVB->Lock(0, dwVBSize, (BYTE**)&akDstVertex, 0)//D3DLOCK_DISCARD) + )) return false; + + SoftwareTransformPatch_STLVertex* pkSrcVertex; + SoftwareTransformPatch_SSplatVertex* pkDstVertex; + for (UINT uIndex=0; uIndex!=CTerrainPatch::TERRAIN_VERTEX_COUNT; ++uIndex) + { + pkSrcVertex=akSrcVertex+uIndex; + pkDstVertex=akDstVertex+uIndex; + pkDstVertex->kPosition=pkSrcVertex->kPosition; + pkDstVertex->dwDiffuse=pkSrcVertex->dwDiffuse; + pkDstVertex->dwSpecular=pkSrcVertex->dwFog; + pkDstVertex->kTex1=pkSrcVertex->kTexStaticShadow; + pkDstVertex->kTex2=pkSrcVertex->kTexDynamicShadow; + } + pkVB->Unlock(); + + + ms_lpd3dDevice->SetStreamSource(0, pkVB, sizeof(SoftwareTransformPatch_SSplatVertex)); + return true; } void CMapOutdoor::__SoftwareTransformPatch_Initialize() @@ -91,3 +811,5 @@ void CMapOutdoor::__SoftwareTransformPatch_Destroy() } __SoftwareTransformPatch_Initialize(); } + + diff --git a/extension/src/platform/UserInterface/PythonApplication.cpp b/extension/src/platform/UserInterface/PythonApplication.cpp index 071931ee..1fc42de9 100644 --- a/extension/src/platform/UserInterface/PythonApplication.cpp +++ b/extension/src/platform/UserInterface/PythonApplication.cpp @@ -17,6 +17,9 @@ #include "../EterLib/UIRenderCommands.h" #include "../EterLib/RenderCommands3D.h" #include "EterLib/GrpDevice.h" +#include "EterGrnLib/LODController.h" +#include "UserInterface/PythonBackground.h" +#include "UserInterface/PythonSystem.h" #include "../ScriptLib/PythonBoot.h" #include "ServerClock.h" @@ -85,6 +88,12 @@ bool CPythonApplication::Create(PyObject*, const char*, int, int, int) return false; } + // 40250 PythonApplication.cpp:1196-1199. + extern bool GRAPHICS_CAPS_SOFTWARE_TILING; + + if (!CPythonSystem::Instance().IsAutoTiling()) + GRAPHICS_CAPS_SOFTWARE_TILING = CPythonSystem::Instance().IsSoftwareTiling(); + // In 40250 m_grpDevice is a member created here (PythonApplication.cpp:909). static CGraphicDevice s_grpDevice; int iRet = s_grpDevice.Create(NULL, width, height, true, 32, 0); @@ -117,6 +126,23 @@ bool CPythonApplication::Create(PyObject*, const char*, int, int, int) return false; } + // 40250 PythonApplication.cpp:1206-1220. + if (CPythonSystem::Instance().IsAutoTiling()) + { + if (s_grpDevice.IsFastTNL()) + { + CPythonBackground::Instance().ReserveSoftwareTilingEnable(false); + } + else + { + CPythonBackground::Instance().ReserveSoftwareTilingEnable(true); + } + } + else + { + CPythonBackground::Instance().ReserveSoftwareTilingEnable(CPythonSystem::Instance().IsSoftwareTiling()); + } + // In 40250 m_netDevice is a member created here (PythonApplication.cpp:1242). static CNetworkDevice s_netDevice; if (!s_netDevice.Create()) @@ -126,6 +152,10 @@ bool CPythonApplication::Create(PyObject*, const char*, int, int, int) return false; } + // 40250 PythonApplication.cpp:1250-1251. + if (!s_grpDevice.IsFastTNL()) + CGrannyLODController::SetMinLODMode(true); + // 40250 m_pyItem.Create() (PythonApplication.cpp:1253): registers dropitem.mse with CEffectManager. CPythonItem::Instance().Create(); diff --git a/native_render/draw_capture.h b/native_render/draw_capture.h index f913988a..d02181a8 100644 --- a/native_render/draw_capture.h +++ b/native_render/draw_capture.h @@ -23,11 +23,12 @@ // - Version 8: eight lights, back-buffer clear entries, viewport MinZ/MaxZ, border color, // TEXCOORDINDEX / TEXTURETRANSFORMFLAGS / D3DTS_TEXTUREn per stage, emissive // material source, NORMALIZENORMALS and LOCALVIEWER; UVs are raw vertex sets. +// - Version 9: adds XYZRHW vertex fog (specular alpha). // Capture is opt-in and never runs in the normal game path. namespace native_draw_capture { struct Capture { - std::uint32_t version = 8; + std::uint32_t version = 9; std::vector draws; std::unordered_map> textures; std::uint32_t ui_width = 960; @@ -113,7 +114,7 @@ inline void write( std::ofstream file(path, std::ios::binary | std::ios::trunc); if (!file) throw std::runtime_error("cannot create native draw capture: " + path); const std::uint32_t magic = 0x4d544452; // MTDR - const std::uint32_t version = 8; + const std::uint32_t version = 9; const auto count = static_cast(draws.size()); write_scalar(file, magic); write_scalar(file, version); write_scalar(file, count); for (const auto& draw : draws) { @@ -188,6 +189,7 @@ inline void write( write_scalar(file, draw.emissive_material_source); write_scalar(file, draw.normalize_normals); write_scalar(file, draw.local_viewer); + write_vector(file, draw.vertex_fog); write_vector(file, draw.bone_indices); write_vector(file, draw.bone_weights); write_vector(file, draw.bone_matrices); @@ -237,7 +239,7 @@ inline Capture read_capture(const std::string& path) { if (!file) throw std::runtime_error("cannot open native draw capture: " + path); std::uint32_t magic = 0, version = 0, count = 0; read_scalar(file, magic); read_scalar(file, version); read_scalar(file, count); - if (magic != 0x4d544452 || (version < 1 || version > 8) || count > 10'000) + if (magic != 0x4d544452 || (version < 1 || version > 9) || count > 10'000) throw std::runtime_error("unsupported native draw capture format"); Capture capture; capture.version = version; @@ -328,6 +330,7 @@ inline Capture read_capture(const std::string& path) { read_scalar(file, draw.emissive_material_source); read_scalar(file, draw.normalize_normals); read_scalar(file, draw.local_viewer); + if (version >= 9) read_vector(file, draw.vertex_fog); } else if (draw.light0) { auto& light = draw.lights[0]; light.type = 3; diff --git a/native_render/main.cpp b/native_render/main.cpp index 3c33541b..94b70d83 100644 --- a/native_render/main.cpp +++ b/native_render/main.cpp @@ -59,6 +59,7 @@ struct Vertex { float weights[4]; float mask_uv[2]; float rhw = 1.0f; + float vertex_fog = 1.0f; }; struct PushConstants { @@ -166,7 +167,7 @@ FixedFunctionState make_fixed_function_state(const Render3DDraw& draw) { state.fog_params = {draw.fog_start, draw.fog_end, draw.fog_density, draw.fog_range_enable ? 1.0f : 0.0f}; const std::uint32_t fog_mode = draw.fog_enable - ? (draw.fog_table_mode ? draw.fog_table_mode : draw.fog_vertex_mode) : 0; + ? (draw.fog_table_mode ? draw.fog_table_mode : (draw.pretransformed ? 4u : draw.fog_vertex_mode)) : 0; state.flags = {1u, !draw.texture0.empty() ? 1u : 0u, !draw.texture1.empty() ? 1u : 0u, fog_mode}; state.lighting_flags = {draw.lighting, draw.color_vertex, draw.diffuse.empty() ? 0u : 1u, draw.normalize_normals}; @@ -221,6 +222,7 @@ std::uint64_t geometry_hash(const Render3DDraw& draw) { hash = hash_bytes(hash, &flags, sizeof(flags)); hash = hash_bytes(hash, draw.positions.data(), draw.positions.size() * sizeof(float)); hash = hash_bytes(hash, draw.rhw.data(), draw.rhw.size() * sizeof(float)); + hash = hash_bytes(hash, draw.vertex_fog.data(), draw.vertex_fog.size() * sizeof(float)); hash = hash_bytes(hash, draw.normals.data(), draw.normals.size() * sizeof(float)); hash = hash_bytes(hash, draw.uv0.data(), draw.uv0.size() * sizeof(float)); hash = hash_bytes(hash, draw.uv1.data(), draw.uv1.size() * sizeof(float)); @@ -1422,10 +1424,11 @@ public: if (draw.pretransformed) { const float screen_width = float(logical_width()); const float screen_height = float(logical_height()); + // Screen pixels (y down) to D3D NDC (y up); the shader's Y flip then maps to Vulkan. constants.mvp = {2.0f / screen_width, 0, 0, 0, - 0, 2.0f / screen_height, 0, 0, + 0, -2.0f / screen_height, 0, 0, 0, 0, 1, 0, - -1, -1, 0, 1}; + -1, 1, 0, 1}; } PreparedDraw prepared_draw{}; prepared_draw.geometry = &geometry; @@ -2354,7 +2357,7 @@ private: stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; stages[1].module = fragment_module_; stages[1].pName = "main"; VkVertexInputBindingDescription binding{0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX}; - VkVertexInputAttributeDescription attributes[8] = { + VkVertexInputAttributeDescription attributes[9] = { {0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, position)}, {1, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, normal)}, {2, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv)}, @@ -2362,10 +2365,11 @@ private: {4, 0, VK_FORMAT_R8G8B8A8_UINT, offsetof(Vertex, joints)}, {5, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Vertex, weights)}, {6, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, mask_uv)}, - {7, 0, VK_FORMAT_R32_SFLOAT, offsetof(Vertex, rhw)}}; + {7, 0, VK_FORMAT_R32_SFLOAT, offsetof(Vertex, rhw)}, + {8, 0, VK_FORMAT_R32_SFLOAT, offsetof(Vertex, vertex_fog)}}; VkPipelineVertexInputStateCreateInfo input{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; input.vertexBindingDescriptionCount = 1; input.pVertexBindingDescriptions = &binding; - input.vertexAttributeDescriptionCount = 8; input.pVertexAttributeDescriptions = attributes; + input.vertexAttributeDescriptionCount = 9; input.pVertexAttributeDescriptions = attributes; VkPipelineInputAssemblyStateCreateInfo assembly{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; assembly.topology = lines ? VK_PRIMITIVE_TOPOLOGY_LINE_LIST : VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; @@ -2867,6 +2871,7 @@ private: vertices[i].position[1] = draw.positions[3*i+1]; vertices[i].position[2] = draw.positions[3*i+2]; vertices[i].rhw = i < draw.rhw.size() ? draw.rhw[i] : 1.0f; + vertices[i].vertex_fog = i < draw.vertex_fog.size() ? draw.vertex_fog[i] : 1.0f; if (3*i + 2 < draw.normals.size()) { vertices[i].normal[0] = draw.normals[3*i]; vertices[i].normal[1] = draw.normals[3*i+1]; diff --git a/native_render/native.vert b/native_render/native.vert index dee669b2..c5e2cbf4 100644 --- a/native_render/native.vert +++ b/native_render/native.vert @@ -7,6 +7,7 @@ layout(location = 4) in uvec4 in_joints; layout(location = 5) in vec4 in_weights; layout(location = 6) in vec2 in_mask_uv; layout(location = 7) in float in_rhw; +layout(location = 8) in float in_vertex_fog; layout(location = 0) out vec4 out_color; layout(location = 1) out vec2 out_uv; @@ -73,7 +74,9 @@ vec2 stage_texcoord(int stage, bool pretransformed, vec3 eye, vec3 n) { ? (length(eye) > 0.0 ? -normalize(eye) : vec3(0.0)) : vec3(0.0, 0.0, -1.0); coord = vec4(2.0 * dot(e, n) * n - e, 1.0); } - uint transform_flags = fixed_state.stage_alpha[stage].w; + // Pretransformed (XYZRHW) vertices bypass D3D8 T&L, texture transforms included + // (RecordingDevice's CPU texcoord path does the same). + uint transform_flags = pretransformed ? 0u : fixed_state.stage_alpha[stage].w; uint elements = transform_flags & 0xFFu; vec4 r = coord; if (elements != 0u) @@ -109,7 +112,9 @@ void main() { // D3D row-vector matrices are uploaded row-major. GLSL reads the bytes as their transpose. gl_Position = draw.mvp * vec4(pos, 1.0); if (pretransformed) { - float clip_w = in_rhw > 0.0 ? 1.0 / in_rhw : 1.0; + // Vertices behind the eye carry a negative rhw (STP terrain): rebuilding clip space + // with w = 1/rhw < 0 lets the clipper drop them instead of drawing w = 1 spikes. + float clip_w = in_rhw != 0.0 ? 1.0 / in_rhw : 1.0; gl_Position.xyz *= clip_w; gl_Position.w = clip_w; } @@ -172,8 +177,13 @@ void main() { out_mask_uv = stage_texcoord(1, pretransformed, eye, n); out_fog = 1.0; - if (fixed_state.flags.w != 0u && !pretransformed) { - float distance_to_eye = fixed_state.fog_params.w > 0.5 ? length(eye) : abs(eye.z); + if (fixed_state.flags.w == 4u) { + // XYZRHW with FOGTABLEMODE NONE: D3D8 takes the vertex fog from the specular alpha. + out_fog = in_vertex_fog; + } else if (fixed_state.flags.w != 0u) { + // Table fog on XYZRHW vertices runs on eye depth w = 1/rhw. + float distance_to_eye = pretransformed ? gl_Position.w + : (fixed_state.fog_params.w > 0.5 ? length(eye) : abs(eye.z)); if (fixed_state.flags.w == 3u) { float span = fixed_state.fog_params.y - fixed_state.fog_params.x; if (span > 0.0001)