From 8d69af0aa1fc2353985664a12b0ea892b5d6a67c Mon Sep 17 00:00:00 2001 From: Sam Loeschen Date: Wed, 3 Jun 2026 22:50:46 -0500 Subject: [PATCH 1/5] v1 grass instancing on dx10/dx11 (requires modified shaders!) --- src/Layers/xrRender/DetailManager_VS.cpp | 17 +++++++--- src/Layers/xrRender/R_Backend.h | 4 +++ .../xrRenderDX10/dx10DetailManager_VS.cpp | 21 +++++------- .../xrRenderDX10/dx10R_Backend_Runtime.h | 32 +++++++++++++++++++ 4 files changed, 57 insertions(+), 17 deletions(-) diff --git a/src/Layers/xrRender/DetailManager_VS.cpp b/src/Layers/xrRender/DetailManager_VS.cpp index 99227c24e9..b43bb76cb1 100644 --- a/src/Layers/xrRender/DetailManager_VS.cpp +++ b/src/Layers/xrRender/DetailManager_VS.cpp @@ -52,14 +52,23 @@ void CDetailManager::hw_Load_Geom() clamp(hw_BatchSize, (u32)0, (u32)64); Msg("* [DETAILS] VertexConsts(%d), Batch(%d)", u32(HW.Caps.geometry.dwRegisters), hw_BatchSize); + // On DX10/11 we draw with hardware instancing (DrawIndexedInstanced), so the geometry + // buffers hold a single copy of each mesh and the per-instance transform is fetched in + // the vertex shader via SV_InstanceID. The legacy DX9 path still bakes hw_BatchSize copies. +#if defined(USE_DX10) || defined(USE_DX11) + const u32 dwCopies = 1; +#else + const u32 dwCopies = hw_BatchSize; +#endif + // Pre-process objects u32 dwVerts = 0; u32 dwIndices = 0; for (u32 o = 0; o < objects.size(); o++) { const CDetail& D = *objects[o]; - dwVerts += D.number_vertices * hw_BatchSize; - dwIndices += D.number_indices * hw_BatchSize; + dwVerts += D.number_vertices * dwCopies; + dwIndices += D.number_indices * dwCopies; } u32 vSize = sizeof(vertHW); Msg("* [DETAILS] %d v(%d), %d p", dwVerts, vSize, dwIndices / 3); @@ -90,7 +99,7 @@ void CDetailManager::hw_Load_Geom() for (u32 o = 0; o < objects.size(); o++) { const CDetail& D = *objects[o]; - for (u32 batch = 0; batch < hw_BatchSize; batch++) + for (u32 batch = 0; batch < dwCopies; batch++) { u32 mid = batch * c_size; for (u32 v = 0; v < D.number_vertices; v++) @@ -130,7 +139,7 @@ void CDetailManager::hw_Load_Geom() { const CDetail& D = *objects[o]; u16 offset = 0; - for (u32 batch = 0; batch < hw_BatchSize; batch++) + for (u32 batch = 0; batch < dwCopies; batch++) { for (u32 i = 0; i < u32(D.number_indices); i++) *pI++ = u16(u16(D.indices[i]) + u16(offset)); diff --git a/src/Layers/xrRender/R_Backend.h b/src/Layers/xrRender/R_Backend.h index 7bbe6624cc..29559dbaed 100644 --- a/src/Layers/xrRender/R_Backend.h +++ b/src/Layers/xrRender/R_Backend.h @@ -416,6 +416,10 @@ class ECORE_API CBackend ICF void Render(D3DPRIMITIVETYPE T, u32 baseV, u32 startV, u32 countV, u32 startI, u32 PC); ICF void Render(D3DPRIMITIVETYPE T, u32 startV, u32 PC); +#if defined(USE_DX10) || defined(USE_DX11) + ICF void RenderInstanced(D3DPRIMITIVETYPE T, u32 instanceCount, u32 baseV, u32 startV, u32 countV, u32 startI, u32 PC); +#endif + #ifdef USE_DX11 ICF void Compute(UINT ThreadGroupCountX, UINT ThreadGroupCountY, UINT ThreadGroupCountZ); #endif diff --git a/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp b/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp index 4abf7a8362..ef99658ec5 100644 --- a/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp +++ b/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp @@ -322,13 +322,10 @@ void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave dwBatch ++; if (dwBatch == hw_BatchSize) { - // flush Device.Statistic->RenderDUMP_DT_Count += dwBatch; - u32 dwCNT_verts = dwBatch * Object.number_vertices; - u32 dwCNT_prims = (dwBatch * Object.number_indices) / 3; - //RCache.get_ConstantCache_Vertex().b_dirty = TRUE; - //RCache.get_ConstantCache_Vertex().get_array_f().dirty (c_base,c_base+dwBatch*4); - RCache.Render(D3DPT_TRIANGLELIST, vOffset, 0, dwCNT_verts, iOffset, dwCNT_prims); + u32 dwCNT_verts = dwBatch * Object.number_vertices; // for stats only + RCache.RenderInstanced(D3DPT_TRIANGLELIST, dwBatch, vOffset, 0, Object.number_vertices, + iOffset, Object.number_indices / 3); RCache.stat.r.s_details.add(dwCNT_verts); // restart @@ -350,11 +347,9 @@ void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave if (dwBatch) { Device.Statistic->RenderDUMP_DT_Count += dwBatch; - u32 dwCNT_verts = dwBatch * Object.number_vertices; - u32 dwCNT_prims = (dwBatch * Object.number_indices) / 3; - //RCache.get_ConstantCache_Vertex().b_dirty = TRUE; - //RCache.get_ConstantCache_Vertex().get_array_f().dirty (c_base,c_base+dwBatch*4); - RCache.Render(D3DPT_TRIANGLELIST, vOffset, 0, dwCNT_verts, iOffset, dwCNT_prims); + u32 dwCNT_verts = dwBatch * Object.number_vertices; // for stats only + RCache.RenderInstanced(D3DPT_TRIANGLELIST, dwBatch, vOffset, 0, Object.number_vertices, + iOffset, Object.number_indices / 3); RCache.stat.r.s_details.add(dwCNT_verts); } } @@ -371,7 +366,7 @@ void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave vis.clear_not_free(); } } - vOffset += hw_BatchSize * Object.number_vertices; - iOffset += hw_BatchSize * Object.number_indices; + vOffset += Object.number_vertices; + iOffset += Object.number_indices; } } diff --git a/src/Layers/xrRenderDX10/dx10R_Backend_Runtime.h b/src/Layers/xrRenderDX10/dx10R_Backend_Runtime.h index 2c293bf3cf..f2fa9a92a0 100644 --- a/src/Layers/xrRenderDX10/dx10R_Backend_Runtime.h +++ b/src/Layers/xrRenderDX10/dx10R_Backend_Runtime.h @@ -327,6 +327,38 @@ IC void CBackend::Render(D3DPRIMITIVETYPE T, u32 baseV, u32 startV, u32 countV, PGO(Msg("PGO:DIP:%dv/%df",countV,PC)); } +IC void CBackend::RenderInstanced(D3DPRIMITIVETYPE T, u32 instanceCount, u32 baseV, u32 startV, u32 countV, u32 startI, + u32 PC) +{ + D3D_PRIMITIVE_TOPOLOGY Topology = TranslateTopology(T); + u32 iIndexCount = GetIndexCount(T, PC); + + //!!! HACK !!! (mirror of CBackend::Render - tessellation patch list) +#ifdef USE_DX11 + if (hs != 0 || ds != 0) + { + R_ASSERT(Topology == D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + Topology = D3D11_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST; + } +#endif + + stat.calls++; + stat.verts += countV * instanceCount; + stat.polys += PC * instanceCount; + + ApplyPrimitieTopology(Topology); + SRVSManager.Apply(); + ApplyRTandZB(); + ApplyVertexLayout(); + StateManager.Apply(); + // State manager may alter constants + constants.flush(); + + HW.pContext->DrawIndexedInstanced(iIndexCount, instanceCount, startI, baseV, 0); + + PGO(Msg("PGO:DIPInst:%dv/%df x%d",countV,PC,instanceCount)); +} + IC void CBackend::Render(D3DPRIMITIVETYPE T, u32 startV, u32 PC) { // TODO: DX10: Remove triangle fan usage from the engine From ef67c26436c1330449281476f9e21da854d129e7 Mon Sep 17 00:00:00 2001 From: Sam Loeschen Date: Thu, 4 Jun 2026 09:05:07 -0500 Subject: [PATCH 2/5] grass draw call batching --- src/Layers/xrRender/DetailManager.cpp | 3 + src/Layers/xrRender/DetailManager.h | 6 + src/Layers/xrRender/DetailManager_VS.cpp | 35 ++ src/Layers/xrRender/R_Backend.h | 2 +- src/Layers/xrRenderDX10/DXCommonTypes.h | 2 + src/Layers/xrRenderDX10/dx10BufferUtils.cpp | 15 +- .../xrRenderDX10/dx10DetailManager_VS.cpp | 314 ++++++++---------- .../xrRenderDX10/dx10R_Backend_Runtime.h | 4 +- 8 files changed, 209 insertions(+), 172 deletions(-) diff --git a/src/Layers/xrRender/DetailManager.cpp b/src/Layers/xrRender/DetailManager.cpp index 114aecaeba..3a58b6e074 100644 --- a/src/Layers/xrRender/DetailManager.cpp +++ b/src/Layers/xrRender/DetailManager.cpp @@ -85,6 +85,9 @@ CDetailManager::CDetailManager() hw_BatchSize = 0; hw_VB = 0; hw_IB = 0; +#if defined(USE_DX10) || defined(USE_DX11) + hw_instanceVB = 0; +#endif m_time_rot_1 = 0; m_time_rot_2 = 0; m_time_pos = 0; diff --git a/src/Layers/xrRender/DetailManager.h b/src/Layers/xrRender/DetailManager.h index 9ee004a495..7f6bae216f 100644 --- a/src/Layers/xrRender/DetailManager.h +++ b/src/Layers/xrRender/DetailManager.h @@ -227,6 +227,12 @@ class ECORE_API CDetailManager u32 hw_BatchSize; ID3DVertexBuffer* hw_VB; ID3DIndexBuffer* hw_IB; +#if defined(USE_DX10) || defined(USE_DX11) + // DX10/11 instancing + // One record = 5 float4 (3x4 transform rows + color + terrain-normal/alpha) = 80b + enum { hw_InstanceStride = 5 * sizeof(Fvector4), hw_InstanceCapacity = 1 << 17 }; + ID3DVertexBuffer* hw_instanceVB; +#endif ref_constant hwc_consts; ref_constant hwc_wave; ref_constant hwc_wind; diff --git a/src/Layers/xrRender/DetailManager_VS.cpp b/src/Layers/xrRender/DetailManager_VS.cpp index b43bb76cb1..e2f8de1cd2 100644 --- a/src/Layers/xrRender/DetailManager_VS.cpp +++ b/src/Layers/xrRender/DetailManager_VS.cpp @@ -17,12 +17,28 @@ const int quant = 16384; const int c_hdr = 10; const int c_size = 4; +#if defined(USE_DX10) || defined(USE_DX11) +// Slot 0 = per-vertex mesh data; slot 1 = per-instance data (marked PER_INSTANCE by +// ConvertVertexDeclaration since Stream>=1) +static D3DVERTEXELEMENT9 dwDecl[] = +{ + {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, // pos + {0, 12, D3DDECLTYPE_SHORT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0}, // uv,t,mid + {1, 0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1}, // inst m0 + {1, 16, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 2}, // inst m1 + {1, 32, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 3}, // inst m2 + {1, 48, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 4}, // inst color + {1, 64, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 5}, // inst exdata + D3DDECL_END() +}; +#else static D3DVERTEXELEMENT9 dwDecl[] = { {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, // pos {0, 12, D3DDECLTYPE_SHORT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0}, // uv D3DDECL_END() }; +#endif #pragma pack(push,1) struct vertHW @@ -157,6 +173,21 @@ void CDetailManager::hw_Load_Geom() // Declare geometry hw_Geom.create(dwDecl, hw_VB, hw_IB); + +#if defined(USE_DX10) || defined(USE_DX11) + // Dynamic per-instance vertex buffer (slot 1) - filled each pass in hw_Render_dump + { + D3D_BUFFER_DESC idesc; + ZeroMemory(&idesc, sizeof(idesc)); + idesc.ByteWidth = hw_InstanceCapacity * hw_InstanceStride; + idesc.Usage = D3D_USAGE_DYNAMIC; + idesc.BindFlags = D3D_BIND_VERTEX_BUFFER; + idesc.CPUAccessFlags = D3D_CPU_ACCESS_WRITE; + R_CHK(HW.pDevice->CreateBuffer(&idesc, 0, &hw_instanceVB)); + HW.stats_manager.increment_stats_vb(hw_instanceVB); + Msg("* [DETAILS] InstanceVB(%dK), cap(%d)", (hw_InstanceCapacity * hw_InstanceStride) / 1024, hw_InstanceCapacity); + } +#endif } void CDetailManager::hw_Unload() @@ -167,6 +198,10 @@ void CDetailManager::hw_Unload() HW.stats_manager.decrement_stats_ib(hw_IB); _RELEASE(hw_IB); _RELEASE(hw_VB); +#if defined(USE_DX10) || defined(USE_DX11) + HW.stats_manager.decrement_stats_vb(hw_instanceVB); + _RELEASE(hw_instanceVB); +#endif } #if !defined(USE_DX10) && !defined(USE_DX11) diff --git a/src/Layers/xrRender/R_Backend.h b/src/Layers/xrRender/R_Backend.h index 29559dbaed..5f08cf971c 100644 --- a/src/Layers/xrRender/R_Backend.h +++ b/src/Layers/xrRender/R_Backend.h @@ -417,7 +417,7 @@ class ECORE_API CBackend ICF void Render(D3DPRIMITIVETYPE T, u32 startV, u32 PC); #if defined(USE_DX10) || defined(USE_DX11) - ICF void RenderInstanced(D3DPRIMITIVETYPE T, u32 instanceCount, u32 baseV, u32 startV, u32 countV, u32 startI, u32 PC); + ICF void RenderInstanced(D3DPRIMITIVETYPE T, u32 instanceCount, u32 baseV, u32 startV, u32 countV, u32 startI, u32 PC, u32 startInstance = 0); #endif #ifdef USE_DX11 diff --git a/src/Layers/xrRenderDX10/DXCommonTypes.h b/src/Layers/xrRenderDX10/DXCommonTypes.h index c7f3b5f104..348ab27bb6 100644 --- a/src/Layers/xrRenderDX10/DXCommonTypes.h +++ b/src/Layers/xrRenderDX10/DXCommonTypes.h @@ -149,6 +149,7 @@ typedef ID3D11DeviceContext ID3DDeviceContext; #define D3D_FILL_WIREFRAME D3D11_FILL_WIREFRAME #define D3D_INPUT_PER_VERTEX_DATA D3D11_INPUT_PER_VERTEX_DATA +#define D3D_INPUT_PER_INSTANCE_DATA D3D11_INPUT_PER_INSTANCE_DATA #define D3D_BIND_INDEX_BUFFER D3D11_BIND_INDEX_BUFFER #define D3D_BIND_VERTEX_BUFFER D3D11_BIND_VERTEX_BUFFER @@ -405,6 +406,7 @@ typedef ID3D10Resource ID3DResource; #define D3D_FILL_WIREFRAME D3D10_FILL_WIREFRAME #define D3D_INPUT_PER_VERTEX_DATA D3D10_INPUT_PER_VERTEX_DATA +#define D3D_INPUT_PER_INSTANCE_DATA D3D10_INPUT_PER_INSTANCE_DATA #define D3D_BIND_INDEX_BUFFER D3D10_BIND_INDEX_BUFFER #define D3D_BIND_VERTEX_BUFFER D3D10_BIND_VERTEX_BUFFER diff --git a/src/Layers/xrRenderDX10/dx10BufferUtils.cpp b/src/Layers/xrRenderDX10/dx10BufferUtils.cpp index 6e78939f1c..6289904504 100644 --- a/src/Layers/xrRenderDX10/dx10BufferUtils.cpp +++ b/src/Layers/xrRenderDX10/dx10BufferUtils.cpp @@ -142,8 +142,19 @@ namespace dx10BufferUtils descOut.Format = ConvertVertexFormat((D3DDECLTYPE)descIn.Type); descOut.InputSlot = descIn.Stream; descOut.AlignedByteOffset = descIn.Offset; - descOut.InputSlotClass = D3D_INPUT_PER_VERTEX_DATA; - descOut.InstanceDataStepRate = 0; + // Convention: stream 0 = per-vertex; stream >= 1 = per-instance (step rate 1). + // Nothing else in the engine binds a second vertex stream, so this is safe; it + // enables hardware-instanced detail/grass rendering (see CDetailManager). + if (descIn.Stream >= 1) + { + descOut.InputSlotClass = D3D_INPUT_PER_INSTANCE_DATA; + descOut.InstanceDataStepRate = 1; + } + else + { + descOut.InputSlotClass = D3D_INPUT_PER_VERTEX_DATA; + descOut.InstanceDataStepRate = 0; + } } ZeroMemory(&declOut[iDeclSize], sizeof(declOut[iDeclSize])); diff --git a/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp b/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp index ef99658ec5..b69bb1f93c 100644 --- a/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp +++ b/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp @@ -92,6 +92,14 @@ void CDetailManager::hw_Render() // Setup geometry and DMA RCache.set_Geometry(hw_Geom); + // Bind the per-instance vertex stream to slot 1 (filled per-pass in hw_Render_dump). + // The backend only tracks slot 0, so we bind slot 1 directly each frame. + { + UINT istride = hw_InstanceStride; + UINT ioffset = 0; + HW.pContext->IASetVertexBuffers(1, 1, &hw_instanceVB, &istride, &ioffset); + } + // Wave0 float scale = 1.f / float(quant); Fvector4 wave, prev_wave; @@ -167,10 +175,6 @@ void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave Device.Statistic->RenderDUMP_DT_Count = 0; - // Matrices and offsets - u32 vOffset = 0; - u32 iOffset = 0; - vis_list& list = m_visibles[var_id]; CEnvDescriptor& desc = *g_pGamePersistent->Environment().CurrentEnv; @@ -180,179 +184,81 @@ void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave c_ambient.set(desc.ambient.x, desc.ambient.y, desc.ambient.z); c_hemi.set(desc.hemi_color.x, desc.hemi_color.y, desc.hemi_color.z); - // Iterate + // One per-instance record: 3x4 transform rows + color + terrain-normal/alpha (80 bytes, + // matches dwDecl slot-1 layout and hw_InstanceStride). + struct InstanceHW { Fvector4 m0, m1, m2, c0, data; }; + struct DrawRange { u32 instBase, instCount, vOffset, iOffset, prims; }; + DrawRange ranges[dm_max_objects]; + + // ---- FILL: pack all visible instances of this variant into the instance VB (one map) ---- + void* pInstData; +#ifdef USE_DX11 + D3D11_MAPPED_SUBRESOURCE mapped; + CHK_DX(HW.pContext->Map(hw_instanceVB, 0, D3D_MAP_WRITE_DISCARD, 0, &mapped)); + pInstData = mapped.pData; +#else + CHK_DX(hw_instanceVB->Map(D3D_MAP_WRITE_DISCARD, 0, &pInstData)); +#endif + InstanceHW* pInst = (InstanceHW*)pInstData; + + u32 instTotal = 0; + u32 vOffset = 0; + u32 iOffset = 0; for (u32 O = 0; O < objects.size(); O++) { CDetail& Object = *objects[O]; + ranges[O].vOffset = vOffset; + ranges[O].iOffset = iOffset; + ranges[O].prims = Object.number_indices / 3; + ranges[O].instBase = instTotal; + ranges[O].instCount = 0; + xr_vector& vis = list[O]; if (!vis.empty()) { - for (u32 iPass = 0; iPass < Object.shader->E[lod_id]->passes.size(); ++iPass) + xr_vector::iterator _vI = vis.begin(); + xr_vector::iterator _vE = vis.end(); + for (; _vI != _vE; _vI++) { - // Setup matrices + colors (and flush it as necessary) - //RCache.set_Element (Object.shader->E[lod_id]); - RCache.set_Element(Object.shader->E[lod_id], iPass); - RImplementation.apply_lmaterial(); - - // This could be cached in the corresponding consatant buffer - // as it is done for DX9 - RCache.set_c(strConsts, consts); - RCache.set_c(strWave, wave); - RCache.set_c(strDir2D, wind); - RCache.set_c(strXForm, Device.mFullTransform); - RCache.set_c(strGrassAlign, ps_ssfx_terrain_grass_align); - - RCache.set_c(strWavePrev, prev_wave); - RCache.set_c(strDir2DPrev, prev_wind); - - if (ps_ssfx_grass_interactive.y > 0) - { - RCache.set_c(strGrassSetup, ps_ssfx_int_grass_params_1); - - Fvector4* c_grass; - { - void* GrassData; - RCache.get_ConstantDirect(strPos, BendersQty * sizeof(Fvector4) * 2, &GrassData, 0, 0); - c_grass = (Fvector4*)GrassData; - } - VERIFY(c_grass); - - if (c_grass) - { - c_grass[0].set(player_pos); - c_grass[16].set(0.0f, -99.0f, 0.0f, 1.0f); - - for (int Bend = 1; Bend < BendersQty; Bend++) - { - c_grass[Bend].set(GData.pos[Bend].x, GData.pos[Bend].y, GData.pos[Bend].z, GData.radius_curr[Bend]); - c_grass[Bend + 16].set(GData.dir[Bend].x, GData.dir[Bend].y, GData.dir[Bend].z, GData.str[Bend]); - } - } - - Fvector4* c_prev_grass; - { - void* prev_GrassData; - RCache.get_ConstantDirect(strPrevPos, BendersQty * sizeof(Fvector4) * 2, &prev_GrassData, 0, 0); - c_prev_grass = (Fvector4*)prev_GrassData; - } - VERIFY(c_prev_grass); - - if (c_prev_grass) - { - for (int Bend = 0; Bend < BendersQty; Bend++) - { - c_prev_grass[Bend].set(GData.prev_pos[Bend]); - c_prev_grass[Bend + 16].set(GData.prev_dir[Bend]); - } - } - } - - Fvector4* c_ExData = 0; + SlotItemVec* items = *_vI; + SlotItemVecIt _iI = items->begin(); + SlotItemVecIt _iE = items->end(); + for (; _iI != _iE; _iI++) { - void* pExtraData; - RCache.get_ConstantDirect(strExData, hw_BatchSize * sizeof(Fvector4), &pExtraData, 0, 0); - c_ExData = (Fvector4*)pExtraData; - } - VERIFY(c_ExData); - - //ref_constant constArray = RCache.get_c(strArray); - //VERIFY(constArray); - - //u32 c_base = x_array->vs.index; - //Fvector4* c_storage = RCache.get_ConstantCache_Vertex().get_array_f().access(c_base); - Fvector4* c_storage = 0; - // Map constants to memory directly - { - void* pVData; - RCache.get_ConstantDirect(strArray, - hw_BatchSize * sizeof(Fvector4) * 4, - &pVData, 0, 0); - c_storage = (Fvector4*)pVData; - } - VERIFY(c_storage); - - u32 dwBatch = 0; - - xr_vector::iterator _vI = vis.begin(); - xr_vector::iterator _vE = vis.end(); - for (; _vI != _vE; _vI++) - { - SlotItemVec* items = *_vI; - SlotItemVecIt _iI = items->begin(); - SlotItemVecIt _iE = items->end(); - for (; _iI != _iE; _iI++) - { - SlotItem& Instance = **_iI; - u32 base = dwBatch * 4; - - Instance.alpha += GoToValue(Instance.alpha, Instance.alpha_target); - - float scale = Instance.scale_calculated; - - // Sort of fade using the scale - // fade_distance == -1 use light_position to define "fade", anything else uses fade_distance - if (fade_distance <= -1) - scale *= 1.0f - Instance.position.distance_to_xz_sqr(light_position) * 0.005f; - else if (Instance.distance > fade_distance) - scale *= 1.0f - abs(Instance.distance - fade_distance) * 0.005f; - - if (scale <= 0 || Instance.alpha <= 0) - break; - - // Build matrix ( 3x4 matrix, last row - color ) - //float scale = Instance.scale_calculated; - Fmatrix& M = Instance.mRotY; - c_storage[base + 0].set(M._11 * scale, M._21 * scale, M._31 * scale, M._41); - c_storage[base + 1].set(M._12 * scale, M._22 * scale, M._32 * scale, M._42); - c_storage[base + 2].set(M._13 * scale, M._23 * scale, M._33 * scale, M._43); - //RCache.set_ca(&*constArray, base+0, M._11*scale, M._21*scale, M._31*scale, M._41 ); - //RCache.set_ca(&*constArray, base+1, M._12*scale, M._22*scale, M._32*scale, M._42 ); - //RCache.set_ca(&*constArray, base+2, M._13*scale, M._23*scale, M._33*scale, M._43 ); - - // Build color - // R2 only needs hemisphere - float h = Instance.c_hemi; - float s = Instance.c_sun; - c_storage[base + 3].set(s, s, s, h); - - if (c_ExData) - c_ExData[dwBatch].set(Instance.normal.x, Instance.normal.y, Instance.normal.z, Instance.alpha); - - //RCache.set_ca(&*constArray, base+3, s, s, s, h ); - dwBatch ++; - if (dwBatch == hw_BatchSize) - { - Device.Statistic->RenderDUMP_DT_Count += dwBatch; - u32 dwCNT_verts = dwBatch * Object.number_vertices; // for stats only - RCache.RenderInstanced(D3DPT_TRIANGLELIST, dwBatch, vOffset, 0, Object.number_vertices, - iOffset, Object.number_indices / 3); - RCache.stat.r.s_details.add(dwCNT_verts); - - // restart - dwBatch = 0; - - // Remap constants to memory directly (just in case anything goes wrong) - { - void* pVData; - RCache.get_ConstantDirect(strArray, - hw_BatchSize * sizeof(Fvector4) * 4, - &pVData, 0, 0); - c_storage = (Fvector4*)pVData; - } - VERIFY(c_storage); - } - } - } - // flush if nessecary - if (dwBatch) - { - Device.Statistic->RenderDUMP_DT_Count += dwBatch; - u32 dwCNT_verts = dwBatch * Object.number_vertices; // for stats only - RCache.RenderInstanced(D3DPT_TRIANGLELIST, dwBatch, vOffset, 0, Object.number_vertices, - iOffset, Object.number_indices / 3); - RCache.stat.r.s_details.add(dwCNT_verts); + SlotItem& Instance = **_iI; + + Instance.alpha += GoToValue(Instance.alpha, Instance.alpha_target); + + float scale = Instance.scale_calculated; + + // Sort of fade using the scale + // fade_distance == -1 use light_position to define "fade", anything else uses fade_distance + if (fade_distance <= -1) + scale *= 1.0f - Instance.position.distance_to_xz_sqr(light_position) * 0.005f; + else if (Instance.distance > fade_distance) + scale *= 1.0f - abs(Instance.distance - fade_distance) * 0.005f; + + if (scale <= 0 || Instance.alpha <= 0) + break; + + if (instTotal >= (u32)hw_InstanceCapacity) + break; + + // Build matrix ( 3x4 matrix ) + color + terrain-normal/alpha + Fmatrix& M = Instance.mRotY; + InstanceHW& R = pInst[instTotal]; + R.m0.set(M._11 * scale, M._21 * scale, M._31 * scale, M._41); + R.m1.set(M._12 * scale, M._22 * scale, M._32 * scale, M._42); + R.m2.set(M._13 * scale, M._23 * scale, M._33 * scale, M._43); + R.c0.set(Instance.c_sun, Instance.c_sun, Instance.c_sun, Instance.c_hemi); + R.data.set(Instance.normal.x, Instance.normal.y, Instance.normal.z, Instance.alpha); + instTotal++; } + if (instTotal >= (u32)hw_InstanceCapacity) + break; } + ranges[O].instCount = instTotal - ranges[O].instBase; + // Clean up // KD: we must not clear vis on r2 since we want details shadows if (ps_ssfx_grass_shadows.x <= 0) @@ -369,4 +275,78 @@ void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave vOffset += Object.number_vertices; iOffset += Object.number_indices; } + +#ifdef USE_DX11 + HW.pContext->Unmap(hw_instanceVB, 0); +#else + hw_instanceVB->Unmap(); +#endif + + if (instTotal == 0) + return; + + // ---- DRAW: set element + globals once (all detail objects share one shader), then one + // hardware-instanced draw per mesh type using StartInstanceLocation. No per-draw CB churn. ---- + ShaderElement* E = &*objects[0]->shader->E[lod_id]; + for (u32 iPass = 0; iPass < E->passes.size(); ++iPass) + { + RCache.set_Element(E, iPass); + RImplementation.apply_lmaterial(); + + RCache.set_c(strConsts, consts); + RCache.set_c(strWave, wave); + RCache.set_c(strDir2D, wind); + RCache.set_c(strXForm, Device.mFullTransform); + RCache.set_c(strGrassAlign, ps_ssfx_terrain_grass_align); + RCache.set_c(strWavePrev, prev_wave); + RCache.set_c(strDir2DPrev, prev_wind); + + if (ps_ssfx_grass_interactive.y > 0) + { + RCache.set_c(strGrassSetup, ps_ssfx_int_grass_params_1); + + Fvector4* c_grass; + { + void* GrassData; + RCache.get_ConstantDirect(strPos, BendersQty * sizeof(Fvector4) * 2, &GrassData, 0, 0); + c_grass = (Fvector4*)GrassData; + } + if (c_grass) + { + c_grass[0].set(player_pos); + c_grass[16].set(0.0f, -99.0f, 0.0f, 1.0f); + + for (int Bend = 1; Bend < BendersQty; Bend++) + { + c_grass[Bend].set(GData.pos[Bend].x, GData.pos[Bend].y, GData.pos[Bend].z, GData.radius_curr[Bend]); + c_grass[Bend + 16].set(GData.dir[Bend].x, GData.dir[Bend].y, GData.dir[Bend].z, GData.str[Bend]); + } + } + + Fvector4* c_prev_grass; + { + void* prev_GrassData; + RCache.get_ConstantDirect(strPrevPos, BendersQty * sizeof(Fvector4) * 2, &prev_GrassData, 0, 0); + c_prev_grass = (Fvector4*)prev_GrassData; + } + if (c_prev_grass) + { + for (int Bend = 0; Bend < BendersQty; Bend++) + { + c_prev_grass[Bend].set(GData.prev_pos[Bend]); + c_prev_grass[Bend + 16].set(GData.prev_dir[Bend]); + } + } + } + + for (u32 O = 0; O < objects.size(); O++) + { + if (ranges[O].instCount == 0) + continue; + RCache.RenderInstanced(D3DPT_TRIANGLELIST, ranges[O].instCount, ranges[O].vOffset, 0, + objects[O]->number_vertices, ranges[O].iOffset, ranges[O].prims, ranges[O].instBase); + Device.Statistic->RenderDUMP_DT_Count += ranges[O].instCount; + RCache.stat.r.s_details.add(ranges[O].instCount * objects[O]->number_vertices); + } + } } diff --git a/src/Layers/xrRenderDX10/dx10R_Backend_Runtime.h b/src/Layers/xrRenderDX10/dx10R_Backend_Runtime.h index f2fa9a92a0..4842956be9 100644 --- a/src/Layers/xrRenderDX10/dx10R_Backend_Runtime.h +++ b/src/Layers/xrRenderDX10/dx10R_Backend_Runtime.h @@ -328,7 +328,7 @@ IC void CBackend::Render(D3DPRIMITIVETYPE T, u32 baseV, u32 startV, u32 countV, } IC void CBackend::RenderInstanced(D3DPRIMITIVETYPE T, u32 instanceCount, u32 baseV, u32 startV, u32 countV, u32 startI, - u32 PC) + u32 PC, u32 startInstance) { D3D_PRIMITIVE_TOPOLOGY Topology = TranslateTopology(T); u32 iIndexCount = GetIndexCount(T, PC); @@ -354,7 +354,7 @@ IC void CBackend::RenderInstanced(D3DPRIMITIVETYPE T, u32 instanceCount, u32 bas // State manager may alter constants constants.flush(); - HW.pContext->DrawIndexedInstanced(iIndexCount, instanceCount, startI, baseV, 0); + HW.pContext->DrawIndexedInstanced(iIndexCount, instanceCount, startI, baseV, startInstance); PGO(Msg("PGO:DIPInst:%dv/%df x%d",countV,PC,instanceCount)); } From c924064a0f117de33e756750d69fb2c3df0315cb Mon Sep 17 00:00:00 2001 From: Sam Loeschen Date: Mon, 8 Jun 2026 18:44:17 -0500 Subject: [PATCH 3/5] try a different falloff function for grass --- src/Layers/xrRender/DetailManager.cpp | 38 ++++++++++++++++++++++++ src/Layers/xrRender/xrRender_console.cpp | 8 +++++ src/Layers/xrRender/xrRender_console.h | 2 ++ 3 files changed, 48 insertions(+) diff --git a/src/Layers/xrRender/DetailManager.cpp b/src/Layers/xrRender/DetailManager.cpp index 3a58b6e074..ee77be4588 100644 --- a/src/Layers/xrRender/DetailManager.cpp +++ b/src/Layers/xrRender/DetailManager.cpp @@ -24,6 +24,18 @@ const float dbgOffset = 0.f; const int dbgItems = 128; + +ICF float dm_grass_instance_hash(float x, float z) +{ + s32 ix = iFloor(x * 137.f); + s32 iz = iFloor(z * 137.f); + u32 h = u32(ix) * 73856093u ^ u32(iz) * 19349663u; + h ^= h >> 13; + h *= 0x85ebca6bu; + h ^= h >> 16; + return float(h & 0xFFFFFFu) * (1.f / float(0x1000000)); +} + //--------------------------------------------------- Decompression static int magic4x4[4][4] = { @@ -349,6 +361,25 @@ void CDetailManager::UpdateVisibleM() float alpha_i = 1.f - alpha; float dist_sq_rcp = 1.f / dist_sq; + // Distance density falloff falls to zero after power curve knee + // 0 at knee -> 1 at edge + // curve > 1: drops fast right after the knee then a long tail + // curve < 1: holds then cliffs at the edge + // curve = 1: linear + float keepP = 1.f; + { + float knee = ps_r__Detail_density_knee; + float dist_frac = _sqrt(dist_sq) / dm_fade; + if (dist_frac >= 1.f) + keepP = 0.f; + else if (dist_frac > knee && knee < 1.f) + { + + float t = (dist_frac - knee) / (1.f - knee); + keepP = powf(1.f - t, ps_r__Detail_density_curve); + } + } + S.frame = RDEVICE.dwFrame + Random.randI(15, 30); for (int sp_id = 0; sp_id < dm_obj_in_slot; sp_id++) { @@ -375,6 +406,13 @@ void CDetailManager::UpdateVisibleM() Item.alpha_target = 0; continue; } + // Distance density falloff: drop this instance if its stable hash + // exceeds the slot's keep-probability. + if (keepP < 1.f && dm_grass_instance_hash(Item.mRotY.c.x, Item.mRotY.c.z) > keepP) + { + Item.alpha_target = 0; + continue; + } u32 vis_id = 0; if (ssa > r_ssaCHEAP) vis_id = Item.vis_ID; diff --git a/src/Layers/xrRender/xrRender_console.cpp b/src/Layers/xrRender/xrRender_console.cpp index aa8636e3d3..976fa9ea32 100644 --- a/src/Layers/xrRender/xrRender_console.cpp +++ b/src/Layers/xrRender/xrRender_console.cpp @@ -127,6 +127,12 @@ float ps_r__Detail_l_ambient = 0.9f; float ps_r__Detail_l_aniso = 0.25f; float ps_r__Detail_density = 0.3f; float ps_r__Detail_height = 1.0f; +// Distance-based grass density falloff (render-time, RADIUS-RELATIVE). Full density +// within 'knee' (a FRACTION of the grass radius dm_fade / r__detail_radius), then +// falls to zero at the radius edge along a power curve. Auto-covers the field at any +// radius. knee>=1.0 disables it. +float ps_r__Detail_density_knee = 0.30f; // radius fraction kept fully dense (1.0 = off) +float ps_r__Detail_density_curve = 2.00f; // falloff exponent: >1 drop hard after knee, <1 hold then cliff at edge, 1 linear float ps_r__Detail_rainbow_hemi = 0.75f; float ps_r__Tree_w_rot = 10.0f; @@ -1104,6 +1110,8 @@ void xrRender_initconsole() CMD4(CCC_Float, "r__detail_density", &ps_current_detail_density/*&ps_r__Detail_density*/, 0.04f/*.2f*/, 1.f); //AVO: extended from 0.2 to 0.04 and replaced variable CMD4(CCC_Float, "r__detail_height", &ps_current_detail_height, 0.5f, 2.0f); + CMD4(CCC_Float, "r__detail_density_knee", &ps_r__Detail_density_knee, 0.0f, 1.0f); + CMD4(CCC_Float, "r__detail_density_curve", &ps_r__Detail_density_curve, 0.25f, 12.0f); #ifdef DEBUG CMD4(CCC_Float, "r__detail_l_ambient", &ps_r__Detail_l_ambient, .5f, .95f ); diff --git a/src/Layers/xrRender/xrRender_console.h b/src/Layers/xrRender/xrRender_console.h index 749cc71d7c..63bae4012e 100644 --- a/src/Layers/xrRender/xrRender_console.h +++ b/src/Layers/xrRender/xrRender_console.h @@ -75,6 +75,8 @@ extern ECORE_API float ps_r__Detail_l_ambient; extern ECORE_API float ps_r__Detail_l_aniso; extern ECORE_API float ps_r__Detail_density; extern ECORE_API float ps_r__Detail_height; +extern ECORE_API float ps_r__Detail_density_knee; +extern ECORE_API float ps_r__Detail_density_curve; extern ECORE_API float ps_r__Tree_w_rot; extern ECORE_API float ps_r__Tree_w_speed; From a05898dbd4b13cf84f613ee5435b3cec4ac9e0f4 Mon Sep 17 00:00:00 2001 From: Sam Loeschen Date: Wed, 10 Jun 2026 21:40:25 -0500 Subject: [PATCH 4/5] some more grass upgrades. only fill instances oncer per frame (fade gets moved to VS as a result). compress instances to 64b, and sort by depth --- src/Layers/xrRender/DetailManager.cpp | 17 + src/Layers/xrRender/DetailManager.h | 14 +- src/Layers/xrRender/DetailManager_VS.cpp | 19 +- .../xrRenderDX10/dx10DetailManager_VS.cpp | 346 ++++++++++-------- 4 files changed, 229 insertions(+), 167 deletions(-) diff --git a/src/Layers/xrRender/DetailManager.cpp b/src/Layers/xrRender/DetailManager.cpp index ee77be4588..3ad5ef4493 100644 --- a/src/Layers/xrRender/DetailManager.cpp +++ b/src/Layers/xrRender/DetailManager.cpp @@ -99,6 +99,7 @@ CDetailManager::CDetailManager() hw_IB = 0; #if defined(USE_DX10) || defined(USE_DX11) hw_instanceVB = 0; + hw_frame_filled = u32(-1); #endif m_time_rot_1 = 0; m_time_rot_2 = 0; @@ -450,6 +451,22 @@ void CDetailManager::UpdateVisibleM() } } } + // Sort each object's visible slot-parts front-to-back so draws rasterize near + // grass first, helping with early-z + for (u32 vid = 0; vid < 3; ++vid) + { + vis_list& list = m_visibles[vid]; + for (u32 O = 0; O < list.size(); O++) + { + xr_vector& vis = list[O]; + if (vis.size() > 1) + std::sort(vis.begin(), vis.end(), [](const SlotItemVec* a, const SlotItemVec* b) + { + return a->front()->distance < b->front()->distance; + }); + } + } + RDEVICE.Statistic->RenderDUMP_DT_VIS.End(); } diff --git a/src/Layers/xrRender/DetailManager.h b/src/Layers/xrRender/DetailManager.h index 7f6bae216f..4a2b177550 100644 --- a/src/Layers/xrRender/DetailManager.h +++ b/src/Layers/xrRender/DetailManager.h @@ -229,9 +229,19 @@ class ECORE_API CDetailManager ID3DIndexBuffer* hw_IB; #if defined(USE_DX10) || defined(USE_DX11) // DX10/11 instancing - // One record = 5 float4 (3x4 transform rows + color + terrain-normal/alpha) = 80b - enum { hw_InstanceStride = 5 * sizeof(Fvector4), hw_InstanceCapacity = 1 << 17 }; + // One record = one cache line: 3x4 transform rows (3 float4) + half4 (terrain normal + // xyz + alpha) + half4 (sun, hemi, spare, spare) = 64b + enum { hw_InstanceStride = 64, hw_InstanceCapacity = 1 << 18 }; ID3DVertexBuffer* hw_instanceVB; + + // instead of filling the instance buffer multiple times per frame, we will + // just fill it once and then re-draw from it with offsets using the ranges below. + // this required moving the scale fade into the VS. + + u32 hw_frame_filled; + u32 hw_inst_base[3][dm_max_objects]; + u32 hw_inst_count[3][dm_max_objects]; + void hw_Fill_Instances(); #endif ref_constant hwc_consts; ref_constant hwc_wave; diff --git a/src/Layers/xrRender/DetailManager_VS.cpp b/src/Layers/xrRender/DetailManager_VS.cpp index e2f8de1cd2..c3f3a63a47 100644 --- a/src/Layers/xrRender/DetailManager_VS.cpp +++ b/src/Layers/xrRender/DetailManager_VS.cpp @@ -18,17 +18,17 @@ const int c_hdr = 10; const int c_size = 4; #if defined(USE_DX10) || defined(USE_DX11) -// Slot 0 = per-vertex mesh data; slot 1 = per-instance data (marked PER_INSTANCE by -// ConvertVertexDeclaration since Stream>=1) + +// grass vertices are now compressed to in 64b record per instance, which should be exactly one cache line static D3DVERTEXELEMENT9 dwDecl[] = { - {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, // pos - {0, 12, D3DDECLTYPE_SHORT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0}, // uv,t,mid - {1, 0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1}, // inst m0 - {1, 16, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 2}, // inst m1 - {1, 32, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 3}, // inst m2 - {1, 48, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 4}, // inst color - {1, 64, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 5}, // inst exdata + {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, // pos + {0, 12, D3DDECLTYPE_SHORT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0}, // uv,t,mid + {1, 0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1}, // inst m0 + {1, 16, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 2}, // inst m1 + {1, 32, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 3}, // inst m2 + {1, 48, D3DDECLTYPE_FLOAT16_4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 4}, // inst terrain normal xyz + alpha + {1, 56, D3DDECLTYPE_FLOAT16_4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 5}, // inst sun + hemi (zw spare) D3DDECL_END() }; #else @@ -185,6 +185,7 @@ void CDetailManager::hw_Load_Geom() idesc.CPUAccessFlags = D3D_CPU_ACCESS_WRITE; R_CHK(HW.pDevice->CreateBuffer(&idesc, 0, &hw_instanceVB)); HW.stats_manager.increment_stats_vb(hw_instanceVB); + hw_frame_filled = u32(-1); Msg("* [DETAILS] InstanceVB(%dK), cap(%d)", (hw_InstanceCapacity * hw_InstanceStride) / 1024, hw_InstanceCapacity); } #endif diff --git a/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp b/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp index b69bb1f93c..42a13af6d9 100644 --- a/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp +++ b/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp @@ -51,6 +51,34 @@ float GoToValue(float& current, float go_to) return current < go_to ? r_value : -r_value; } +// f32 -> f16 (round-to-nearest; inputs are tame: [-1..1] normals, [0..1] scalars) +ICF u16 dm_f32tof16(float v) +{ + union { float f; u32 u; } c; + c.f = v; + u32 sign = (c.u >> 16) & 0x8000u; + s32 exp = s32((c.u >> 23) & 0xFFu) - 127 + 15; + u32 mant = c.u & 0x7FFFFFu; + if (exp <= 0) return u16(sign); // underflow -> signed zero + if (exp >= 31) return u16(sign | 0x7BFFu); // overflow -> max finite half + u32 h = sign | (u32(exp) << 10) | (mant >> 13); + h += (mant >> 12) & 1u; // round up; carry into exponent is correct + return u16(h); +} + +// new instance record layout +// half4(terrain normal xyz, alpha) +// half4(sun, hemi, _, _) +#pragma pack(push, 1) +struct InstanceHW +{ + Fvector4 m0, m1, m2; + u16 na[4]; // terrain normal xyz + alpha + u16 sh[4]; // sun, hemi, spare, spare +}; +#pragma pack(pop) +static_assert(sizeof(InstanceHW) == CDetailManager::hw_InstanceStride, "InstanceHW must match hw_InstanceStride"); + void CDetailManager::hw_Load_Shaders() { // Create shader to access constant storage @@ -92,14 +120,20 @@ void CDetailManager::hw_Render() // Setup geometry and DMA RCache.set_Geometry(hw_Geom); - // Bind the per-instance vertex stream to slot 1 (filled per-pass in hw_Render_dump). - // The backend only tracks slot 0, so we bind slot 1 directly each frame. + // bind the per-instance vertex stream to slot 1 (filled once per frame in hw_Fill_Instances) { UINT istride = hw_InstanceStride; UINT ioffset = 0; HW.pContext->IASetVertexBuffers(1, 1, &hw_instanceVB, &istride, &ioffset); } + // fill this once per frame, and let later render phases (cascades, grass shadow lights) reuse it + if (hw_frame_filled != Device.dwFrame) + { + Device.Statistic->RenderDUMP_DT_Count = 0; // accumulates across phases this frame + hw_Fill_Instances(); + } + // Wave0 float scale = 1.f / float(quant); Fvector4 wave, prev_wave; @@ -108,32 +142,21 @@ void CDetailManager::hw_Render() //wave.set (1.f/5.f, 1.f/7.f, 1.f/3.f, Device.fTimeGlobal*swing_current.speed); wave.set(1.f / 5.f, 1.f / 7.f, 1.f / 3.f, m_time_pos); prev_wave.set(1.f / 5.f, 1.f / 7.f, 1.f / 3.f, prev_time); - //RCache.set_c (&*hwc_consts, scale, scale, ps_r__Detail_l_aniso, ps_r__Detail_l_ambient); // consts - //RCache.set_c (&*hwc_wave, wave.div(PI_MUL_2)); // wave - //RCache.set_c (&*hwc_wind, dir1); // wind-dir - //hw_Render_dump (&*hwc_array, 1, 0, c_hdr ); hw_Render_dump(consts, wave.div(PI_MUL_2), dir1, prev_wave.div(PI_MUL_2), prev_dir1, 1, 0); // Wave1 - //wave.set (1.f/3.f, 1.f/7.f, 1.f/5.f, Device.fTimeGlobal*swing_current.speed); wave.set(1.f / 3.f, 1.f / 7.f, 1.f / 5.f, m_time_pos); prev_wave.set(1.f / 3.f, 1.f / 7.f, 1.f / 5.f, prev_time); - //RCache.set_c (&*hwc_wave, wave.div(PI_MUL_2)); // wave - //RCache.set_c (&*hwc_wind, dir2); // wind-dir - //hw_Render_dump (&*hwc_array, 2, 0, c_hdr ); hw_Render_dump(consts, wave.div(PI_MUL_2), dir2, prev_wave.div(PI_MUL_2), prev_dir2, 2, 0); // Still consts.set(scale, scale, scale, 1.f); - //RCache.set_c (&*hwc_s_consts,scale, scale, scale, 1.f); - //RCache.set_c (&*hwc_s_xform, Device.mFullTransform); - //hw_Render_dump (&*hwc_s_array, 0, 1, c_hdr ); hw_Render_dump(consts, wave.div(PI_MUL_2), dir2, prev_wave.div(PI_MUL_2), prev_dir2, 0, 1); - if (prev_frame != Device.dwFrame) + if (prev_frame != Device.dwFrame) { prev_frame = Device.dwFrame; - + // Prev Frame swing time prev_time = m_time_pos; @@ -143,54 +166,9 @@ void CDetailManager::hw_Render() } } -void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave, const Fvector4& wind, - const Fvector4& prev_wave, const Fvector4& prev_wind, u32 var_id, u32 lod_id) +void CDetailManager::hw_Fill_Instances() { - static shared_str strConsts("consts"); - static shared_str strWave("wave"); - static shared_str strDir2D("dir2D"); - static shared_str strArray("array"); - static shared_str strXForm("xform"); - - // Vanilla grass/trees wind - static shared_str strWavePrev("wave_prev"); - static shared_str strDir2DPrev("dir2D_prev"); - - // Grass Benders - static shared_str strPrevPos("benders_prevpos"); - static shared_str strPos("benders_pos"); - static shared_str strGrassSetup("benders_setup"); - - static shared_str strExData("exdata"); - static shared_str strGrassAlign("grass_align"); - - // Grass benders data - IGame_Persistent::grass_data& GData = g_pGamePersistent->grass_shader_data; - Fvector4 player_pos = { 0, 0, 0, 0 }; - int BendersQty = _min(16, ps_ssfx_grass_interactive.y + 1); - - // Add Player? - if (ps_ssfx_grass_interactive.x > 0) - player_pos.set(Device.vCameraPosition.x, Device.vCameraPosition.y, Device.vCameraPosition.z, -1); - - Device.Statistic->RenderDUMP_DT_Count = 0; - - vis_list& list = m_visibles[var_id]; - - CEnvDescriptor& desc = *g_pGamePersistent->Environment().CurrentEnv; - Fvector c_sun, c_ambient, c_hemi; - c_sun.set(desc.sun_color.x, desc.sun_color.y, desc.sun_color.z); - c_sun.mul(.5f); - c_ambient.set(desc.ambient.x, desc.ambient.y, desc.ambient.z); - c_hemi.set(desc.hemi_color.x, desc.hemi_color.y, desc.hemi_color.z); - - // One per-instance record: 3x4 transform rows + color + terrain-normal/alpha (80 bytes, - // matches dwDecl slot-1 layout and hw_InstanceStride). - struct InstanceHW { Fvector4 m0, m1, m2, c0, data; }; - struct DrawRange { u32 instBase, instCount, vOffset, iOffset, prims; }; - DrawRange ranges[dm_max_objects]; - - // ---- FILL: pack all visible instances of this variant into the instance VB (one map) ---- + // packs all the visible instances into a contiguous instance buffer with a single map void* pInstData; #ifdef USE_DX11 D3D11_MAPPED_SUBRESOURCE mapped; @@ -202,20 +180,15 @@ void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave InstanceHW* pInst = (InstanceHW*)pInstData; u32 instTotal = 0; - u32 vOffset = 0; - u32 iOffset = 0; - for (u32 O = 0; O < objects.size(); O++) + BOOL bOverflow = FALSE; + for (u32 vid = 0; vid < 3; vid++) { - CDetail& Object = *objects[O]; - ranges[O].vOffset = vOffset; - ranges[O].iOffset = iOffset; - ranges[O].prims = Object.number_indices / 3; - ranges[O].instBase = instTotal; - ranges[O].instCount = 0; - - xr_vector& vis = list[O]; - if (!vis.empty()) + vis_list& list = m_visibles[vid]; + for (u32 O = 0; O < objects.size(); O++) { + hw_inst_base[vid][O] = instTotal; + + xr_vector& vis = list[O]; xr_vector::iterator _vI = vis.begin(); xr_vector::iterator _vE = vis.end(); for (; _vI != _vE; _vI++) @@ -227,53 +200,44 @@ void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave { SlotItem& Instance = **_iI; + // we only need to run alpha smoothing once per frame now! Instance.alpha += GoToValue(Instance.alpha, Instance.alpha_target); float scale = Instance.scale_calculated; - - // Sort of fade using the scale - // fade_distance == -1 use light_position to define "fade", anything else uses fade_distance - if (fade_distance <= -1) - scale *= 1.0f - Instance.position.distance_to_xz_sqr(light_position) * 0.005f; - else if (Instance.distance > fade_distance) - scale *= 1.0f - abs(Instance.distance - fade_distance) * 0.005f; - if (scale <= 0 || Instance.alpha <= 0) break; if (instTotal >= (u32)hw_InstanceCapacity) + { + bOverflow = TRUE; break; + } - // Build matrix ( 3x4 matrix ) + color + terrain-normal/alpha + // 3x4 transform rows + packed terrain-normal/alpha + sun/hemi Fmatrix& M = Instance.mRotY; InstanceHW& R = pInst[instTotal]; R.m0.set(M._11 * scale, M._21 * scale, M._31 * scale, M._41); R.m1.set(M._12 * scale, M._22 * scale, M._32 * scale, M._42); R.m2.set(M._13 * scale, M._23 * scale, M._33 * scale, M._43); - R.c0.set(Instance.c_sun, Instance.c_sun, Instance.c_sun, Instance.c_hemi); - R.data.set(Instance.normal.x, Instance.normal.y, Instance.normal.z, Instance.alpha); + R.na[0] = dm_f32tof16(Instance.normal.x); + R.na[1] = dm_f32tof16(Instance.normal.y); + R.na[2] = dm_f32tof16(Instance.normal.z); + R.na[3] = dm_f32tof16(Instance.alpha); + R.sh[0] = dm_f32tof16(Instance.c_sun); + R.sh[1] = dm_f32tof16(Instance.c_hemi); + R.sh[2] = 0; + R.sh[3] = 0; instTotal++; } if (instTotal >= (u32)hw_InstanceCapacity) break; } - ranges[O].instCount = instTotal - ranges[O].instBase; + hw_inst_count[vid][O] = instTotal - hw_inst_base[vid][O]; - // Clean up - // KD: we must not clear vis on r2 since we want details shadows - if (ps_ssfx_grass_shadows.x <= 0) - { - if (!psDeviceFlags2.test(rsGrassShadow) || ((ps_r2_ls_flags.test(R2FLAG_SUN_DETAILS) && (RImplementation.PHASE_SMAP == - RImplementation.phase)) // phase smap with shadows - || (ps_r2_ls_flags.test(R2FLAG_SUN_DETAILS) && (RImplementation.PHASE_NORMAL == RImplementation.phase) - && (!RImplementation.is_sun())) // phase normal with shadows without sun - || (!ps_r2_ls_flags.test(R2FLAG_SUN_DETAILS) && (RImplementation.PHASE_NORMAL == RImplementation.phase)) - )) // phase normal without shadows - vis.clear_not_free(); - } + // UpdateVisibleM rebuilds the lists on the next frame + if (!vis.empty()) + vis.clear_not_free(); } - vOffset += Object.number_vertices; - iOffset += Object.number_indices; } #ifdef USE_DX11 @@ -282,71 +246,141 @@ void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave hw_instanceVB->Unmap(); #endif - if (instTotal == 0) + if (bOverflow) + Msg("! [DETAILS] instance buffer overflow, cap(%d) - some grass dropped this frame", (u32)hw_InstanceCapacity); + + hw_frame_filled = Device.dwFrame; +} + +void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave, const Fvector4& wind, + const Fvector4& prev_wave, const Fvector4& prev_wind, u32 var_id, u32 lod_id) +{ + static shared_str strConsts("consts"); + static shared_str strWave("wave"); + static shared_str strDir2D("dir2D"); + static shared_str strXForm("xform"); + + static shared_str strWavePrev("wave_prev"); + static shared_str strDir2DPrev("dir2D_prev"); + + static shared_str strPrevPos("benders_prevpos"); + static shared_str strPos("benders_pos"); + static shared_str strGrassSetup("benders_setup"); + + static shared_str strGrassAlign("grass_align"); + + // phase scale fading (now applied in the vs) + static shared_str strFadeParams("dt_fade_params"); + + u32 total = 0; + for (u32 obj = 0; obj < objects.size(); obj++) { + total += hw_inst_count[var_id][obj]; + if (total == 0) return; - // ---- DRAW: set element + globals once (all detail objects share one shader), then one - // hardware-instanced draw per mesh type using StartInstanceLocation. No per-draw CB churn. ---- - ShaderElement* E = &*objects[0]->shader->E[lod_id]; - for (u32 iPass = 0; iPass < E->passes.size(); ++iPass) - { - RCache.set_Element(E, iPass); - RImplementation.apply_lmaterial(); - - RCache.set_c(strConsts, consts); - RCache.set_c(strWave, wave); - RCache.set_c(strDir2D, wind); - RCache.set_c(strXForm, Device.mFullTransform); - RCache.set_c(strGrassAlign, ps_ssfx_terrain_grass_align); - RCache.set_c(strWavePrev, prev_wave); - RCache.set_c(strDir2DPrev, prev_wind); - - if (ps_ssfx_grass_interactive.y > 0) - { - RCache.set_c(strGrassSetup, ps_ssfx_int_grass_params_1); + // grass benders data + IGame_Persistent::grass_data& GData = g_pGamePersistent->grass_shader_data; + Fvector4 player_pos = { 0, 0, 0, 0 }; + int BendersQty = _min(16, ps_ssfx_grass_interactive.y + 1); - Fvector4* c_grass; - { - void* GrassData; - RCache.get_ConstantDirect(strPos, BendersQty * sizeof(Fvector4) * 2, &GrassData, 0, 0); - c_grass = (Fvector4*)GrassData; - } - if (c_grass) - { - c_grass[0].set(player_pos); - c_grass[16].set(0.0f, -99.0f, 0.0f, 1.0f); + // add player if the grass is interactive + if (ps_ssfx_grass_interactive.x > 0) + player_pos.set(Device.vCameraPosition.x, Device.vCameraPosition.y, Device.vCameraPosition.z, -1); - for (int Bend = 1; Bend < BendersQty; Bend++) - { - c_grass[Bend].set(GData.pos[Bend].x, GData.pos[Bend].y, GData.pos[Bend].z, GData.radius_curr[Bend]); - c_grass[Bend + 16].set(GData.dir[Bend].x, GData.dir[Bend].y, GData.dir[Bend].z, GData.str[Bend]); - } - } + // fade mode (mirrors the legacy CPU-side formulas, see DetailManager.cpp): + // x = 1: camera-distance fade vs y (squared distance threshold); + // x = 2: light xz-distance fade vs zw (light position) + Fvector4 fade_params; + if (fade_distance <= -1) + fade_params.set(2.f, 0.f, light_position.x, light_position.z); + else + fade_params.set(1.f, fade_distance, 0.f, 0.f); - Fvector4* c_prev_grass; - { - void* prev_GrassData; - RCache.get_ConstantDirect(strPrevPos, BendersQty * sizeof(Fvector4) * 2, &prev_GrassData, 0, 0); - c_prev_grass = (Fvector4*)prev_GrassData; - } - if (c_prev_grass) + u32 maxPasses = 1; + for (u32 O = 0; O < objects.size(); O++) + { + if (!hw_inst_count[var_id][O]) + continue; + ShaderElement* E = objects[O]->shader->E[lod_id]._get(); + if (E) + maxPasses = _max(maxPasses, (u32)E->passes.size()); + } + + for (u32 iPass = 0; iPass < maxPasses; ++iPass) + { + // detail meshes may use different textures/shaders. avoid unnecessary + // rebinds by grouping consecutive objects + ShaderElement* curE = 0; + u32 vOffset = 0; + u32 iOffset = 0; + for (u32 O = 0; O < objects.size(); O++) + { + CDetail& Object = *objects[O]; + u32 count = hw_inst_count[var_id][O]; + ShaderElement* E = Object.shader->E[lod_id]._get(); + if (count && E && iPass < E->passes.size()) { - for (int Bend = 0; Bend < BendersQty; Bend++) + if (E != curE) { - c_prev_grass[Bend].set(GData.prev_pos[Bend]); - c_prev_grass[Bend + 16].set(GData.prev_dir[Bend]); + curE = E; + RCache.set_Element(E, iPass); + RImplementation.apply_lmaterial(); + + RCache.set_c(strConsts, consts); + RCache.set_c(strWave, wave); + RCache.set_c(strDir2D, wind); + RCache.set_c(strXForm, Device.mFullTransform); + RCache.set_c(strGrassAlign, ps_ssfx_terrain_grass_align); + RCache.set_c(strWavePrev, prev_wave); + RCache.set_c(strDir2DPrev, prev_wind); + RCache.set_c(strFadeParams, fade_params); + + if (ps_ssfx_grass_interactive.y > 0) + { + RCache.set_c(strGrassSetup, ps_ssfx_int_grass_params_1); + + Fvector4* c_grass; + { + void* GrassData; + RCache.get_ConstantDirect(strPos, BendersQty * sizeof(Fvector4) * 2, &GrassData, 0, 0); + c_grass = (Fvector4*)GrassData; + } + if (c_grass) + { + c_grass[0].set(player_pos); + c_grass[16].set(0.0f, -99.0f, 0.0f, 1.0f); + + for (int Bend = 1; Bend < BendersQty; Bend++) + { + c_grass[Bend].set(GData.pos[Bend].x, GData.pos[Bend].y, GData.pos[Bend].z, GData.radius_curr[Bend]); + c_grass[Bend + 16].set(GData.dir[Bend].x, GData.dir[Bend].y, GData.dir[Bend].z, GData.str[Bend]); + } + } + + Fvector4* c_prev_grass; + { + void* prev_GrassData; + RCache.get_ConstantDirect(strPrevPos, BendersQty * sizeof(Fvector4) * 2, &prev_GrassData, 0, 0); + c_prev_grass = (Fvector4*)prev_GrassData; + } + if (c_prev_grass) + { + for (int Bend = 0; Bend < BendersQty; Bend++) + { + c_prev_grass[Bend].set(GData.prev_pos[Bend]); + c_prev_grass[Bend + 16].set(GData.prev_dir[Bend]); + } + } + } } - } - } - for (u32 O = 0; O < objects.size(); O++) - { - if (ranges[O].instCount == 0) - continue; - RCache.RenderInstanced(D3DPT_TRIANGLELIST, ranges[O].instCount, ranges[O].vOffset, 0, - objects[O]->number_vertices, ranges[O].iOffset, ranges[O].prims, ranges[O].instBase); - Device.Statistic->RenderDUMP_DT_Count += ranges[O].instCount; - RCache.stat.r.s_details.add(ranges[O].instCount * objects[O]->number_vertices); + RCache.RenderInstanced(D3DPT_TRIANGLELIST, count, vOffset, 0, Object.number_vertices, + iOffset, Object.number_indices / 3, hw_inst_base[var_id][O]); + Device.Statistic->RenderDUMP_DT_Count += count; + RCache.stat.r.s_details.add(count * Object.number_vertices); + } + vOffset += Object.number_vertices; + iOffset += Object.number_indices; } } } From dfde5a4ef19327efc4d8edd4b26942036be42b3b Mon Sep 17 00:00:00 2001 From: Sam Loeschen Date: Thu, 18 Jun 2026 20:27:03 -0500 Subject: [PATCH 5/5] [CLAUDE] adapt optimizations to dx10 --- src/Layers/xrRender/DetailManager.cpp | 2 +- src/Layers/xrRender/DetailManager.h | 6 +- src/Layers/xrRender/DetailManager_VS.cpp | 18 +- src/Layers/xrRender/xrRender_console.cpp | 2 +- .../xrRenderDX10/dx10DetailManager_VS.cpp | 167 +++++++++++++++++- 5 files changed, 173 insertions(+), 22 deletions(-) diff --git a/src/Layers/xrRender/DetailManager.cpp b/src/Layers/xrRender/DetailManager.cpp index 291b012abb..be0645be72 100644 --- a/src/Layers/xrRender/DetailManager.cpp +++ b/src/Layers/xrRender/DetailManager.cpp @@ -99,7 +99,7 @@ CDetailManager::CDetailManager() hw_BatchSize = 0; hw_VB = 0; hw_IB = 0; -#if defined(USE_DX10) || defined(USE_DX11) +#ifdef USE_DX11 hw_instanceVB = 0; hw_frame_filled = u32(-1); #endif diff --git a/src/Layers/xrRender/DetailManager.h b/src/Layers/xrRender/DetailManager.h index 06b5c1c3d0..03c4bf7717 100644 --- a/src/Layers/xrRender/DetailManager.h +++ b/src/Layers/xrRender/DetailManager.h @@ -228,9 +228,9 @@ class ECORE_API CDetailManager u32 hw_BatchSize; ID3DVertexBuffer* hw_VB; ID3DIndexBuffer* hw_IB; -#if defined(USE_DX10) || defined(USE_DX11) - // DX10/11 instancing - // One record = one cache line: 3x4 transform rows (3 float4) + half4 (terrain normal +#ifdef USE_DX11 + // DX11 hardware instancing (DX10/R3 keeps the legacy baked-copies path). + // One record = 3x4 transform rows (3 float4) + half4 (terrain normal // xyz + alpha) + half4 (sun, hemi, spare, spare) = 64b enum { hw_InstanceStride = 64, hw_InstanceCapacity = 1 << 18 }; ID3DVertexBuffer* hw_instanceVB; diff --git a/src/Layers/xrRender/DetailManager_VS.cpp b/src/Layers/xrRender/DetailManager_VS.cpp index e12c67a6a5..8a132b98d6 100644 --- a/src/Layers/xrRender/DetailManager_VS.cpp +++ b/src/Layers/xrRender/DetailManager_VS.cpp @@ -17,9 +17,9 @@ const int quant = 16384; const int c_hdr = 10; const int c_size = 4; -#if defined(USE_DX10) || defined(USE_DX11) +#ifdef USE_DX11 -// grass vertices are now compressed to in 64b record per instance, which should be exactly one cache line +// DX11 instancing: slot 0 = single mesh copy, slot 1 = per-instance 64b record (one cache line) static D3DVERTEXELEMENT9 dwDecl[] = { {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, // pos @@ -32,6 +32,7 @@ static D3DVERTEXELEMENT9 dwDecl[] = D3DDECL_END() }; #else +// DX10 (R3) and DX9: single stream, per-vertex 'mid' selects the transform from array[mid] static D3DVERTEXELEMENT9 dwDecl[] = { {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, // pos @@ -68,10 +69,11 @@ void CDetailManager::hw_Load_Geom() clamp(hw_BatchSize, (u32)0, (u32)64); Msg("* [DETAILS] VertexConsts(%d), Batch(%d)", u32(HW.Caps.geometry.dwRegisters), hw_BatchSize); - // On DX10/11 we draw with hardware instancing (DrawIndexedInstanced), so the geometry - // buffers hold a single copy of each mesh and the per-instance transform is fetched in - // the vertex shader via SV_InstanceID. The legacy DX9 path still bakes hw_BatchSize copies. -#if defined(USE_DX10) || defined(USE_DX11) + // On DX11 we draw with hardware instancing (DrawIndexedInstanced), so the geometry + // buffers hold a single copy of each mesh and the per-instance transform comes from a + // per-instance vertex stream. DX10 (R3) has no instancing here, so it bakes hw_BatchSize + // copies and selects the transform per-vertex via 'mid' (like the legacy DX9 path). +#ifdef USE_DX11 const u32 dwCopies = 1; #else const u32 dwCopies = hw_BatchSize; @@ -174,7 +176,7 @@ void CDetailManager::hw_Load_Geom() // Declare geometry hw_Geom.create(dwDecl, hw_VB, hw_IB); -#if defined(USE_DX10) || defined(USE_DX11) +#ifdef USE_DX11 // Dynamic per-instance vertex buffer (slot 1) - filled each pass in hw_Render_dump { D3D_BUFFER_DESC idesc; @@ -199,7 +201,7 @@ void CDetailManager::hw_Unload() HW.stats_manager.decrement_stats_ib(hw_IB); _RELEASE(hw_IB); _RELEASE(hw_VB); -#if defined(USE_DX10) || defined(USE_DX11) +#ifdef USE_DX11 HW.stats_manager.decrement_stats_vb(hw_instanceVB); _RELEASE(hw_instanceVB); #endif diff --git a/src/Layers/xrRender/xrRender_console.cpp b/src/Layers/xrRender/xrRender_console.cpp index 184a9b2b3d..c1000e3622 100644 --- a/src/Layers/xrRender/xrRender_console.cpp +++ b/src/Layers/xrRender/xrRender_console.cpp @@ -131,7 +131,7 @@ float ps_r__Detail_height = 1.0f; // within 'knee' (a FRACTION of the grass radius dm_fade / r__detail_radius), then // falls to zero at the radius edge along a power curve. Auto-covers the field at any // radius. knee>=1.0 disables it. -float ps_r__Detail_density_knee = 0.30f; // radius fraction kept fully dense (1.0 = off) +float ps_r__Detail_density_knee = 1.00f; // radius fraction kept fully dense (1.0 -> linear/vanilla SSA distribution) float ps_r__Detail_density_curve = 2.00f; // falloff exponent: >1 drop hard after knee, <1 hold then cliff at edge, 1 linear float ps_r__Detail_rainbow_hemi = 0.75f; diff --git a/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp b/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp index 593fa36d2b..ae449c5e82 100644 --- a/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp +++ b/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp @@ -51,6 +51,7 @@ float GoToValue(float& current, float go_to) return current < go_to ? r_value : -r_value; } +#ifdef USE_DX11 // f32 -> f16 (round-to-nearest; inputs are tame: [-1..1] normals, [0..1] scalars) ICF u16 dm_f32tof16(float v) { @@ -78,6 +79,7 @@ struct InstanceHW }; #pragma pack(pop) static_assert(sizeof(InstanceHW) == CDetailManager::hw_InstanceStride, "InstanceHW must match hw_InstanceStride"); +#endif void CDetailManager::hw_Load_Shaders() { @@ -123,6 +125,7 @@ void CDetailManager::hw_Render(light* L) RCache.set_xform_world(Fidentity); RCache.set_Geometry(hw_Geom); +#ifdef USE_DX11 // bind the per-instance vertex stream to slot 1 (filled once per frame in hw_Fill_Instances) { UINT istride = hw_InstanceStride; @@ -136,6 +139,10 @@ void CDetailManager::hw_Render(light* L) Device.Statistic->RenderDUMP_DT_Count = 0; // accumulates across phases this frame hw_Fill_Instances(); } +#else + // DX10 (R3): no instancing - the baked-copies path fills the 'array' constant per dump + Device.Statistic->RenderDUMP_DT_Count = 0; // accumulates across this frame's dumps +#endif // Wave0 float scale = 1.f / float(quant); @@ -171,6 +178,7 @@ void CDetailManager::hw_Render(light* L) RCache.set_CullMode(CULL_CCW); } +#ifdef USE_DX11 void CDetailManager::hw_Fill_Instances() { // packs all the visible instances into a contiguous instance buffer with a single map @@ -255,6 +263,7 @@ void CDetailManager::hw_Fill_Instances() hw_frame_filled = Device.dwFrame; } +#endif // USE_DX11 (hw_Fill_Instances) void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave, const Fvector4& wind, const Fvector4& prev_wave, const Fvector4& prev_wind, u32 var_id, u32 lod_id, light* L) @@ -287,15 +296,6 @@ void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave static shared_str strGrassAlign("grass_align"); - // phase scale fading (now applied in the vs) - static shared_str strFadeParams("dt_fade_params"); - - u32 total = 0; - for (u32 obj = 0; obj < objects.size(); obj++) - total += hw_inst_count[var_id][obj]; - if (total == 0) - return; - // grass benders data IGame_Persistent::grass_data& GData = g_pGamePersistent->grass_shader_data; Fvector4 player_pos = { 0, 0, 0, 0 }; @@ -305,6 +305,16 @@ void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave if (ps_ssfx_grass_interactive.x > 0) player_pos.set(Device.vCameraPosition.x, Device.vCameraPosition.y, Device.vCameraPosition.z, -1); +#ifdef USE_DX11 + // phase scale fading (now applied in the vs) + static shared_str strFadeParams("dt_fade_params"); + + u32 total = 0; + for (u32 obj = 0; obj < objects.size(); obj++) + total += hw_inst_count[var_id][obj]; + if (total == 0) + return; + // fade mode (mirrors the legacy CPU-side formulas, see DetailManager.cpp): // x = 1: camera-distance fade vs y (squared distance threshold); // x = 2: light xz-distance fade vs zw (light position) @@ -401,4 +411,143 @@ void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave iOffset += Object.number_indices; } } +#else + // DX10/R3 has no hardware instancing support. Bake hw_BatchSize mesh copies (see hw_Load_Geom) + // and, per batch, fill the 'array' constant with hw_BatchSize transforms - 3 matrix rows + // (scale baked into mRotY_calculated) + (sun,sun,sun,hemi). The VS picks its transform via + // the per-vertex 'mid' + static shared_str strArray("array"); + + vis_list& list = m_visibles[var_id]; + + // per-instance light-range cull on shadow passes (no VS light-fade on DX10) + const bool bIsSMAP = (RImplementation.phase == CRender::PHASE_SMAP && L != nullptr); + const float cull_sqr_range = bIsSMAP ? _sqr(L->range) : 0.0f; + + u32 vOffset = 0; + u32 iOffset = 0; + for (u32 O = 0; O < objects.size(); O++) + { + CDetail& Object = *objects[O]; + xr_vector& vis = list[O]; + if (!vis.empty()) + { + ShaderElement* E = Object.shader->E[lod_id]._get(); + u32 passCount = E ? (u32)E->passes.size() : 0; + for (u32 iPass = 0; iPass < passCount; ++iPass) + { + RCache.set_Element(E, iPass); + RImplementation.apply_lmaterial(); + + RCache.set_c(strConsts, consts); + RCache.set_c(strWave, wave); + RCache.set_c(strDir2D, wind); + RCache.set_c(strXForm, Device.mFullTransform); + RCache.set_c(strGrassAlign, ps_ssfx_terrain_grass_align); + RCache.set_c(strWavePrev, prev_wave); + RCache.set_c(strDir2DPrev, prev_wind); + + if (ps_ssfx_grass_interactive.y > 0) + { + RCache.set_c(strGrassSetup, ps_ssfx_int_grass_params_1); + + Fvector4* c_grass; + { + void* GrassData; + RCache.get_ConstantDirect(strPos, BendersQty * sizeof(Fvector4) * 2, &GrassData, 0, 0); + c_grass = (Fvector4*)GrassData; + } + if (c_grass) + { + c_grass[0].set(player_pos); + c_grass[16].set(0.0f, -99.0f, 0.0f, 1.0f); + + for (int Bend = 1; Bend < BendersQty; Bend++) + { + c_grass[Bend].set(GData.pos[Bend].x, GData.pos[Bend].y, GData.pos[Bend].z, GData.radius_curr[Bend]); + c_grass[Bend + 16].set(GData.dir[Bend].x, GData.dir[Bend].y, GData.dir[Bend].z, GData.str[Bend]); + } + } + + Fvector4* c_prev_grass; + { + void* prev_GrassData; + RCache.get_ConstantDirect(strPrevPos, BendersQty * sizeof(Fvector4) * 2, &prev_GrassData, 0, 0); + c_prev_grass = (Fvector4*)prev_GrassData; + } + if (c_prev_grass) + { + for (int Bend = 0; Bend < BendersQty; Bend++) + { + c_prev_grass[Bend].set(GData.prev_pos[Bend]); + c_prev_grass[Bend + 16].set(GData.prev_dir[Bend]); + } + } + } + + u32 dwBatch = 0; + Fvector4* c_storage = nullptr; + for (SlotItemVec* items : vis) + { + for (SlotItem* pInstance : *items) + { + SlotItem& Instance = *pInstance; + + if (bIsSMAP && L->position.distance_to_sqr(Instance.position) >= cull_sqr_range) + continue; + + // (re)acquire the constant storage at the start of each batch + if (dwBatch == 0) + { + void* pData; + RCache.get_ConstantDirect(strArray, hw_BatchSize * 4 * sizeof(Fvector4), &pData, 0, 0); + c_storage = (Fvector4*)pData; + } + if (!c_storage) + continue; + + u32 base = dwBatch * 4; + Fmatrix& M = Instance.mRotY_calculated; + c_storage[base + 0].set(M._11, M._21, M._31, M._41); + c_storage[base + 1].set(M._12, M._22, M._32, M._42); + c_storage[base + 2].set(M._13, M._23, M._33, M._43); + const float s = Instance.c_sun; + const float h = Instance.c_hemi; + c_storage[base + 3].set(s, s, s, h); + + if (++dwBatch == hw_BatchSize) + { + Device.Statistic->RenderDUMP_DT_Count += dwBatch; + u32 dwCNT_verts = dwBatch * Object.number_vertices; + u32 dwCNT_prims = (dwBatch * Object.number_indices) / 3; + RCache.Render(D3DPT_TRIANGLELIST, vOffset, 0, dwCNT_verts, iOffset, dwCNT_prims); + RCache.stat.r.s_details.add(dwCNT_verts); + dwBatch = 0; + } + } + } + + // flush the remainder + if (dwBatch) + { + Device.Statistic->RenderDUMP_DT_Count += dwBatch; + u32 dwCNT_verts = dwBatch * Object.number_vertices; + u32 dwCNT_prims = (dwBatch * Object.number_indices) / 3; + RCache.Render(D3DPT_TRIANGLELIST, vOffset, 0, dwCNT_verts, iOffset, dwCNT_prims); + RCache.stat.r.s_details.add(dwCNT_verts); + } + } + + // Clear the visible list when grass shadows are off; with shadows on, details_clear() + // clears once at frame end after all shadow passes have drawn from these lists. + if (ps_ssfx_grass_shadows.x <= 0) + { + if (!psDeviceFlags2.test(rsGrassShadow) || RImplementation.PHASE_NORMAL == RImplementation.phase) + vis.clear_not_free(); + } + } + vOffset += hw_BatchSize * Object.number_vertices; + iOffset += hw_BatchSize * Object.number_indices; + } +#endif }