Skip to content
57 changes: 57 additions & 0 deletions src/Layers/xrRender/DetailManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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] =
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<SlotItemVec*>& 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();
}

Expand Down
16 changes: 16 additions & 0 deletions src/Layers/xrRender/DetailManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
55 changes: 51 additions & 4 deletions src/Layers/xrRender/DetailManager_VS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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++)
Expand Down Expand Up @@ -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));
Expand All @@ -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()
Expand All @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions src/Layers/xrRender/R_Backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/Layers/xrRender/xrRender_console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 );
Expand Down
2 changes: 2 additions & 0 deletions src/Layers/xrRender/xrRender_console.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/Layers/xrRenderDX10/DXCommonTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
15 changes: 13 additions & 2 deletions src/Layers/xrRenderDX10/dx10BufferUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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]));
Expand Down
Loading