diff --git a/src/Layers/xrRender/DetailManager.cpp b/src/Layers/xrRender/DetailManager.cpp index 4737e4bec9..dbc4497043 100644 --- a/src/Layers/xrRender/DetailManager.cpp +++ b/src/Layers/xrRender/DetailManager.cpp @@ -26,6 +26,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] = { @@ -87,6 +99,10 @@ CDetailManager::CDetailManager() hw_BatchSize = 0; hw_VB = 0; hw_IB = 0; +#ifdef USE_DX11 + hw_instanceVB = 0; + hw_frame_filled = u32(-1); +#endif m_time_rot_1 = 0; m_time_rot_2 = 0; m_time_pos = 0; @@ -378,6 +394,24 @@ 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); + } + } + if(ps_r2_ls_flags.test(R2FLAG_FAST_DETAILS_UPDATE)) S.frame = RDEVICE.dwFrame+1; else @@ -435,6 +469,13 @@ void CDetailManager::UpdateVisibleM() } } + // 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; @@ -478,6 +519,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 cf14153eb0..b5736b94f2 100644 --- a/src/Layers/xrRender/DetailManager.h +++ b/src/Layers/xrRender/DetailManager.h @@ -228,6 +228,22 @@ class ECORE_API CDetailManager u32 hw_BatchSize; ID3DVertexBuffer* hw_VB; ID3DIndexBuffer* hw_IB; +#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; + + // 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; ref_constant hwc_wind; diff --git a/src/Layers/xrRender/DetailManager_VS.cpp b/src/Layers/xrRender/DetailManager_VS.cpp index cae9626547..8a132b98d6 100644 --- a/src/Layers/xrRender/DetailManager_VS.cpp +++ b/src/Layers/xrRender/DetailManager_VS.cpp @@ -17,12 +17,29 @@ const int quant = 16384; const int c_hdr = 10; const int c_size = 4; +#ifdef USE_DX11 + +// 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 + {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 +// 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 {0, 12, D3DDECLTYPE_SHORT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0}, // uv D3DDECL_END() }; +#endif #pragma pack(push,1) struct vertHW @@ -52,14 +69,24 @@ 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 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; +#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 +117,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 +157,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)); @@ -148,6 +175,22 @@ void CDetailManager::hw_Load_Geom() // Declare geometry hw_Geom.create(dwDecl, hw_VB, hw_IB); + +#ifdef 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); + hw_frame_filled = u32(-1); + Msg("* [DETAILS] InstanceVB(%dK), cap(%d)", (hw_InstanceCapacity * hw_InstanceStride) / 1024, hw_InstanceCapacity); + } +#endif } void CDetailManager::hw_Unload() @@ -158,6 +201,10 @@ void CDetailManager::hw_Unload() HW.stats_manager.decrement_stats_ib(hw_IB); _RELEASE(hw_IB); _RELEASE(hw_VB); +#ifdef 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 24b4575bc3..8dd1e22128 100644 --- a/src/Layers/xrRender/R_Backend.h +++ b/src/Layers/xrRender/R_Backend.h @@ -418,6 +418,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, u32 startInstance = 0); +#endif + #ifdef USE_DX11 ICF void Compute(UINT ThreadGroupCountX, UINT ThreadGroupCountY, UINT ThreadGroupCountZ); #endif diff --git a/src/Layers/xrRender/xrRender_console.cpp b/src/Layers/xrRender/xrRender_console.cpp index 33372a9138..c1000e3622 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 = 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; float ps_r__Tree_w_rot = 10.0f; @@ -1117,6 +1123,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 5c856633fa..546e5ab326 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; 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 6be0bd9981..ae449c5e82 100644 --- a/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp +++ b/src/Layers/xrRenderDX10/dx10DetailManager_VS.cpp @@ -51,6 +51,36 @@ 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) +{ + 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"); +#endif + void CDetailManager::hw_Load_Shaders() { // Create shader to access constant storage @@ -94,41 +124,49 @@ void CDetailManager::hw_Render(light* L) RCache.set_CullMode(CULL_NONE); 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; + 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(); + } +#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); Fvector4 wave, prev_wave; Fvector4 consts; - - // Wave0 consts.set(scale, scale, ps_r__Detail_l_aniso, ps_r__Detail_l_ambient); //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, L); // 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, L); // 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, L); - if (prev_frame != Device.dwFrame) + if (prev_frame != Device.dwFrame) { prev_frame = Device.dwFrame; - + // Prev Frame swing time prev_time = m_time_pos; @@ -140,76 +178,272 @@ void CDetailManager::hw_Render(light* L) RCache.set_CullMode(CULL_CCW); } -void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave, const Fvector4& wind, +#ifdef USE_DX11 +void CDetailManager::hw_Fill_Instances() +{ + // packs all the visible instances into a contiguous instance buffer with a single 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; + BOOL bOverflow = FALSE; + for (u32 vid = 0; vid < 3; vid++) + { + 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++) + { + SlotItemVec* items = *_vI; + SlotItemVecIt _iI = items->begin(); + SlotItemVecIt _iE = items->end(); + for (; _iI != _iE; _iI++) + { + SlotItem& Instance = **_iI; + + // we only need to run alpha smoothing once per frame now! + Instance.alpha += GoToValue(Instance.alpha, Instance.alpha_target); + + if (Instance.alpha <= 0) + break; + + if (instTotal >= (u32)hw_InstanceCapacity) + { + bOverflow = TRUE; + break; + } + + // 3x4 transform rows (scale pre-multiplied in UpdateVisibleM) + packed terrain-normal/alpha + sun/hemi + Fmatrix& M = Instance.mRotY_calculated; + InstanceHW& R = pInst[instTotal]; + R.m0.set(M._11, M._21, M._31, M._41); + R.m1.set(M._12, M._22, M._32, M._42); + R.m2.set(M._13, M._23, M._33, M._43); + 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; + } + hw_inst_count[vid][O] = instTotal - hw_inst_base[vid][O]; + + // UpdateVisibleM rebuilds the lists on the next frame + if (!vis.empty()) + vis.clear_not_free(); + } + } + +#ifdef USE_DX11 + HW.pContext->Unmap(hw_instanceVB, 0); +#else + hw_instanceVB->Unmap(); +#endif + + if (bOverflow) + Msg("! [DETAILS] instance buffer overflow, cap(%d) - some grass dropped this frame", (u32)hw_InstanceCapacity); + + 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) { + // still details (var 0) don't cast shadows if (RImplementation.phase == CRender::PHASE_SMAP && var_id == 0) return; + // no point drawing grass if the outdoor sector isn't visible this frame + if (!RImplementation.GMBase.is_sector_visible(RImplementation.pOutdoorSector)) + return; + + if (RImplementation.phase == CRender::PHASE_SMAP && L) + { + if (!L->GMLight.is_sector_visible(RImplementation.pOutdoorSector)) + return; + } + 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 + // 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? + // 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); - Device.Statistic->RenderDUMP_DT_Count = 0; +#ifdef USE_DX11 + // phase scale fading (now applied in the vs) + static shared_str strFadeParams("dt_fade_params"); - // Matrices and offsets - u32 vOffset = 0; - u32 iOffset = 0; + 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) + 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); + + 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()) + { + if (E != curE) + { + 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]); + } + } + } + } + + 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; + } + } +#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]; - 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); + // 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; - // Iterate + 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()) { - for (u32 iPass = 0; iPass < Object.shader->E[lod_id]->passes.size(); ++iPass) + ShaderElement* E = Object.shader->E[lod_id]._get(); + u32 passCount = E ? (u32)E->passes.size() : 0; + for (u32 iPass = 0; iPass < passCount; ++iPass) { - // 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); + RCache.set_Element(E, 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); @@ -223,8 +457,6 @@ void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave 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); @@ -243,8 +475,6 @@ void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave 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++) @@ -255,138 +485,69 @@ void CDetailManager::hw_Render_dump(const Fvector4& consts, const Fvector4& wave } } - Fvector4* c_ExData = 0; - { - 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++) + Fvector4* c_storage = nullptr; + for (SlotItemVec* items : vis) { - SlotItemVec* items = *_vI; - SlotItemVecIt _iI = items->begin(); - SlotItemVecIt _iE = items->end(); - for (; _iI != _iE; _iI++) + for (SlotItem* pInstance : *items) { - SlotItem& Instance = **_iI; + SlotItem& Instance = *pInstance; - if (!RImplementation.GMBase.is_sector_visible(RImplementation.pOutdoorSector)) + if (bIsSMAP && L->position.distance_to_sqr(Instance.position) >= cull_sqr_range) continue; - if (RImplementation.phase == CRender::PHASE_SMAP && L) + // (re)acquire the constant storage at the start of each batch + if (dwBatch == 0) { - if (!L->GMLight.is_sector_visible(RImplementation.pOutdoorSector)) - continue; - - if (L->position.distance_to_sqr(Instance.position) >= _sqr(L->range)) - continue; + 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; - - Instance.alpha += GoToValue(Instance.alpha, Instance.alpha_target); - - float scale = 1.f; - - // 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 ) Fmatrix& M = Instance.mRotY_calculated; - 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 + 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 (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) + 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); 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 + + // 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.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); RCache.stat.r.s_details.add(dwCNT_verts); } } - // Clean up - // KD: we must not clear vis on r2 since we want details shadows + + // 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) // phase normal without shadows + 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 } diff --git a/src/Layers/xrRenderDX10/dx10R_Backend_Runtime.h b/src/Layers/xrRenderDX10/dx10R_Backend_Runtime.h index e83dfad2a3..2297abca0a 100644 --- a/src/Layers/xrRenderDX10/dx10R_Backend_Runtime.h +++ b/src/Layers/xrRenderDX10/dx10R_Backend_Runtime.h @@ -330,6 +330,39 @@ 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, u32 startInstance) +{ + PROF_EVENT("RCache.RenderInstanced"); + 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, startInstance); + + PGO(Msg("PGO:DIPInst:%dv/%df x%d",countV,PC,instanceCount)); +} + IC void CBackend::Render(D3DPRIMITIVETYPE T, u32 startV, u32 PC) { PROF_EVENT("RCache.Render_vb");