diff --git a/CMakeLists.txt b/CMakeLists.txt index d064ef55730..b1f8bbe737f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,9 @@ option(XRAY_USE_ASAN "Use AddressSanitizer" OFF) option(XRAY_ENABLE_TRACY "Enable tracy profiler" OFF) option(XRAY_USE_AI_PBR "Enable AI PBR texture converter (requires ONNX Runtime)" OFF) +option(XRAY_USE_DLSS "Enable NVIDIA DLSS / NGX (requires SDK)" OFF) +option(XRAY_USE_NRD "Enable NVIDIA NRD denoise via NRDIntegration+NRI (Linux Vulkan: -DXRAY_USE_NRD=ON)" OFF) + include(XRay.Build) include(XRay.Windows.Externals) include(XRay.Packaging) diff --git a/Externals/CMakeLists.txt b/Externals/CMakeLists.txt index 75133bb5cb6..ece6ea73f80 100644 --- a/Externals/CMakeLists.txt +++ b/Externals/CMakeLists.txt @@ -134,3 +134,64 @@ if (NOT TARGET xrLuabind) "Read the build instructions: https://github.com/OpenXRay/xray-16/wiki" ) endif() +if (XRAY_USE_NRD) + if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/NRD/CMakeLists.txt") + message(FATAL_ERROR "XRAY_USE_NRD=ON but Externals/NRD is missing") + endif() + set(NRD_STATIC_LIBRARY ON CACHE BOOL "Build NRD as static library" FORCE) + set(NRD_NRI ON CACHE BOOL "Pull NRI for NRDIntegration" FORCE) + set(NRD_EMBEDS_SPIRV_SHADERS ON CACHE BOOL "NRD embeds SPIRV shaders" FORCE) + set(NRD_EMBEDS_DXIL_SHADERS OFF CACHE BOOL "NRD: no DXIL on Linux Vulkan" FORCE) + set(NRD_EMBEDS_DXBC_SHADERS OFF CACHE BOOL "NRD: no DXBC on Linux Vulkan" FORCE) + set(NRD_NORMAL_ENCODING "3" CACHE STRING "NRD normal encoding (RGBA16_UNORM)" FORCE) + set(NRD_ROUGHNESS_ENCODING "1" CACHE STRING "NRD roughness encoding (LINEAR)" FORCE) + set(NRI_ENABLE_NVAPI OFF CACHE BOOL "" FORCE) + set(NRI_ENABLE_AMDAGS OFF CACHE BOOL "" FORCE) + set(NRI_ENABLE_NVTX_SUPPORT OFF CACHE BOOL "" FORCE) + set(NRI_ENABLE_NONE_SUPPORT OFF CACHE BOOL "" FORCE) + set(NRI_ENABLE_D3D11_SUPPORT OFF CACHE BOOL "" FORCE) + set(NRI_ENABLE_D3D12_SUPPORT OFF CACHE BOOL "" FORCE) + set(NRI_ENABLE_VK_SUPPORT ON CACHE BOOL "" FORCE) + add_subdirectory(NRD) + + set(_xray_nri_vk_core "${CMAKE_BINARY_DIR}/_deps/vulkan_headers-src/include/vulkan/vulkan_core.h") + set(_xray_nri_vk_ok FALSE) + if (EXISTS "${_xray_nri_vk_core}") + file(STRINGS "${_xray_nri_vk_core}" _xray_nri_vk_ver REGEX "^#define VK_HEADER_VERSION ") + if (_xray_nri_vk_ver MATCHES "VK_HEADER_VERSION ([0-9]+)" AND CMAKE_MATCH_1 GREATER_EQUAL 354) + set(_xray_nri_vk_ok TRUE) + endif() + endif() + if (NOT _xray_nri_vk_ok) + set(_xray_nri_vk_zip "${CMAKE_BINARY_DIR}/_deps/vulkan_headers-v1.4.354.zip") + set(_xray_nri_vk_extract "${CMAKE_BINARY_DIR}/_deps/vulkan_headers-v1.4.354-extract") + message(STATUS "NRD: upgrading Vulkan-Headers for NRI to v1.4.354") + file(DOWNLOAD + "https://github.com/KhronosGroup/Vulkan-Headers/archive/refs/tags/v1.4.354.zip" + "${_xray_nri_vk_zip}" + SHOW_PROGRESS + STATUS _xray_nri_vk_dl + ) + list(GET _xray_nri_vk_dl 0 _xray_nri_vk_dl_code) + if (NOT _xray_nri_vk_dl_code EQUAL 0) + message(FATAL_ERROR "NRD: failed to download Vulkan-Headers v1.4.354") + endif() + file(REMOVE_RECURSE "${_xray_nri_vk_extract}") + file(ARCHIVE_EXTRACT INPUT "${_xray_nri_vk_zip}" DESTINATION "${_xray_nri_vk_extract}") + file(REMOVE_RECURSE "${CMAKE_BINARY_DIR}/_deps/vulkan_headers-src") + file(RENAME "${_xray_nri_vk_extract}/Vulkan-Headers-1.4.354" "${CMAKE_BINARY_DIR}/_deps/vulkan_headers-src") + file(REMOVE_RECURSE "${_xray_nri_vk_extract}") + endif() + + if (SHADERMAKE_PATH AND NOT EXISTS "${SHADERMAKE_PATH}") + get_filename_component(_sm_dir "${SHADERMAKE_PATH}" DIRECTORY) + get_filename_component(_sm_alt "${_sm_dir}/../ShaderMake" ABSOLUTE) + if (EXISTS "${_sm_alt}") + file(MAKE_DIRECTORY "${_sm_dir}") + file(CREATE_LINK "${_sm_alt}" "${SHADERMAKE_PATH}" SYMBOLIC) + message(STATUS "NRD: linked ShaderMake -> ${SHADERMAKE_PATH}") + endif() + endif() + + message(STATUS "NRD: enabled (NRI + SPIRV, normalEnc=3 roughnessEnc=1)") +endif() diff --git a/res/gamedata/shaders/r5/atmosphere.h b/res/gamedata/shaders/r5/atmosphere.h new file mode 100644 index 00000000000..76db4cd5acd --- /dev/null +++ b/res/gamedata/shaders/r5/atmosphere.h @@ -0,0 +1,47 @@ +#ifndef ATMOSPHERE_H +#define ATMOSPHERE_H + +static const float3 ATM_RAYLEIGH = float3(5.8e-3, 1.35e-2, 3.31e-2); +static const float ATM_MIE = 2.1e-2; +static const float ATM_MIE_G = 0.76; + +float AtmosphereRayleighPhase(float cosTheta) +{ + return 0.0596831 * (1.0 + cosTheta * cosTheta); +} + +float AtmosphereMiePhase(float cosTheta, float g) +{ + float g2 = g * g; + float denom = 1.0 + g2 - 2.0 * g * cosTheta; + return 0.1193662 * (1.0 - g2) / max(pow(max(denom, 1e-4), 1.5), 1e-4); +} + +void AtmosphereAerial(float3 viewDir, float dist, float3 sunDir, float3 sunColor, float3 fogColor, float strength, + out float3 transmittance, out float3 inscatter) +{ + float t = max(dist, 0.0); + float height = saturate(viewDir.y * 0.5 + 0.5); + float density = exp(-height * 2.4) * strength; + float3 betaR = ATM_RAYLEIGH * density; + float betaM = ATM_MIE * density; + float3 ext = betaR + betaM; + transmittance = exp(-ext * t); + float cosTheta = saturate(dot(viewDir, sunDir)); + float3 ray = betaR * AtmosphereRayleighPhase(cosTheta); + float mie = betaM * AtmosphereMiePhase(cosTheta, ATM_MIE_G); + float3 scatter = (ray + mie) * sunColor + fogColor * betaR * 0.18; + float3 invExt = 1.0 / max(ext, 1e-4); + inscatter = scatter * invExt * (1.0 - transmittance); +} + +float3 AtmosphereSkyInscatter(float3 viewDir, float3 sunDir, float3 sunColor, float3 fogColor, float3 skyTint, float strength) +{ + float3 T, insc; + AtmosphereAerial(viewDir, 80.0, sunDir, sunColor, fogColor, strength, T, insc); + float horizon = saturate(1.0 - abs(viewDir.y)); + float sunGlow = pow(saturate(dot(viewDir, sunDir)), 32.0); + return (insc * skyTint + sunColor * sunGlow * 0.12 * horizon) * strength; +} + +#endif diff --git a/res/gamedata/shaders/r5/bindless_common.h b/res/gamedata/shaders/r5/bindless_common.h index b510a69bff9..ab187097d1f 100644 --- a/res/gamedata/shaders/r5/bindless_common.h +++ b/res/gamedata/shaders/r5/bindless_common.h @@ -28,17 +28,20 @@ Texture2D GetBindlessTexture(uint index) struct MaterialData { - uint diffuseIndex; // Descriptor heap index - uint normalIndex; // Descriptor heap index - uint detailIndex; // Descriptor heap index - uint pbrIndex; // Descriptor heap index + uint diffuseIndex; + uint normalIndex; + uint detailIndex; + uint pbrIndex; float detailScale; float alphaRef; uint flags; uint shaderVariant; + uint lmapIndex; + float emissiveIntensity; + uint _pad1; + uint _pad2; }; -// Material flags #define MAT_FLAG_ALPHA_TEST (1 << 0) #define MAT_FLAG_TWO_SIDED (1 << 1) #define MAT_FLAG_EMISSIVE (1 << 2) @@ -49,6 +52,33 @@ struct MaterialData #define MAT_FLAG_HAS_PBR_LAYER (1 << 7) #define MAT_FLAG_ALPHA_BLEND (1 << 8) #define MAT_FLAG_WATER (1 << 9) +#define MAT_FLAG_FOLIAGE (1 << 10) +#define MAT_FLAG_STEEP_PARALLAX (1 << 11) +#define MAT_FLAG_HAS_LMAP (1 << 12) +#define MAT_FLAG_GLASS (1 << 13) +#define MAT_FLAG_SCOPE (1 << 14) +#define MAT_FLAG_HUD3D (1 << 15) +#define MAT_FLAG_WMARK (1 << 16) + +float GlowTexelAlpha(float4 tex) +{ + return saturate(tex.a); +} + +float GlowTexelMask(float4 tex) +{ + return GlowTexelAlpha(tex); +} + +float ParticleTexelAlpha(float4 tex) +{ + return saturate(tex.a); +} + +float3 GlowEmissiveRgb(float4 tex, float intensity) +{ + return tex.rgb * intensity * GlowTexelAlpha(tex); +} // ═══════════════════════════════════════════════════════ // TERRAIN MATERIAL DATA (matches C++ TerrainMaterialData) @@ -79,9 +109,8 @@ struct TerrainMaterialData uint pbrB_Index; // PBR for mask.b channel uint pbrA_Index; // PBR for mask.a channel - // Properties - float detailScale; // Uniform scale for all 4 detail layers - uint flags; // MAT_FLAG_TERRAIN, MAT_FLAG_HAS_PBR_LAYER + float detailScale; + uint flags; }; // ═══════════════════════════════════════════════════════ diff --git a/res/gamedata/shaders/r5/bindless_forward.ps b/res/gamedata/shaders/r5/bindless_forward.ps index b6fa2fac332..2457ff8fe64 100644 Binary files a/res/gamedata/shaders/r5/bindless_forward.ps and b/res/gamedata/shaders/r5/bindless_forward.ps differ diff --git a/res/gamedata/shaders/r5/bindless_forward.vs b/res/gamedata/shaders/r5/bindless_forward.vs index 5db41f277cc..3398ff71269 100644 --- a/res/gamedata/shaders/r5/bindless_forward.vs +++ b/res/gamedata/shaders/r5/bindless_forward.vs @@ -41,7 +41,9 @@ struct VS_OUTPUT float3 normal : TEXCOORD2; float3 tangent : TEXCOORD3; float3 bitangent: TEXCOORD4; - nointerpolation uint materialID : TEXCOORD5; // Direct material ID (no indirection) + nointerpolation uint materialID : TEXCOORD5; + float hemi : TEXCOORD6; + float2 lmUV : TEXCOORD7; }; // ═══════════════════════════════════════════════════════ @@ -91,7 +93,7 @@ VS_OUTPUT main(VS_INPUT input) float4 worldPos = mul(worldMatrix, float4(input.position.xyz, 1.0)); output.worldPos = worldPos.xyz; float3 clipPos = worldPos.xyz; - if (g_Materials[materialID].flags & MAT_FLAG_ALPHA_BLEND) + if ((instanceData.flags & 0x1) == 0 && (g_Materials[materialID].flags & MAT_FLAG_ALPHA_BLEND)) clipPos += (eye_position - clipPos) * 0.002; output.position = mul(m_VP, float4(clipPos, 1.0)); @@ -101,11 +103,10 @@ VS_OUTPUT main(VS_INPUT input) output.tangent = normalize(mul(worldMatrix3x3, tangentUnpacked)); output.bitangent = normalize(mul(worldMatrix3x3, binormalUnpacked)); - // UVs are pre-unpacked in UnifiedVertex format - pass through directly output.texcoord = input.texcoord; - - // Pass material ID to pixel shader + output.lmUV = input.texcoord1; output.materialID = materialID; + output.hemi = input.normal.a; return output; } diff --git a/res/gamedata/shaders/r5/bindless_lightplanes.ps b/res/gamedata/shaders/r5/bindless_lightplanes.ps new file mode 100644 index 00000000000..e81f9df1926 Binary files /dev/null and b/res/gamedata/shaders/r5/bindless_lightplanes.ps differ diff --git a/res/gamedata/shaders/r5/bindless_particle.ps b/res/gamedata/shaders/r5/bindless_particle.ps index a3298c35f38..66344f8545c 100644 Binary files a/res/gamedata/shaders/r5/bindless_particle.ps and b/res/gamedata/shaders/r5/bindless_particle.ps differ diff --git a/res/gamedata/shaders/r5/bindless_particle_distort.ps b/res/gamedata/shaders/r5/bindless_particle_distort.ps index ef5f6cab6e6..a92516e4d94 100644 Binary files a/res/gamedata/shaders/r5/bindless_particle_distort.ps and b/res/gamedata/shaders/r5/bindless_particle_distort.ps differ diff --git a/res/gamedata/shaders/r5/bindless_skinned.ps b/res/gamedata/shaders/r5/bindless_skinned.ps index b3b38fda58e..1b8b1584440 100644 Binary files a/res/gamedata/shaders/r5/bindless_skinned.ps and b/res/gamedata/shaders/r5/bindless_skinned.ps differ diff --git a/res/gamedata/shaders/r5/bindless_skinned_hud.ps b/res/gamedata/shaders/r5/bindless_skinned_hud.ps index 56a2ca29e59..b4b4b45a491 100644 Binary files a/res/gamedata/shaders/r5/bindless_skinned_hud.ps and b/res/gamedata/shaders/r5/bindless_skinned_hud.ps differ diff --git a/res/gamedata/shaders/r5/bindless_skinned_mdi.ps b/res/gamedata/shaders/r5/bindless_skinned_mdi.ps index d55ce4f6f84..cf4914c0075 100644 Binary files a/res/gamedata/shaders/r5/bindless_skinned_mdi.ps and b/res/gamedata/shaders/r5/bindless_skinned_mdi.ps differ diff --git a/res/gamedata/shaders/r5/bindless_terrain.ps b/res/gamedata/shaders/r5/bindless_terrain.ps index cb576c1a304..3380d68f1ad 100644 Binary files a/res/gamedata/shaders/r5/bindless_terrain.ps and b/res/gamedata/shaders/r5/bindless_terrain.ps differ diff --git a/res/gamedata/shaders/r5/bindless_wallmark.ps b/res/gamedata/shaders/r5/bindless_wallmark.ps index 387a500d140..38096bbffd8 100644 Binary files a/res/gamedata/shaders/r5/bindless_wallmark.ps and b/res/gamedata/shaders/r5/bindless_wallmark.ps differ diff --git a/res/gamedata/shaders/r5/bindless_wallmark_mult.ps b/res/gamedata/shaders/r5/bindless_wallmark_mult.ps index 2f189514208..fc4392273cb 100644 Binary files a/res/gamedata/shaders/r5/bindless_wallmark_mult.ps and b/res/gamedata/shaders/r5/bindless_wallmark_mult.ps differ diff --git a/res/gamedata/shaders/r5/bindless_wallmark_set.ps b/res/gamedata/shaders/r5/bindless_wallmark_set.ps new file mode 100644 index 00000000000..452b3ba0713 Binary files /dev/null and b/res/gamedata/shaders/r5/bindless_wallmark_set.ps differ diff --git a/res/gamedata/shaders/r5/bloom_blur.cs b/res/gamedata/shaders/r5/bloom_blur.cs new file mode 100644 index 00000000000..d3f0ca3374c --- /dev/null +++ b/res/gamedata/shaders/r5/bloom_blur.cs @@ -0,0 +1,38 @@ +cbuffer BloomParams : register(b5) +{ + float2 g_SrcSize; + float2 g_DstSize; + float g_Threshold; + float g_Intensity; + float2 g_Dir; +}; + +Texture2D t_In : register(t0); +RWTexture2D u_Out : register(u0); + +[numthreads(8, 8, 1)] +void main(uint3 id : SV_DispatchThreadID) +{ + uint2 pixel = id.xy; + if (pixel.x >= (uint)g_DstSize.x || pixel.y >= (uint)g_DstSize.y) + return; + + const float w[8] = { + 0.1346, 0.1273, 0.1078, 0.0816, + 0.0553, 0.0335, 0.0182, 0.0088 + }; + + float step = max(g_Intensity, 0.5); + int2 maxP = int2(g_DstSize) - 1; + float4 c = t_In.Load(int3(pixel, 0)) * w[0]; + [unroll] + for (int i = 1; i < 8; ++i) { + int2 o = int2(g_Dir * (float(i) * step) + 0.5); + o = max(o, int2(g_Dir)); + int2 p0 = clamp(int2(pixel) + o, int2(0, 0), maxP); + int2 p1 = clamp(int2(pixel) - o, int2(0, 0), maxP); + c += t_In.Load(int3(p0, 0)) * w[i]; + c += t_In.Load(int3(p1, 0)) * w[i]; + } + u_Out[pixel] = c; +} diff --git a/res/gamedata/shaders/r5/bloom_extract.cs b/res/gamedata/shaders/r5/bloom_extract.cs new file mode 100644 index 00000000000..b316b9428a7 --- /dev/null +++ b/res/gamedata/shaders/r5/bloom_extract.cs @@ -0,0 +1,84 @@ +cbuffer BloomParams : register(b5) +{ + float2 g_SrcSize; + float2 g_DstSize; + float g_Threshold; + float g_Intensity; + float2 g_Pad; +}; + +Texture2D t_Hdr : register(t0); +Texture2D t_exposure : register(t1); +Texture2D t_depth : register(t2); +Texture2D t_WorldPos : register(t3); +RWTexture2D u_Bloom : register(u0); + +#include "shared/surface_marks.h" + +float3 HighTap(float3 rgb, float depth, float scale) +{ + float3 lin = rgb * scale; + float defHdr = 9.0; + if (depth <= 1e-7) + { + lin *= 2.0; + defHdr = 3.0; + } + return lin / defHdr; +} + +[numthreads(8, 8, 1)] +void main(uint3 id : SV_DispatchThreadID) +{ + uint2 dst = id.xy; + if (dst.x >= (uint)g_DstSize.x || dst.y >= (uint)g_DstSize.y) + return; + + int2 src = int2((float2(dst) + 0.5) * g_SrcSize / g_DstSize); + int2 maxS = int2(g_SrcSize) - 1; + + uint dw, dh; + t_depth.GetDimensions(dw, dh); + float2 depthScale = float2(dw, dh) / max(g_SrcSize, float2(1.0, 1.0)); + + float scale = t_exposure.Load(int3(0, 0, 0)); + scale = clamp(scale, 1.0 / 128.0, 20.0); + + int2 o0 = clamp(src + int2(-1, -1), int2(0, 0), maxS); + int2 o1 = clamp(src + int2( 1, -1), int2(0, 0), maxS); + int2 o2 = clamp(src + int2(-1, 1), int2(0, 0), maxS); + int2 o3 = clamp(src + int2( 1, 1), int2(0, 0), maxS); + + float3 c0 = t_Hdr.Load(int3(o0, 0)).rgb; + float3 c1 = t_Hdr.Load(int3(o1, 0)).rgb; + float3 c2 = t_Hdr.Load(int3(o2, 0)).rgb; + float3 c3 = t_Hdr.Load(int3(o3, 0)).rgb; + + int2 d0 = clamp(int2(float2(o0) * depthScale), int2(0, 0), int2(dw, dh) - 1); + int2 d1 = clamp(int2(float2(o1) * depthScale), int2(0, 0), int2(dw, dh) - 1); + int2 d2 = clamp(int2(float2(o2) * depthScale), int2(0, 0), int2(dw, dh) - 1); + int2 d3 = clamp(int2(float2(o3) * depthScale), int2(0, 0), int2(dw, dh) - 1); + + uint ww, wh; + t_WorldPos.GetDimensions(ww, wh); + float2 wpScale = float2(ww, wh) / max(g_SrcSize, float2(1.0, 1.0)); + int2 w0 = clamp(int2(float2(o0) * wpScale), int2(0, 0), int2(ww, wh) - 1); + int2 w1 = clamp(int2(float2(o1) * wpScale), int2(0, 0), int2(ww, wh) - 1); + int2 w2 = clamp(int2(float2(o2) * wpScale), int2(0, 0), int2(ww, wh) - 1); + int2 w3 = clamp(int2(float2(o3) * wpScale), int2(0, 0), int2(ww, wh) - 1); + float hud0 = IsHudSurfMark(t_WorldPos.Load(int3(w0, 0)).w) ? 0.0 : 1.0; + float hud1 = IsHudSurfMark(t_WorldPos.Load(int3(w1, 0)).w) ? 0.0 : 1.0; + float hud2 = IsHudSurfMark(t_WorldPos.Load(int3(w2, 0)).w) ? 0.0 : 1.0; + float hud3 = IsHudSurfMark(t_WorldPos.Load(int3(w3, 0)).w) ? 0.0 : 1.0; + float hudW = hud0 + hud1 + hud2 + hud3; + + float3 s0 = HighTap(c0, t_depth.Load(int3(d0, 0)), scale) * hud0; + float3 s1 = HighTap(c1, t_depth.Load(int3(d1, 0)), scale) * hud1; + float3 s2 = HighTap(c2, t_depth.Load(int3(d2, 0)), scale) * hud2; + float3 s3 = HighTap(c3, t_depth.Load(int3(d3, 0)), scale) * hud3; + + float3 avg = (hudW > 0.5) ? ((s0 + s1) + (s2 + s3)) * (2.0 / hudW) : 0.0; + float hi = max(dot(avg, float3(1.0, 1.0, 1.0)) - g_Threshold, 0.0); + hi *= max(g_Intensity, 0.5); + u_Bloom[dst] = float4(avg, hi); +} diff --git a/res/gamedata/shaders/r5/clouds.ps b/res/gamedata/shaders/r5/clouds.ps new file mode 100644 index 00000000000..ec3fd308b25 Binary files /dev/null and b/res/gamedata/shaders/r5/clouds.ps differ diff --git a/res/gamedata/shaders/r5/clouds.vs b/res/gamedata/shaders/r5/clouds.vs new file mode 100644 index 00000000000..98556892e71 --- /dev/null +++ b/res/gamedata/shaders/r5/clouds.vs @@ -0,0 +1,30 @@ +#include "shared/common.h" +#include "shared/cloudconfig.h" + +struct VS_INPUT +{ + float3 p : POSITION; + float4 dir : COLOR0; + float4 color : COLOR1; +}; + +struct VS_OUTPUT +{ + float4 hpos : SV_Position; + float4 color : COLOR0; + float2 tc0 : TEXCOORD0; + float2 tc1 : TEXCOORD1; +}; + +VS_OUTPUT main(VS_INPUT v) +{ + VS_OUTPUT o; + o.hpos = mul(m_WVP, float4(v.p, 1.0)); + float2 d0 = v.dir.xy * 2.0 - 1.0; + float2 d1 = v.dir.wz * 2.0 - 1.0; + o.tc0 = v.p.xz * CLOUD_TILE0 + d0 * timers.z * CLOUD_SPEED0; + o.tc1 = v.p.xz * CLOUD_TILE1 + d1 * timers.z * CLOUD_SPEED1; + o.color = v.color; + o.color.w *= pow(max(v.p.y, 0.0), 25.0); + return o; +} diff --git a/res/gamedata/shaders/r5/cluster_light_assign.cs b/res/gamedata/shaders/r5/cluster_light_assign.cs index 7fcc98c4562..607091401b9 100644 --- a/res/gamedata/shaders/r5/cluster_light_assign.cs +++ b/res/gamedata/shaders/r5/cluster_light_assign.cs @@ -46,17 +46,24 @@ void main(uint3 dtid : SV_DispatchThreadID) float2 tileMax = float2(min((tileX + 1) * tileSize, cb_screenSize.x), min((tileY + 1) * tileSize, cb_screenSize.y)); - uint numVisible = min(g_VisibleLightCount.Load(0), 1024u); + uint numLights = min((uint)cb_gridDims.w, 2048u); uint lightCount = 0; - uint lightIndices[256]; + uint lightIndices[128]; - for (uint iter = 0; iter < numVisible; iter++) + for (uint i = 0; i < numLights; i++) { - uint i = g_VisibleLightIndices[iter]; + if (lightCount >= 128) + break; + + if (g_VisibleLightIndices[i] == 0u) + continue; + GPULightData ld = g_Lights[i]; float3 lightPos = ld.positionAndInvRangeSq.xyz; float range = ld.colorAndRange.w; + if (range <= 1e-4 || abs(ld.positionAndInvRangeSq.w) <= 1e-8) + continue; float4 clipPos = mul(m_VP, float4(lightPos, 1.0)); float lightDepth = clipPos.w; @@ -78,7 +85,9 @@ void main(uint3 dtid : SV_DispatchThreadID) screenPos.y = (0.5 - ndc.y * 0.5) * cb_screenSize.y; float zNearOverlap = max(sliceNear, max(depthNear, zNear)); - float screenRadius = (range / zNearOverlap) * cb_screenSize.y * 0.5; + float cotFovY = max(cb_pad.x, 0.5); + float screenRadius = (range / zNearOverlap) * cotFovY * cb_screenSize.y * 0.5; + screenRadius += tileSize; float2 closest = clamp(screenPos, tileMin, tileMax); float2 diff = screenPos - closest; @@ -86,13 +95,10 @@ void main(uint3 dtid : SV_DispatchThreadID) continue; } - if (lightCount < 256) - lightIndices[lightCount] = i; + lightIndices[lightCount] = i; lightCount++; } - lightCount = min(lightCount, 256); - if (lightCount > 0) { uint globalOffset; diff --git a/res/gamedata/shaders/r5/common_functions.h b/res/gamedata/shaders/r5/common_functions.h index b6c5926c8c8..36dc2d53308 100644 --- a/res/gamedata/shaders/r5/common_functions.h +++ b/res/gamedata/shaders/r5/common_functions.h @@ -109,6 +109,12 @@ float get_sun( float4 lmh) return lmh.g; } +float calc_model_hemi(float3 norm_w) +{ + float ny = normalize(norm_w).y; + return saturate(0.52f + 0.48f * ny); +} + float3 v_hemi(float3 n) { return L_hemi_color.rgb*(.5f + .5f*n.y); @@ -219,6 +225,7 @@ float gbuf_unpack_mtl( float mtl_hemi ) #include "shared/pbr_brdf.h" #include "shared/clustered_lighting.h" +#include "shared/basecolor_pack.h" float3 worldNormalToView(float3 N) { @@ -227,22 +234,43 @@ float3 worldNormalToView(float3 N) float3 reconstruct_world_pos(float2 svPosXY, float depth) { - float2 uv = svPosXY * screen_res.zw; + float2 uv = svPosXY * pos_decompression_params2.zw; float4 clip = float4(uv * 2.0 - 1.0, depth, 1.0); clip.y = -clip.y; float4 world = mul(m_InvVP, clip); return world.xyz / world.w; } +bool IsSkyDepth(float d) +{ + return d <= 1e-7; +} + f_forward output_forward_color(float3 albedo, float3 normal, float3 worldPos, float metallic, float roughness) { f_forward res; res.color = float4(albedo, 1.0); res.normal = float4(normalize(normal), roughness); - res.baseColor = float4(albedo, metallic); + res.baseColor = float4(albedo, PackBaseColorA(metallic, 0.0)); return res; } +#ifdef CLUSTERED_LIGHTING_FORWARD +TextureCube env_s0 : register(t25); +TextureCube env_s1 : register(t26); + +float3 SampleEnvHemiIBL(float3 N) +{ + float3 e0 = env_s0.SampleLevel(smp_rtlinear, N, 0).rgb; + float3 e1 = env_s1.SampleLevel(smp_rtlinear, N, 0).rgb; + float3 envSamp = lerp(e0, e1, saturate(L_ambient.w)); + float envLum = dot(envSamp, float3(0.3333, 0.3333, 0.3333)); + float3 env_d = L_hemi_color.rgb * envSamp; + env_d *= envSamp; + return lerp(L_hemi_color.rgb, env_d, saturate(envLum * 8.0)); +} +#endif + f_forward output_forward_pbr( float3 albedo, float3 worldNormal, @@ -250,7 +278,12 @@ f_forward output_forward_pbr( float metallic, float roughness, float ao, - float4 svPosition = float4(0, 0, 0, 0)) + float4 svPosition = float4(0, 0, 0, 0), + float hemi = 1.0, + float sunOcclusion = 1.0, + bool hasLmap = false, + bool forceHudLit = false, + float ambientScale = 1.0) { f_forward res; @@ -258,35 +291,58 @@ f_forward output_forward_pbr( float3 V = normalize(eye_position - worldPos); float3 L = normalize(-L_sun_dir_w); - float3 sunLight = PBRDirectLighting( - albedo, N, V, L, - L_sun_color, - metallic, roughness, (uint)pbr_diffuse_mode - ); + float hemiTerm = saturate(hemi); + const bool rtgiUnlit = parallax.w < -0.5; +#if defined(OX_HUD_FORWARD) + const bool hudLit = !rtgiUnlit; +#else + const bool hudLit = forceHudLit; +#endif + + if (!hasLmap && hemiTerm < 0.01) + hemiTerm = saturate(0.5 + 0.5 * N.y); - float3 ambientColor = L_ambient.rgb + L_hemi_color.rgb * L_hemi_color.w; +#if defined(OX_FLAT_HEMI) + float3 ambientColor = L_ambient.rgb * ambientScale + L_hemi_color.rgb * L_hemi_color.w * hemiTerm; +#elif defined(CLUSTERED_LIGHTING_FORWARD) + float3 ambientColor = L_ambient.rgb * ambientScale + SampleEnvHemiIBL(N) * hemiTerm; +#else + float3 ambientColor = L_ambient.rgb * ambientScale + L_hemi_color.rgb * L_hemi_color.w * hemiTerm; +#endif float3 ambient = PBRAmbient( albedo, N, V, metallic, roughness, ao, ambientColor ); - float3 finalColor = sunLight + ambient; + float3 finalColor = ambient; + if (!rtgiUnlit || hudLit) + { + float3 sunLight = PBRDirectLighting( + albedo, N, V, L, + L_sun_color * saturate(sunOcclusion), + metallic, roughness, (uint)pbr_diffuse_mode + ); + finalColor = sunLight + ambient; + } #ifdef CLUSTERED_LIGHTING_FORWARD - if (svPosition.w != 0) + if (!hudLit && svPosition.w != 0 && !rtgiUnlit) { - float linearDepth = mul(m_V, float4(worldPos, 1.0)).z; - float3 clusterLights = EvaluateClusteredLights( + float linearDepth = abs(mul(m_V, float4(worldPos, 1.0)).z); + finalColor += EvaluateClusteredLights( worldPos, N, V, albedo, metallic, roughness, svPosition.xy, linearDepth, (uint)pbr_diffuse_mode); - finalColor += clusterLights; } #endif - res.color = float4(finalColor, 1.0); + float dist = length(worldPos - eye_position.xyz); + float fog = saturate(dist * fog_params.w + fog_params.x); + finalColor = lerp(finalColor, fog_color.rgb, fog); + + res.color = float4(finalColor, saturate(sunOcclusion)); res.normal = float4(N, roughness); - res.baseColor = float4(albedo, metallic); + res.baseColor = float4(albedo, PackBaseColorA(metallic, 0.0)); return res; } diff --git a/res/gamedata/shaders/r5/common_iostructs.h b/res/gamedata/shaders/r5/common_iostructs.h index 39e7d3581f7..1f8e0f60cdb 100644 --- a/res/gamedata/shaders/r5/common_iostructs.h +++ b/res/gamedata/shaders/r5/common_iostructs.h @@ -384,6 +384,7 @@ struct v2p_flat float3 rotatedNormal2 : TEXCOORD8; nointerpolation uint objectId : TEXCOORD9; float bladeHash : TEXCOORD10; + float sunOcclusion : TEXCOORD11; float4 hpos : SV_Position; }; @@ -408,6 +409,7 @@ struct p_flat float3 rotatedNormal2 : TEXCOORD8; nointerpolation uint objectId : TEXCOORD9; float bladeHash : TEXCOORD10; + float sunOcclusion : TEXCOORD11; float4 hpos : SV_Position; }; @@ -422,6 +424,7 @@ struct v2p_decal #endif float4 position : TEXCOORD1; float3 N : TEXCOORD2; + float sunOcclusion : TEXCOORD3; float4 hpos : SV_Position; }; @@ -434,6 +437,7 @@ struct p_decal #endif float4 position : TEXCOORD1; float3 N : TEXCOORD2; + float sunOcclusion : TEXCOORD3; float4 hpos : SV_Position; }; @@ -450,6 +454,7 @@ struct v2p_billboard float3 N : TEXCOORD2; float heightParam : TEXCOORD3; float bladeHash : TEXCOORD4; + float sunOcclusion : TEXCOORD5; float4 hpos : SV_Position; }; @@ -464,6 +469,7 @@ struct p_billboard float3 N : TEXCOORD2; float heightParam : TEXCOORD3; float bladeHash : TEXCOORD4; + float sunOcclusion : TEXCOORD5; float4 hpos : SV_Position; }; diff --git a/res/gamedata/shaders/r5/common_samplers.h b/res/gamedata/shaders/r5/common_samplers.h index 61e45db7168..b266612d061 100644 --- a/res/gamedata/shaders/r5/common_samplers.h +++ b/res/gamedata/shaders/r5/common_samplers.h @@ -79,4 +79,4 @@ Texture2D s_image; // used in various post-processing Texture2D s_tonemap; // actually MidleGray / exp(Lw + eps) -#endif // #ifndef common_samplers_h_included \ No newline at end of file +#endif // #ifndef common_samplers_h_included diff --git a/res/gamedata/shaders/r5/copy_depth_r32.cs b/res/gamedata/shaders/r5/copy_depth_r32.cs new file mode 100644 index 00000000000..6777bdbe198 --- /dev/null +++ b/res/gamedata/shaders/r5/copy_depth_r32.cs @@ -0,0 +1,17 @@ +Texture2D t_Depth : register(t0); +RWTexture2D u_DepthR32 : register(u0); + +cbuffer CopyDepthParams : register(b0) { + uint g_Width; + uint g_Height; + uint g_Pad0; + uint g_Pad1; +}; + +[numthreads(8, 8, 1)] +void main(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= g_Width || id.y >= g_Height) + return; + u_DepthR32[id.xy] = t_Depth.Load(int3(id.xy, 0)); +} diff --git a/res/gamedata/shaders/r5/cull_utils.h b/res/gamedata/shaders/r5/cull_utils.h index d5d1f88c4b5..1d9528867db 100644 --- a/res/gamedata/shaders/r5/cull_utils.h +++ b/res/gamedata/shaders/r5/cull_utils.h @@ -116,6 +116,13 @@ HiZTestResult HiZTestSphereEx( result.frontDepth = 0.0; result.hiZDepth = 0.0; + float3 toCenter = center - cameraPos; + float distSq = dot(toCenter, toCenter); + float r = max(radius, 0.05); + float r2 = r * r; + if (distSq <= r2 * 6.25 || distSq <= (r + 2.0) * (r + 2.0) || distSq <= 9.0) + return result; + float4 clipPos = mul(pyramidViewProj, float4(center, 1.0)); if (clipPos.w <= 0.001) return result; @@ -123,16 +130,16 @@ HiZTestResult HiZTestSphereEx( float3 ndc = clipPos.xyz / clipPos.w; float projScale = max(abs(pyramidViewProj[0][0]), abs(pyramidViewProj[1][1])); - float2 ndcSize = float2(radius, radius) * projScale / clipPos.w; + float2 ndcSize = float2(r, r) * projScale / clipPos.w * 1.75; float2 minNDC = ndc.xy - ndcSize; float2 maxNDC = ndc.xy + ndcSize; - if (any(minNDC < -1.0) || any(maxNDC > 1.0)) + if (any(minNDC > 1.0) || any(maxNDC < -1.0)) return result; - float2 minUV = minNDC * 0.5 + 0.5; - float2 maxUV = maxNDC * 0.5 + 0.5; + float2 minUV = saturate(minNDC * 0.5 + 0.5); + float2 maxUV = saturate(maxNDC * 0.5 + 0.5); minUV.y = 1.0 - minUV.y; maxUV.y = 1.0 - maxUV.y; @@ -142,6 +149,11 @@ HiZTestResult HiZTestSphereEx( float boxWidth = (boxUV.z - boxUV.x) * float(hiZWidth); float boxHeight = (boxUV.w - boxUV.y) * float(hiZHeight); + float screenArea = boxWidth * boxHeight; + float fullArea = float(hiZWidth) * float(hiZHeight); + if (screenArea > fullArea * 0.08) + return result; + float mipLevel = ceil(log2(max(1.0, max(boxWidth, boxHeight)))); mipLevel = clamp(mipLevel, 0.0, float(hiZMipLevels - 1)); @@ -153,13 +165,14 @@ HiZTestResult HiZTestSphereEx( result.hiZDepth = min(min(d1, d2), min(d3, d4)); float3 viewDir = normalize(center - cameraPos); - float3 frontPoint = center - viewDir * radius; + float3 frontPoint = center - viewDir * r; float4 frontClip = mul(pyramidViewProj, float4(frontPoint, 1.0)); if (frontClip.w <= 0.001) return result; result.frontDepth = frontClip.z / frontClip.w; - result.visible = result.frontDepth >= result.hiZDepth; + const float depthSlop = 0.005; + result.visible = result.frontDepth + depthSlop >= result.hiZDepth; return result; } diff --git a/res/gamedata/shaders/r5/decal_box.ps b/res/gamedata/shaders/r5/decal_box.ps index cd3a04c5433..f20e5363427 100644 Binary files a/res/gamedata/shaders/r5/decal_box.ps and b/res/gamedata/shaders/r5/decal_box.ps differ diff --git a/res/gamedata/shaders/r5/detail_billboard.ps b/res/gamedata/shaders/r5/detail_billboard.ps index 4f2b226b895..7953a5a93bb 100644 Binary files a/res/gamedata/shaders/r5/detail_billboard.ps and b/res/gamedata/shaders/r5/detail_billboard.ps differ diff --git a/res/gamedata/shaders/r5/detail_billboard.vs b/res/gamedata/shaders/r5/detail_billboard.vs index a0909d5997d..8185dd855b8 100644 --- a/res/gamedata/shaders/r5/detail_billboard.vs +++ b/res/gamedata/shaders/r5/detail_billboard.vs @@ -177,6 +177,7 @@ v2p_billboard main(uint vertex_id : SV_VertexID, uint instance_id : SV_InstanceI uint bh = asuint(bc.x * 73856093 + bc.y * 19349663); bh ^= bh >> 16; O.bladeHash = float(bh & 0xFFFFu) / 65535.0; + O.sunOcclusion = sun; O.hpos = mul(g_detail_VP, world_pos); return O; } diff --git a/res/gamedata/shaders/r5/detail_billboard_shadow.ps b/res/gamedata/shaders/r5/detail_billboard_shadow.ps new file mode 100644 index 00000000000..f9b1fdcdc71 Binary files /dev/null and b/res/gamedata/shaders/r5/detail_billboard_shadow.ps differ diff --git a/res/gamedata/shaders/r5/detail_billboard_shadow.vs b/res/gamedata/shaders/r5/detail_billboard_shadow.vs new file mode 100644 index 00000000000..0b4a949d6fc --- /dev/null +++ b/res/gamedata/shaders/r5/detail_billboard_shadow.vs @@ -0,0 +1,114 @@ +// detail_billboard_shadow.vs — CoP billboard grass caster for CSM (r__detail_gpu 0) +#define SM_6_0 + +cbuffer ShadowCascadeCB : register(b5) +{ + float4x4 cb_LightVP; +}; + +cbuffer GrassShadowCB : register(b6) +{ + float grass_blade_height; + uint build_details_index; + float wind_angle_deg; + float wind_speed; + float time; + float wind_displacement; + float2 grass_shadow_pad; +}; + +struct InstanceData +{ + float3 pos; + uint packed; +}; + +struct DetailModelGPU +{ + float minScale; + float maxScale; + float flags; + float geomExtentX; + float geomExtentZ; + float uv_min_x; + float uv_min_y; + float uv_max_x; + float uv_max_y; + uint pulledVertexBase; + uint pulledIndexCount; + float geomExtentY; +}; + +struct PulledVertex +{ + float px, py, pz; + float u, v; +}; + +static const float PACK_MAX_SCALE = 4.0; +static const float TWO_PI = 6.28318530718; + +StructuredBuffer visible_indices : register(t0); +StructuredBuffer detail_models : register(t1); +StructuredBuffer pulled_vertices : register(t2); +StructuredBuffer all_instances : register(t3); +Texture3D g_Perlin4D : register(t4); +SamplerState smp_linear : register(s0); + +struct VS_OUTPUT +{ + float4 position : SV_Position; + float2 tc : TEXCOORD0; +}; + +VS_OUTPUT main(uint vertex_id : SV_VertexID, uint instance_id : SV_InstanceID) +{ + VS_OUTPUT O; + O.position = float4(0, 0, 0, 1); + O.tc = 0; + + uint src_idx = visible_indices[instance_id]; + InstanceData raw = all_instances[src_idx]; + + uint object_id = raw.packed & 0x3F; + float rotation = float((raw.packed >> 8) & 0x3FF) / 1023.0 * TWO_PI; + float scale = float((raw.packed >> 18) & 0x3FF) / 1023.0 * PACK_MAX_SCALE; + + DetailModelGPU mdl = detail_models[object_id]; + if (vertex_id >= mdl.pulledIndexCount) + { + O.position = float4(asfloat(0x7FC00000), asfloat(0x7FC00000), + asfloat(0x7FC00000), asfloat(0x7FC00000)); + return O; + } + + PulledVertex v = pulled_vertices[mdl.pulledVertexBase + vertex_id]; + float3 local_pos = float3(v.px, v.py, v.pz) * scale; + + float c = cos(rotation); + float s = sin(rotation); + float3 rotated; + rotated.x = local_pos.x * c - local_pos.z * s; + rotated.y = local_pos.y; + rotated.z = local_pos.x * s + local_pos.z * c; + + float4 world_pos = float4(rotated + raw.pos, 1.0); + float height_factor = saturate(v.py / max(mdl.geomExtentY, 0.01)); + float speed = max(wind_speed, 0.1); + float wind_angle = wind_angle_deg * (3.14159265359 / 180.0); + float2 global_wind_dir = float2(sin(wind_angle), cos(wind_angle)); + float2 dir_uv = world_pos.zx * (0.005 / speed) + time * (0.005 * speed); + float wind_dir_noise = g_Perlin4D.SampleLevel(smp_linear, float3(dir_uv, 0), 0).r; + float2 str_uv = world_pos.xz * (0.025 / speed) + time * 0.05; + float wind_str_noise = g_Perlin4D.SampleLevel(smp_linear, float3(str_uv, 0), 0).r; + float wind_strength = lerp(0.25, 1.0, wind_str_noise); + wind_strength *= wind_strength * speed; + float turbulence = (wind_dir_noise * 2.0 - 1.0) * 0.3; + float2 perpendicular_dir = float2(-global_wind_dir.y, global_wind_dir.x); + float2 wind_dir = normalize(global_wind_dir + perpendicular_dir * turbulence); + float displacement = wind_strength * wind_displacement * height_factor; + world_pos.xz += displacement * wind_dir; + O.position = mul(cb_LightVP, world_pos); + O.tc = float2(v.u, v.v); + return O; +} diff --git a/res/gamedata/shaders/r5/detail_decal.ps b/res/gamedata/shaders/r5/detail_decal.ps index 46b1f8e36b4..0901687cd3e 100644 Binary files a/res/gamedata/shaders/r5/detail_decal.ps and b/res/gamedata/shaders/r5/detail_decal.ps differ diff --git a/res/gamedata/shaders/r5/detail_decal.vs b/res/gamedata/shaders/r5/detail_decal.vs index 498891289b4..035fbe381b0 100644 --- a/res/gamedata/shaders/r5/detail_decal.vs +++ b/res/gamedata/shaders/r5/detail_decal.vs @@ -130,6 +130,7 @@ v2p_decal main(uint vertex_id : SV_VertexID, uint instance_id : SV_InstanceID) O.position = float4(world_pos.xyz, hemi); O.N = float3(0, 1, 0); + O.sunOcclusion = sun; O.hpos = mul(g_detail_VP, world_pos); return O; diff --git a/res/gamedata/shaders/r5/detail_gpu.ps b/res/gamedata/shaders/r5/detail_gpu.ps index 6a7617124a4..dc63bb4fa0c 100644 Binary files a/res/gamedata/shaders/r5/detail_gpu.ps and b/res/gamedata/shaders/r5/detail_gpu.ps differ diff --git a/res/gamedata/shaders/r5/detail_gpu.vs b/res/gamedata/shaders/r5/detail_gpu.vs index 907fb7e5f16..d107f3c4092 100644 --- a/res/gamedata/shaders/r5/detail_gpu.vs +++ b/res/gamedata/shaders/r5/detail_gpu.vs @@ -42,7 +42,8 @@ cbuffer DetailGlobals : register(b3) float4 grass_sss_color; // RGB + intensity (subsurface scattering) float grass_color_variation; // Per-blade color variation amount float grass_blade_height; // Blade height multiplier (default 1.0) - float _pad0, _pad1; // Padding to 16-byte alignment + uint build_details_index; + uint build_details_pbr_index; }; static const float M_PI = 3.1415926; @@ -456,6 +457,7 @@ v2p_flat main(v_blade_sdf I, uint instance_id : SV_InstanceID) uint bh = asuint(bc.x * 73856093 + bc.y * 19349663); bh ^= bh >> 16; O.bladeHash = float(bh & 0xFFFFu) / 65535.0; + O.sunOcclusion = sun; O.hpos = mul(g_detail_VP, pos); return O; } diff --git a/res/gamedata/shaders/r5/detail_gpu_shadow.ps b/res/gamedata/shaders/r5/detail_gpu_shadow.ps new file mode 100644 index 00000000000..688201b493a Binary files /dev/null and b/res/gamedata/shaders/r5/detail_gpu_shadow.ps differ diff --git a/res/gamedata/shaders/r5/detail_gpu_shadow.vs b/res/gamedata/shaders/r5/detail_gpu_shadow.vs new file mode 100644 index 00000000000..219f9850475 --- /dev/null +++ b/res/gamedata/shaders/r5/detail_gpu_shadow.vs @@ -0,0 +1,67 @@ +// detail_gpu_shadow.vs — depth-only grass caster for CSM (LOD0/1) +// Instance packing must match detail_gpu.vs +#define SM_6_0 + +cbuffer ShadowCascadeCB : register(b5) +{ + float4x4 cb_LightVP; +}; + +cbuffer GrassShadowCB : register(b6) +{ + float grass_blade_height; + uint build_details_index; + float wind_angle_deg; + float wind_speed; + float time; + float wind_displacement; + float2 grass_shadow_pad; +}; + +struct v_blade_sdf +{ + float3 pos : POSITION; + float2 tc : TEXCOORD; + float t : COLOR0; + float width_scale : COLOR1; +}; + +struct InstanceData +{ + float3 pos; + uint packed; +}; + +StructuredBuffer visible_indices : register(t0); +StructuredBuffer all_instances : register(t1); + +struct VS_OUTPUT +{ + float4 position : SV_Position; + float2 tc : TEXCOORD0; +}; + +static const float PACK_MAX_SCALE = 4.0; +static const float TWO_PI = 6.28318530718; + +VS_OUTPUT main(v_blade_sdf I, uint instanceID : SV_InstanceID) +{ + VS_OUTPUT O; + uint idx = visible_indices[instanceID]; + InstanceData raw = all_instances[idx]; + + float rotation = float((raw.packed >> 8) & 0x3FF) / 1023.0 * TWO_PI; + float scale = float((raw.packed >> 18) & 0x3FF) / 1023.0 * PACK_MAX_SCALE; + float blade_height = max(scale * grass_blade_height, 0.05); + + float3 facing = normalize(float3(sin(rotation), 0.0, cos(rotation))); + float3 right = normalize(float3(facing.z, 0.0, -facing.x)); + + float3 world = raw.pos; + world += right * (I.pos.x * I.width_scale * scale * 0.5); + world.y += I.t * blade_height; + + O.position = mul(cb_LightVP, float4(world, 1.0)); + O.tc = I.tc; + return O; +} diff --git a/res/gamedata/shaders/r5/distortion_apply.ps b/res/gamedata/shaders/r5/distortion_apply.ps index d1a8e73296b..d9a7d823673 100644 Binary files a/res/gamedata/shaders/r5/distortion_apply.ps and b/res/gamedata/shaders/r5/distortion_apply.ps differ diff --git a/res/gamedata/shaders/r5/dlss_rr_guides.cs b/res/gamedata/shaders/r5/dlss_rr_guides.cs new file mode 100644 index 00000000000..45352d3c4c9 --- /dev/null +++ b/res/gamedata/shaders/r5/dlss_rr_guides.cs @@ -0,0 +1,74 @@ +#include "shared/pbr_brdf.h" +#include "shared/basecolor_pack.h" + +cbuffer DlssRrGuideParams : register(b5) { + float4 g_CameraPos; + float2 g_ScreenSize; + float2 g_Pad; +}; + +Texture2D g_BaseColor : register(t0); +Texture2D g_Normal : register(t1); +Texture2D g_WorldPos : register(t2); +Texture2D g_NoisySpecular : register(t3); +Texture2D g_Depth : register(t4); + +RWTexture2D u_DiffuseAlbedo : register(u0); +RWTexture2D u_SpecularAlbedo : register(u1); +RWTexture2D u_SpecularHitDist : register(u2); + +float3 EnvBRDFApprox2(float3 specularColor, float alpha, float NoV) +{ + NoV = abs(NoV); + float4 X = float4(1.0, NoV, NoV * NoV, NoV * NoV * NoV); + float4 Y = float4(1.0, alpha, alpha * alpha, alpha * alpha * alpha); + float2x2 M1 = float2x2(0.99044, -1.28514, 1.29678, -0.755907); + float3x3 M2 = float3x3(1.0, 2.92338, 59.4188, 20.3225, -27.0302, 222.592, 121.563, 626.13, 316.627); + float2x2 M3 = float2x2(0.0365463, 3.32707, 9.0632, -9.04756); + float3x3 M4 = float3x3(1.0, 3.59685, -1.36772, 9.04401, -16.3174, 9.22949, 5.56589, 19.7886, -20.2123); + float bias = dot(mul(M1, X.xy), Y.xy) * rcp(dot(mul(M2, X.xyw), Y.xyw)); + float scale = dot(mul(M3, X.xy), Y.xy) * rcp(dot(mul(M4, X.xzw), Y.xyw)); + bias *= saturate(specularColor.g * 50.0); + return mad(specularColor, max(0.0, scale), max(0.0, bias)); +} + +[numthreads(8, 8, 1)] +void main(uint3 dtid : SV_DispatchThreadID) +{ + uint2 pixel = dtid.xy; + if (pixel.x >= (uint)g_ScreenSize.x || pixel.y >= (uint)g_ScreenSize.y) + return; + + float depth = g_Depth.Load(int3(pixel, 0)); + if (depth <= 0.0) + { + u_DiffuseAlbedo[pixel] = float4(1.0, 1.0, 1.0, 1.0); + u_SpecularAlbedo[pixel] = 0; + u_SpecularHitDist[pixel] = 0; + return; + } + + float4 base = g_BaseColor.Load(int3(pixel, 0)); + float4 nrm = g_Normal.Load(int3(pixel, 0)); + float3 worldPos = g_WorldPos.Load(int3(pixel, 0)).xyz; + float3 N = normalize(nrm.xyz); + float roughness = saturate(nrm.w); + float metallic = UnpackMetallicFromBaseA(base.a); + float3 albedo = max(base.rgb, 0.0); + float3 F0 = CalculateF0(albedo, metallic); + float3 V = normalize(g_CameraPos.xyz - worldPos); + float NoV = saturate(dot(N, V)); + float alpha = roughness * roughness; + + float3 diffAlb = albedo * (1.0 - metallic); + float3 specAlb = EnvBRDFApprox2(F0, alpha, NoV); + float specHit = max(g_NoisySpecular.Load(int3(pixel, 0)).a, 0.0); + float r2 = roughness * roughness; + float minFootprint = (0.15 + 4.5 * r2) / max(NoV, 0.12); + specHit = max(specHit, minFootprint); + specHit *= (1.0 + 5.0 * r2); + + u_DiffuseAlbedo[pixel] = float4(diffAlb, 1.0); + u_SpecularAlbedo[pixel] = float4(specAlb, 1.0); + u_SpecularHitDist[pixel] = specHit; +} diff --git a/res/gamedata/shaders/r5/effects_flare.vs b/res/gamedata/shaders/r5/effects_flare.vs index cd0bf2b5e94..eaf721b752e 100644 --- a/res/gamedata/shaders/r5/effects_flare.vs +++ b/res/gamedata/shaders/r5/effects_flare.vs @@ -28,6 +28,7 @@ VSOutput main(VSInput input) { VSOutput output; output.position = mul(m_WVP, float4(input.position, 1.0)); + output.position.z = output.position.w * 0.0001; output.color = input.color.bgra; output.color.a *= g_FlareVis[0]; output.texcoord = input.texcoord; diff --git a/res/gamedata/shaders/r5/effects_lightplanes.s.json b/res/gamedata/shaders/r5/effects_lightplanes.s.json new file mode 100644 index 00000000000..96590d7bee6 --- /dev/null +++ b/res/gamedata/shaders/r5/effects_lightplanes.s.json @@ -0,0 +1,12 @@ +{ + "sorting": { "priority": 2, "backToFront": false }, + "fog": false, + "emissive": true, + "transparent": true, + "ps": "bindless_lightplanes", + "blend": { "src": "SrcAlpha", "dst": "One" }, + "depth": { "test": true, "write": false, "func": "GreaterOrEqual" }, + "raster": { "cull": "None" }, + "alphaTest": { "ref": 0 }, + "colorWriteMask": "RGB" +} diff --git a/res/gamedata/shaders/r5/effects_sun_disc.ps b/res/gamedata/shaders/r5/effects_sun_disc.ps new file mode 100644 index 00000000000..1265b295ef1 Binary files /dev/null and b/res/gamedata/shaders/r5/effects_sun_disc.ps differ diff --git a/res/gamedata/shaders/r5/effects_sun_disc.vs b/res/gamedata/shaders/r5/effects_sun_disc.vs new file mode 100644 index 00000000000..964193cf1a0 --- /dev/null +++ b/res/gamedata/shaders/r5/effects_sun_disc.vs @@ -0,0 +1,33 @@ +cbuffer DynamicTransforms : register(b0) +{ + float4x4 m_WVP; + float4x4 m_WV; + float4x4 m_W; + float4 L_material; + float4 hemi_cube_pos_faces; + float4 hemi_cube_neg_faces; +}; + +struct VSInput +{ + float3 position : POSITION; + float4 color : COLOR; + float2 texcoord : TEXCOORD0; +}; + +struct VSOutput +{ + float4 position : SV_Position; + float4 color : COLOR; + float2 texcoord : TEXCOORD0; +}; + +VSOutput main(VSInput input) +{ + VSOutput output; + output.position = mul(m_WVP, float4(input.position, 1.0)); + output.position.z = output.position.w * 0.0001; + output.color = input.color.bgra; + output.texcoord = input.texcoord; + return output; +} diff --git a/res/gamedata/shaders/r5/effects_wallmark.s.json b/res/gamedata/shaders/r5/effects_wallmark.s.json index 9ea2519c56c..02697b333c5 100644 --- a/res/gamedata/shaders/r5/effects_wallmark.s.json +++ b/res/gamedata/shaders/r5/effects_wallmark.s.json @@ -2,7 +2,9 @@ "sorting": { "priority": 1, "backToFront": false }, "fog": false, "wmark": true, + "transparent": true, "ps": "bindless_wallmark_mult", "blend": { "src": "DstColor", "dst": "SrcColor" }, - "depth": { "test": true, "write": false } + "depth": { "test": true, "write": false }, + "colorWriteMask": "RGB" } diff --git a/res/gamedata/shaders/r5/effects_wallmarkblend.s.json b/res/gamedata/shaders/r5/effects_wallmarkblend.s.json index 9a218c4dae2..a3443822bb1 100644 --- a/res/gamedata/shaders/r5/effects_wallmarkblend.s.json +++ b/res/gamedata/shaders/r5/effects_wallmarkblend.s.json @@ -2,7 +2,9 @@ "sorting": { "priority": 2, "backToFront": true }, "fog": false, "wmark": true, + "transparent": true, "ps": "bindless_wallmark", "blend": { "src": "SrcAlpha", "dst": "InvSrcAlpha" }, - "depth": { "test": true, "write": false } + "depth": { "test": true, "write": false }, + "colorWriteMask": "RGB" } diff --git a/res/gamedata/shaders/r5/effects_wallmarkmult.s.json b/res/gamedata/shaders/r5/effects_wallmarkmult.s.json index e8b609d6359..78287c1358c 100644 --- a/res/gamedata/shaders/r5/effects_wallmarkmult.s.json +++ b/res/gamedata/shaders/r5/effects_wallmarkmult.s.json @@ -2,7 +2,9 @@ "sorting": { "priority": 2, "backToFront": false }, "fog": false, "wmark": true, + "transparent": true, "ps": "bindless_wallmark_mult", "blend": { "src": "DstColor", "dst": "SrcColor" }, - "depth": { "test": true, "write": false } + "depth": { "test": true, "write": false }, + "colorWriteMask": "RGB" } diff --git a/res/gamedata/shaders/r5/effects_wallmarkset.s.json b/res/gamedata/shaders/r5/effects_wallmarkset.s.json new file mode 100644 index 00000000000..91c6f42be81 --- /dev/null +++ b/res/gamedata/shaders/r5/effects_wallmarkset.s.json @@ -0,0 +1,10 @@ +{ + "sorting": { "priority": 1, "backToFront": false }, + "fog": false, + "wmark": true, + "transparent": true, + "ps": "bindless_wallmark_set", + "blend": { "src": "One", "dst": "Zero" }, + "depth": { "test": true, "write": false }, + "colorWriteMask": "RGB" +} diff --git a/res/gamedata/shaders/r5/exposure_adapt.cs b/res/gamedata/shaders/r5/exposure_adapt.cs index e8a7c0ec203..54074b659ca 100644 --- a/res/gamedata/shaders/r5/exposure_adapt.cs +++ b/res/gamedata/shaders/r5/exposure_adapt.cs @@ -1,162 +1,53 @@ -// exposure_adapt.cs - Compute adapted exposure from luminance histogram -// Implements histogram-based auto-exposure with temporal eye adaptation -// -// References: -// - Krzysztof Narkowicz: "Automatic Exposure" (2016) -// - Epic Games: "Auto Exposure in UE 4.25" (2020) -// #define SM_5_0 -#include "common.h" -// ═══════════════════════════════════════════════════════ -// CONSTANTS -// ═══════════════════════════════════════════════════════ - -cbuffer ExposureAdaptParams : register(b5) // b5 to avoid conflicts with common.h (b0-b2) +cbuffer ExposureAdaptParams : register(b5) { - float g_min_log_luminance; // Minimum log2 luminance - float g_log_luminance_range; // Range of log2 luminance - float g_low_percentile; // Skip this fraction of darkest pixels (0.5 = 50%) - float g_high_percentile; // Skip pixels above this percentile (0.98 = 98%) - - float g_adapt_speed_up; // Speed when brightening (f-stops/sec) - float g_adapt_speed_down; // Speed when darkening (f-stops/sec) - float g_delta_time; // Frame delta time in seconds - float g_exposure_compensation; // Manual EV adjustment - - float g_min_exposure; // Minimum exposure clamp - float g_max_exposure; // Maximum exposure clamp - float g_calibration_constant; // Reflected-light meter constant K (12.5) - float g_padding; + float4 g_MiddleGray; }; -// ═══════════════════════════════════════════════════════ -// RESOURCES -// ═══════════════════════════════════════════════════════ - -StructuredBuffer g_histogram : register(t0); // 64 bins from histogram pass -RWTexture2D g_exposure : register(u0); // 1x1 output (also read for adaptation) - -// ═══════════════════════════════════════════════════════ -// EXPOSURE CALCULATION -// ═══════════════════════════════════════════════════════ +StructuredBuffer g_histogram : register(t0); +RWTexture2D g_exposure : register(u0); -float ComputeAverageLuminance() +float LuminanceFromBin(uint bin) { - // Count total pixels in histogram - uint totalPixels = 0; - for (uint i = 0; i < 64; i++) - { - totalPixels += g_histogram[i]; - } + const float minLog = -10.0; + const float logRange = 14.0; + float t = (float(bin) + 0.5) / 64.0; + return exp2(minLog + t * logRange); +} +float AverageLuminance(uint totalPixels) +{ if (totalPixels == 0) - return 0.5; // Default mid-gray - - // Find percentile boundaries - uint lowThreshold = (uint)(totalPixels * g_low_percentile); - uint highThreshold = (uint)(totalPixels * g_high_percentile); + return 1.0; - // Accumulate weighted luminance, skipping extremes float weightedSum = 0.0; - uint validPixels = 0; - uint runningCount = 0; - for (uint bin = 0; bin < 64; bin++) { - uint binCount = g_histogram[bin]; - uint prevCount = runningCount; - runningCount += binCount; - - // Skip if entirely below low percentile - if (runningCount <= lowThreshold) - continue; - - // Stop if we've passed high percentile - if (prevCount >= highThreshold) - break; - - // Calculate how many pixels in this bin are within our range - uint startInBin = max(prevCount, lowThreshold) - prevCount; - uint endInBin = min(runningCount, highThreshold) - prevCount; - uint countInRange = endInBin - startInBin; - - if (countInRange > 0) - { - // Convert bin index to log luminance (use bin center) - float t = (float(bin) + 0.5) / 64.0; - float logLum = g_min_log_luminance + t * g_log_luminance_range; - float luminance = exp2(logLum); - - weightedSum += luminance * float(countInRange); - validPixels += countInRange; - } + uint c = g_histogram[bin]; + if (c > 0) + weightedSum += LuminanceFromBin(bin) * float(c); } - - if (validPixels == 0) - return 0.5; - - return weightedSum / float(validPixels); -} - -float ComputeTargetExposure(float avgLuminance) -{ - // Standard exposure equation: - // EV100 = log2(L * S / K) - // where L = luminance, S = ISO 100 sensitivity, K = calibration constant - // - // Exposure = 1 / (2^EV100) for proper exposure - // Simplified: exposure = K / (L * 100) for ISO 100 reference - - // Avoid division by zero - float lum = max(avgLuminance, 0.0001); - - // Calculate exposure to achieve middle gray (18% reflectance) - // The formula K / (luminance * 100) gives exposure for ISO 100 - float exposure = g_calibration_constant / (lum * 100.0); - - // Apply exposure compensation (in EV stops) - exposure *= exp2(g_exposure_compensation); - - // Clamp to valid range - return clamp(exposure, g_min_exposure, g_max_exposure); + return max(weightedSum / float(totalPixels), 1e-6); } -float AdaptExposure(float currentExposure, float targetExposure) -{ - // Asymmetric adaptation: faster when brightening, slower when darkening - // This mimics human eye adaptation behavior - float adaptSpeed = (targetExposure > currentExposure) - ? g_adapt_speed_up - : g_adapt_speed_down; - - // Exponential approach to target - // adaptFactor = 1 - e^(-dt * speed) - float adaptFactor = 1.0 - exp(-g_delta_time * adaptSpeed); - - // Lerp toward target - return lerp(currentExposure, targetExposure, adaptFactor); -} - -// ═══════════════════════════════════════════════════════ -// MAIN COMPUTE SHADER -// ═══════════════════════════════════════════════════════ - [numthreads(1, 1, 1)] void main(uint3 dispatch_id : SV_DispatchThreadID) { - // Read current exposure (for temporal adaptation) - float currentExposure = g_exposure[uint2(0, 0)]; + uint totalPixels = 0; + for (uint i = 0; i < 64; i++) + totalPixels += g_histogram[i]; + + float result = AverageLuminance(totalPixels); - // Compute average luminance from histogram - float avgLuminance = ComputeAverageLuminance(); + float scale = g_MiddleGray.x / max(result * g_MiddleGray.y + g_MiddleGray.z, 1e-6); - // Compute target exposure - float targetExposure = ComputeTargetExposure(avgLuminance); + float scale_prev = g_exposure[uint2(0, 0)]; + if (scale_prev <= 1e-6) + scale_prev = 1.0; - // Apply temporal adaptation - float newExposure = AdaptExposure(currentExposure, targetExposure); + float rvalue = lerp(scale_prev, scale, saturate(g_MiddleGray.w)); + rvalue = clamp(rvalue, 1.0 / 128.0, 20.0); - // Write output - g_exposure[uint2(0, 0)] = newExposure; + g_exposure[uint2(0, 0)] = rvalue; } diff --git a/res/gamedata/shaders/r5/flare_visibility.cs b/res/gamedata/shaders/r5/flare_visibility.cs index 15ae89f4b84..a73ee14fb8c 100644 --- a/res/gamedata/shaders/r5/flare_visibility.cs +++ b/res/gamedata/shaders/r5/flare_visibility.cs @@ -28,7 +28,7 @@ void main(uint3 tid : SV_GroupThreadID) uint vis = 1; if (p.x >= 0.0 && p.y >= 0.0 && p.x < dim_x && p.y < dim_y) - vis = (g_Depth.Load(int3(int2(p), 0)) <= 0.0) ? 1 : 0; + vis = (g_Depth.Load(int3(int2(p), 0)) <= 1e-5) ? 1 : 0; InterlockedAdd(gs_visible, vis); GroupMemoryBarrierWithGroupSync(); diff --git a/res/gamedata/shaders/r5/glass_distort.ps b/res/gamedata/shaders/r5/glass_distort.ps new file mode 100644 index 00000000000..ced4bf47325 Binary files /dev/null and b/res/gamedata/shaders/r5/glass_distort.ps differ diff --git a/res/gamedata/shaders/r5/glow_forward.ps b/res/gamedata/shaders/r5/glow_forward.ps new file mode 100644 index 00000000000..7650ac6827a Binary files /dev/null and b/res/gamedata/shaders/r5/glow_forward.ps differ diff --git a/res/gamedata/shaders/r5/glow_forward.vs b/res/gamedata/shaders/r5/glow_forward.vs new file mode 100644 index 00000000000..aa43937d516 --- /dev/null +++ b/res/gamedata/shaders/r5/glow_forward.vs @@ -0,0 +1,23 @@ +#define SM_6_0 +#include "shared/common.h" + +struct VS_INPUT { + float4 position : POSITION; + float4 color : COLOR0; + float2 tc : TEXCOORD0; +}; + +struct VS_OUTPUT { + float4 hpos : SV_POSITION; + float4 color : COLOR0; + float2 tc : TEXCOORD0; +}; + +VS_OUTPUT main(VS_INPUT v) +{ + VS_OUTPUT o; + o.hpos = mul(m_WVP, float4(v.position.xyz, 1.0)); + o.color = v.color; + o.tc = v.tc; + return o; +} diff --git a/res/gamedata/shaders/r5/hdr10_encode.ps b/res/gamedata/shaders/r5/hdr10_encode.ps new file mode 100644 index 00000000000..bab923fe89c Binary files /dev/null and b/res/gamedata/shaders/r5/hdr10_encode.ps differ diff --git a/res/gamedata/shaders/r5/hud/default.ps b/res/gamedata/shaders/r5/hud/default.ps new file mode 100644 index 00000000000..3a62e7c3024 Binary files /dev/null and b/res/gamedata/shaders/r5/hud/default.ps differ diff --git a/res/gamedata/shaders/r5/hud/default.vs b/res/gamedata/shaders/r5/hud/default.vs new file mode 100644 index 00000000000..2428ea22dfc --- /dev/null +++ b/res/gamedata/shaders/r5/hud/default.vs @@ -0,0 +1,25 @@ +#include "common_iostructs.h" + +////////////////////////////////////////////////////////////////////////////////////////// +// Vertex +v2p_TL main ( v_TL_positiont I ) +{ + v2p_TL O; + +// O.HPos = P; + + { + I.P.xy += 0.5f; +// O.HPos.x = I.P.x/1024 * 2 - 1; +// O.HPos.y = (I.P.y/768 * 2 - 1)*-1; + O.HPos.x = I.P.x * screen_res.z * 2 - 1; + O.HPos.y = (I.P.y * screen_res.w * 2 - 1)*-1; + O.HPos.zw = I.P.zw; + } + + O.Tex0 = I.Tex0; + O.Color = I.Color.bgra; // swizzle vertex colour + O.TexIdx = I.TexIdx; + + return O; +} \ No newline at end of file diff --git a/res/gamedata/shaders/r5/hud_font.ps b/res/gamedata/shaders/r5/hud_font.ps index 8123d7bfdd1..ee7f6b03070 100644 Binary files a/res/gamedata/shaders/r5/hud_font.ps and b/res/gamedata/shaders/r5/hud_font.ps differ diff --git a/res/gamedata/shaders/r5/hud_movie.ps b/res/gamedata/shaders/r5/hud_movie.ps new file mode 100644 index 00000000000..3a0a478f3f0 Binary files /dev/null and b/res/gamedata/shaders/r5/hud_movie.ps differ diff --git a/res/gamedata/shaders/r5/hud_movie.vs b/res/gamedata/shaders/r5/hud_movie.vs new file mode 100644 index 00000000000..fe993e0bed4 --- /dev/null +++ b/res/gamedata/shaders/r5/hud_movie.vs @@ -0,0 +1,14 @@ +#include "common_iostructs.h" + +v2p_TL main(v_TL_positiont I) +{ + v2p_TL O; + I.P.xy += 0.5f; + O.HPos.x = I.P.x * screen_res.z * 2 - 1; + O.HPos.y = (I.P.y * screen_res.w * 2 - 1) * -1; + O.HPos.zw = I.P.zw; + O.Tex0 = I.Tex0; + O.Color = I.Color.bgra; + O.TexIdx = I.TexIdx; + return O; +} diff --git a/res/gamedata/shaders/r5/imgui.ps b/res/gamedata/shaders/r5/imgui.ps index c7195e15109..e0679cd7eae 100644 Binary files a/res/gamedata/shaders/r5/imgui.ps and b/res/gamedata/shaders/r5/imgui.ps differ diff --git a/res/gamedata/shaders/r5/imgui.vs b/res/gamedata/shaders/r5/imgui.vs index da25a3e73c9..f199a52632e 100644 --- a/res/gamedata/shaders/r5/imgui.vs +++ b/res/gamedata/shaders/r5/imgui.vs @@ -4,6 +4,8 @@ cbuffer vertexBuffer : register(b0) { float4x4 ProjectionMatrix; + float uiScale; + float3 uiPad; }; struct VS_INPUT diff --git a/res/gamedata/shaders/r5/light_hiz_cull.cs b/res/gamedata/shaders/r5/light_hiz_cull.cs index 8d0d171ef65..41a1e69709a 100644 --- a/res/gamedata/shaders/r5/light_hiz_cull.cs +++ b/res/gamedata/shaders/r5/light_hiz_cull.cs @@ -31,26 +31,20 @@ void main(uint3 dtid : SV_DispatchThreadID) float range = ld.colorAndRange.w; float3 toLight = lightPos - cb_cameraPos.xyz; - if (dot(toLight, toLight) <= range * range) + bool visible = dot(toLight, toLight) <= range * range; + if (!visible) { - uint idx; - g_VisibleLightCount.InterlockedAdd(0, 1, idx); - if (idx < 1024) - g_VisibleLightIndices[idx] = lightIdx; - return; + visible = HiZTestSphere( + lightPos, range, cb_cameraPos.xyz, + cb_prevViewProj, + g_HiZPyramid, smp_nofilter, + cb_hizWidth, cb_hizHeight, cb_hizMipLevels); } - bool visible = HiZTestSphere( - lightPos, range, cb_cameraPos.xyz, - cb_prevViewProj, - g_HiZPyramid, smp_nofilter, - cb_hizWidth, cb_hizHeight, cb_hizMipLevels); - if (visible) { + g_VisibleLightIndices[lightIdx] = 1u; uint idx; g_VisibleLightCount.InterlockedAdd(0, 1, idx); - if (idx < 1024) - g_VisibleLightIndices[idx] = lightIdx; } } diff --git a/res/gamedata/shaders/r5/lod_forward.ps b/res/gamedata/shaders/r5/lod_forward.ps new file mode 100644 index 00000000000..4c650057fa7 Binary files /dev/null and b/res/gamedata/shaders/r5/lod_forward.ps differ diff --git a/res/gamedata/shaders/r5/lod_forward.vs b/res/gamedata/shaders/r5/lod_forward.vs new file mode 100644 index 00000000000..56b0596a115 --- /dev/null +++ b/res/gamedata/shaders/r5/lod_forward.vs @@ -0,0 +1,30 @@ +#include "shared/common.h" + +struct VS_INPUT +{ + float3 pos : POSITION; + float4 color : COLOR0; + float2 tc0 : TEXCOORD0; + float2 tc1 : TEXCOORD1; + float4 af : TEXCOORD2; +}; + +struct VS_OUTPUT +{ + float4 hpos : SV_Position; + float4 color : COLOR0; + float2 tc0 : TEXCOORD0; + float2 tc1 : TEXCOORD1; + float4 af : TEXCOORD2; +}; + +VS_OUTPUT main(VS_INPUT v) +{ + VS_OUTPUT o; + o.hpos = mul(m_WVP, float4(v.pos, 1.0)); + o.color = v.color; + o.tc0 = v.tc0; + o.tc1 = v.tc1; + o.af = v.af; + return o; +} diff --git a/res/gamedata/shaders/r5/luminance_histogram.cs b/res/gamedata/shaders/r5/luminance_histogram.cs index 971bd74ee28..be8c4244aeb 100644 --- a/res/gamedata/shaders/r5/luminance_histogram.cs +++ b/res/gamedata/shaders/r5/luminance_histogram.cs @@ -32,8 +32,7 @@ float ComputeLuminance(float3 color) { - // ITU BT.709 luminance coefficients - return dot(color, float3(0.2126, 0.7152, 0.0722)); + return dot(color, LUMINANCE_VECTOR * def_hdr); } uint ComputeBinIndex(float luminance) @@ -78,7 +77,7 @@ void main(uint3 dispatch_id : SV_DispatchThreadID, uint group_index : SV_GroupIn float luminance = ComputeLuminance(color.rgb); // Skip very dark pixels (effectively transparent/sky) - if (luminance > 0.0001) + if (luminance > 0.0001 && luminance < 16.0) { // Get bin index uint binIndex = ComputeBinIndex(luminance); diff --git a/res/gamedata/shaders/r5/models_lightplanes.s.json b/res/gamedata/shaders/r5/models_lightplanes.s.json new file mode 100644 index 00000000000..b67f5ea4f3c --- /dev/null +++ b/res/gamedata/shaders/r5/models_lightplanes.s.json @@ -0,0 +1,12 @@ +{ + "sorting": { "priority": 2, "backToFront": true }, + "fog": false, + "emissive": true, + "transparent": true, + "ps": "bindless_lightplanes", + "blend": { "src": "SrcAlpha", "dst": "One" }, + "depth": { "test": true, "write": false, "func": "GreaterOrEqual" }, + "raster": { "cull": "None" }, + "alphaTest": { "ref": 0 }, + "colorWriteMask": "RGB" +} diff --git a/res/gamedata/shaders/r5/nrd_composite.cs b/res/gamedata/shaders/r5/nrd_composite.cs new file mode 100644 index 00000000000..8f3a73ea95d --- /dev/null +++ b/res/gamedata/shaders/r5/nrd_composite.cs @@ -0,0 +1,130 @@ +#include "shared/pbr_brdf.h" +#include "shared/basecolor_pack.h" +#include "shared/nrd_helpers.h" +#include "shared/surface_marks.h" + +cbuffer NrdCompositeParams : register(b5) { + float4 g_Params; + float4 g_CameraPos; + float4 g_FogParams; + float4 g_FogColor; + float4x4 g_InvViewProj; +}; + +Texture2D t_DirectLighting : register(t0); +Texture2D t_DenoisedDiffuse : register(t1); +Texture2D t_DenoisedSpecular : register(t2); +Texture2D t_Depth : register(t3); +Texture2D t_SceneColorIn : register(t4); +Texture2D t_BaseColor : register(t5); +Texture2D t_Normal : register(t6); +Texture2D t_WorldPos : register(t7); +Texture2D t_ClassifyWorldPos : register(t8); + +RWTexture2D u_SceneColor : register(u0); +RWTexture2D u_OutDiffuse : register(u1); +RWTexture2D u_OutSpecular : register(u2); + +float3 ReconstructWorldPosReverseZ(float2 uv, float depth, float4x4 invViewProj) +{ + float4 clip = float4(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, depth, 1.0); + float4 world = mul(invViewProj, clip); + return world.xyz / max(world.w, 1e-8); +} + +float Luminance(float3 c) +{ + return dot(c, float3(0.2126, 0.7152, 0.0722)); +} + +[numthreads(8, 8, 1)] +void main(uint3 dispatchID : SV_DispatchThreadID) +{ + uint2 pixel = dispatchID.xy; + float2 screenSize = g_Params.xy; + uint method = (uint)g_Params.z; + if (pixel.x >= (uint)screenSize.x || pixel.y >= (uint)screenSize.y) + return; + + float depth = t_Depth.Load(int3(pixel, 0)); + if (depth <= 0.0) { + float4 passthrough = t_SceneColorIn.Load(int3(pixel, 0)); + u_SceneColor[pixel] = passthrough; + u_OutDiffuse[pixel] = 0; + u_OutSpecular[pixel] = 0; + return; + } + + float guideMark = t_WorldPos.Load(int3(pixel, 0)).w; + float classifyMark = t_ClassifyWorldPos.Load(int3(pixel, 0)).w; + if (SkipRtSurfLighting(classifyMark, guideMark)) { + u_SceneColor[pixel] = t_SceneColorIn.Load(int3(pixel, 0)); + u_OutDiffuse[pixel] = 0; + u_OutSpecular[pixel] = 0; + return; + } + + float2 uv = (float2(pixel) + 0.5) / screenSize; + float3 worldPos = ReconstructWorldPosReverseZ(uv, depth, g_InvViewProj); + + float3 direct = t_DirectLighting.Load(int3(pixel, 0)).rgb; + float4 dIn = t_DenoisedDiffuse.Load(int3(pixel, 0)); + float4 sIn = t_DenoisedSpecular.Load(int3(pixel, 0)); + float4 baseColor = t_BaseColor.Load(int3(pixel, 0)); + float4 nData = t_Normal.Load(int3(pixel, 0)); + float3 N = normalize(nData.xyz); + float roughness = max(saturate(abs(nData.w)), MIN_ROUGHNESS); + float3 albedo = max(baseColor.rgb, 0.0); + float metallic = UnpackMetallicFromBaseA(baseColor.a); + float3 V = normalize(g_CameraPos.xyz - worldPos); + float3 F0 = CalculateF0(albedo, metallic); + + float3 diffFactor, specFactor; + NRD_MaterialFactors(N, V, albedo, F0, roughness, diffFactor, specFactor); + + float3 diff = (method == 0) ? NRD_YCoCgToLinear(dIn.xyz) : max(dIn.xyz, 0.0); + float3 spec = (method == 0) ? NRD_YCoCgToLinear(sIn.xyz) : max(sIn.xyz, 0.0); + diff = NRD_SanitizeRadiance(diff) * diffFactor; + spec = NRD_SanitizeRadiance(spec) * specFactor; + + float giIntensity = max(g_CameraPos.w, 0.0); + diff *= giIntensity; + spec *= giIntensity; + + { + float dLum = max(Luminance(direct), 0.04); + float maxInd = dLum * 4.0 + 0.12; + float3 ind = diff + spec; + float iLum = Luminance(ind); + if (iLum > maxInd) { + float s = maxInd / iLum; + diff *= s; + spec *= s; + } + } + + float rejitter = 1.0; + if (g_Params.w > 0.5) { + float3 Ne = normalize(t_Normal.Load(int3(int2(pixel) + int2(1, 0), 0)).xyz); + float3 Nw = normalize(t_Normal.Load(int3(int2(pixel) + int2(-1, 0), 0)).xyz); + float3 Nn = normalize(t_Normal.Load(int3(int2(pixel) + int2(0, 1), 0)).xyz); + float3 Ns = normalize(t_Normal.Load(int3(int2(pixel) + int2(0, -1), 0)).xyz); + float edge = 1.0 - 0.25 * (saturate(dot(N, Ne)) + saturate(dot(N, Nw)) + saturate(dot(N, Nn)) + saturate(dot(N, Ns))); + float3 L = normalize(diff + spec + 1e-4); + float lobe = saturate(dot(N, L)); + rejitter = lerp(1.08, 0.92, saturate(edge * 2.0)) * lerp(1.05, 0.97, lobe); + diff *= rejitter; + spec *= rejitter; + } + + u_OutDiffuse[pixel] = float4(diff, 1.0); + u_OutSpecular[pixel] = float4(spec, 1.0); + float3 ambientBase = t_SceneColorIn.Load(int3(pixel, 0)).rgb; + float sunL = Luminance(direct); + float giShade = saturate(1.0 - sunL * 2.5); + giShade = lerp(0.55, 1.0, giShade); + float3 lighting = direct + (diff + spec) * giShade; + float dist = length(worldPos - g_CameraPos.xyz); + float fog = saturate(dist * g_FogParams.w + g_FogParams.x); + u_SceneColor[pixel] = float4(ambientBase + lighting * (1.0 - fog), 1.0); +} diff --git a/res/gamedata/shaders/r5/nrd_pack_inputs.cs b/res/gamedata/shaders/r5/nrd_pack_inputs.cs new file mode 100644 index 00000000000..aa1977503fd --- /dev/null +++ b/res/gamedata/shaders/r5/nrd_pack_inputs.cs @@ -0,0 +1,131 @@ +#include "shared/pbr_brdf.h" +#include "shared/basecolor_pack.h" +#include "shared/nrd_helpers.h" + +cbuffer NrdPackParams : register(b5) { + float4x4 g_WorldToView; + float4x4 g_WorldToViewPrev; + float4x4 g_WorldToClip; + float4x4 g_WorldToClipPrev; + float4x4 g_InvViewProj; + float4x4 g_InvViewProjPrev; + float4 g_ScreenNearFar; + float4 g_HitDistMethod; + float4 g_CameraPos_Range; +}; + +Texture2D t_NoisyDiffuse : register(t0); +Texture2D t_NoisySpecular : register(t1); +Texture2D t_HitDist : register(t2); +Texture2D t_Normal : register(t3); +Texture2D t_Depth : register(t4); +Texture2D t_WorldPos : register(t5); +Texture2D t_BaseColor : register(t6); + +RWTexture2D u_DiffRadianceHitDist : register(u0); +RWTexture2D u_SpecRadianceHitDist : register(u1); +RWTexture2D u_NormalRoughness : register(u2); +RWTexture2D u_ViewZ : register(u3); +RWTexture2D u_MotionVectors : register(u4); + +float4 PackNormalRoughness(float3 N, float roughness) +{ + N /= max(abs(N.x), max(abs(N.y), abs(N.z))); + return float4(N * 0.5 + 0.5, saturate(roughness)); +} + +float3 ReconstructWorldPosReverseZ(float2 uv, float depth, float4x4 invViewProj) +{ + float4 clip = float4(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, depth, 1.0); + float4 world = mul(invViewProj, clip); + return world.xyz / max(world.w, 1e-8); +} + +float2 ProjectToUv(float4x4 worldToClip, float3 worldPos) +{ + float4 clip = mul(worldToClip, float4(worldPos, 1.0)); + float2 ndc = clip.xy / max(clip.w, 1e-5); + ndc.y = -ndc.y; + return ndc * 0.5 + 0.5; +} + +[numthreads(8, 8, 1)] +void main(uint3 dispatchID : SV_DispatchThreadID) +{ + uint2 pixel = dispatchID.xy; + float2 screenSize = g_ScreenNearFar.xy; + if (pixel.x >= (uint)screenSize.x || pixel.y >= (uint)screenSize.y) + return; + + float nearZ = g_ScreenNearFar.z; + float denoisingRange = g_CameraPos_Range.w; + float3 hitDistParams = g_HitDistMethod.xyz; + uint method = (uint)g_HitDistMethod.w; + float3 cameraPos = g_CameraPos_Range.xyz; + + float depth = t_Depth.Load(int3(pixel, 0)); + if (depth <= 0.0) { + u_DiffRadianceHitDist[pixel] = 0; + u_SpecRadianceHitDist[pixel] = 0; + u_NormalRoughness[pixel] = float4(0.5, 0.5, 1.0, 1.0); + u_ViewZ[pixel] = denoisingRange * 2.0; + u_MotionVectors[pixel] = 0; + return; + } + + float2 uv = (float2(pixel) + 0.5) / screenSize; + float3 worldPos = ReconstructWorldPosReverseZ(uv, depth, g_InvViewProj); + float viewZ = mul(g_WorldToView, float4(worldPos, 1.0)).z; + viewZ = max(abs(viewZ), nearZ); + + float viewZPrev = mul(g_WorldToViewPrev, float4(worldPos, 1.0)).z; + viewZPrev = max(abs(viewZPrev), nearZ); + + float2 uvPrev = ProjectToUv(g_WorldToClipPrev, worldPos); + float3 motion; + motion.xy = (uvPrev - uv) * screenSize; + motion.z = viewZPrev - viewZ; + u_MotionVectors[pixel] = float4(motion, 0.0); + + float4 nData = t_Normal.Load(int3(pixel, 0)); + float3 N = normalize(nData.xyz); + float roughness = max(saturate(abs(nData.w)), MIN_ROUGHNESS); + float4 baseColor = t_BaseColor.Load(int3(pixel, 0)); + float3 albedo = max(baseColor.rgb, 0.0); + float metallic = UnpackMetallicFromBaseA(baseColor.a); + float3 V = normalize(cameraPos - worldPos); + float3 F0 = CalculateF0(albedo, metallic); + + float3 diffFactor, specFactor; + NRD_MaterialFactors(N, V, albedo, F0, roughness, diffFactor, specFactor); + + float diffHit = NRD_TrimHitDistance(max(t_HitDist.Load(int3(pixel, 0)), 0.0), 1e-3); + float4 specData = t_NoisySpecular.Load(int3(pixel, 0)); + float specHit = NRD_TrimHitDistance(max(specData.a, 0.0), 1e-3); + + float3 diffIrradiance = max(t_NoisyDiffuse.Load(int3(pixel, 0)).rgb, 0.0); + float3 specIrradiance = max(specData.rgb, 0.0); + + float3 diff = NRD_SanitizeRadiance(diffIrradiance / max(diffFactor, 1e-3)); + float3 spec = NRD_SanitizeRadiance(specIrradiance / max(specFactor, 1e-3)); + + if (metallic >= 0.999) { + diff = 0; + diffHit = 0; + } + + float4 packedDiff; + float4 packedSpec; + if (method == 0) { + packedDiff = float4(NRD_LinearToYCoCg(diff), REBLUR_GetNormHitDist(diffHit, viewZ, hitDistParams, 1.0)); + packedSpec = float4(NRD_LinearToYCoCg(spec), REBLUR_GetNormHitDist(specHit, viewZ, hitDistParams, roughness)); + } else { + packedDiff = float4(diff, diffHit); + packedSpec = float4(spec, specHit); + } + + u_DiffRadianceHitDist[pixel] = packedDiff; + u_SpecRadianceHitDist[pixel] = packedSpec; + u_NormalRoughness[pixel] = PackNormalRoughness(N, roughness); + u_ViewZ[pixel] = viewZ; +} diff --git a/res/gamedata/shaders/r5/pnv.h b/res/gamedata/shaders/r5/pnv.h index 316816d4c72..9c6133fb28c 100644 --- a/res/gamedata/shaders/r5/pnv.h +++ b/res/gamedata/shaders/r5/pnv.h @@ -20,10 +20,9 @@ #define SCANLINES_INTENSITY 0.015 // ��������� ������������� #define VIGNETTE_RADIUS 1.0 // -// Pixel -// Note: screen_res is now in static_globals cbuffer (shared/common.h) -// Note: m_zoom_deviation remains as loose uniform (engine binds it individually) +#ifndef PNV_ZOOM_DEVIATION_DEFINED uniform float4 m_zoom_deviation; +#endif float4 calc_night_vision_effect(float2 tc0, float4 color, float3 NV_COLOR) { diff --git a/res/gamedata/shaders/r5/portal.ps b/res/gamedata/shaders/r5/portal.ps new file mode 100644 index 00000000000..408ee5f7a37 Binary files /dev/null and b/res/gamedata/shaders/r5/portal.ps differ diff --git a/res/gamedata/shaders/r5/portal.vs b/res/gamedata/shaders/r5/portal.vs new file mode 100644 index 00000000000..8301eed2a8d --- /dev/null +++ b/res/gamedata/shaders/r5/portal.vs @@ -0,0 +1,23 @@ +#include "shared/common.h" + +struct VS_INPUT +{ + float3 pos : POSITION; + float4 color : COLOR0; +}; + +struct VS_OUTPUT +{ + float4 hpos : SV_Position; + float4 color : COLOR0; +}; + +VS_OUTPUT main(VS_INPUT v) +{ + VS_OUTPUT o; + o.hpos = mul(m_VP, float4(v.pos, 1.0)); + float fog = saturate(dot(float4(v.pos, 1.0), fog_plane)); + o.color.rgb = lerp(fog_color.rgb, v.color.rgb, fog); + o.color.a = fog * v.color.a; + return o; +} diff --git a/res/gamedata/shaders/r5/postprocess.ps b/res/gamedata/shaders/r5/postprocess.ps new file mode 100644 index 00000000000..72b7f2e2536 Binary files /dev/null and b/res/gamedata/shaders/r5/postprocess.ps differ diff --git a/res/gamedata/shaders/r5/restir_ddgi.cs b/res/gamedata/shaders/r5/restir_ddgi.cs new file mode 100644 index 00000000000..2b14a9bb848 --- /dev/null +++ b/res/gamedata/shaders/r5/restir_ddgi.cs @@ -0,0 +1,74 @@ +#include "common.h" +#include "rt_common.h" +#include "restir_gi_common.h" + +cbuffer DDGIParams : register(b5) { + float4x4 g_InvViewProj; + float4 g_CameraPos; + float4 g_GridOrigin_Spacing; + float4 g_GridDims_Intensity; + float2 g_ScreenSize; + uint g_FrameIndex; + float g_EnvAdapt; +}; + +Texture2D t_Depth : register(t0); +Texture2D t_Normal : register(t1); +Texture2D t_BaseColor : register(t2); +Texture2D t_DirectLighting : register(t3); +RWTexture3D u_ProbeIrradiance : register(u0); +RWTexture2D u_AmbientOut : register(u1); + +float3 ProbeIndexToWorld(uint3 idx) +{ + return g_GridOrigin_Spacing.xyz + (float3(idx) + 0.5) * g_GridOrigin_Spacing.w; +} + +uint3 WorldToProbeIndex(float3 worldPos) +{ + float3 local = (worldPos - g_GridOrigin_Spacing.xyz) / max(g_GridOrigin_Spacing.w, 1e-3); + uint3 dims = (uint3)g_GridDims_Intensity.xyz; + return uint3( + clamp((int)local.x, 0, (int)dims.x - 1), + clamp((int)local.y, 0, (int)dims.y - 1), + clamp((int)local.z, 0, (int)dims.z - 1)); +} + +[numthreads(8, 8, 1)] +void main(uint3 dispatchID : SV_DispatchThreadID) +{ + uint2 pixel = dispatchID.xy; + if (pixel.x >= (uint)g_ScreenSize.x || pixel.y >= (uint)g_ScreenSize.y) + return; + + float2 giSize = g_ScreenSize; + uint fullW = 0, fullH = 0; + t_Depth.GetDimensions(fullW, fullH); + float2 fullSize = float2(max(fullW, 1u), max(fullH, 1u)); + + float depth = RestirLoadDepth(t_Depth, pixel, giSize, fullSize); + if (depth <= 0.0) { + u_AmbientOut[pixel] = 0; + return; + } + + float2 uv = (float2(pixel) + 0.5) / giSize; + float3 worldPos = ReconstructWorldPosReverseZ(uv, depth, g_InvViewProj); + float3 N = normalize(RestirLoadTex4(t_Normal, pixel, giSize, fullSize).xyz); + float3 albedo = RestirLoadTex4(t_BaseColor, pixel, giSize, fullSize).rgb; + float3 direct = t_DirectLighting.Load(int3(pixel, 0)).rgb; + + uint3 pidx = WorldToProbeIndex(worldPos); + float3 probePos = ProbeIndexToWorld(pidx); + float3 toProbe = normalize(probePos - worldPos); + float weight = saturate(dot(N, toProbe) * 0.5 + 0.5); + + float3 irradiance = u_ProbeIrradiance[pidx].rgb * g_EnvAdapt; + float3 gather = (direct * 0.15 + albedo * 0.05) * weight; + irradiance = lerp(irradiance, gather, 0.05); + u_ProbeIrradiance[pidx] = float4(irradiance, 1.0); + + float intensity = g_GridDims_Intensity.w; + float3 ambient = irradiance * albedo / PI * intensity; + u_AmbientOut[pixel] = float4(min(ambient, RESTIR_MAX_RADIANCE), 1.0); +} diff --git a/res/gamedata/shaders/r5/restir_di_common.h b/res/gamedata/shaders/r5/restir_di_common.h new file mode 100644 index 00000000000..6befa3fb495 --- /dev/null +++ b/res/gamedata/shaders/r5/restir_di_common.h @@ -0,0 +1,85 @@ +#ifndef RESTIR_DI_COMMON_H +#define RESTIR_DI_COMMON_H + +#ifndef RESTIR_GI_COMMON_H +#error "restir_gi_common.h must be included before restir_di_common.h" +#endif + +static const float RESTIR_DI_W_MAX = 4.0; + +struct DIReservoir +{ + uint lightIndex; + float targetPdf; + float W; + float w_sum; + uint M; + uint age; + uint zone; +}; + +float ClampDIReservoirW(float W) +{ + if (isnan(W) || isinf(W) || W <= 0) + return 0; + return min(W, RESTIR_DI_W_MAX); +} + +DIReservoir EmptyDIReservoir() +{ + DIReservoir r; + r.lightIndex = RESTIR_INVALID_ID; + r.targetPdf = 0; + r.W = 0; + r.w_sum = 0; + r.M = 0; + r.age = 0; + r.zone = 0; + return r; +} + +bool IsDIReservoirValid(DIReservoir r) +{ + return r.lightIndex != RESTIR_INVALID_ID && r.M > 0 && r.W > 0; +} + +bool DIReservoirUpdate(inout DIReservoir r, float weight, uint lightIndex, float targetPdf, inout uint rng) +{ + r.M += 1; + if (isnan(weight) || isinf(weight) || weight <= 0) + return false; + + r.w_sum += weight; + + float xi = rand_float(rng); + if (xi < weight / max(r.w_sum, 1e-6)) + { + r.lightIndex = lightIndex; + r.targetPdf = targetPdf; + return true; + } + return false; +} + +float4 PackDIReservoir(DIReservoir r) +{ + uint meta = ((min(r.M, 65535u) & 0xFFFFu) << 16) | + ((min(r.age, 32767u) & 0x7FFFu) | ((r.zone & 1u) << 15)); + return float4(asfloat(r.lightIndex), r.W, r.targetPdf, asfloat(meta)); +} + +DIReservoir UnpackDIReservoir(float4 packed) +{ + DIReservoir r; + r.lightIndex = asuint(packed.x); + r.W = packed.y; + r.targetPdf = packed.z; + uint meta = asuint(packed.w); + r.M = (meta >> 16) & 0xFFFFu; + r.age = meta & 0x7FFFu; + r.zone = (meta >> 15) & 1u; + r.w_sum = 0; + return r; +} + +#endif diff --git a/res/gamedata/shaders/r5/restir_di_eval.h b/res/gamedata/shaders/r5/restir_di_eval.h new file mode 100644 index 00000000000..317a2c060ac --- /dev/null +++ b/res/gamedata/shaders/r5/restir_di_eval.h @@ -0,0 +1,117 @@ +#ifndef RESTIR_DI_EVAL_H +#define RESTIR_DI_EVAL_H + +struct GPULightDataDI { + float4 positionAndInvRangeSq; + float4 colorAndRange; + float4 directionAndSpotScale; + float4 spotParamsAndType; + float4x4 spotVP; +}; + +float PointAttenDI(float distSq, float invRangeSq) +{ + return saturate(1.0 - distSq * abs(invRangeSq)); +} + +float SpotAttenDI(float3 toLight, float3 spotDir, float scale, float offset) +{ + float cosAngle = dot(normalize(-toLight), spotDir); + return saturate(cosAngle * scale + offset); +} + +bool IsLiveLightDI(GPULightDataDI light) +{ + return light.colorAndRange.w > 1e-4 && abs(light.positionAndInvRangeSq.w) > 1e-8; +} + +float EvalLocalLightAttenuationDI(GPULightDataDI light, float3 worldPos, out float3 L, out float dist, out float3 lightColor) +{ + L = float3(0, 0, 1); + dist = 0; + lightColor = 0; + if (!IsLiveLightDI(light)) + return 0; + + float3 lightPos = light.positionAndInvRangeSq.xyz; + float invRangeSq = abs(light.positionAndInvRangeSq.w); + lightColor = light.colorAndRange.xyz; + float lightType = light.spotParamsAndType.y; + + float3 toLight = lightPos - worldPos; + float distSq = dot(toLight, toLight); + dist = sqrt(max(distSq, 1e-8)); + L = toLight / dist; + float atten = PointAttenDI(distSq, invRangeSq); + + if (lightType > 0.5) { + uint texIdx = asuint(light.spotParamsAndType.z); + if (texIdx != 0xFFFFFFFFu) { + float4 projPos = mul(light.spotVP, float4(worldPos, 1.0)); + float3 spotDir = light.directionAndSpotScale.xyz; + float spotScale = light.directionAndSpotScale.w; + float spotOffset = light.spotParamsAndType.x; + float cone = SpotAttenDI(toLight, spotDir, spotScale, spotOffset); + if (projPos.w > 1e-4) { + float2 projUV = projPos.xy / projPos.w * 0.5 + 0.5; + projUV.y = 1.0 - projUV.y; + if (all(projUV >= 0.0) && all(projUV <= 1.0)) { + Texture2D spotTex = GetBindlessTexture(texIdx); + atten *= spotTex.SampleLevel(smp_linear, projUV, 0).r * cone; + } else { + atten = 0; + } + } else { + atten = 0; + } + } else { + float3 spotDir = light.directionAndSpotScale.xyz; + float spotScale = light.directionAndSpotScale.w; + float spotOffset = light.spotParamsAndType.x; + atten *= SpotAttenDI(toLight, spotDir, spotScale, spotOffset); + } + } + return atten; +} + +float EvalLocalLightTargetPdfDI( + GPULightDataDI light, float3 worldPos, float3 N, float3 V, + float3 albedo, float metallic, float roughness) +{ + float3 L, lightColor; + float dist; + float atten = EvalLocalLightAttenuationDI(light, worldPos, L, dist, lightColor); + if (atten <= 0.001) + return 0; + float3 lit = PBRDirectLighting(albedo, N, V, L, lightColor * atten, metallic, roughness, 1); + return Luminance(lit); +} + +bool IsHudLightDI(GPULightDataDI light) +{ + return light.positionAndInvRangeSq.w < 0.0; +} + +bool IsTransientPointLightDI(GPULightDataDI light) +{ + return light.spotParamsAndType.y < 0.5 && light.spotParamsAndType.x > 0.5; +} + +float LightEmitterRadiusDI(GPULightDataDI light) +{ + float range = max(abs(light.colorAndRange.w), 0.5); + return clamp(range * 0.02, 0.03, 0.4); +} + +float SoftShadowAmountDI(float dist) +{ + return saturate((dist - 0.35) / 6.0); +} + +float LightEmitterRadiusDISoft(GPULightDataDI light) +{ + float range = max(abs(light.colorAndRange.w), 0.5); + return clamp(range * 0.035, 0.05, 0.65); +} + +#endif diff --git a/res/gamedata/shaders/r5/restir_di_shade.cs b/res/gamedata/shaders/r5/restir_di_shade.cs new file mode 100644 index 00000000000..1b50b343f93 --- /dev/null +++ b/res/gamedata/shaders/r5/restir_di_shade.cs @@ -0,0 +1,202 @@ +#include "bindless_common.h" +#include "rt_common.h" +#include "rt_visibility.h" +#include "shared/pbr_brdf.h" +#include "shared/basecolor_pack.h" +#include "shared/foliage_sss.h" +#include "shared/skin_sss.h" +#include "shared/clustered_lighting.h" +#include "restir_gi_common.h" +#include "restir_di_common.h" +#include "restir_di_eval.h" +#include "shared/surface_marks.h" + +cbuffer ReSTIRDIShadeParams : register(b5) { + float4x4 g_InvViewProj; + float4x4 g_WorldToView; + float4 g_CameraPos; + float2 g_ScreenSize; + uint g_GrassBatchStart; + uint g_DetailAtlasIndex; + float4 g_ClusterParams; + float4 g_ClusterDepth; + uint g_IdentityStaticCount; + uint g_TerrainBatchCount; + uint g_SkinnedBatchStart; + uint g_ParticleBatchStart; + uint g_HudSkinnedStart; + float g_FullWidth; + float g_FullHeight; + uint g_FrameIndex; +}; + +Texture3D t_BlueNoise : register(t21); +Texture2D t_SkyOpen : register(t22); + +RaytracingAccelerationStructure g_SceneTLAS : register(t1); +StructuredBuffer g_LightData : register(t20); +StructuredBuffer g_ClusterGrid : register(t15); +StructuredBuffer g_LightIndexList : register(t16); +StructuredBuffer g_BatchInfo : register(t2); +ByteAddressBuffer g_MegaVB : register(t3); +ByteAddressBuffer g_MegaIB : register(t18); +ByteAddressBuffer g_GrassVB : register(t12); +ByteAddressBuffer g_GrassIB : register(t13); +ByteAddressBuffer g_ParticleVB : register(t19); +ByteAddressBuffer g_ParticleIB : register(t17); +Texture2D t_DIReservoir : register(t0); +Texture2D t_Depth : register(t14); +Texture2D t_BaseColor : register(t6); +Texture2D t_WorldPos : register(t7); +Texture2D t_Normal : register(t11); + +RWTexture2D u_DirectLighting : register(u0); + +float TraceShadowRayDI(float3 origin, float3 dir, float tMax, float skinnedSelfMax, uint2 pixel, uint mask) +{ + return TraceVisibilityAtten( + g_SceneTLAS, g_BatchInfo, g_MegaVB, g_MegaIB, g_GrassVB, g_GrassIB, g_ParticleVB, g_ParticleIB, + origin, dir, tMax, mask, + g_IdentityStaticCount, g_TerrainBatchCount, g_SkinnedBatchStart, g_GrassBatchStart, + g_ParticleBatchStart, g_DetailAtlasIndex, true, skinnedSelfMax, g_HudSkinnedStart, + t_BlueNoise, pixel, g_FrameIndex); +} + +float TraceHardShadowDI(float3 origin, float3 lightPos, float range, bool hudLight, bool isSpot, float skinnedSelfMax, uint2 pixel, uint mask) +{ + float3 toLight = lightPos - origin; + float dist = length(toLight); + if (dist < 1e-4) + return 1.0; + float endSkip = 0.02; + if (!hudLight && !isSpot && range < 6.0) + endSkip = 0.28; + else if (!hudLight && isSpot) + endSkip = 0.03; + float tMax = max(dist - endSkip, 0.02); + return TraceShadowRayDI(origin, toLight / dist, tMax, skinnedSelfMax, pixel, mask); +} + +float3 ShadeOneDI(GPULightDataDI light, float3 worldPos, float3 N, float3 V, float3 albedo, + float metallic, float roughness, float sssMask, bool vegSurf, float surfMark, + float3 biasN, float skinnedSelfMax, uint2 pixel, float2 giSize, float2 fullSize) +{ + if (!IsLiveLightDI(light)) + return 0; + + const bool hudLight = IsHudLightDI(light); + float3 L, lightColor; + float dist; + float atten = EvalLocalLightAttenuationDI(light, worldPos, L, dist, lightColor); + if (atten <= 0.001) + return 0; + + const bool isSpot = light.spotParamsAndType.y > 0.5; + const bool transientPoint = IsTransientPointLightDI(light); + float shadow = 1.0; + if (!hudLight && !transientPoint) { + float range = max(abs(light.colorAndRange.w), 0.5); + shadow = TraceHardShadowDI( + biasN, light.positionAndInvRangeSq.xyz, range, + false, isSpot, skinnedSelfMax, pixel, RT_MASK_SHADOW); + } + if (shadow <= 0.001) + return 0; + + float3 Ns = N; + if (vegSurf && dot(N, L) < 0.0) + Ns = -N; + float3 shaded = PBRDirectLighting(albedo, Ns, V, L, lightColor * atten * shadow, metallic, roughness, 1); + if (vegSurf && sssMask > 0.01) { + float sssThickness = saturate(0.35 + sssMask * 0.3); + shaded += EvaluateFoliageSSS( + albedo, Ns, V, L, lightColor * atten, shadow, + LeafSSSTint(), sssThickness, sssMask); + } + return min(shaded, RESTIR_MAX_RADIANCE); +} + +[numthreads(8, 8, 1)] +void main(uint3 dispatchID : SV_DispatchThreadID) +{ + uint2 pixel = dispatchID.xy; + if (pixel.x >= (uint)g_ScreenSize.x || pixel.y >= (uint)g_ScreenSize.y) + return; + + float2 giSize = g_ScreenSize; + float2 fullSize = float2(g_FullWidth, g_FullHeight); + if (fullSize.x < 1.0 || fullSize.y < 1.0) { + uint fw = 0, fh = 0; + t_Depth.GetDimensions(fw, fh); + fullSize = float2(max(fw, 1u), max(fh, 1u)); + } + int2 fullPx = RestirFullPixel(pixel, giSize, fullSize); + + float depth = RestirLoadDepth(t_Depth, pixel, giSize, fullSize); + if (depth <= 0.0) + return; + + const uint numLights = (uint)g_ClusterParams.w; + if (numLights == 0) + return; + + float4 worldPosData = RestirLoadTex4(t_WorldPos, pixel, giSize, fullSize); + float2 uv = (float2(pixel) + 0.5) / giSize; + float3 worldPos = ResolveGBufferWorldPos(uv, depth, worldPosData, g_InvViewProj); + float4 baseColorData = RestirLoadTex4(t_BaseColor, pixel, giSize, fullSize); + const float surfMark = SurfMarkFromGBuffer(worldPosData.w, baseColorData.a); + const bool hudSurf = IsHudSurfMark(surfMark); + const bool charSurf = IsCharSurfMark(surfMark); + const float skinnedSelfMax = hudSurf ? 0.12 : (charSurf ? 0.06 : 0.0); + float4 normalData = RestirLoadTex4(t_Normal, pixel, giSize, fullSize); + float3 N = normalize(normalData.xyz); + float roughness = max(abs(normalData.w), MIN_ROUGHNESS); + float3 albedo = max(baseColorData.rgb, 0.0); + float sssMask = 0.0; + const bool vegSurf = IsVegSurfMark(surfMark); + float metallic = UnpackGBufferMetallic(baseColorData.a, vegSurf, sssMask); + float3 V = normalize(g_CameraPos.xyz - worldPos); + float3 biasN = worldPos + N * (skinnedSelfMax > 0.0 ? 0.03 : 0.012); + + float linearDepth = max(abs(mul(g_WorldToView, float4(worldPos, 1.0)).z), 0.01); + uint clusterIdx = GetClusterIndex(float2(fullPx) + 0.5, linearDepth, g_ClusterParams.xyz, g_ClusterDepth); + uint2 clusterData = g_ClusterGrid[clusterIdx]; + uint lightOffset = clusterData.x; + uint tileLightCount = min(clusterData.y, RESTIR_MAX_LIGHTS_PER_TILE); + uint shadeCount = min(tileLightCount, RESTIR_MAX_CLUSTER_LIGHTS); + + float3 accum = 0; + float viewDist = length(g_CameraPos.xyz - worldPos); + bool stableDirect = charSurf || IsInteriorSurfMark(surfMark) || viewDist < 4.0; + DIReservoir di = UnpackDIReservoir(t_DIReservoir.Load(int3(pixel, 0))); + if (stableDirect) { + for (uint li = 0; li < shadeCount; li++) { + uint lightIdx = g_LightIndexList[lightOffset + li]; + if (lightIdx >= numLights) + continue; + accum += ShadeOneDI(g_LightData[lightIdx], worldPos, N, V, albedo, + metallic, roughness, sssMask, vegSurf, surfMark, biasN, skinnedSelfMax, + pixel, giSize, fullSize); + } + } else if (IsDIReservoirValid(di) && di.lightIndex < numLights) { + accum = ShadeOneDI(g_LightData[di.lightIndex], worldPos, N, V, albedo, + metallic, roughness, sssMask, vegSurf, surfMark, biasN, skinnedSelfMax, + pixel, giSize, fullSize) * di.W; + } else { + for (uint li = 0; li < shadeCount; li++) { + uint lightIdx = g_LightIndexList[lightOffset + li]; + if (lightIdx >= numLights) + continue; + accum += ShadeOneDI(g_LightData[lightIdx], worldPos, N, V, albedo, + metallic, roughness, sssMask, vegSurf, surfMark, biasN, skinnedSelfMax, + pixel, giSize, fullSize); + } + } + + accum = min(accum, RESTIR_MAX_RADIANCE); + if (Luminance(accum) <= 1e-6) + return; + + float3 direct = u_DirectLighting[pixel].rgb + accum; + u_DirectLighting[pixel] = float4(direct, 1.0); +} diff --git a/res/gamedata/shaders/r5/restir_di_spatial.cs b/res/gamedata/shaders/r5/restir_di_spatial.cs new file mode 100644 index 00000000000..075b4f89fae --- /dev/null +++ b/res/gamedata/shaders/r5/restir_di_spatial.cs @@ -0,0 +1,200 @@ +#define SM_6_0 +#include "bindless_common.h" +#include "rt_common.h" +#include "shared/pbr_brdf.h" +#include "shared/basecolor_pack.h" +#include "restir_gi_common.h" +#include "restir_di_common.h" +#include "restir_di_eval.h" +#include "shared/surface_marks.h" + +cbuffer ReSTIRDISpatialParams : register(b5) { + float4x4 g_InvViewProj; + float4x4 g_WorldToView; + float4 g_CameraPos; + float2 g_ScreenSize; + float2 g_InvScreenSize; + uint g_FrameIndex; + uint g_SpatialSamples; + float g_SpatialRadius; + uint g_MMax; + float4 g_ClusterParams; + float4 g_ClusterScales; + float g_FullWidth; + float g_FullHeight; +}; + +StructuredBuffer g_LightData : register(t20); +Texture2D t_SrcDI : register(t0); +Texture2D t_Depth : register(t14); +Texture2D t_BaseColor : register(t6); +Texture2D t_WorldPos : register(t7); +Texture2D t_Normal : register(t11); + +RWTexture2D u_DIReservoir : register(u0); + +[numthreads(8, 8, 1)] +void main(uint3 dispatchID : SV_DispatchThreadID) +{ + uint2 pixel = dispatchID.xy; + if (pixel.x >= (uint)g_ScreenSize.x || pixel.y >= (uint)g_ScreenSize.y) + return; + + float2 giSize = g_ScreenSize; + float2 fullSize = float2(g_FullWidth, g_FullHeight); + if (fullSize.x < 1.0 || fullSize.y < 1.0) { + uint fw = 0, fh = 0; + t_Depth.GetDimensions(fw, fh); + fullSize = float2(max(fw, 1u), max(fh, 1u)); + } + + float depth = RestirLoadDepth(t_Depth, pixel, giSize, fullSize); + if (depth <= 0.0 || g_SpatialSamples == 0) { + u_DIReservoir[pixel] = t_SrcDI.Load(int3(pixel, 0)); + return; + } + + float4 worldPosData = RestirLoadTex4(t_WorldPos, pixel, giSize, fullSize); + float4 baseColorData = RestirLoadTex4(t_BaseColor, pixel, giSize, fullSize); + float surfMark = SurfMarkFromGBuffer(worldPosData.w, baseColorData.a); + if (IsWaterSurfMark(surfMark) || IsCharSurfMark(surfMark)) { + u_DIReservoir[pixel] = t_SrcDI.Load(int3(pixel, 0)); + return; + } + float2 uv = (float2(pixel) + 0.5) * g_InvScreenSize; + float3 worldPos = ResolveGBufferWorldPos(uv, depth, worldPosData, g_InvViewProj); + const float centerMark = surfMark; + const bool hudSurf = IsHudSurfMark(centerMark); + float4 normalData = RestirLoadTex4(t_Normal, pixel, giSize, fullSize); + float3 N = normalize(normalData.xyz); + float roughness = max(normalData.w, MIN_ROUGHNESS); + float3 albedo = max(baseColorData.rgb, 0.0); + float sssMaskUnused = 0.0; + float metallic = UnpackGBufferMetallic(baseColorData.a, hudSurf || IsCharSurfMark(centerMark), sssMaskUnused); + float3 V = normalize(g_CameraPos.xyz - worldPos); + float linearDepth = abs(mul(g_WorldToView, float4(worldPos, 1.0)).z); + const uint lightCount = (uint)g_ClusterParams.w; + + DIReservoir center = UnpackDIReservoir(t_SrcDI.Load(int3(pixel, 0))); + uint rng = pcg_hash(pixel.x + pixel.y * 1973u + g_FrameIndex * 26699u); + DIReservoir output = EmptyDIReservoir(); + + float targetCenter = 0; + if (IsDIReservoirValid(center) && center.lightIndex < lightCount && + IsLiveLightDI(g_LightData[center.lightIndex]) && + (hudSurf || !IsHudLightDI(g_LightData[center.lightIndex]))) { + targetCenter = EvalLocalLightTargetPdfDI( + g_LightData[center.lightIndex], worldPos, N, V, albedo, metallic, roughness); + if (targetCenter > 0) { + output.lightIndex = center.lightIndex; + output.targetPdf = targetCenter; + output.w_sum = targetCenter * center.W; + output.M = 1; + output.age = center.age; + } + } + + float viewDist = length(worldPos - g_CameraPos.xyz); + const bool interiorCenter = IsInteriorSurfMark(centerMark); + float spatialRadius = g_SpatialRadius; + uint spatialN = g_SpatialSamples; + if (interiorCenter) { + spatialRadius = g_SpatialRadius * 2.5; + spatialN = min(max(spatialN, 8u), 16u); + } else { + spatialRadius = min(g_SpatialRadius, 8.0); + if (viewDist > 60.0) + spatialN = min(spatialN, 1u); + else if (viewDist > 30.0) + spatialN = min(spatialN, max(spatialN / 2u, 1u)); + } + for (uint i = 0; i < spatialN; i++) { + float2 disc = float2(rand_float(rng), rand_float(rng)); + float ang = disc.x * 6.2831853; + float rad = sqrt(disc.y) * spatialRadius; + int2 nPixel = int2(float2(pixel) + float2(cos(ang), sin(ang)) * rad); + if (nPixel.x < 0 || nPixel.y < 0 || + nPixel.x >= (int)g_ScreenSize.x || nPixel.y >= (int)g_ScreenSize.y) + continue; + + float4 nWorldData = RestirLoadTex4(t_WorldPos, uint2(nPixel), giSize, fullSize); + float4 nBase = RestirLoadTex4(t_BaseColor, uint2(nPixel), giSize, fullSize); + float nMark = SurfMarkFromGBuffer(nWorldData.w, nBase.a); + if (!SameHudSurfClass(centerMark, nMark)) + continue; + if (!SameLightZone(centerMark, nMark)) + continue; + if (interiorCenter && (IsVegSurfMark(nMark) || IsTerrainSurfMark(nMark))) + continue; + float2 nUV = (float2(nPixel) + 0.5) * g_InvScreenSize; + float nDepthRaw = RestirLoadDepth(t_Depth, uint2(nPixel), giSize, fullSize); + float3 nWorld = ReconstructWorldPosReverseZ(nUV, nDepthRaw, g_InvViewProj); + float3 nN = normalize(RestirLoadTex4(t_Normal, uint2(nPixel), giSize, fullSize).xyz); + float nDepth = abs(mul(g_WorldToView, float4(nWorld, 1.0)).z); + if (abs(nDepth - linearDepth) / max(linearDepth, 1e-3) > 0.1) + continue; + if (dot(N, nN) < 0.906) + continue; + + DIReservoir neighbor = UnpackDIReservoir(t_SrcDI.Load(int3(nPixel, 0))); + if (!IsDIReservoirValid(neighbor) || neighbor.lightIndex >= lightCount) + continue; + if (!IsLiveLightDI(g_LightData[neighbor.lightIndex])) + continue; + if ((!hudSurf && IsHudLightDI(g_LightData[neighbor.lightIndex]))) + continue; + + float targetN = EvalLocalLightTargetPdfDI( + g_LightData[neighbor.lightIndex], worldPos, N, V, albedo, metallic, roughness); + if (targetN <= 0) + continue; + + if (!IsDIReservoirValid(output)) { + output.lightIndex = neighbor.lightIndex; + output.targetPdf = targetN; + output.w_sum = targetN * neighbor.W * min(neighbor.M, g_MMax); + output.M = min(neighbor.M, g_MMax); + output.age = neighbor.age; + } else { + uint clampedM = min(neighbor.M, g_MMax); + float w = targetN * neighbor.W * clampedM; + DIReservoirUpdate(output, w, neighbor.lightIndex, targetN, rng); + output.M += clampedM - 1; + } + } + + if (!IsDIReservoirValid(output) && IsDIReservoirValid(center) && + center.lightIndex < lightCount && IsLiveLightDI(g_LightData[center.lightIndex]) && + (hudSurf || !IsHudLightDI(g_LightData[center.lightIndex]))) + output = center; + + float outPdf = output.targetPdf; + if (outPdf <= 0 && IsDIReservoirValid(output) && output.lightIndex < lightCount && + IsLiveLightDI(g_LightData[output.lightIndex])) { + outPdf = EvalLocalLightTargetPdfDI( + g_LightData[output.lightIndex], worldPos, N, V, albedo, metallic, roughness); + output.targetPdf = outPdf; + } + + if (outPdf > 0 && output.M > 0) + output.W = ClampDIReservoirW(output.w_sum / max(outPdf * (float)output.M, 1e-6)); + else if (IsDIReservoirValid(center) && center.lightIndex < lightCount && + IsLiveLightDI(g_LightData[center.lightIndex]) && + (hudSurf || !IsHudLightDI(g_LightData[center.lightIndex]))) + output = center; + else + output = EmptyDIReservoir(); + + if (IsDIReservoirValid(output) && !IsLiveLightDI(g_LightData[output.lightIndex])) + output = EmptyDIReservoir(); + + if (IsDIReservoirValid(center) && output.w_sum > 8.0 * max(center.w_sum, 1e-6)) + output = center; + + if (output.W <= 0 && IsDIReservoirValid(center)) + output = center; + + output.M = min(max(output.M, 1u), g_MMax); + output.zone = interiorCenter ? 1u : 0u; + u_DIReservoir[pixel] = PackDIReservoir(output); +} diff --git a/res/gamedata/shaders/r5/restir_di_temporal.cs b/res/gamedata/shaders/r5/restir_di_temporal.cs new file mode 100644 index 00000000000..0b5c82be4b0 --- /dev/null +++ b/res/gamedata/shaders/r5/restir_di_temporal.cs @@ -0,0 +1,189 @@ +#include "bindless_common.h" +#include "rt_common.h" +#include "shared/pbr_brdf.h" +#include "shared/basecolor_pack.h" +#include "restir_gi_common.h" +#include "restir_di_common.h" +#include "restir_di_eval.h" +#include "shared/surface_marks.h" + +cbuffer ReSTIRDITemporalParams : register(b5) { + float4x4 g_InvViewProj; + float4x4 g_PrevInvViewProj; + float4 g_CameraPos; + float2 g_ScreenSize; + float2 g_InvScreenSize; + uint g_FrameIndex; + uint g_MMax; + float g_CurrJitterX; + float g_CurrJitterY; + float g_PrevJitterX; + float g_PrevJitterY; + float g_FullWidth; + float g_FullHeight; + float4 g_ClusterParams; + float4 g_ClusterScales; +}; + +StructuredBuffer g_LightData : register(t20); +Texture2D t_PrevDI : register(t0); +Texture2D t_MotionVectors : register(t2); +Texture2D t_Depth : register(t3); +Texture2D t_PrevNormal : register(t5); +Texture2D t_BaseColor : register(t6); +Texture2D t_WorldPos : register(t7); +Texture2D t_PrevDepth : register(t11); +Texture2D t_Normal : register(t12); +Texture2D t_SkyOpen : register(t13); + +RWTexture2D u_DIReservoir : register(u0); + +[numthreads(8, 8, 1)] +void main(uint3 dispatchID : SV_DispatchThreadID) +{ + uint2 pixel = dispatchID.xy; + if (pixel.x >= (uint)g_ScreenSize.x || pixel.y >= (uint)g_ScreenSize.y) + return; + + float2 giSize = g_ScreenSize; + float2 fullSize = float2(g_FullWidth, g_FullHeight); + if (fullSize.x < 1.0 || fullSize.y < 1.0) { + uint fw = 0, fh = 0; + t_Depth.GetDimensions(fw, fh); + fullSize = float2(max(fw, 1u), max(fh, 1u)); + } + + float depth = RestirLoadDepth(t_Depth, pixel, giSize, fullSize); + if (depth <= 0.0) { + u_DIReservoir[pixel] = PackDIReservoir(EmptyDIReservoir()); + return; + } + + DIReservoir curr = UnpackDIReservoir(u_DIReservoir[pixel]); + float4 worldPosData = RestirLoadTex4(t_WorldPos, pixel, giSize, fullSize); + float2 uv = (float2(pixel) + 0.5) * g_InvScreenSize; + float3 worldPos = ResolveGBufferWorldPos(uv, depth, worldPosData, g_InvViewProj); + float4 baseColorData = RestirLoadTex4(t_BaseColor, pixel, giSize, fullSize); + const float centerMark = SurfMarkFromGBuffer(worldPosData.w, baseColorData.a); + const bool hudSurf = IsHudSurfMark(centerMark); + float4 normalData = RestirLoadTex4(t_Normal, pixel, giSize, fullSize); + float3 N = normalize(normalData.xyz); + float roughness = max(normalData.w, MIN_ROUGHNESS); + float3 albedo = max(baseColorData.rgb, 0.0); + float sssMaskUnused = 0.0; + float metallic = UnpackGBufferMetallic(baseColorData.a, hudSurf || IsCharSurfMark(centerMark), sssMaskUnused); + float3 V = normalize(g_CameraPos.xyz - worldPos); + + uint rng = pcg_hash(pixel.x + pixel.y * 7919u + g_FrameIndex * 48611u); + DIReservoir output = EmptyDIReservoir(); + const uint lightCount = (uint)g_ClusterParams.w; + + float targetCurr = 0; + if (IsDIReservoirValid(curr) && curr.lightIndex < lightCount && + IsLiveLightDI(g_LightData[curr.lightIndex])) { + if (hudSurf || !IsHudLightDI(g_LightData[curr.lightIndex])) { + targetCurr = EvalLocalLightTargetPdfDI( + g_LightData[curr.lightIndex], worldPos, N, V, albedo, metallic, roughness); + if (targetCurr > 0) { + output.lightIndex = curr.lightIndex; + output.targetPdf = targetCurr; + output.w_sum = targetCurr * curr.W; + output.M = 1; + output.age = curr.age; + } + } + } + + int2 fullPx = RestirFullPixel(pixel, giSize, fullSize); + float2 motion = t_MotionVectors.Load(int3(fullPx, 0)); + float2 prevUV = uv + motion; + float motionPx = length(motion * fullSize); + + if (!IsCharSurfMark(centerMark) && motionPx < 16.0 && all(prevUV >= 0) && all(prevUV < 1.0)) { + int2 prevPixel = int2(prevUV * giSize); + prevPixel = clamp(prevPixel, int2(0, 0), int2(giSize) - 1); + int2 prevFull = clamp(int2(prevUV * fullSize), int2(0, 0), int2(fullSize) - 1); + float prevDepth = t_PrevDepth.Load(int3(prevFull, 0)); + float3 prevN = normalize(t_PrevNormal.Load(int3(prevFull, 0)).xyz); + float viewDist = length(worldPos - g_CameraPos.xyz); + bool valid = false; + float3 prevWorldPos = worldPos; + if (prevDepth > 0.0 && prevDepth < 1.0) { + float2 prevNdcUV = (float2(prevFull) + 0.5) / fullSize; + prevWorldPos = ReconstructWorldPosReverseZ(prevNdcUV, prevDepth, g_PrevInvViewProj); + float posDist = length(worldPos - prevWorldPos); + float posTol = (motionPx < 1.0) ? 0.16 : 0.12; + valid = posDist < posTol * max(viewDist, 1.0) && dot(N, prevN) > 0.8; + } + + if (valid) { + DIReservoir prev = UnpackDIReservoir(t_PrevDI.Load(int3(prevPixel, 0))); + if (IsDIReservoirValid(prev) && prev.lightIndex < lightCount && + IsLiveLightDI(g_LightData[prev.lightIndex]) && + (hudSurf || !IsHudLightDI(g_LightData[prev.lightIndex]))) { + float targetPrev = EvalLocalLightTargetPdfDI( + g_LightData[prev.lightIndex], worldPos, N, V, albedo, metallic, roughness); + if (targetPrev > 0) { + uint mCap = g_MMax; + if (motionPx > 6.0) + mCap = max(1u, g_MMax / 4u); + else if (motionPx > 2.0) + mCap = max(1u, g_MMax / 2u); + float skyOpenC = saturate(RestirLoadTex1(t_SkyOpen, pixel, giSize, fullSize)); + float skyOpenP = saturate(RestirLoadTex1(t_SkyOpen, uint2(prevFull), fullSize, fullSize)); + uint currZone = IsInteriorSurfMark(centerMark) ? 1u : 0u; + if (currZone != prev.zone) { + if (abs(skyOpenC - skyOpenP) > 0.35) + mCap = min(mCap, 4u); + else + mCap = min(mCap, 8u); + } + if (targetCurr > 0 && max(targetPrev, targetCurr) / max(min(targetPrev, targetCurr), 1e-6) > 8.0) + mCap = min(mCap, 8u); + if (!IsDIReservoirValid(output)) { + output.lightIndex = prev.lightIndex; + output.targetPdf = targetPrev; + output.w_sum = targetPrev * prev.W * min(prev.M, mCap); + output.M = min(prev.M, mCap); + output.age = prev.age + 1; + } else { + uint clampedM = min(prev.M, mCap); + float wPrev = targetPrev * prev.W * clampedM; + DIReservoirUpdate(output, wPrev, prev.lightIndex, targetPrev, rng); + output.M += clampedM - 1; + output.age = prev.age + 1; + } + } + } + } + } + + if (!IsDIReservoirValid(output) && IsDIReservoirValid(curr) && curr.lightIndex < lightCount && + IsLiveLightDI(g_LightData[curr.lightIndex]) && + (hudSurf || !IsHudLightDI(g_LightData[curr.lightIndex]))) + output = curr; + + float outPdf = output.targetPdf; + if (outPdf <= 0 && IsDIReservoirValid(output) && output.lightIndex < lightCount && + IsLiveLightDI(g_LightData[output.lightIndex])) { + outPdf = EvalLocalLightTargetPdfDI( + g_LightData[output.lightIndex], worldPos, N, V, albedo, metallic, roughness); + output.targetPdf = outPdf; + } + + if (outPdf > 0 && output.M > 0) + output.W = ClampDIReservoirW(output.w_sum / max(outPdf * (float)output.M, 1e-6)); + else if (IsDIReservoirValid(curr) && curr.lightIndex < lightCount && + IsLiveLightDI(g_LightData[curr.lightIndex]) && + (hudSurf || !IsHudLightDI(g_LightData[curr.lightIndex]))) + output = curr; + else + output = EmptyDIReservoir(); + + if (output.W <= 0 || (IsDIReservoirValid(output) && !IsLiveLightDI(g_LightData[output.lightIndex]))) + output = EmptyDIReservoir(); + + output.M = min(max(output.M, 1u), g_MMax); + output.zone = IsInteriorSurfMark(centerMark) ? 1u : 0u; + u_DIReservoir[pixel] = PackDIReservoir(output); +} diff --git a/res/gamedata/shaders/r5/restir_gi_blur.cs b/res/gamedata/shaders/r5/restir_gi_blur.cs new file mode 100644 index 00000000000..4f65026244f --- /dev/null +++ b/res/gamedata/shaders/r5/restir_gi_blur.cs @@ -0,0 +1,150 @@ +#include "rt_common.h" +#include "restir_gi_common.h" +#include "shared/surface_marks.h" + +cbuffer BlurParams : register(b5) { + float2 g_ScreenSize; + float2 g_InvScreenSize; + float g_PhiNormal; + float g_PhiDepth; + uint g_Step; + uint g_Mode; +}; + +Texture2D t_DirectLighting : register(t0); +Texture2D t_NoisyDiffuse : register(t1); +Texture2D t_Depth : register(t2); +Texture2D t_Normal : register(t3); +Texture2D t_SceneColorIn : register(t4); +Texture2D t_NoisySpecular : register(t5); +Texture2D t_ClassifyWorldPos : register(t6); +Texture2D t_WorldPos : register(t7); + +RWTexture2D u_SceneColor : register(u0); +RWTexture2D u_FilteredDiffuse : register(u1); +RWTexture2D u_FilteredSpecular : register(u2); + +static const float kKernel[5] = { 0.0625, 0.25, 0.375, 0.25, 0.0625 }; + +float Luma(float3 c) +{ + return dot(c, float3(0.2126, 0.7152, 0.0722)); +} + +float3 SoftClampFirefly(float3 c, float centerLuma, float maxScale) +{ + float l = Luma(c); + float limit = max(centerLuma * maxScale, 0.05); + if (l > limit) + c *= limit / max(l, 1e-4); + return c; +} + +[numthreads(8, 8, 1)] +void main(uint3 dispatchID : SV_DispatchThreadID) +{ + uint2 pixel = dispatchID.xy; + if (pixel.x >= (uint)g_ScreenSize.x || pixel.y >= (uint)g_ScreenSize.y) + return; + + float2 giSize = g_ScreenSize; + uint fullW = 0, fullH = 0; + t_Depth.GetDimensions(fullW, fullH); + float2 fullSize = float2(max(fullW, 1u), max(fullH, 1u)); + + float depth = RestirLoadDepth(t_Depth, pixel, giSize, fullSize); + float3 direct = t_DirectLighting.Load(int3(pixel, 0)).rgb; + float3 noisyD = t_NoisyDiffuse.Load(int3(pixel, 0)).rgb; + float4 noisyS4 = t_NoisySpecular.Load(int3(pixel, 0)); + float3 noisyS = noisyS4.rgb; + float specHitDist = noisyS4.a; + + float guideMark = RestirLoadTex4(t_WorldPos, pixel, giSize, fullSize).w; + float classifyMark = RestirLoadTex4(t_ClassifyWorldPos, pixel, giSize, fullSize).w; + if (depth <= 0.0 || SkipRtSurfLighting(classifyMark, guideMark)) { + if (g_Mode == 1) + u_SceneColor[pixel] = RestirLoadTex4(t_SceneColorIn, pixel, giSize, fullSize); + u_FilteredDiffuse[pixel] = 0; + u_FilteredSpecular[pixel] = 0; + return; + } + + float4 nPack = RestirLoadTex4(t_Normal, pixel, giSize, fullSize); + float3 N = normalize(nPack.xyz); + float roughness = saturate(nPack.w); + float centerLumaD = Luma(noisyD); + float centerLumaS = Luma(noisyS); + float directLuma = max(Luma(direct), 0.02); + int step = (int)max(g_Step, 1u); + float hitBlur = saturate(specHitDist * 0.04); + float roughBlur = saturate(roughness * roughness * 2.5 + hitBlur); + int stepS = max(step, (int)ceil((float)step * (1.0 + roughBlur * 2.5))); + + float3 sumD = 0; + float3 sumS = 0; + float wSumD = 0; + float wSumS = 0; + float3 nbMinD = noisyD; + float3 nbMaxD = noisyD; + float3 nbMinS = noisyS; + float3 nbMaxS = noisyS; + + [unroll] for (int iy = -2; iy <= 2; ++iy) { + [unroll] for (int ix = -2; ix <= 2; ++ix) { + int2 npD = int2(pixel) + int2(ix, iy) * step; + int2 npS = int2(pixel) + int2(ix, iy) * stepS; + if (npD.x >= 0 && npD.y >= 0 && npD.x < (int)g_ScreenSize.x && npD.y < (int)g_ScreenSize.y) + { + float nd = RestirLoadDepth(t_Depth, uint2(npD), giSize, fullSize); + if (nd > 0.0 && SameHudSurfClass(guideMark, RestirLoadTex4(t_WorldPos, uint2(npD), giSize, fullSize).w)) + { + float3 nN = normalize(RestirLoadTex4(t_Normal, uint2(npD), giSize, fullSize).xyz); + float3 nD = SoftClampFirefly(t_NoisyDiffuse.Load(int3(npD, 0)).rgb, max(centerLumaD, 0.35), 8.0); + nbMinD = min(nbMinD, nD); + nbMaxD = max(nbMaxD, nD); + float w = kKernel[ix + 2] * kKernel[iy + 2]; + float wGeo = exp(-abs(depth - nd) * g_PhiDepth); + wGeo *= pow(saturate(dot(N, nN)), g_PhiNormal); + w *= max(wGeo, 0.05); + float wD = w * exp(-abs(Luma(nD) - centerLumaD) * 0.35); + sumD += nD * wD; + wSumD += wD; + } + } + + if (npS.x < 0 || npS.y < 0 || npS.x >= (int)g_ScreenSize.x || npS.y >= (int)g_ScreenSize.y) + continue; + float ndS = RestirLoadDepth(t_Depth, uint2(npS), giSize, fullSize); + if (ndS <= 0.0) + continue; + if (!SameHudSurfClass(guideMark, RestirLoadTex4(t_WorldPos, uint2(npS), giSize, fullSize).w)) + continue; + + float3 nNs = normalize(RestirLoadTex4(t_Normal, uint2(npS), giSize, fullSize).xyz); + float3 nS = SoftClampFirefly(t_NoisySpecular.Load(int3(npS, 0)).rgb, max(centerLumaS, 0.35), 10.0); + nbMinS = min(nbMinS, nS); + nbMaxS = max(nbMaxS, nS); + + float wS0 = kKernel[ix + 2] * kKernel[iy + 2]; + float wGeoS = exp(-abs(depth - ndS) * (g_PhiDepth * lerp(1.0, 0.45, roughBlur))); + wGeoS *= pow(saturate(dot(N, nNs)), g_PhiNormal * lerp(1.0, 0.35, roughBlur)); + wS0 *= max(wGeoS, 0.08); + float wS = wS0 * exp(-abs(Luma(nS) - centerLumaS) * lerp(0.75, 0.2, roughBlur)); + sumS += nS * wS; + wSumS += wS; + } + } + + float3 filteredD = (wSumD > 1e-4) ? (sumD / wSumD) : SoftClampFirefly(noisyD, max(centerLumaD, 0.35), 8.0); + float3 filteredS = (wSumS > 1e-4) ? (sumS / wSumS) : SoftClampFirefly(noisyS, max(centerLumaS, 0.35), 10.0); + filteredD = clamp(filteredD, nbMinD * 0.25, nbMaxD * 3.0 + 0.02); + filteredS = clamp(filteredS, nbMinS * 0.2, nbMaxS * 3.5 + 0.02); + + u_FilteredDiffuse[pixel] = float4(filteredD, 1.0); + u_FilteredSpecular[pixel] = float4(filteredS, specHitDist); + + if (g_Mode == 1) { + float3 ambientBase = RestirLoadTex4(t_SceneColorIn, pixel, giSize, fullSize).rgb; + u_SceneColor[pixel] = float4(ambientBase + direct + filteredD + filteredS, 1.0); + } +} diff --git a/res/gamedata/shaders/r5/restir_gi_common.h b/res/gamedata/shaders/r5/restir_gi_common.h index fb09095e8f0..a9f2926e4b0 100644 --- a/res/gamedata/shaders/r5/restir_gi_common.h +++ b/res/gamedata/shaders/r5/restir_gi_common.h @@ -8,6 +8,9 @@ static const uint RESTIR_INVALID_ID = 0xFFFFFFFF; static const float RESTIR_MAX_RADIANCE = 100.0; static const uint RESTIR_M_MAX = 20; +static const uint RESTIR_MAX_LOCAL_LIGHT_SAMPLES = 16; +static const uint RESTIR_MAX_CLUSTER_LIGHTS = 32; +static const uint RESTIR_MAX_LIGHTS_PER_TILE = 128; struct GIReservoir { @@ -18,6 +21,7 @@ struct GIReservoir float w_sum; uint M; uint age; + uint lightId; }; GIReservoir EmptyReservoir() @@ -30,6 +34,7 @@ GIReservoir EmptyReservoir() r.w_sum = 0; r.M = 0; r.age = 0; + r.lightId = RESTIR_INVALID_ID; return r; } @@ -43,29 +48,25 @@ float Luminance(float3 c) return dot(c, float3(0.2126, 0.7152, 0.0722)); } -bool ReservoirUpdate(inout GIReservoir r, float weight, float3 pos, float3 normal, float3 lo, inout uint rng) +float3 ReconstructWorldPosReverseZ(float2 uv, float depth, float4x4 invViewProj) { - if (isnan(weight) || isinf(weight)) - return false; - - r.w_sum += weight; - r.M += 1; + float4 clip = float4(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, depth, 1.0); + float4 world = mul(invViewProj, clip); + return world.xyz / max(world.w, 1e-8); +} - float xi = rand_float(rng); - if (xi < weight / max(r.w_sum, 1e-6)) { - r.samplePos = pos; - r.sampleNormal = normal; - r.Lo = lo; - return true; - } - return false; +float3 ResolveGBufferWorldPos(float2 uv, float depth, float4 wpSample, float4x4 invViewProj) +{ + if (wpSample.w > 1.5 && length(wpSample.xyz) > 0.01) + return wpSample.xyz; + return ReconstructWorldPosReverseZ(uv, depth, invViewProj); } float2 OctEncode(float3 n) { - n /= (abs(n.x) + abs(n.y) + abs(n.z)); + n /= (abs(n.x) + abs(n.y) + abs(n.z) + 1e-8); if (n.z < 0) { - float2 wrap = (1.0 - abs(n.yx)) * (n.xy >= 0 ? 1.0 : -1.0); + float2 wrap = (1.0 - abs(n.yx)) * (float2(n.xy >= 0) * 2.0 - 1.0); n.xy = wrap; } return n.xy * 0.5 + 0.5; @@ -76,12 +77,24 @@ float3 OctDecode(float2 e) e = e * 2.0 - 1.0; float3 n = float3(e.xy, 1.0 - abs(e.x) - abs(e.y)); if (n.z < 0) { - float2 wrap = (1.0 - abs(n.yx)) * (n.xy >= 0 ? 1.0 : -1.0); + float2 wrap = (1.0 - abs(n.yx)) * (float2(n.xy >= 0) * 2.0 - 1.0); n.xy = wrap; } return normalize(n); } +uint PackUnorm2To16(float2 v) +{ + uint x = (uint)(saturate(v.x) * 65535.0 + 0.5); + uint y = (uint)(saturate(v.y) * 65535.0 + 0.5); + return (x & 0xFFFF) | ((y & 0xFFFF) << 16); +} + +float2 UnpackUnorm2From16(uint p) +{ + return float2(float(p & 0xFFFF), float((p >> 16) & 0xFFFF)) / 65535.0; +} + uint PackNormalMAge(float3 normal, uint M, uint age) { float2 oct = OctEncode(normal); @@ -102,30 +115,103 @@ void UnpackNormalMAge(uint packed, out float3 normal, out uint M, out uint age) age = packed & 0xFF; } -// ReservoirA (RGBA32_FLOAT): samplePos.xyz, W -// ReservoirB (RGBA32_FLOAT): Lo.rgb, packed(normal_oct16 | M_u8 | age_u8) -void PackReservoir(GIReservoir r, out float4 A, out float4 B) +void PackReservoirAB(GIReservoir r, out float4 A, out float4 B) { A = float4(r.samplePos, r.W); B = float4(r.Lo, asfloat(PackNormalMAge(r.sampleNormal, r.M, r.age))); } -GIReservoir UnpackReservoir(float4 A, float4 B) +GIReservoir UnpackReservoirAB(float4 A, float4 B) { - GIReservoir r; + GIReservoir r = EmptyReservoir(); r.samplePos = A.xyz; r.W = A.w; r.Lo = B.xyz; UnpackNormalMAge(asuint(B.w), r.sampleNormal, r.M, r.age); + r.lightId = RESTIR_INVALID_ID; + return r; +} + +uint PackRGB9E5(float3 c) +{ + c = clamp(c, 0.0, RESTIR_MAX_RADIANCE); + float maxc = max(max(c.r, c.g), max(c.b, 1e-6)); + int e = (int)ceil(log2(maxc)) + 15; + e = clamp(e, 0, 31); + float scale = exp2(float(15 - e)); + uint r = (uint)clamp(c.r * scale * 511.0 + 0.5, 0.0, 511.0); + uint g = (uint)clamp(c.g * scale * 511.0 + 0.5, 0.0, 511.0); + uint b = (uint)clamp(c.b * scale * 511.0 + 0.5, 0.0, 511.0); + return (r) | (g << 9) | (b << 18) | ((uint)e << 27); +} + +float3 UnpackRGB9E5(uint p) +{ + float scale = exp2(float(int((p >> 27) & 31) - 15)); + float r = float(p & 511) / 511.0; + float g = float((p >> 9) & 511) / 511.0; + float b = float((p >> 18) & 511) / 511.0; + return float3(r, g, b) * scale; +} + +uint4 PackReservoirU4(GIReservoir r, float3 primaryPos) +{ + float3 toSample = r.samplePos - primaryPos; + float dist = length(toSample); + float3 dir = dist > 1e-5 ? toSample / dist : float3(0, 1, 0); + uint4 o; + bool isGI = (r.lightId == RESTIR_INVALID_ID); + o.x = isGI ? PackUnorm2To16(OctEncode(dir)) : r.lightId; + o.y = PackRGB9E5(r.Lo); + uint age = r.age & 0x7F; + if (isGI) + age |= 0x80; + o.z = PackNormalMAge(r.sampleNormal, r.M, age); + o.w = (f32tof16(min(r.W, 65000.0)) & 0xFFFF) + | ((f32tof16(min(dist, 65000.0)) & 0xFFFF) << 16); + return o; +} + +GIReservoir UnpackReservoirU4(uint4 p, float3 primaryPos) +{ + GIReservoir r = EmptyReservoir(); + r.Lo = UnpackRGB9E5(p.y); + UnpackNormalMAge(p.z, r.sampleNormal, r.M, r.age); + bool isGI = (r.age & 0x80) != 0; + r.age &= 0x7F; + r.W = f16tof32(p.w & 0xFFFF); + float dist = f16tof32((p.w >> 16) & 0xFFFF); + if (isGI) { + r.lightId = RESTIR_INVALID_ID; + float3 dir = OctDecode(UnpackUnorm2From16(p.x)); + r.samplePos = primaryPos + dir * max(dist, 0.05); + } else { + r.lightId = p.x; + r.samplePos = primaryPos + normalize(r.sampleNormal) * max(dist, 0.05); + } r.w_sum = 0; return r; } -// Jacobian of reconnection shift: reusing sample from pixel q at pixel r -// x1_new = primary surface at destination pixel (r) -// x1_old = primary surface at source pixel (q) -// x2 = secondary surface (sample point, fixed) -// x2_normal = normal at secondary surface +bool ReservoirUpdate(inout GIReservoir r, float weight, float3 pos, float3 normal, float3 lo, uint lightId, inout uint rng) +{ + if (isnan(weight) || isinf(weight) || weight <= 0) + return false; + + r.w_sum += weight; + r.M += 1; + + float xi = rand_float(rng); + if (xi < weight / max(r.w_sum, 1e-6)) { + r.samplePos = pos; + r.sampleNormal = normal; + r.Lo = lo; + r.lightId = lightId; + return true; + } + return false; +} + float JacobianReconnectionShift(float3 x2_normal, float3 x1_new, float3 x1_old, float3 x2) { float3 v_new = x1_new - x2; @@ -152,4 +238,40 @@ bool ValidateTemporalNeighbor(float currLinearDepth, float3 currNormal, float pr return true; } +uint TemporalMClamp(uint M, uint age, uint mMax) +{ + uint ageCut = age > 16u ? (mMax / 2u) : mMax; + if (age > 40u) + ageCut = max(mMax / 4u, 1u); + return min(M, max(ageCut, 1u)); +} + +float AgeConfidence(uint age, uint M) +{ + float a = 1.0 - saturate((float)age / 48.0); + float m = saturate((float)M / 8.0); + return saturate(0.35 + 0.65 * a * m); +} + +int2 RestirFullPixel(uint2 giPixel, float2 giSize, float2 fullSize) +{ + float2 uv = (float2(giPixel) + 0.5) / max(giSize, float2(1.0, 1.0)); + return clamp(int2(uv * fullSize), int2(0, 0), int2(fullSize) - 1); +} + +float RestirLoadDepth(Texture2D depthTex, uint2 giPixel, float2 giSize, float2 fullSize) +{ + return depthTex.Load(int3(RestirFullPixel(giPixel, giSize, fullSize), 0)); +} + +float4 RestirLoadTex4(Texture2D tex, uint2 giPixel, float2 giSize, float2 fullSize) +{ + return tex.Load(int3(RestirFullPixel(giPixel, giSize, fullSize), 0)); +} + +float RestirLoadTex1(Texture2D tex, uint2 giPixel, float2 giSize, float2 fullSize) +{ + return tex.Load(int3(RestirFullPixel(giPixel, giSize, fullSize), 0)); +} + #endif diff --git a/res/gamedata/shaders/r5/restir_gi_composite.cs b/res/gamedata/shaders/r5/restir_gi_composite.cs index c517ed8857b..a23e767b592 100644 --- a/res/gamedata/shaders/r5/restir_gi_composite.cs +++ b/res/gamedata/shaders/r5/restir_gi_composite.cs @@ -1,71 +1,177 @@ -#include "common.h" #include "rt_common.h" #include "shared/pbr_brdf.h" +#include "shared/basecolor_pack.h" +#include "shared/nrd_helpers.h" +#include "shared/surface_marks.h" #include "restir_gi_common.h" +#include "rt_irradiance_cache.h" +#include "atmosphere.h" cbuffer CompositeParams : register(b5) { float4x4 g_InvViewProj; float4 g_CameraPos; float2 g_ScreenSize; float g_GIIntensity; - uint g_Pad; + uint g_DenoiseApply; + float4 g_FogParams; + float4 g_FogColor; + float4 g_SunDir; + float4 g_SunColor; + float g_AmbientScale; + uint g_CacheSize; + float g_CacheCellSize; + uint g_UseDdgi; + uint g_AddDirect; + float g_GiWidth; + float g_GiHeight; + uint g_ShaftWidth; + uint g_ShaftHeight; + uint g_FrameIndex; + uint g_Pad1; + uint g_Pad2; }; Texture2D t_DirectLighting : register(t0); -Texture2D t_ReservoirA : register(t1); -Texture2D t_ReservoirB : register(t2); -Texture2D t_Depth : register(t3); -Texture2D t_BaseColor : register(t5); -Texture2D t_SceneColorIn : register(t6); -Texture2D t_Normal : register(t8); +StructuredBuffer t_Reservoir : register(t1); +Texture2D t_Depth : register(t2); +Texture2D t_BaseColor : register(t3); +Texture2D t_SceneColorIn : register(t4); +Texture2D t_Normal : register(t5); +Texture2D t_NoisyDiffuse : register(t6); +Texture2D t_NoisySpecular : register(t7); +Texture2D t_Sunshafts : register(t8); +Texture2D t_DDGIAmbient : register(t9); +Texture2D t_WorldPos : register(t10); +Texture2D t_SpecReservoirA : register(t11); +Texture2D t_SpecReservoirB : register(t12); +StructuredBuffer g_IrradianceCache : register(t13); +Texture2D t_SkyOpen : register(t14); +TextureCube g_Sky0 : register(t15); +TextureCube g_Sky1 : register(t16); RWTexture2D u_SceneColor : register(u0); +SamplerState smp_linear : register(s0); + +float3 SampleSkyIncident(float3 dir, float mip) +{ + float3 d = normalize(dir); + float3 s0 = g_Sky0.SampleLevel(smp_linear, d, mip).rgb; + float3 s1 = g_Sky1.SampleLevel(smp_linear, d, mip).rgb; + float3 sky = lerp(s0, s1, saturate(g_CameraPos.w)) * g_FogColor.rgb * 0.80; + if (dot(sky, sky) < 1e-6) + sky = g_FogColor.rgb * 0.80; + return sky; +} + +float3 UpsampleHalfDepthAware(Texture2D tex, float2 halfSize, float2 uv, float centerDepth) +{ + float2 hs = max(halfSize, float2(1.0, 1.0)); + float2 p = uv * hs - 0.5; + int2 i0 = int2(floor(p)); + float2 f = saturate(p - float2(i0)); + int2 maxP = int2(hs) - 1; + int2 c00 = clamp(i0 + int2(0, 0), int2(0, 0), maxP); + int2 c10 = clamp(i0 + int2(1, 0), int2(0, 0), maxP); + int2 c01 = clamp(i0 + int2(0, 1), int2(0, 0), maxP); + int2 c11 = clamp(i0 + int2(1, 1), int2(0, 0), maxP); + + float2 fullSize = g_ScreenSize; + float d00 = RestirLoadDepth(t_Depth, uint2(c00), hs, fullSize); + float d10 = RestirLoadDepth(t_Depth, uint2(c10), hs, fullSize); + float d01 = RestirLoadDepth(t_Depth, uint2(c01), hs, fullSize); + float d11 = RestirLoadDepth(t_Depth, uint2(c11), hs, fullSize); + + float3 v00 = tex.Load(int3(c00, 0)).rgb; + float3 v10 = tex.Load(int3(c10, 0)).rgb; + float3 v01 = tex.Load(int3(c01, 0)).rgb; + float3 v11 = tex.Load(int3(c11, 0)).rgb; + + float w00 = (1.0 - f.x) * (1.0 - f.y) / (1e-3 + abs(d00 - centerDepth) * 80.0); + float w10 = f.x * (1.0 - f.y) / (1e-3 + abs(d10 - centerDepth) * 80.0); + float w01 = (1.0 - f.x) * f.y / (1e-3 + abs(d01 - centerDepth) * 80.0); + float w11 = f.x * f.y / (1e-3 + abs(d11 - centerDepth) * 80.0); + float wSum = w00 + w10 + w01 + w11; + if (wSum < 1e-6) { + float best = abs(d00 - centerDepth); + float3 bestV = v00; + float e10 = abs(d10 - centerDepth); + float e01 = abs(d01 - centerDepth); + float e11 = abs(d11 - centerDepth); + if (e10 < best) { best = e10; bestV = v10; } + if (e01 < best) { best = e01; bestV = v01; } + if (e11 < best) { bestV = v11; } + return bestV; + } + return (v00 * w00 + v10 * w10 + v01 * w01 + v11 * w11) / wSum; +} [numthreads(8, 8, 1)] void main(uint3 dispatchID : SV_DispatchThreadID) { uint2 pixel = dispatchID.xy; - if (pixel.x >= (uint)g_ScreenSize.x || pixel.y >= (uint)g_ScreenSize.y) + uint width = (uint)g_ScreenSize.x; + uint height = (uint)g_ScreenSize.y; + if (pixel.x >= width || pixel.y >= height) return; + float4 sceneIn = t_SceneColorIn.Load(int3(pixel, 0)); + float2 uv = (float2(pixel) + 0.5) / g_ScreenSize; float depth = t_Depth.Load(int3(pixel, 0)); - if (depth <= 0.0 || depth >= 0.9) { - u_SceneColor[pixel] = t_SceneColorIn.Load(int3(pixel, 0)); + float3 shafts = UpsampleHalfDepthAware(t_Sunshafts, max(float2(g_ShaftWidth, g_ShaftHeight), float2(1.0, 1.0)), uv, depth); + + if (depth <= 0.0 || depth >= 1.0) { + u_SceneColor[pixel] = float4(sceneIn.rgb + shafts, 1.0); return; } - float3 direct = t_DirectLighting.Load(int3(pixel, 0)).rgb; - - GIReservoir r = UnpackReservoir( - t_ReservoirA.Load(int3(pixel, 0)), - t_ReservoirB.Load(int3(pixel, 0)) - ); - - float3 indirect = 0; - if (IsReservoirValid(r) && r.W > 0) { - float2 giUV = (float2(pixel) + 0.5) / g_ScreenSize; - float4 giClip = float4(giUV.x * 2.0 - 1.0, 1.0 - giUV.y * 2.0, depth, 1.0); - float4 giWorld = mul(g_InvViewProj, giClip); - float3 worldPos = giWorld.xyz / giWorld.w; - float4 normalData = t_Normal.Load(int3(pixel, 0)); - float4 baseColorData = t_BaseColor.Load(int3(pixel, 0)); - - float3 N = normalize(normalData.xyz); - float roughness = abs(normalData.w); - float3 albedo = baseColorData.rgb; - float metallic = baseColorData.a; - - float3 wi = normalize(r.samplePos - worldPos); - float cosTheta = max(dot(N, wi), 0); - float3 F0 = CalculateF0(albedo, metallic); - float3 kD = (1.0 - F_Schlick(cosTheta, F0)) * (1.0 - metallic); - float3 brdfCos = kD * albedo / PI * cosTheta; - - indirect = r.Lo * brdfCos * r.W; - indirect = min(indirect, RESTIR_MAX_RADIANCE); - indirect *= g_GIIntensity; + if (IsWaterSurfMark(t_WorldPos.Load(int3(pixel, 0)).w)) { + u_SceneColor[pixel] = float4(sceneIn.rgb + shafts, 1.0); + return; + } + + if (g_DenoiseApply != 0) { + u_SceneColor[pixel] = float4(sceneIn.rgb + shafts, 1.0); + return; + } + + float2 giSize = max(float2(g_GiWidth, g_GiHeight), float2(1.0, 1.0)); + const bool fullGi = giSize.x >= g_ScreenSize.x && giSize.y >= g_ScreenSize.y; + float3 direct = 0; + if (g_AddDirect != 0) + direct = fullGi ? t_DirectLighting.Load(int3(pixel, 0)).rgb : UpsampleHalfDepthAware(t_DirectLighting, giSize, uv, depth); + float3 worldPos = ResolveGBufferWorldPos(uv, depth, t_WorldPos.Load(int3(pixel, 0)), g_InvViewProj); + + float3 indirect = (fullGi ? t_NoisyDiffuse.Load(int3(pixel, 0)).rgb : UpsampleHalfDepthAware(t_NoisyDiffuse, giSize, uv, depth)) * g_GIIntensity; + float3 specular = fullGi ? t_NoisySpecular.Load(int3(pixel, 0)).rgb : UpsampleHalfDepthAware(t_NoisySpecular, giSize, uv, depth); + + if (g_AddDirect != 0) + { + float dLum = max(Luminance(max(direct, 0.xxx)), 0.04); + float maxInd = max(dLum * 3.0 + 0.4, 4.0); + float iLum = Luminance(indirect); + if (iLum > maxInd) + indirect *= maxInd / iLum; } - float3 finalColor = direct + indirect; - u_SceneColor[pixel] = float4(finalColor, 1.0); + float3 ambient = 0; + if (g_UseDdgi != 0) + ambient = fullGi ? t_DDGIAmbient.Load(int3(pixel, 0)).rgb : UpsampleHalfDepthAware(t_DDGIAmbient, giSize, uv, depth); + float3 lighting = direct + indirect + specular + ambient + shafts; + if (any(isnan(lighting))) + lighting = max(indirect + specular + ambient + shafts, 0.xxx); + + float dist = length(worldPos - g_CameraPos.xyz); + float fog = saturate(dist * g_FogParams.w + g_FogParams.x); + float3 outRgb = max(sceneIn.rgb + lighting * (1.0 - fog), 0.xxx); + float farW = saturate((dist - 28.0) / 55.0); + if (g_FogColor.w > 0.001 && any(g_FogParams.yzw > 0) && farW > 0.001) { + float3 viewDir = normalize(worldPos - g_CameraPos.xyz); + float3 sunDir = normalize(-g_SunDir.xyz); + float3 Tatm, inscAtm; + AtmosphereAerial(viewDir, dist, sunDir, g_SunColor.rgb, SampleSkyIncident(float3(0.0, 1.0, 0.0), 4.0), g_FogColor.w * farW, Tatm, inscAtm); + outRgb = outRgb * Tatm + inscAtm; + } + if (any(isnan(outRgb))) + outRgb = sceneIn.rgb; + u_SceneColor[pixel] = float4(outRgb, sceneIn.a); } diff --git a/res/gamedata/shaders/r5/restir_gi_initial.cs b/res/gamedata/shaders/r5/restir_gi_initial.cs index 222cf91094f..e9708862105 100644 --- a/res/gamedata/shaders/r5/restir_gi_initial.cs +++ b/res/gamedata/shaders/r5/restir_gi_initial.cs @@ -1,7 +1,20 @@ #include "bindless_common.h" +#include "shared/terrain_blend.h" #include "rt_common.h" #include "shared/pbr_brdf.h" +#include "shared/clustered_lighting.h" +#include "shared/surface_marks.h" +#include "shared/nrd_helpers.h" +#include "shared/basecolor_pack.h" #include "restir_gi_common.h" +#include "restir_di_common.h" +#include "rt_irradiance_cache.h" +#include "rt_shade_hit.h" +#include "rt_grass_alpha.h" +#include "rt_material_alpha.h" +#include "rt_visibility.h" +#include "shared/foliage_sss.h" +#include "shared/skin_sss.h" cbuffer ReSTIRGIParams : register(b5) { float4x4 g_InvViewProj; @@ -9,6 +22,7 @@ float4 g_CameraPos; float4 g_SunDir_Intensity; float4 g_SunColor_SkyWeight; + float4 g_SkyColor; float2 g_ScreenSize; float g_GIIntensity; uint g_FrameIndex; @@ -17,7 +31,40 @@ uint g_SkinnedBatchStart; uint g_GrassBatchStart; uint g_DetailAtlasIndex; - uint3 g_Pad; + uint g_NumLights; + uint g_WetEnabled; + float g_WetStrength; + float4 g_ClusterParams; + float4 g_ClusterDepth; + float4 g_DISampleParams; + uint g_Bounces; + uint g_CacheSize; + float g_CacheCellSize; + uint g_CacheMaxAge; + uint g_GrassShadowEnabled; + uint g_PadA0; + uint g_PadA1; + uint g_PadA2; + float4x4 g_GrassShadowVP; + float4x4 g_WorldToView; + float4 g_HemiColor; + float g_LodDist; + float g_AmbientScale; + float g_SunAngular; + uint g_HudSkinnedStart; + uint g_ParticleBatchStart; + float g_FullWidth; + float g_FullHeight; + uint g_PadEnd2; + float4x4 g_PrevInvViewProj; + uint g_HasPrevSunVis; + float g_CurrJitterX; + float g_CurrJitterY; + float g_PrevJitterX; + float g_PrevJitterY; + float g_WindSpeed; + uint g_PadSun1; + uint g_PadSun2; }; RaytracingAccelerationStructure g_SceneTLAS : register(t1); @@ -33,22 +80,58 @@ Texture2D t_Depth : register(t14); Texture2D t_Normal : register(t15); Texture2D t_BaseColor : register(t16); +Texture2D t_WorldPos : register(t23); +Texture2D t_SceneColorIn : register(t24); +StructuredBuffer g_Lights : register(t17); +Texture2D t_WetAccum : register(t18); +Texture2D t_SkyOpen : register(t25); +Texture2D t_GrassShadow : register(t26); +ByteAddressBuffer g_ParticleVB : register(t27); +ByteAddressBuffer g_ParticleIB : register(t28); +Texture3D t_BlueNoise : register(t29); +Texture2D t_PrevSunVis : register(t30); +Texture2D t_MotionVectors : register(t31); +Texture2D t_PrevDepth : register(t32); +Texture2D t_PrevNormal : register(t33); +StructuredBuffer g_ClusterGrid : register(t19); +StructuredBuffer g_LightIndexList : register(t20); +StructuredBuffer g_DILightIndices : register(t21); +StructuredBuffer g_DILightCDF : register(t22); RWTexture2D u_DirectLighting : register(u0); -RWTexture2D u_ReservoirA : register(u1); -RWTexture2D u_ReservoirB : register(u2); - -static const uint MAX_SHADOW_SKIPS = 8; +RWStructuredBuffer u_Reservoir : register(u1); +RWTexture2D u_NoisyDiffuse : register(u2); +RWTexture2D u_NoisySpecular : register(u3); +RWTexture2D u_HitDistance : register(u4); +RWTexture2D u_DIReservoir : register(u5); +RWStructuredBuffer u_IrradianceCache : register(u6); +RWTexture2D u_SpecReservoirA : register(u7); +RWTexture2D u_SpecReservoirB : register(u8); +RWTexture2D u_SunVis : register(u9); + +bool IsParticleBatch(uint batchIdx) +{ + return g_ParticleBatchStart != 0xFFFFFFFFu && batchIdx >= g_ParticleBatchStart; +} bool IsSkinnedBatch(uint batchIdx) { - return g_SkinnedBatchStart > 0 && batchIdx >= g_SkinnedBatchStart && - !(g_GrassBatchStart > 0 && batchIdx >= g_GrassBatchStart); + return g_SkinnedBatchStart != 0xFFFFFFFFu && batchIdx >= g_SkinnedBatchStart && + (g_GrassBatchStart == 0xFFFFFFFFu || batchIdx < g_GrassBatchStart) && + !IsParticleBatch(batchIdx); +} + +bool IsHudSkinnedBatch(uint batchIdx) +{ + return g_HudSkinnedStart != 0xFFFFFFFFu && batchIdx >= g_HudSkinnedStart && + (g_GrassBatchStart == 0xFFFFFFFFu || batchIdx < g_GrassBatchStart) && + !IsParticleBatch(batchIdx); } bool IsGrassBatch(uint batchIdx) { - return g_GrassBatchStart > 0 && batchIdx >= g_GrassBatchStart; + return g_GrassBatchStart != 0xFFFFFFFFu && batchIdx >= g_GrassBatchStart && + !IsParticleBatch(batchIdx); } bool IsTerrainBatch(uint batchIdx) @@ -62,82 +145,195 @@ float3 SampleSky(float3 dir) float w = g_SunColor_SkyWeight.w; float3 s0 = g_Sky0.SampleLevel(smp_linear, dir, 0).rgb; float3 s1 = g_Sky1.SampleLevel(smp_linear, dir, 0).rgb; - return lerp(s0, s1, w); + return lerp(s0, s1, w) * g_SkyColor.rgb * 0.80; } -float TraceShadow(float3 origin, float3 sunDir) +float3 SampleSkyDiffuse(float3 dir) { - float atten = 1.0; - float3 shadowOrigin = origin; - - for (uint si = 0; si < MAX_SHADOW_SKIPS; si++) { - RayDesc ray; - ray.Origin = shadowOrigin; - ray.Direction = sunDir; - ray.TMin = 0.001; - ray.TMax = 10000.0; + float w = g_SunColor_SkyWeight.w; + float3 s0 = g_Sky0.SampleLevel(smp_linear, dir, 4.0).rgb; + float3 s1 = g_Sky1.SampleLevel(smp_linear, dir, 4.0).rgb; + return lerp(s0, s1, w) * g_SkyColor.rgb * 0.80; +} - RayQuery q; - q.TraceRayInline(g_SceneTLAS, RAY_FLAG_NONE, 0xFF, ray); - while (q.Proceed()) { - if (q.CandidateType() == CANDIDATE_NON_OPAQUE_TRIANGLE) { - uint candBatch = q.CandidateInstanceID() + q.CandidateGeometryIndex(); - if (IsGrassBatch(candBatch) && g_DetailAtlasIndex > 0) { - RTBatchInfo candInfo = g_BatchInfo[candBatch]; - float2 candUV = GetSkinnedHitUV(g_GrassVB, g_GrassIB, candInfo, - q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics()); - float4 texel = GetBindlessTexture(g_DetailAtlasIndex).SampleLevel(smp_linear, candUV, 0); - if (texel.a >= 0.3) - q.CommitNonOpaqueTriangleHit(); - } - } - } +float3 SampleSkySpec(float3 dir, float roughness) +{ + float mip = saturate(roughness) * 5.0; + float w = g_SunColor_SkyWeight.w; + float3 s0 = g_Sky0.SampleLevel(smp_linear, dir, mip).rgb; + float3 s1 = g_Sky1.SampleLevel(smp_linear, dir, mip).rgb; + return lerp(s0, s1, w) * g_SkyColor.rgb * 0.80; +} - if (q.CommittedStatus() != COMMITTED_TRIANGLE_HIT) - break; +float SampleGrassShadow(float3 worldPos) +{ + if (g_GrassShadowEnabled == 0) + return 1.0; + float4 shadowPos = mul(g_GrassShadowVP, float4(worldPos, 1.0)); + float3 shadowCoord = shadowPos.xyz / max(abs(shadowPos.w), 1e-6); + if (any(shadowCoord.xy < 0.0) || any(shadowCoord.xy > 1.0) || + shadowCoord.z < 0.0 || shadowCoord.z > 1.0) + return 1.0; + uint width, height; + t_GrassShadow.GetDimensions(width, height); + int2 texel = clamp(int2(shadowCoord.xy * float2(width, height)), int2(0, 0), int2(width, height) - 1); + float blockerDepth = t_GrassShadow.Load(int3(texel, 0)); + return shadowCoord.z <= blockerDepth + 0.0015 ? 1.0 : 0.0; +} - uint sBatchIdx = q.CommittedInstanceID() + q.CommittedGeometryIndex(); - RTBatchInfo sInfo = g_BatchInfo[sBatchIdx]; +float TraceSoftShadowSun(float3 origin, float3 sunDir, float viewDist, uint2 pixel, float skyOpen) +{ + uint mask = RT_MASK_SHADOW; + float2 giSize = max(g_ScreenSize, 1.0); + float2 fullSize = max(float2(g_FullWidth, g_FullHeight), 1.0); + float2 jGi = float2(g_CurrJitterX, -g_CurrJitterY) * (giSize / fullSize); + uint2 seedPx = uint2(clamp(int2(pixel) - int2(round(jGi)), int2(0, 0), int2(giSize) - 1)); + float u0 = SampleSTBN(t_BlueNoise, seedPx, 0, 0); + float u1 = SampleSTBN(t_BlueNoise, seedPx, 0, 1); + float contact = saturate(viewDist / 16.0); + float radius = clamp(g_SunAngular, 0.001, 0.05) * lerp(0.2, 1.0, contact); + float ang = u1 * 6.2831853; + float r = sqrt(u0) * radius; + float3 up = abs(sunDir.y) < 0.99 ? float3(0, 1, 0) : float3(1, 0, 0); + float3 tangent = normalize(cross(up, sunDir)); + float3 bitangent = cross(sunDir, tangent); + float3 dir = normalize(sunDir + tangent * (cos(ang) * r) + bitangent * (sin(ang) * r)); + return EvaluateSunVisibilityWithGrass( + g_SceneTLAS, g_BatchInfo, g_MegaVB, g_MegaIB, g_GrassVB, g_GrassIB, + origin, dir, 10000.0, + g_IdentityStaticCount, g_TerrainBatchCount, g_SkinnedBatchStart, g_GrassBatchStart, + g_ParticleBatchStart, g_DetailAtlasIndex, g_HudSkinnedStart, + t_BlueNoise, seedPx, 0, mask); +} - if (IsGrassBatch(sBatchIdx)) { - if (g_DetailAtlasIndex > 0) { atten = 0; break; } - atten *= 0.5; - shadowOrigin = shadowOrigin + sunDir * (q.CommittedRayT() + 0.002); - continue; +float FilterSunVisibility(float rawVis, uint2 pixel, float2 giSize, float2 fullSize, float depth, float3 N, float3 worldPos, bool charSurf) +{ + float vis = rawVis; + float wSum = 1.0; + if (g_HasPrevSunVis == 0 || charSurf) + return rawVis; + int2 fullPx = RestirFullPixel(pixel, giSize, fullSize); + float2 uv = (float2(pixel) + 0.5) / giSize; + float2 motion = t_MotionVectors.Load(int3(fullPx, 0)); + float motionPx = length(motion * giSize); + bool still = motionPx < 0.4; + float2 prevUV = uv + motion; + float viewDist = length(worldPos - g_CameraPos.xyz); + float histW = still ? 16.0 : lerp(8.0, 2.0, saturate(motionPx / 4.0)); + histW *= saturate(1.0 - g_WindSpeed * 0.08); + bool histOk = !any(prevUV < 0.0) && !any(prevUV >= 1.0); + int2 prevPixel = clamp(int2(round(prevUV * giSize)), int2(0, 0), int2(giSize) - 1); + int2 prevFull = clamp(int2(round(prevUV * fullSize)), int2(0, 0), int2(fullSize) - 1); + float prevDepth = histOk ? t_PrevDepth.Load(int3(prevFull, 0)) : 0.0; + if (!histOk || prevDepth <= 0.0 || prevDepth >= 1.0) + histW = still ? 8.0 : 1.0; + else { + float3 prevN = normalize(t_PrevNormal.Load(int3(prevFull, 0)).xyz); + float2 prevNdcUV = (float2(prevFull) + 0.5) / fullSize; + float3 prevWorld = ReconstructWorldPosReverseZ(prevNdcUV, prevDepth, g_PrevInvViewProj); + float skyOpenC = saturate(RestirLoadTex1(t_SkyOpen, pixel, giSize, fullSize)); + float skyOpenP = saturate(t_SkyOpen.Load(int3(prevFull, 0))); + if (abs(skyOpenC - skyOpenP) > 0.25) { + histW = 0.0; + still = false; } - - MaterialData sMat = g_Materials[sInfo.materialID]; - - if (sMat.flags & MAT_FLAG_WATER) { - atten *= 0.85; - shadowOrigin = shadowOrigin + sunDir * (q.CommittedRayT() + 0.002); - continue; + else if (length(worldPos - prevWorld) >= 0.08 * max(viewDist, 1.0) || dot(N, prevN) < 0.94) + histW = still ? 8.0 : 1.0; + } + vis += t_PrevSunVis.Load(int3(prevPixel, 0)) * histW; + wSum += histW; + if (still) { + float harden = saturate(viewDist / max(g_LodDist, 1.0)); + float rad = lerp(1.0, 2.0, harden); + const int2 baseOff[4] = { int2(-1, -1), int2(1, -1), int2(-1, 1), int2(1, 1) }; + [unroll] for (uint i = 0; i < 4u; i++) + { + int2 np = int2(pixel) + int2(round(float2(baseOff[i]) * rad)); + if (np.x < 0 || np.y < 0 || np.x >= (int)giSize.x || np.y >= (int)giSize.y) + continue; + int2 nf = clamp(int2((float2(np) + 0.5) / giSize * fullSize), int2(0, 0), int2(fullSize) - 1); + float nd = t_PrevDepth.Load(int3(nf, 0)); + if (nd <= 0.0 || nd >= 1.0) + continue; + if (abs(nd - prevDepth) / max(prevDepth, 1e-4) > 0.06) + continue; + float3 nN = normalize(t_PrevNormal.Load(int3(nf, 0)).xyz); + if (dot(N, nN) < 0.94) + continue; + vis += t_PrevSunVis.Load(int3(np, 0)); + wSum += 1.0; } + } + return vis / wSum; +} - if (IsTerrainBatch(sBatchIdx) || IsSkinnedBatch(sBatchIdx)) { atten = 0; break; } +float3 ImportanceSampleGGXDir(float2 Xi, float3 N, float roughness) +{ + float a = max(roughness, 0.04) * max(roughness, 0.04); + float phi = 2.0 * PI * Xi.x; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a * a - 1.0) * Xi.y)); + float sinTheta = sqrt(max(1.0 - cosTheta * cosTheta, 0.0)); + float3 H = float3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta); + float3 up = abs(N.z) < 0.999 ? float3(0, 0, 1) : float3(1, 0, 0); + float3 tangent = normalize(cross(up, N)); + float3 bitangent = cross(N, tangent); + return normalize(tangent * H.x + bitangent * H.y + N * H.z); +} - float2 sUV; - if (IsSkinnedBatch(sBatchIdx)) - sUV = GetSkinnedHitUV(g_SkinnedVB, g_SkinnedIB, sInfo, q.CommittedPrimitiveIndex(), q.CommittedTriangleBarycentrics()); - else - sUV = GetHitUV(g_MegaVB, g_MegaIB, sInfo, q.CommittedPrimitiveIndex(), q.CommittedTriangleBarycentrics()); +float TraceShadowRay(float3 origin, float3 dir, float tMax) +{ + return TraceVisibilityAtten( + g_SceneTLAS, g_BatchInfo, g_MegaVB, g_MegaIB, g_GrassVB, g_GrassIB, + g_ParticleVB, g_ParticleIB, + origin, dir, max(tMax, 0.001), RT_MASK_SHADOW, + g_IdentityStaticCount, g_TerrainBatchCount, g_SkinnedBatchStart, g_GrassBatchStart, + g_ParticleBatchStart, g_DetailAtlasIndex, false, 0.0, g_HudSkinnedStart, + t_BlueNoise, uint2(0, 0), g_FrameIndex); +} - float4 sDiffuse = SampleDiffuseLevel(sMat, sUV); +float TraceBounceSunVis(float3 origin, float3 dir, float tMax, uint2 pixel) +{ + return EvaluateSunVisibilityWithGrass( + g_SceneTLAS, g_BatchInfo, g_MegaVB, g_MegaIB, g_GrassVB, g_GrassIB, + origin, dir, max(tMax, 0.001), + g_IdentityStaticCount, g_TerrainBatchCount, g_SkinnedBatchStart, g_GrassBatchStart, + g_ParticleBatchStart, g_DetailAtlasIndex, g_HudSkinnedStart, + t_BlueNoise, pixel, g_FrameIndex, RT_MASK_SHADOW); +} - if ((sMat.flags & MAT_FLAG_ALPHA_TEST) && sDiffuse.a < sMat.alphaRef) { - shadowOrigin = shadowOrigin + sunDir * (q.CommittedRayT() + 0.002); - continue; - } - if ((sMat.flags & MAT_FLAG_ALPHA_BLEND) && sDiffuse.a < 0.5) { - atten *= (1.0 - sDiffuse.a); - shadowOrigin = shadowOrigin + sunDir * (q.CommittedRayT() + 0.002); - continue; - } +float TraceVegSkyVis(float3 origin, uint2 pixel) +{ + float2 giSize = max(g_ScreenSize, 1.0); + float2 fullSize = max(float2(g_FullWidth, g_FullHeight), 1.0); + float2 jGi = float2(g_CurrJitterX, -g_CurrJitterY) * (giSize / fullSize); + uint2 seedPx = uint2(clamp(int2(pixel) - int2(round(jGi)), int2(0, 0), int2(giSize) - 1)); + return EvaluateSunVisibilityWithGrass( + g_SceneTLAS, g_BatchInfo, g_MegaVB, g_MegaIB, g_GrassVB, g_GrassIB, + origin, float3(0.0, 1.0, 0.0), 10000.0, + g_IdentityStaticCount, g_TerrainBatchCount, g_SkinnedBatchStart, g_GrassBatchStart, + g_ParticleBatchStart, g_DetailAtlasIndex, g_HudSkinnedStart, + t_BlueNoise, seedPx, 0, RT_MASK_SHADOW_MAPPED); +} - atten = 0; - break; - } - return atten; +float4 SampleTerrainTexture(uint index, float2 uv) +{ + if (index == INVALID_TEXTURE_INDEX) + return float4(0.5, 0.5, 0.5, 1.0); + return GetBindlessTexture(index).SampleLevel(smp_linear, uv, 0); +} + +float3 SampleTerrainAlbedo(TerrainMaterialData mat, float2 uv) +{ + float2 baseUV = uv; + float2 detailUV = uv * mat.detailScale; + float4 baseSample = SampleTerrainTexture(mat.baseAlbedoIndex, baseUV); + float4 mask = TerrainNormalizeMask(SampleTerrainTexture(mat.blendMaskIndex, baseUV)); + float4 detailR = SampleTerrainTexture(mat.detailR_Index, detailUV); + float4 detailG = SampleTerrainTexture(mat.detailG_Index, detailUV); + float4 detailB = SampleTerrainTexture(mat.detailB_Index, detailUV); + float4 detailA = SampleTerrainTexture(mat.detailA_Index, detailUV); + float3 blendedDetail = TerrainBlendRGB(detailR.rgb, detailG.rgb, detailB.rgb, detailA.rgb, mask); + return baseSample.rgb * blendedDetail * 2.0; } struct BounceHit { @@ -145,16 +341,24 @@ struct BounceHit { float3 normal; float3 geoNormal; float3 albedo; + float3 baked; + float3 emissive; float metallic; float roughness; + float sunOcc; float t; bool valid; + bool isWater; }; -BounceHit TraceBounce(float3 origin, float3 direction, inout uint rng) +BounceHit TraceBounce(float3 origin, float3 direction) { BounceHit result; result.valid = false; + result.isWater = false; + result.baked = 0; + result.emissive = 0; + result.sunOcc = 1.0; float3 rayOrigin = origin; for (uint skip = 0; skip < 4; skip++) { @@ -165,17 +369,26 @@ BounceHit TraceBounce(float3 origin, float3 direction, inout uint rng) ray.TMax = 10000.0; RayQuery q; - q.TraceRayInline(g_SceneTLAS, RAY_FLAG_NONE, 0xFF, ray); + q.TraceRayInline(g_SceneTLAS, RAY_FLAG_NONE, RT_MASK_GI, ray); while (q.Proceed()) { if (q.CandidateType() == CANDIDATE_NON_OPAQUE_TRIANGLE) { uint candBatch = q.CandidateInstanceID() + q.CandidateGeometryIndex(); - if (IsGrassBatch(candBatch) && g_DetailAtlasIndex > 0) { - RTBatchInfo candInfo = g_BatchInfo[candBatch]; - float2 candUV = GetSkinnedHitUV(g_GrassVB, g_GrassIB, candInfo, - q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics()); - float4 texel = GetBindlessTexture(g_DetailAtlasIndex).SampleLevel(smp_linear, candUV, 0); - if (texel.a >= 0.3) + if (IsParticleBatch(candBatch)) + continue; + if (IsGrassBatch(candBatch)) { + if (GrassTexelOpaque(g_GrassVB, g_GrassIB, g_BatchInfo, candBatch, + q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics(), + g_DetailAtlasIndex)) q.CommitNonOpaqueTriangleHit(); + } else if (IsSkinnedBatch(candBatch)) { + if (SkinnedMaterialOpaque(g_SkinnedVB, g_SkinnedIB, g_BatchInfo, candBatch, + q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics())) + q.CommitNonOpaqueTriangleHit(); + } else if (MegaMaterialOpaque(g_MegaVB, g_MegaIB, g_BatchInfo, candBatch, + q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics()) || + MegaEmissiveHit(g_MegaVB, g_MegaIB, g_BatchInfo, candBatch, + q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics())) { + q.CommitNonOpaqueTriangleHit(); } } } @@ -184,6 +397,10 @@ BounceHit TraceBounce(float3 origin, float3 direction, inout uint rng) return result; uint batchIdx = q.CommittedInstanceID() + q.CommittedGeometryIndex(); + if (IsParticleBatch(batchIdx)) { + rayOrigin = rayOrigin + direction * (q.CommittedRayT() + 0.002); + continue; + } RTBatchInfo info = g_BatchInfo[batchIdx]; uint primIdx = q.CommittedPrimitiveIndex(); float2 bary = q.CommittedTriangleBarycentrics(); @@ -191,6 +408,8 @@ BounceHit TraceBounce(float3 origin, float3 direction, inout uint rng) float3 hitN, geoN; float2 hitUV; + float hemi = 0.55; + float2 lmUV = 0; if (IsGrassBatch(batchIdx)) { hitUV = GetSkinnedHitUV(g_GrassVB, g_GrassIB, info, primIdx, bary); hitN = GetSkinnedHitNormal(g_GrassVB, g_GrassIB, info, primIdx, bary); @@ -203,6 +422,8 @@ BounceHit TraceBounce(float3 origin, float3 direction, inout uint rng) hitUV = GetHitUV(g_MegaVB, g_MegaIB, info, primIdx, bary); hitN = TransformNormalToWorld(GetHitNormal(g_MegaVB, g_MegaIB, info, primIdx, bary), objectToWorld); geoN = TransformNormalToWorld(GetHitGeometricNormal(g_MegaVB, g_MegaIB, info, primIdx), objectToWorld); + hemi = GetHitHemi(g_MegaVB, g_MegaIB, info, primIdx, bary); + lmUV = GetHitLightmapUV(g_MegaVB, g_MegaIB, info, primIdx, bary); } if (dot(geoN, direction) > 0) geoN = -geoN; @@ -211,162 +432,505 @@ BounceHit TraceBounce(float3 origin, float3 direction, inout uint rng) float3 albedo = float3(0.5, 0.5, 0.5); float metallic = 0; float roughness = 1.0; + bool water = false; + float3 baked = 0; + float sunOcc = 1.0; if (IsGrassBatch(batchIdx)) { - if (g_DetailAtlasIndex > 0) { - float4 texel = GetBindlessTexture(g_DetailAtlasIndex).SampleLevel(smp_linear, hitUV, 0); - albedo = texel.rgb; - if (texel.a < 0.3) { - rayOrigin = rayOrigin + direction * (q.CommittedRayT() + 0.002); - continue; - } - } else { + if (g_DetailAtlasIndex > 0) + albedo = GetBindlessTexture(g_DetailAtlasIndex).SampleLevel(smp_linear, hitUV, 0).rgb; + else albedo = lerp(float3(0.08, 0.18, 0.03), float3(0.15, 0.35, 0.06), 1.0 - hitUV.y); - } + baked = ShadeBakedFromHemi(0.55, albedo, g_HemiColor.rgb); } else if (IsTerrainBatch(batchIdx)) { TerrainMaterialData tmat = g_TerrainMaterials[info.materialID]; - albedo = SampleTerrainAlbedo(tmat, hitUV); + float hitDist = q.CommittedRayT(); + if (hitDist > g_LodDist * 0.5) + albedo = SampleTerrainTexture(tmat.baseAlbedoIndex, hitUV).rgb; + else + albedo = SampleTerrainAlbedo(tmat, hitUV); + baked = ShadeBakedFromTerrainLmap(tmat, lmUV, albedo, g_HemiColor.rgb, hemi); } else { MaterialData mat = g_Materials[info.materialID]; float4 diffuse = SampleDiffuseLevel(mat, hitUV); albedo = diffuse.rgb; + water = (mat.flags & MAT_FLAG_WATER) != 0; + bool emHit = EmissiveTexelLit(mat, diffuse); - if ((mat.flags & MAT_FLAG_ALPHA_TEST) && diffuse.a < mat.alphaRef) { + if (!water && !emHit && !MaterialDiffuseOpaque(mat, diffuse)) { rayOrigin = rayOrigin + direction * (q.CommittedRayT() + 0.002); continue; } - if (mat.flags & MAT_FLAG_HAS_PBR) { + if ((mat.flags & MAT_FLAG_HAS_PBR) != 0) { float3 pbr = SamplePBR(mat, hitUV); metallic = pbr.r; roughness = pbr.g; } + if (water) { + rayOrigin = rayOrigin + direction * (q.CommittedRayT() + 0.002); + continue; + } + baked = ShadeBakedFromHemi(hemi, albedo, g_HemiColor.rgb); + if ((mat.flags & MAT_FLAG_HAS_LMAP) != 0 && mat.lmapIndex != INVALID_TEXTURE_INDEX + && dot(lmUV, lmUV) > 1e-8) + { + float4 lmh = GetBindlessTexture(mat.lmapIndex).SampleLevel(smp_rtlinear, lmUV, 0); + sunOcc = smoothstep(0.04, 0.96, saturate(lmh.g)); + baked = ShadeBakedFromHemi(max(hemi, lmh.a), albedo, g_HemiColor.rgb) * sunOcc; + } + else + { + baked *= sunOcc; + } + if (emHit && mat.emissiveIntensity > 0.0) + result.emissive = GlowEmissiveRgb(diffuse, mat.emissiveIntensity); } result.position = rayOrigin + direction * q.CommittedRayT(); result.normal = hitN; result.geoNormal = geoN; result.albedo = albedo; + result.baked = baked; + if (IsGrassBatch(batchIdx) || IsTerrainBatch(batchIdx)) + result.emissive = 0; result.metallic = metallic; result.roughness = roughness; + result.sunOcc = sunOcc; result.t = q.CommittedRayT(); result.valid = true; + result.isWater = water; return result; } return result; } -float4 SampleTerrainTexture(uint index, float2 uv) +float3 EvaluateLocalLight(GPULightData light, float3 worldPos, float3 N, float3 albedo, float metallic, float roughness, float3 V) { - if (index == INVALID_TEXTURE_INDEX) - return float4(0.5, 0.5, 0.5, 1.0); - return GetBindlessTexture(index).SampleLevel(smp_linear, uv, 0); + float3 lightPos = light.positionAndInvRangeSq.xyz; + float invRangeSq = abs(light.positionAndInvRangeSq.w); + float3 toLight = lightPos - worldPos; + float distSq = dot(toLight, toLight); + float dist = sqrt(max(distSq, 1e-8)); + float3 L = toLight / dist; + float NdotL = max(dot(N, L), 0.0); + if (NdotL <= 0.0) + return 0; + + float atten = PointLightAttenuation(distSq, invRangeSq, 0.1225); + if (light.spotParamsAndType.y > 0.5) { + atten *= SpotLightAttenuation(toLight, light.directionAndSpotScale.xyz, + light.directionAndSpotScale.w, light.spotParamsAndType.x); + } + if (atten <= 1e-5) + return 0; + + float3 radiance = light.colorAndRange.xyz * atten; + float3 F0 = CalculateF0(albedo, metallic); + float3 H = normalize(V + L); + float NdotV = max(dot(N, V), 0.0); + float NdotH = max(dot(N, H), 0.0); + float VdotH = max(dot(V, H), 0.0); + float r = max(roughness, 0.04); + float D = D_GGX(NdotH, r); + float G = G_Smith(NdotV, NdotL, r); + float3 F = F_Schlick(VdotH, F0); + float3 spec = D * G * F / max(4.0 * NdotV * NdotL, 1e-4); + float3 kD = (1.0 - F) * (1.0 - metallic); + return (kD * albedo / PI + spec) * radiance * NdotL; } -float3 SampleTerrainAlbedo(TerrainMaterialData mat, float2 uv) +float3 ShadeLocalLightRT( + GPULightData light, float3 worldPos, float3 biasedPos, float3 N, float3 V, + float3 albedo, float metallic, float roughness) { - float2 baseUV = uv; - float2 detailUV = uv * mat.detailScale; - float4 baseSample = SampleTerrainTexture(mat.baseAlbedoIndex, baseUV); - float4 mask = SampleTerrainTexture(mat.blendMaskIndex, baseUV); - float maskSum = dot(mask, float4(1, 1, 1, 1)); - mask = maskSum > 0.001 ? mask / maskSum : float4(0.25, 0.25, 0.25, 0.25); - float3 detailR = SampleTerrainTexture(mat.detailR_Index, detailUV).rgb; - float3 detailG = SampleTerrainTexture(mat.detailG_Index, detailUV).rgb; - float3 detailB = SampleTerrainTexture(mat.detailB_Index, detailUV).rgb; - float3 detailA = SampleTerrainTexture(mat.detailA_Index, detailUV).rgb; - float3 blendedDetail = detailR * mask.r + detailG * mask.g + detailB * mask.b + detailA * mask.a; - return baseSample.rgb * blendedDetail * 2.0; + float3 lit = EvaluateLocalLight(light, worldPos, N, albedo, metallic, roughness, V); + if (Luminance(lit) <= 1e-6) + return 0; + float3 lightPos = light.positionAndInvRangeSq.xyz; + float3 toLight = lightPos - biasedPos; + float dist = length(toLight); + if (dist < 1e-4) + return 0; + float3 L = toLight / dist; + float shadowL = TraceShadowRay(biasedPos, L, dist * 0.998); + return min(lit * shadowL, RESTIR_MAX_RADIANCE); +} + +float3 ShadeLocalLightUnshadowed( + GPULightData light, float3 worldPos, float3 N, float3 V, + float3 albedo, float metallic, float roughness) +{ + return min(EvaluateLocalLight(light, worldPos, N, albedo, metallic, roughness, V), RESTIR_MAX_RADIANCE); +} + +float3 EvaluateSecondaryLo( + BounceHit h, + float3 primaryPos, + float3 sunDir, + float3 sunColor, + float primaryViewDist, + float primaryOutdoor, + uint2 pixel, + inout uint rngState) +{ + if (Luminance(h.albedo) < 1e-4 && any(h.emissive > 0)) + return min(h.emissive, RESTIR_MAX_RADIANCE); + + float3 hitBiased = h.position + h.geoNormal * 0.005; + float3 hitV = normalize(primaryPos - h.position); + float hitShadow = TraceBounceSunVis(hitBiased, sunDir, 10000.0, pixel) * h.sunOcc; + float3 Lo = ShadeHitDirect(h.albedo, h.normal, hitV, h.metallic, h.roughness, sunDir, sunColor, hitShadow, h.baked); + Lo += min(h.emissive, RESTIR_MAX_RADIANCE); + float farLod = saturate(primaryViewDist / max(g_LodDist, 1.0)); + if (g_Bounces >= 2u && farLod < 0.45 && rand_float(rngState) < 0.75) { + float2 u2 = float2(rand_float(rngState), rand_float(rngState)); + float3 dir2 = cosine_weighted_hemisphere(u2, h.normal); + BounceHit h2 = TraceBounce(hitBiased, dir2); + if (h2.valid) { + float3 hit2Biased = h2.position + h2.geoNormal * 0.005; + float3 hit2V = normalize(h.position - h2.position); + float sh2 = TraceBounceSunVis(hit2Biased, sunDir, 10000.0, pixel) * h2.sunOcc; + float3 Lo2 = ShadeHitDirect(h2.albedo, h2.normal, hit2V, h2.metallic, h2.roughness, sunDir, sunColor, sh2, h2.baked); + Lo2 += min(h2.emissive, RESTIR_MAX_RADIANCE); + Lo += Lo2 * h2.albedo * (1.0 / 0.75) * 0.55; + } else { + Lo += SampleSkyDiffuse(dir2) * (1.0 / 0.75) * 0.7 * saturate(primaryOutdoor); + } + } + return min(Lo, RESTIR_MAX_RADIANCE); +} + +uint SampleDILightIS(float u, out float lightPdf) +{ + uint count = (uint)g_DISampleParams.x; + float powerSum = g_DISampleParams.y; + lightPdf = 0; + if (count == 0 || powerSum <= 1e-8) + return 0; + float target = u * powerSum; + uint lo = 0; + uint hi = count; + while (lo < hi) { + uint mid = (lo + hi) >> 1; + if (g_DILightCDF[mid] < target) + lo = mid + 1; + else + hi = mid; + } + uint i = min(lo, count - 1); + float prev = (i == 0) ? 0.0 : g_DILightCDF[i - 1]; + float w = max(g_DILightCDF[i] - prev, 1e-8); + lightPdf = w / powerSum; + return g_DILightIndices[i]; } [numthreads(8, 8, 1)] void main(uint3 dispatchID : SV_DispatchThreadID) { uint2 pixel = dispatchID.xy; - if (pixel.x >= (uint)g_ScreenSize.x || pixel.y >= (uint)g_ScreenSize.y) + uint width = (uint)g_ScreenSize.x; + uint height = (uint)g_ScreenSize.y; + if (pixel.x >= width || pixel.y >= height) + return; + + float2 giSize = g_ScreenSize; + float2 fullSize = float2(g_FullWidth, g_FullHeight); + if (fullSize.x < 1.0 || fullSize.y < 1.0) { + uint fw = 0, fh = 0; + t_Depth.GetDimensions(fw, fh); + fullSize = float2(max(fw, 1u), max(fh, 1u)); + } + int2 fullPx = RestirFullPixel(pixel, giSize, fullSize); + + uint pixelIdx = pixel.y * width + pixel.x; + + float depth = RestirLoadDepth(t_Depth, pixel, giSize, fullSize); + if (depth <= 0.0 || depth >= 1.0) { + u_DirectLighting[pixel] = 0; + u_Reservoir[pixelIdx] = 0; + u_NoisyDiffuse[pixel] = 0; + u_NoisySpecular[pixel] = 0; + u_HitDistance[pixel] = 0; + u_DIReservoir[pixel] = PackDIReservoir(EmptyDIReservoir()); + u_SpecReservoirA[pixel] = 0; + u_SpecReservoirB[pixel] = 0; + u_SunVis[pixel] = 0; return; + } - float depth = t_Depth.Load(int3(pixel, 0)); - if (depth <= 0.0 || depth >= 0.9) { + float4 worldPosMark = RestirLoadTex4(t_WorldPos, pixel, giSize, fullSize); + float4 baseColorData = RestirLoadTex4(t_BaseColor, pixel, giSize, fullSize); + float surfMark = SurfMarkFromGBuffer(worldPosMark.w, baseColorData.a); + const bool isHud = IsHudSurfMark(surfMark); + const bool isVeg = IsVegSurfMark(surfMark); + if (IsWaterSurfMark(surfMark) && !isHud) { u_DirectLighting[pixel] = 0; - u_ReservoirA[pixel] = 0; - u_ReservoirB[pixel] = 0; + u_Reservoir[pixelIdx] = 0; + u_NoisyDiffuse[pixel] = 0; + u_NoisySpecular[pixel] = 0; + u_HitDistance[pixel] = 0; + u_DIReservoir[pixel] = PackDIReservoir(EmptyDIReservoir()); + u_SpecReservoirA[pixel] = 0; + u_SpecReservoirB[pixel] = 0; + u_SunVis[pixel] = 0; return; } - float2 giUV = (float2(pixel) + 0.5) / g_ScreenSize; - float4 giClip = float4(giUV.x * 2.0 - 1.0, 1.0 - giUV.y * 2.0, depth, 1.0); - float4 giWorld = mul(g_InvViewProj, giClip); - float3 worldPos = giWorld.xyz / giWorld.w; - float4 normalData = t_Normal.Load(int3(pixel, 0)); - float4 baseColorData = t_BaseColor.Load(int3(pixel, 0)); + float2 uv = (float2(pixel) + 0.5) / giSize; + float3 worldPos = ResolveGBufferWorldPos(uv, depth, worldPosMark, g_InvViewProj); + float4 normalData = RestirLoadTex4(t_Normal, pixel, giSize, fullSize); float3 N = normalize(normalData.xyz); - float roughness = abs(normalData.w); + float roughness = max(abs(normalData.w), MIN_ROUGHNESS); float3 albedo = baseColorData.rgb; - float metallic = baseColorData.a; + float sssMask = 0.0; + float metallic = UnpackGBufferMetallic( + baseColorData.a, isVeg || isHud || IsCharSurfMark(surfMark), sssMask); + + float wet = 0; + if (g_WetEnabled != 0 && !isHud && !IsCharSurfMark(surfMark)) + wet = saturate(RestirLoadTex1(t_WetAccum, pixel, giSize, fullSize) * g_WetStrength); + if (wet > 0) { + albedo = lerp(albedo, albedo * 0.35, wet); + roughness = lerp(roughness, max(roughness * 0.25, 0.02), wet); + metallic = lerp(metallic, min(metallic + 0.15 * wet, 1.0), wet * 0.5); + } + float3 V = normalize(g_CameraPos.xyz - worldPos); float3 sunDir = normalize(-g_SunDir_Intensity.xyz); float sunIntensity = g_SunDir_Intensity.w; float3 sunColor = g_SunColor_SkyWeight.xyz * sunIntensity; + float3 biasedPos = worldPos + N * (isHud ? 0.03 : 0.01); + float viewDist = length(worldPos - g_CameraPos.xyz); - float3 biasedPos = worldPos + N * 0.01; - - // === DIRECT LIGHTING (shadow ray to sun) === - float shadow = TraceShadow(biasedPos, sunDir); + uint rng = pcg_hash(pixel.x + pixel.y * 1973u + g_FrameIndex * 26699u); + uint rngDI = pcg_hash(pixel.x + pixel.y * 1973u + g_FrameIndex * 9176u + 3343u); + float bakedSunOcc = saturate(RestirLoadTex4(t_SceneColorIn, pixel, giSize, fullSize).a); + float skyOpen = saturate(RestirLoadTex1(t_SkyOpen, pixel, giSize, fullSize)); + float outdoor = (bakedSunOcc < 0.97) ? bakedSunOcc : skyOpen; + float rawSunVis = TraceSoftShadowSun(biasedPos, sunDir, viewDist, pixel, skyOpen); + rawSunVis *= SampleGrassShadow(biasedPos); + float filteredSunVis = FilterSunVisibility( + rawSunVis, pixel, giSize, fullSize, depth, N, worldPos, IsCharSurfMark(surfMark)); + u_SunVis[pixel] = filteredSunVis; + float shadow = filteredSunVis; float3 direct = 0; + float3 noisyDiff = 0; + float3 noisySpec = 0; + float hitDist = 0; + if (shadow > 0.001) { - float NdotL = max(0.0, dot(N, sunDir)); - direct = albedo * NdotL * sunColor * shadow; + float3 Ns = N; + if (isVeg && dot(N, sunDir) < 0.0) + Ns = -N; + float3 sunRadiance = sunColor * (isVeg ? 1.55 : 1.35) * shadow; + direct += PBRDirectLighting(albedo, Ns, V, sunDir, sunRadiance, metallic, roughness, 1u); + if (isVeg && sssMask > 0.01) { + float sssThickness = saturate(0.35 + sssMask * 0.3); + direct += EvaluateFoliageSSS( + albedo, Ns, V, sunDir, sunColor * 1.35, shadow, + LeafSSSTint(), sssThickness, sssMask); + } } - // === INDIRECT LIGHTING (1 cosine bounce + NEE) === - uint rng = pcg_hash(pixel.x + pixel.y * 1973u + g_FrameIndex * 26699u); - - float2 u = float2(rand_float(rng), rand_float(rng)); - float3 bounceDir = cosine_weighted_hemisphere(u, N); - float cosPDF = max(dot(bounceDir, N), 0) / PI; - - BounceHit hit = TraceBounce(biasedPos, bounceDir, rng); - GIReservoir reservoir = EmptyReservoir(); - - if (!hit.valid) { - direct += albedo * SampleSky(bounceDir); + GIReservoir specReservoir = EmptyReservoir(); + DIReservoir diRes = EmptyDIReservoir(); + + float linearDepth = max(abs(mul(g_WorldToView, float4(worldPos, 1.0)).z), 0.01); + uint clusterIdx = GetClusterIndex(float2(fullPx) + 0.5, linearDepth, g_ClusterParams.xyz, g_ClusterDepth); + uint2 clusterData = g_ClusterGrid[clusterIdx]; + uint lightOffset = clusterData.x; + uint lightCount = min(clusterData.y, RESTIR_MAX_LIGHTS_PER_TILE); + uint clusterSamples = min(lightCount, RESTIR_MAX_CLUSTER_LIGHTS); + for (uint ci = 0; ci < clusterSamples; ci++) { + uint lightId = g_LightIndexList[lightOffset + ci]; + if (lightId >= g_NumLights) + continue; + float3 lit = ShadeLocalLightUnshadowed(g_Lights[lightId], worldPos, N, V, + albedo, metallic, roughness); + float pdf = Luminance(lit); + if (pdf > 0) + DIReservoirUpdate(diRes, pdf, lightId, pdf, rngDI); } - u_DirectLighting[pixel] = float4(direct, 1.0); + uint diCount = (uint)g_DISampleParams.x; + uint diCandidates = min((uint)g_DISampleParams.z, RESTIR_MAX_LOCAL_LIGHT_SAMPLES); + for (uint li = 0; li < diCandidates; li++) { + if (diCount == 0) + break; + float lightPdf = 0; + uint lightId = SampleDILightIS(rand_float(rngDI), lightPdf); + if (lightPdf <= 1e-8 || lightId >= g_NumLights) + continue; + float3 lit = ShadeLocalLightUnshadowed(g_Lights[lightId], worldPos, N, V, + albedo, metallic, roughness); + float pdf = Luminance(lit); + if (pdf > 0 && lightPdf > 1e-8) + DIReservoirUpdate(diRes, pdf / max(lightPdf, 1e-8), lightId, pdf, rngDI); + } - if (hit.valid) { - float3 hitBiased = hit.position + hit.geoNormal * 0.005; - float3 hitV = normalize(worldPos - hit.position); + float3 F0a = CalculateF0(albedo, metallic); + if (isVeg && skyOpen > 0.01) { + float skyVis = TraceVegSkyVis(biasedPos + float3(0.0, 0.02, 0.0), pixel); + if (skyVis > 0.001) { + float wrap = saturate(abs(N.y) * 0.35 + 0.65); + float3 LoSky = SampleSkyDiffuse(float3(0.0, 1.0, 0.0)) * skyVis; + float3 kD = (1.0 - F_Schlick(wrap, F0a)) * (1.0 - metallic); + direct += min(LoSky * kD * albedo * wrap, RESTIR_MAX_RADIANCE); + } + } + bool diffLobeActive = any((1.0 - metallic) * albedo > 1e-4); + + if (diffLobeActive) { + float3 Nb = N; + if (isVeg && N.y < 0.0) + Nb = -N; + float2 u = float2(rand_float(rng), rand_float(rng)); + float3 bounceDir = cosine_weighted_hemisphere(u, Nb); + float cosPDF = max(dot(bounceDir, Nb), 0) / PI; + BounceHit hit = TraceBounce(biasedPos, bounceDir); + + if (!hit.valid && !isVeg) { + float3 LoSky = SampleSkyDiffuse(bounceDir); + float cosTheta = max(dot(N, bounceDir), 0); + float3 kD = (1.0 - F_Schlick(cosTheta, F0a)) * (1.0 - metallic); + float3 brdfCos = kD * albedo / PI * cosTheta; + float3 gi = (cosPDF > 1e-6) ? (LoSky * brdfCos / cosPDF) : 0; + gi = min(gi, RESTIR_MAX_RADIANCE); + noisyDiff += gi; + float targetLum = Luminance(LoSky * brdfCos); + if (targetLum > 0 && cosPDF > 1e-6) { + float w = targetLum / cosPDF; + ReservoirUpdate(reservoir, w, worldPos + bounceDir * 1000.0, -bounceDir, LoSky, RESTIR_INVALID_ID, rng); + } + hitDist = 1000.0; + } - float hitShadow = TraceShadow(hitBiased, sunDir); - float3 secondaryDirect = 0; - if (hitShadow > 0.001) { - float hitNdotL = max(0.0, dot(hit.normal, sunDir)); - secondaryDirect = hit.albedo * hitNdotL * sunColor * hitShadow; + if (hit.valid) { + float3 Lo = EvaluateSecondaryLo(hit, worldPos, sunDir, sunColor, viewDist, outdoor, pixel, rng); + if (isHud) + Lo = min(Lo, sunColor * 0.35 + 0.08); + + float3 wi = normalize(hit.position - worldPos); + float cosTheta = isVeg ? saturate(abs(dot(N, wi)) * 0.35 + 0.65) : max(dot(N, wi), 0); + float3 kD = (1.0 - F_Schlick(cosTheta, F0a)) * (1.0 - metallic); + float3 brdfCos = kD * albedo / PI * cosTheta; + float3 target = Lo * brdfCos; + float targetLum = Luminance(target); + + if (targetLum > 0 && cosPDF > 1e-6) { + float w = targetLum / cosPDF; + ReservoirUpdate(reservoir, w, hit.position, hit.normal, Lo, RESTIR_INVALID_ID, rng); + noisyDiff += min(target / cosPDF, RESTIR_MAX_RADIANCE); + } + hitDist = hit.t; + + if (!isVeg && skyOpen > 0.02) { + float2 uSky = float2(rand_float(rng), rand_float(rng)); + float3 skyDir = cosine_weighted_hemisphere(uSky, N); + float skyPDF = max(dot(skyDir, N), 0) / PI; + BounceHit skyHit = TraceBounce(biasedPos, skyDir); + if (!skyHit.valid && skyPDF > 1e-6) { + float3 LoSky = SampleSkyDiffuse(skyDir); + float skyCos = max(dot(N, skyDir), 0); + float3 skyKd = (1.0 - F_Schlick(skyCos, F0a)) * (1.0 - metallic); + float3 skyBrdf = skyKd * albedo / PI * skyCos; + float skyTarget = Luminance(LoSky * skyBrdf); + if (skyTarget > 0) + ReservoirUpdate(reservoir, skyTarget / skyPDF, worldPos + skyDir * 1000.0, -skyDir, LoSky, RESTIR_INVALID_ID, rng); + } + } } - float3 Lo = secondaryDirect; - Lo = min(Lo, RESTIR_MAX_RADIANCE); - - float3 wi = normalize(hit.position - worldPos); - float cosTheta = max(dot(N, wi), 0); - float3 F0 = CalculateF0(albedo, metallic); - float3 kD = (1.0 - F_Schlick(cosTheta, F0)) * (1.0 - metallic); - float3 brdfCos = kD * albedo / PI * cosTheta; - float3 target = Lo * brdfCos; - float targetLum = Luminance(target); - - if (targetLum > 0 && cosPDF > 1e-6) { - float w = targetLum / cosPDF; - ReservoirUpdate(reservoir, w, hit.position, hit.normal, Lo, rng); - reservoir.W = 1.0 / cosPDF; + + if (reservoir.M > 0) { + float3 selW = normalize(reservoir.samplePos - worldPos); + float selCos = isVeg ? saturate(abs(dot(N, selW)) * 0.35 + 0.65) : max(dot(N, selW), 0); + float3 selKd = (1.0 - F_Schlick(selCos, F0a)) * (1.0 - metallic); + float selTarget = Luminance(reservoir.Lo * selKd * albedo / PI * selCos); + reservoir.W = (selTarget > 1e-8) ? min(reservoir.w_sum / (selTarget * (float)reservoir.M), 4.0) : 0; } + if (g_CacheSize > 0 && any(noisyDiff > 0)) + UpdateIrradianceCache(u_IrradianceCache, worldPos, + min(noisyDiff, RESTIR_MAX_RADIANCE), + g_CacheCellSize, g_CacheSize, g_FrameIndex, 1.0); } - float4 resA, resB; - PackReservoir(reservoir, resA, resB); - u_ReservoirA[pixel] = resA; - u_ReservoirB[pixel] = resB; + float3 Fenv = NRD_EnvironmentTerm_Rtg(F0a, abs(dot(N, V)), roughness); + float sampleRough = max(roughness, 0.06); + float3 specular = 0; + float specHitDist = 0; + float3 bestSamplePos = worldPos; + float3 bestSampleN = N; + float3 bestLo = 0; + float accW = 0; + { + float2 uSpec = float2(rand_float(rng), rand_float(rng)); + float3 H = ImportanceSampleGGXDir(uSpec, N, sampleRough); + float3 R = normalize(2.0 * max(dot(V, H), 0.0) * H - V); + if (dot(R, N) <= 0.0) + R = reflect(-V, N); + if (dot(R, N) > 0.0) { + BounceHit specHit = TraceBounce(biasedPos, R); + float3 Lo = 0; + float3 samplePos = worldPos + R * 1000.0; + float3 sampleN = -R; + float sDist = 1000.0; + if (specHit.valid) { + Lo = EvaluateSecondaryLo(specHit, worldPos, sunDir, sunColor, viewDist, outdoor, pixel, rng); + if (isHud) + Lo = min(Lo, sunColor * 0.3 + 0.05); + samplePos = specHit.position; + sampleN = specHit.normal; + sDist = max(length(specHit.position - worldPos), 0.0); + } else { + Lo = min(SampleSkySpec(R, sampleRough), RESTIR_MAX_RADIANCE); + } + Lo = min(Lo, RESTIR_MAX_RADIANCE); + specular = Lo * Fenv; + specHitDist = sDist; + accW = 1.0; + bestLo = Lo; + bestSamplePos = samplePos; + bestSampleN = sampleN; + } + } + if (accW > 1e-4) { + float targetLum = Luminance(specular); + if (targetLum > 0) { + ReservoirUpdate(specReservoir, targetLum, bestSamplePos, bestSampleN, bestLo, RESTIR_INVALID_ID, rng); + specReservoir.W = (specReservoir.M > 0) ? min(specReservoir.w_sum / (targetLum * (float)specReservoir.M), 4.0) : 0; + specReservoir.age = 0; + } + } + float dLum = max(Luminance(direct), 0.05); + float specClamp = lerp(4.0, 12.0, saturate(roughness * 5.0)); + if (isHud) + specClamp = min(specClamp, 1.5); + if (Luminance(specular) > dLum * specClamp) + specular *= (dLum * specClamp) / max(Luminance(specular), 1e-4); + noisySpec = specular; + if (specHitDist > 0) + hitDist = specHitDist; + + reservoir.Lo = min(reservoir.Lo, RESTIR_MAX_RADIANCE); + + if (diRes.M > 0 && diRes.targetPdf > 0) + diRes.W = ClampDIReservoirW(diRes.w_sum / max(diRes.targetPdf * (float)diRes.M, 1e-6)); + else + diRes = EmptyDIReservoir(); + diRes.zone = IsInteriorSurfMark(surfMark) ? 1u : 0u; + + float4 sA, sB; + PackReservoirAB(specReservoir, sA, sB); + + u_DirectLighting[pixel] = float4(min(direct, RESTIR_MAX_RADIANCE), 1.0); + u_Reservoir[pixelIdx] = PackReservoirU4(reservoir, worldPos); + u_NoisyDiffuse[pixel] = float4(min(noisyDiff, RESTIR_MAX_RADIANCE), 1.0); + u_NoisySpecular[pixel] = float4(min(noisySpec, RESTIR_MAX_RADIANCE), hitDist); + u_HitDistance[pixel] = hitDist; + u_DIReservoir[pixel] = PackDIReservoir(diRes); + u_SpecReservoirA[pixel] = sA; + u_SpecReservoirB[pixel] = sB; } diff --git a/res/gamedata/shaders/r5/restir_gi_spatial.cs b/res/gamedata/shaders/r5/restir_gi_spatial.cs new file mode 100644 index 00000000000..2134b3594ab --- /dev/null +++ b/res/gamedata/shaders/r5/restir_gi_spatial.cs @@ -0,0 +1,238 @@ +#define SM_6_0 +#include "common.h" +#include "rt_common.h" +#include "rt_visibility.h" +#include "shared/pbr_brdf.h" +#include "shared/basecolor_pack.h" +#include "shared/surface_marks.h" +#include "restir_gi_common.h" + +cbuffer ReSTIRSpatialParams : register(b5) { + float4x4 g_InvViewProj; + float4 g_CameraPos; + float2 g_ScreenSize; + float2 g_InvScreenSize; + uint g_FrameIndex; + uint g_SpatialSamples; + float g_SpatialRadius; + uint g_MMax; + uint g_IdentityStaticCount; + uint g_TerrainBatchCount; + uint g_SkinnedBatchStart; + uint g_GrassBatchStart; + uint g_DetailAtlasIndex; + uint g_ParticleBatchStart; + float g_LodDist; + uint g_HudSkinnedStart; +}; + +RaytracingAccelerationStructure g_SceneTLAS : register(t1); +StructuredBuffer g_BatchInfo : register(t2); +ByteAddressBuffer g_MegaVB : register(t3); +ByteAddressBuffer g_MegaIB : register(t18); +ByteAddressBuffer g_GrassVB : register(t12); +ByteAddressBuffer g_GrassIB : register(t13); +ByteAddressBuffer g_ParticleVB : register(t20); +ByteAddressBuffer g_ParticleIB : register(t21); + +StructuredBuffer t_InReservoir : register(t0); +Texture2D t_Depth : register(t4); +Texture2D t_Normal : register(t5); +Texture2D t_BaseColor : register(t6); +Texture2D t_WorldPos : register(t7); +Texture3D t_BlueNoise : register(t8); +Texture2D t_SkyOpen : register(t9); + +RWStructuredBuffer u_OutReservoir : register(u0); +RWTexture2D u_NoisyDiffuse : register(u1); + +bool VisibilityOK(float3 worldPos, float3 N, float3 samplePos, uint2 pixel) +{ + float3 biasedPos = worldPos + N * 0.01; + return TraceVisibilityClear( + g_SceneTLAS, g_BatchInfo, g_MegaVB, g_MegaIB, g_GrassVB, g_GrassIB, + g_ParticleVB, g_ParticleIB, + biasedPos, samplePos, + g_IdentityStaticCount, g_TerrainBatchCount, g_SkinnedBatchStart, g_GrassBatchStart, + g_ParticleBatchStart, g_DetailAtlasIndex, g_HudSkinnedStart, + t_BlueNoise, pixel, g_FrameIndex); +} + +[numthreads(8, 8, 1)] +void main(uint3 dispatchID : SV_DispatchThreadID) +{ + uint2 pixel = dispatchID.xy; + uint width = (uint)g_ScreenSize.x; + uint height = (uint)g_ScreenSize.y; + if (pixel.x >= width || pixel.y >= height) + return; + + float2 giSize = g_ScreenSize; + uint fullW = 0, fullH = 0; + t_Depth.GetDimensions(fullW, fullH); + float2 fullSize = float2(max(fullW, 1u), max(fullH, 1u)); + + uint pixelIdx = pixel.y * width + pixel.x; + float depth = RestirLoadDepth(t_Depth, pixel, giSize, fullSize); + if (depth <= 0.0 || depth >= 1.0) { + u_OutReservoir[pixelIdx] = 0; + return; + } + + float4 worldPosData = RestirLoadTex4(t_WorldPos, pixel, giSize, fullSize); + float4 baseColorData = RestirLoadTex4(t_BaseColor, pixel, giSize, fullSize); + float surfMark = SurfMarkFromGBuffer(worldPosData.w, baseColorData.a); + if (IsCharSurfMark(surfMark)) { + u_OutReservoir[pixelIdx] = t_InReservoir[pixelIdx]; + return; + } + if (IsWaterSurfMark(surfMark)) { + u_OutReservoir[pixelIdx] = 0; + return; + } + + float2 uv = (float2(pixel) + 0.5) * g_InvScreenSize; + float3 worldPos = ResolveGBufferWorldPos(uv, depth, worldPosData, g_InvViewProj); + float3 N = normalize(RestirLoadTex4(t_Normal, pixel, giSize, fullSize).xyz); + float3 albedo = baseColorData.rgb; + float sssMaskUnused = 0.0; + float metallic = UnpackGBufferMetallic(baseColorData.a, IsHudSurfMark(surfMark) || IsCharSurfMark(surfMark), sssMaskUnused); + float linearDepth = length(worldPos - g_CameraPos.xyz); + const bool hudSurf = IsHudSurfMark(surfMark); + const bool vegSurf = IsVegSurfMark(surfMark); + float skyOpenC = saturate(RestirLoadTex1(t_SkyOpen, pixel, giSize, fullSize)); + + GIReservoir center = UnpackReservoirU4(t_InReservoir[pixelIdx], worldPos); + GIReservoir output = EmptyReservoir(); + uint wx = asuint(worldPos.x * 8.0); + uint wy = asuint(worldPos.y * 8.0); + uint wz = asuint(worldPos.z * 8.0); + uint rng = pcg_hash(wx + wy * 3343u + wz * 9157u + pixel.x + pixel.y * 1973u + g_FrameIndex * 26699u); + + if (IsReservoirValid(center)) { + float3 wi = normalize(center.samplePos - worldPos); + float cosTheta = max(dot(N, wi), 0); + float3 F0 = CalculateF0(albedo, metallic); + float3 kD = (1.0 - F_Schlick(cosTheta, F0)) * (1.0 - metallic); + float targetLum = Luminance(center.Lo * kD * albedo / PI * cosTheta); + if (targetLum > 0) { + output = center; + output.w_sum = targetLum * center.W; + output.M = max(center.M, 1u); + } + } + + float lod = saturate(linearDepth / max(g_LodDist, 1.0)); + uint samples = min(g_SpatialSamples, 16u); + if (vegSurf) + samples = 0; + if (lod > 0.9) + samples = min(samples, max(samples / 2u, 2u)); + for (uint i = 0; i < samples; i++) { + float ang = rand_float(rng) * 6.2831853; + float rad = sqrt(rand_float(rng)) * g_SpatialRadius; + int2 np = int2(pixel) + int2(int(cos(ang) * rad), int(sin(ang) * rad)); + if (np.x < 0 || np.y < 0 || np.x >= (int)width || np.y >= (int)height) + continue; + + float nDepth = RestirLoadDepth(t_Depth, uint2(np), giSize, fullSize); + if (nDepth <= 0.0 || nDepth >= 1.0) + continue; + float4 nWorldPosData = RestirLoadTex4(t_WorldPos, uint2(np), giSize, fullSize); + float4 nBase = RestirLoadTex4(t_BaseColor, uint2(np), giSize, fullSize); + float nMark = SurfMarkFromGBuffer(nWorldPosData.w, nBase.a); + if (IsHudSurfMark(nMark) != hudSurf) + continue; + float skyOpenN = saturate(RestirLoadTex1(t_SkyOpen, uint2(np), giSize, fullSize)); + if (!SameLightZone(surfMark, nMark)) + continue; + if (abs(skyOpenC - skyOpenN) > 0.25) + continue; + + float3 nN = normalize(RestirLoadTex4(t_Normal, uint2(np), giSize, fullSize).xyz); + float2 nUV = (float2(np) + 0.5) * g_InvScreenSize; + float3 nWorld = ReconstructWorldPosReverseZ(nUV, nDepth, g_InvViewProj); + float nLinear = length(nWorld - g_CameraPos.xyz); + if (!ValidateTemporalNeighbor(linearDepth, N, nLinear, nN)) + continue; + + uint nIdx = (uint)np.y * width + (uint)np.x; + GIReservoir neighbor = UnpackReservoirU4(t_InReservoir[nIdx], nWorld); + if (!IsReservoirValid(neighbor)) + continue; + + float3 wi = normalize(neighbor.samplePos - worldPos); + float cosTheta = max(dot(N, wi), 0); + if (cosTheta <= 0) + continue; + + float jac = JacobianReconnectionShift(neighbor.sampleNormal, worldPos, nWorld, neighbor.samplePos); + jac = clamp(jac, 0.25, 4.0); + float3 F0 = CalculateF0(albedo, metallic); + float3 kD = (1.0 - F_Schlick(cosTheta, F0)) * (1.0 - metallic); + float3 target = min(neighbor.Lo, RESTIR_MAX_RADIANCE) * kD * albedo / PI * cosTheta; + float targetLum = Luminance(target); + if (targetLum <= 0) + continue; + + float conf = AgeConfidence(neighbor.age, neighbor.M); + uint clampedM = TemporalMClamp(neighbor.M, neighbor.age, g_MMax); + float w = targetLum * neighbor.W * jac * (float)clampedM * conf; + ReservoirUpdate(output, w, neighbor.samplePos, neighbor.sampleNormal, neighbor.Lo, neighbor.lightId, rng); + output.M += clampedM > 0 ? (clampedM - 1) : 0; + } + + float outTargetLum = 0; + if (IsReservoirValid(output)) { + float3 wi = normalize(output.samplePos - worldPos); + float cosTheta = max(dot(N, wi), 0); + float3 F0 = CalculateF0(albedo, metallic); + float3 kD = (1.0 - F_Schlick(cosTheta, F0)) * (1.0 - metallic); + outTargetLum = Luminance(min(output.Lo, RESTIR_MAX_RADIANCE) * kD * albedo / PI * cosTheta); + } + + output.Lo = min(output.Lo, RESTIR_MAX_RADIANCE); + if (IsReservoirValid(center) && output.w_sum > 8.0 * max(center.w_sum, 1e-6)) + { + output = center; + outTargetLum = 0; + if (IsReservoirValid(output)) { + float3 wiB = normalize(output.samplePos - worldPos); + float cosB = max(dot(N, wiB), 0); + float3 F0b = CalculateF0(albedo, metallic); + float3 kDb = (1.0 - F_Schlick(cosB, F0b)) * (1.0 - metallic); + outTargetLum = Luminance(min(output.Lo, RESTIR_MAX_RADIANCE) * kDb * albedo / PI * cosB); + } + } + output.W = (outTargetLum > 0 && output.M > 0) ? min(output.w_sum / (outTargetLum * output.M), 4.0) : 0; + output.age = min(output.age + 1, 127); + if (IsReservoirValid(output) && output.W > 0) { + float sampleDist = length(output.samplePos - worldPos); + if (sampleDist < 80.0 && !VisibilityOK(worldPos, N, output.samplePos, pixel)) { + if (IsReservoirValid(center)) { + output = center; + outTargetLum = 0; + if (IsReservoirValid(output)) { + float3 wiC = normalize(output.samplePos - worldPos); + float cosC = max(dot(N, wiC), 0); + float3 F0c = CalculateF0(albedo, metallic); + float3 kDc = (1.0 - F_Schlick(cosC, F0c)) * (1.0 - metallic); + outTargetLum = Luminance(min(output.Lo, RESTIR_MAX_RADIANCE) * kDc * albedo / PI * cosC); + } + output.W = (outTargetLum > 0 && output.M > 0) ? min(output.w_sum / (outTargetLum * output.M), 4.0) : min(center.W, 4.0); + } + } + } + u_OutReservoir[pixelIdx] = PackReservoirU4(output, worldPos); + + float3 gi = 0; + if (IsReservoirValid(output) && output.W > 0 && outTargetLum > 0) { + float3 wiS = normalize(output.samplePos - worldPos); + float cosS = max(dot(N, wiS), 0); + float3 F0s = CalculateF0(albedo, metallic); + float3 kDs = (1.0 - F_Schlick(cosS, F0s)) * (1.0 - metallic); + gi = min(output.Lo, RESTIR_MAX_RADIANCE) * min(output.W, 4.0) * kDs * albedo / PI * cosS; + gi = min(gi, RESTIR_MAX_RADIANCE); + } + u_NoisyDiffuse[pixel] = float4(gi, 1.0); +} diff --git a/res/gamedata/shaders/r5/restir_gi_temporal.cs b/res/gamedata/shaders/r5/restir_gi_temporal.cs index 186e2e9f1c7..c91f1a403c7 100644 --- a/res/gamedata/shaders/r5/restir_gi_temporal.cs +++ b/res/gamedata/shaders/r5/restir_gi_temporal.cs @@ -1,8 +1,11 @@ #include "common.h" #include "rt_common.h" #include "shared/pbr_brdf.h" +#include "shared/basecolor_pack.h" +#include "shared/surface_marks.h" #include "restir_gi_common.h" + cbuffer ReSTIRTemporalParams : register(b5) { float4x4 g_InvViewProj; float4x4 g_PrevInvViewProj; @@ -10,49 +13,63 @@ float2 g_ScreenSize; float2 g_InvScreenSize; uint g_FrameIndex; - uint3 g_Pad; + float g_EnvAdapt; + float g_CurrJitterX; + float g_CurrJitterY; + float g_PrevJitterX; + float g_PrevJitterY; }; -Texture2D t_PrevReservoirA : register(t0); -Texture2D t_PrevReservoirB : register(t1); +StructuredBuffer t_PrevReservoir : register(t0); Texture2D t_MotionVectors : register(t2); Texture2D t_Depth : register(t3); Texture2D t_PrevNormal : register(t5); Texture2D t_BaseColor : register(t6); +Texture2D t_WorldPos : register(t7); Texture2D t_PrevDepth : register(t8); Texture2D t_Normal : register(t9); +Texture2D t_SkyOpen : register(t10); -RWTexture2D u_ReservoirA : register(u0); -RWTexture2D u_ReservoirB : register(u1); +RWStructuredBuffer u_Reservoir : register(u0); [numthreads(8, 8, 1)] void main(uint3 dispatchID : SV_DispatchThreadID) { uint2 pixel = dispatchID.xy; - if (pixel.x >= (uint)g_ScreenSize.x || pixel.y >= (uint)g_ScreenSize.y) + uint width = (uint)g_ScreenSize.x; + uint height = (uint)g_ScreenSize.y; + if (pixel.x >= width || pixel.y >= height) return; - float depth = t_Depth.Load(int3(pixel, 0)); - if (depth <= 0.0 || depth >= 0.9) { - u_ReservoirA[pixel] = 0; - u_ReservoirB[pixel] = 0; + float2 giSize = g_ScreenSize; + uint fullW = 0, fullH = 0; + t_Depth.GetDimensions(fullW, fullH); + float2 fullSize = float2(max(fullW, 1u), max(fullH, 1u)); + + uint pixelIdx = pixel.y * width + pixel.x; + float depth = RestirLoadDepth(t_Depth, pixel, giSize, fullSize); + if (depth <= 0.0 || depth >= 1.0) { + u_Reservoir[pixelIdx] = 0; return; } - GIReservoir currRes = UnpackReservoir( - u_ReservoirA[pixel], - u_ReservoirB[pixel] - ); + float4 baseColorData = RestirLoadTex4(t_BaseColor, pixel, giSize, fullSize); + float4 worldPosData = RestirLoadTex4(t_WorldPos, pixel, giSize, fullSize); + float surfMark = SurfMarkFromGBuffer(worldPosData.w, baseColorData.a); + if (IsWaterSurfMark(surfMark)) { + u_Reservoir[pixelIdx] = 0; + return; + } - float2 giUV = (float2(pixel) + 0.5) * g_InvScreenSize; - float4 giClip = float4(giUV.x * 2.0 - 1.0, 1.0 - giUV.y * 2.0, depth, 1.0); - float4 giWorld = mul(g_InvViewProj, giClip); - float3 worldPos = giWorld.xyz / giWorld.w; - float4 normalData = t_Normal.Load(int3(pixel, 0)); + float2 uv = (float2(pixel) + 0.5) * g_InvScreenSize; + float3 worldPos = ResolveGBufferWorldPos(uv, depth, worldPosData, g_InvViewProj); + GIReservoir currRes = UnpackReservoirU4(u_Reservoir[pixelIdx], worldPos); + + float4 normalData = RestirLoadTex4(t_Normal, pixel, giSize, fullSize); float3 N = normalize(normalData.xyz); - float4 baseColorData = t_BaseColor.Load(int3(pixel, 0)); float3 albedo = baseColorData.rgb; - float metallic = baseColorData.a; + float sssMaskUnused = 0.0; + float metallic = UnpackGBufferMetallic(baseColorData.a, IsHudSurfMark(surfMark) || IsCharSurfMark(surfMark), sssMaskUnused); float3 target_curr = 0; if (IsReservoirValid(currRes)) { @@ -70,38 +87,45 @@ void main(uint3 dispatchID : SV_DispatchThreadID) if (targetLum_curr > 0) { output.samplePos = currRes.samplePos; output.sampleNormal = currRes.sampleNormal; - output.Lo = currRes.Lo; + output.Lo = min(currRes.Lo, RESTIR_MAX_RADIANCE); + output.lightId = currRes.lightId; output.w_sum = targetLum_curr * currRes.W; output.M = 1; + output.age = currRes.age; } - float2 motion = t_MotionVectors.Load(int3(pixel, 0)); - float2 currUV = (float2(pixel) + 0.5) * g_InvScreenSize; - float2 prevUV = currUV + motion; + int2 fullPx = RestirFullPixel(pixel, giSize, fullSize); + float2 motion = t_MotionVectors.Load(int3(fullPx, 0)); + float2 prevUV = uv + motion; + float motionPx = length(motion * fullSize); - if (all(prevUV >= 0) && all(prevUV < 1.0)) { - int2 prevPixel = int2(prevUV * g_ScreenSize); - float prevDepth = t_PrevDepth.Load(int3(prevPixel, 0)); - float3 prevN = normalize(t_PrevNormal.Load(int3(prevPixel, 0)).xyz); + if (!IsCharSurfMark(surfMark) && motionPx < 16.0 && all(prevUV >= 0) && all(prevUV < 1.0)) { + int2 prevPixel = int2(prevUV * giSize); + prevPixel = clamp(prevPixel, int2(0, 0), int2(width, height) - 1); + int2 prevFull = clamp(int2(prevUV * fullSize), int2(0, 0), int2(fullSize) - 1); + float prevDepth = t_PrevDepth.Load(int3(prevFull, 0)); + float3 prevN = normalize(t_PrevNormal.Load(int3(prevFull, 0)).xyz); float viewDist = length(worldPos - g_CameraPos.xyz); bool valid = false; - if (prevDepth > 0.0 && prevDepth < 0.9) { - float2 prevNdcUV = (float2(prevPixel) + 0.5) * g_InvScreenSize; - float4 prevClip = float4(prevNdcUV.x * 2.0 - 1.0, 1.0 - prevNdcUV.y * 2.0, prevDepth, 1.0); - float4 prevWorld = mul(g_PrevInvViewProj, prevClip); - float3 prevWorldPos = prevWorld.xyz / prevWorld.w; + float3 prevWorldPos = worldPos; + if (prevDepth > 0.0 && prevDepth < 1.0) { + float2 prevNdcUV = (float2(prevFull) + 0.5) / fullSize; + prevWorldPos = ReconstructWorldPosReverseZ(prevNdcUV, prevDepth, g_PrevInvViewProj); float posDist = length(worldPos - prevWorldPos); - valid = posDist < 0.1 * viewDist && dot(N, prevN) > 0.906; + float posTol = (motionPx < 1.0) ? 0.05 : 0.035; + valid = posDist < posTol * max(min(viewDist, 4.0), 1.0) && dot(N, prevN) > 0.9; + float skyOpenC = saturate(RestirLoadTex1(t_SkyOpen, pixel, giSize, fullSize)); + float skyOpenP = saturate(t_SkyOpen.Load(int3(prevFull, 0))); + valid = valid && abs(skyOpenC - skyOpenP) <= 0.25; } if (valid) { - GIReservoir prevRes = UnpackReservoir( - t_PrevReservoirA.Load(int3(prevPixel, 0)), - t_PrevReservoirB.Load(int3(prevPixel, 0)) - ); + uint prevIdx = (uint)prevPixel.y * width + (uint)prevPixel.x; + GIReservoir prevRes = UnpackReservoirU4(t_PrevReservoir[prevIdx], prevWorldPos); if (IsReservoirValid(prevRes)) { + prevRes.Lo *= g_EnvAdapt; float3 wi_prev = normalize(prevRes.samplePos - worldPos); float cosTheta_prev = max(dot(N, wi_prev), 0); float3 F0 = CalculateF0(albedo, metallic); @@ -109,19 +133,38 @@ void main(uint3 dispatchID : SV_DispatchThreadID) float3 target_prev = prevRes.Lo * kD * albedo / PI * cosTheta_prev; float targetLum_prev = Luminance(target_prev); - if (targetLum_prev > 0) { - uint clampedM = min(prevRes.M, RESTIR_M_MAX); - float w_prev = targetLum_prev * prevRes.W * clampedM; + float jacobian = JacobianReconnectionShift( + prevRes.sampleNormal, worldPos, prevWorldPos, prevRes.samplePos); + jacobian = clamp(jacobian, 0.25, 4.0); - ReservoirUpdate(output, w_prev, prevRes.samplePos, prevRes.sampleNormal, prevRes.Lo, rng); - output.M += clampedM - 1; + if (targetLum_prev > 0) { + uint clampedM = TemporalMClamp(prevRes.M, prevRes.age, RESTIR_M_MAX); + if (motionPx > 6.0) + clampedM = max(1u, clampedM / 4u); + else if (motionPx > 2.0) + clampedM = max(1u, clampedM / 2u); + float skyOpenC = saturate(RestirLoadTex1(t_SkyOpen, pixel, giSize, fullSize)); + float skyOpenP = saturate(t_SkyOpen.Load(int3(prevFull, 0))); + if (abs(skyOpenC - skyOpenP) > 0.35) + clampedM = min(clampedM, 4u); + else if (targetLum_curr > 0 && max(targetLum_prev, targetLum_curr) / max(min(targetLum_prev, targetLum_curr), 1e-6) > 8.0) + clampedM = min(clampedM, 8u); + if (!IsReservoirValid(output)) { + output = prevRes; + output.w_sum = targetLum_prev * prevRes.W * jacobian; + output.M = max(clampedM, 1u); + } else { + float w_prev = targetLum_prev * prevRes.W * clampedM * jacobian; + ReservoirUpdate(output, w_prev, prevRes.samplePos, prevRes.sampleNormal, prevRes.Lo, prevRes.lightId, rng); + output.M += clampedM - 1; + } } } } } float outTargetLum = targetLum_curr; - if (output.samplePos.x != currRes.samplePos.x || output.samplePos.y != currRes.samplePos.y) { + if (IsReservoirValid(output)) { float3 wi_out = normalize(output.samplePos - worldPos); float cosTheta_out = max(dot(N, wi_out), 0); float3 F0 = CalculateF0(albedo, metallic); @@ -130,11 +173,9 @@ void main(uint3 dispatchID : SV_DispatchThreadID) outTargetLum = Luminance(target_out); } - output.W = (outTargetLum > 0 && output.M > 0) ? output.w_sum / (outTargetLum * output.M) : 0; - output.age = min(output.age + 1, 255); + output.Lo = min(output.Lo, RESTIR_MAX_RADIANCE); + output.W = (outTargetLum > 0 && output.M > 0) ? min(output.w_sum / (outTargetLum * output.M), 4.0) : 0; + output.age = min(output.age + 1, 127); - float4 outA, outB; - PackReservoir(output, outA, outB); - u_ReservoirA[pixel] = outA; - u_ReservoirB[pixel] = outB; + u_Reservoir[pixelIdx] = PackReservoirU4(output, worldPos); } diff --git a/res/gamedata/shaders/r5/restir_gi_temporal_filter.cs b/res/gamedata/shaders/r5/restir_gi_temporal_filter.cs new file mode 100644 index 00000000000..8bed8b08e16 --- /dev/null +++ b/res/gamedata/shaders/r5/restir_gi_temporal_filter.cs @@ -0,0 +1,195 @@ +#include "rt_common.h" +#include "restir_gi_common.h" +#include "shared/surface_marks.h" + +cbuffer TemporalFilterParams : register(b5) { + float4x4 g_InvViewProj; + float4x4 g_PrevInvViewProj; + float4 g_CameraPos; + float2 g_ScreenSize; + float2 g_InvScreenSize; + float g_Alpha; + float g_EnvAdapt; + float g_CurrJitterX; + float g_CurrJitterY; + float g_PrevJitterX; + float g_PrevJitterY; + uint g_Enabled; + uint g_Pad1; +}; + +Texture2D t_CurrDiffuse : register(t0); +Texture2D t_CurrSpecular : register(t1); +Texture2D t_HistDiffuse : register(t2); +Texture2D t_HistSpecular : register(t3); +Texture2D t_MotionVectors : register(t4); +Texture2D t_Depth : register(t5); +Texture2D t_Normal : register(t6); +Texture2D t_WorldPos : register(t7); +Texture2D t_PrevDepth : register(t8); +Texture2D t_PrevNormal : register(t9); + +RWTexture2D u_OutDiffuse : register(u0); +RWTexture2D u_OutSpecular : register(u1); + +float Luma(float3 c) +{ + return dot(c, float3(0.2126, 0.7152, 0.0722)); +} + +float3 ClipAABB(float3 hist, float3 minC, float3 maxC) +{ + float3 center = 0.5 * (minC + maxC); + float3 extents = 0.5 * (maxC - minC) + 1e-4; + float3 offset = hist - center; + float3 ts = abs(extents / max(abs(offset), 1e-4)); + float t = saturate(min(min(ts.x, ts.y), ts.z)); + return center + offset * t; +} + +float3 SampleHist(Texture2D tex, float2 uv) +{ + float2 p = uv * g_ScreenSize - 0.5; + int2 i0 = int2(floor(p)); + float2 f = saturate(p - float2(i0)); + int2 maxP = int2(g_ScreenSize) - 1; + int2 i1 = clamp(i0 + int2(1, 0), int2(0, 0), maxP); + int2 i2 = clamp(i0 + int2(0, 1), int2(0, 0), maxP); + int2 i3 = clamp(i0 + int2(1, 1), int2(0, 0), maxP); + i0 = clamp(i0, int2(0, 0), maxP); + float3 a = tex.Load(int3(i0, 0)).rgb; + float3 b = tex.Load(int3(i1, 0)).rgb; + float3 c = tex.Load(int3(i2, 0)).rgb; + float3 d = tex.Load(int3(i3, 0)).rgb; + return lerp(lerp(a, b, f.x), lerp(c, d, f.x), f.y); +} + +[numthreads(8, 8, 1)] +void main(uint3 dispatchID : SV_DispatchThreadID) +{ + uint2 pixel = dispatchID.xy; + if (pixel.x >= (uint)g_ScreenSize.x || pixel.y >= (uint)g_ScreenSize.y) + return; + + float2 giSize = g_ScreenSize; + uint fullW = 0, fullH = 0; + t_Depth.GetDimensions(fullW, fullH); + float2 fullSize = float2(max(fullW, 1u), max(fullH, 1u)); + + float depth = RestirLoadDepth(t_Depth, pixel, giSize, fullSize); + float4 currD4 = t_CurrDiffuse.Load(int3(pixel, 0)); + float4 currS4 = t_CurrSpecular.Load(int3(pixel, 0)); + float3 currD = currD4.rgb; + float3 currS = currS4.rgb; + float specHitDist = currS4.a; + + if (depth <= 0.0 || g_Enabled == 0) { + u_OutDiffuse[pixel] = float4(currD, 1.0); + u_OutSpecular[pixel] = float4(currS, specHitDist); + return; + } + + float2 uv = (float2(pixel) + 0.5) * g_InvScreenSize; + int2 fullPx = RestirFullPixel(pixel, giSize, fullSize); + float2 mv = t_MotionVectors.Load(int3(fullPx, 0)); + float2 histUV = uv + mv; + if (any(histUV < 0.0) || any(histUV > 1.0)) { + u_OutDiffuse[pixel] = float4(currD, 1.0); + u_OutSpecular[pixel] = float4(currS, specHitDist); + return; + } + + int2 histPixel = int2(histUV * giSize); + histPixel = clamp(histPixel, int2(0, 0), int2(giSize) - 1); + int2 histFull = clamp(int2(histUV * fullSize), int2(0, 0), int2(fullSize) - 1); + + float4 worldPosData = RestirLoadTex4(t_WorldPos, pixel, giSize, fullSize); + if (IsCharSurfMark(worldPosData.w)) { + u_OutDiffuse[pixel] = float4(currD, 1.0); + u_OutSpecular[pixel] = float4(currS, specHitDist); + return; + } + float3 worldPos = ReconstructWorldPosReverseZ(uv, depth, g_InvViewProj); + float prevDepth = t_PrevDepth.Load(int3(histFull, 0)); + float2 prevNdcUV = (float2(histFull) + 0.5) / fullSize; + float3 prevWorld = (prevDepth > 0.0 && prevDepth < 1.0) + ? ReconstructWorldPosReverseZ(prevNdcUV, prevDepth, g_PrevInvViewProj) + : worldPos; + float3 prevN = normalize(t_PrevNormal.Load(int3(histFull, 0)).xyz); + float3 N = normalize(RestirLoadTex4(t_Normal, pixel, giSize, fullSize).xyz); + + if (!SameHudSurfClass(worldPosData.w, t_WorldPos.Load(int3(histFull, 0)).w)) { + u_OutDiffuse[pixel] = float4(currD, 1.0); + u_OutSpecular[pixel] = float4(currS, specHitDist); + return; + } + + float viewDist = max(length(worldPos - g_CameraPos.xyz), 1.0); + float motionPx = length(mv * fullSize); + float posErr = length(worldPos - prevWorld) / viewDist; + float staticTol = (motionPx < 1.0) ? 0.18 : 0.10; + if (motionPx < 2.0 && posErr > staticTol) { + u_OutDiffuse[pixel] = float4(currD, 1.0); + u_OutSpecular[pixel] = float4(currS, specHitDist); + return; + } + float nDot = saturate(dot(N, prevN)); + float trust = 1.0; + if (posErr > 0.35 || nDot < 0.4) { + u_OutDiffuse[pixel] = float4(currD, 1.0); + u_OutSpecular[pixel] = float4(currS, specHitDist); + return; + } + if (posErr > 0.10) + trust *= saturate(1.0 - (posErr - 0.10) / 0.25); + if (nDot < 0.85) + trust *= saturate((nDot - 0.4) / 0.45); + if (motionPx > 1.0) + trust *= saturate(1.0 - (motionPx - 1.0) / 14.0); + + float3 histD = SampleHist(t_HistDiffuse, histUV) * g_EnvAdapt; + float3 histS = SampleHist(t_HistSpecular, histUV) * g_EnvAdapt; + + float3 minD = currD, maxD = currD, minS = currS, maxS = currS; + [unroll] for (int iy = -1; iy <= 1; ++iy) { + [unroll] for (int ix = -1; ix <= 1; ++ix) { + int2 np = int2(pixel) + int2(ix, iy); + if (np.x < 0 || np.y < 0 || np.x >= (int)g_ScreenSize.x || np.y >= (int)g_ScreenSize.y) + continue; + if (!SameHudSurfClass(worldPosData.w, RestirLoadTex4(t_WorldPos, uint2(np), giSize, fullSize).w)) + continue; + float3 d = t_CurrDiffuse.Load(int3(np, 0)).rgb; + float3 s = t_CurrSpecular.Load(int3(np, 0)).rgb; + minD = min(minD, d); + maxD = max(maxD, d); + minS = min(minS, s); + maxS = max(maxS, s); + } + } + + if (trust < 0.95 || motionPx > 0.5) { + float lo = lerp(0.15, 0.45, trust); + float hi = lerp(5.0, 2.5, trust); + histD = ClipAABB(histD, minD * lo, maxD * hi + 0.02); + histS = ClipAABB(histS, minS * lo * 0.8, maxS * hi * 1.2 + 0.02); + } + + float alpha = saturate(g_Alpha) * trust; + if (motionPx < 0.5) + alpha = min(0.97, alpha + 0.02); + float3 outD = lerp(currD, histD, alpha); + float3 outS = lerp(currS, histS, alpha * 0.96); + if (trust < 0.85) { + float currLd = max(Luma(currD), 1e-4); + float histLd = max(Luma(outD), 1e-4); + if (currLd > histLd * 6.0) + outD *= (histLd * 6.0) / currLd; + float currLs = max(Luma(currS), 1e-4); + float histLs = max(Luma(outS), 1e-4); + if (currLs > histLs * 8.0) + outS *= (histLs * 8.0) / currLs; + } + + u_OutDiffuse[pixel] = float4(outD, 1.0); + u_OutSpecular[pixel] = float4(outS, specHitDist); +} diff --git a/res/gamedata/shaders/r5/restir_motion_vectors.cs b/res/gamedata/shaders/r5/restir_motion_vectors.cs index 9f299655a84..8db38dbafd1 100644 --- a/res/gamedata/shaders/r5/restir_motion_vectors.cs +++ b/res/gamedata/shaders/r5/restir_motion_vectors.cs @@ -1,14 +1,33 @@ #include "common.h" cbuffer MotionVectorParams : register(b5) { - float4x4 g_InvViewProj; + float4x4 g_ViewProj; float4x4 g_PrevViewProj; + float4x4 g_InvViewProj; float2 g_ScreenSize; float2 g_InvScreenSize; + float4 g_CameraPos; + float4 g_PrevCameraPos; + uint g_HasPrevCamera; + float g_CurrJitterX; + float g_CurrJitterY; + float g_PrevJitterX; + float g_PrevJitterY; + float g_Pad0; + float g_Pad1; + float g_Pad2; }; Texture2D t_Depth : register(t0); -RWTexture2D u_MotionVectors : register(u0); +RWTexture2D u_MotionVectors : register(u0); + +float2 ProjectToUv(float4x4 viewProj, float3 worldPos) +{ + float4 clip = mul(viewProj, float4(worldPos, 1.0)); + float2 ndc = clip.xy / max(abs(clip.w), 1e-5); + ndc.y = -ndc.y; + return ndc * 0.5 + 0.5; +} float3 ReconstructWorldPos(uint2 pixel, float depth) { @@ -16,7 +35,14 @@ float3 ReconstructWorldPos(uint2 pixel, float depth) float4 clip = float4(uv * 2.0 - 1.0, depth, 1.0); clip.y = -clip.y; float4 world = mul(g_InvViewProj, clip); - return world.xyz / world.w; + return world.xyz / max(world.w, 1e-6); +} + +float3 ReconstructFarWorld(float2 uv) +{ + float2 ndc = float2(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0); + float4 farH = mul(g_InvViewProj, float4(ndc, 0.0, 1.0)); + return farH.xyz / max(farH.w, 1e-6); } [numthreads(8, 8, 1)] @@ -26,21 +52,30 @@ void main(uint3 dtid : SV_DispatchThreadID) if (pixel.x >= (uint)g_ScreenSize.x || pixel.y >= (uint)g_ScreenSize.y) return; + float2 uv = (float2(pixel) + 0.5) * g_InvScreenSize; float depth = t_Depth.Load(int3(pixel, 0)); - if (depth <= 0.0) { - u_MotionVectors[pixel] = float2(0, 0); + if (g_HasPrevCamera == 0) { + u_MotionVectors[pixel] = 0; return; } - float3 worldPos = ReconstructWorldPos(pixel, depth); - - float4 prevClip = mul(g_PrevViewProj, float4(worldPos, 1.0)); - float2 prevNDC = prevClip.xy / prevClip.w; - prevNDC.y = -prevNDC.y; - float2 prevUV = prevNDC * 0.5 + 0.5; - - float2 currUV = (float2(pixel) + 0.5) * g_InvScreenSize; + if (depth <= 1e-7) { + float3 farW = ReconstructFarWorld(uv); + float3 dir = normalize(farW - g_CameraPos.xyz); + float3 prevPt = g_PrevCameraPos.xyz + dir * 1e5; + float2 currUnjit = ProjectToUv(g_ViewProj, prevPt); + float2 prevUnjit = ProjectToUv(g_PrevViewProj, prevPt); + float3 dualPt = prevPt + (g_CameraPos.xyz - g_PrevCameraPos.xyz); + float2 dual = ProjectToUv(g_PrevViewProj, dualPt) - currUnjit; + u_MotionVectors[pixel] = float4(prevUnjit - currUnjit, dual); + return; + } - u_MotionVectors[pixel] = prevUV - currUV; + float3 worldPos = ReconstructWorldPos(pixel, depth); + float2 currUnjit = ProjectToUv(g_ViewProj, worldPos); + float2 prevUnjit = ProjectToUv(g_PrevViewProj, worldPos); + float3 dualPos = worldPos + (g_CameraPos.xyz - g_PrevCameraPos.xyz); + float2 dual = ProjectToUv(g_PrevViewProj, dualPos) - currUnjit; + u_MotionVectors[pixel] = float4(prevUnjit - currUnjit, dual); } diff --git a/res/gamedata/shaders/r5/restir_pt_common.h b/res/gamedata/shaders/r5/restir_pt_common.h new file mode 100644 index 00000000000..41e76b979d6 --- /dev/null +++ b/res/gamedata/shaders/r5/restir_pt_common.h @@ -0,0 +1,135 @@ +#ifndef RESTIR_PT_COMMON_H +#define RESTIR_PT_COMMON_H + +#ifndef RESTIR_GI_COMMON_H +#error "restir_gi_common.h must be included before restir_pt_common.h" +#endif + +static const float RESTIR_PT_FOOTPRINT_C = 0.02; +static const float RESTIR_PT_RC_ALPHA = 0.2; + +struct PTReservoir +{ + float3 rcPos; + float W; + float3 Lo; + float targetPdf; + float3 rcN; + uint M; + uint seed; + uint flags; + float hitDist; + uint age; +}; + +PTReservoir EmptyPTReservoir() +{ + PTReservoir r; + r.rcPos = 0; + r.W = 0; + r.Lo = 0; + r.targetPdf = 0; + r.rcN = float3(0, 1, 0); + r.M = 0; + r.seed = 0; + r.flags = 0; + r.hitDist = 0; + r.age = 0; + return r; +} + +bool IsPTReservoirValid(PTReservoir r) +{ + return r.M > 0 && any(r.Lo > 0) && r.W > 0; +} + +uint PackPTFlags(uint length, uint tech, uint specLobe) +{ + return (length & 7) | ((tech & 3) << 3) | ((specLobe & 1) << 5); +} + +uint PTPathLength(uint flags) { return flags & 7; } +uint PTTech(uint flags) { return (flags >> 3) & 3; } +uint PTSpecLobe(uint flags) { return (flags >> 5) & 1; } + +void PackPTReservoir(PTReservoir r, out uint4 A, out uint4 B) +{ + A.x = asuint(r.rcPos.x); + A.y = asuint(r.rcPos.y); + A.z = asuint(r.rcPos.z); + A.w = (f32tof16(min(r.W, 65000.0)) & 0xFFFF) | ((f32tof16(min(r.hitDist, 65000.0)) & 0xFFFF) << 16); + B.x = PackRGB9E5(r.Lo); + B.y = PackUnorm2To16(OctEncode(r.rcN)); + B.z = r.seed; + B.w = (r.M & 0xFF) | ((r.age & 0xFF) << 8) | ((r.flags & 0xFF) << 16); +} + +PTReservoir UnpackPTReservoir(uint4 A, uint4 B) +{ + PTReservoir r = EmptyPTReservoir(); + r.rcPos = float3(asfloat(A.x), asfloat(A.y), asfloat(A.z)); + r.W = f16tof32(A.w & 0xFFFF); + r.hitDist = f16tof32((A.w >> 16) & 0xFFFF); + r.Lo = UnpackRGB9E5(B.x); + r.rcN = OctDecode(UnpackUnorm2From16(B.y)); + r.seed = B.z; + r.M = B.w & 0xFF; + r.age = (B.w >> 8) & 0xFF; + r.flags = (B.w >> 16) & 0xFF; + r.targetPdf = max(Luminance(r.Lo) * max(r.W, 1e-4) * max((float)r.M, 1.0), 1e-4); + return r; +} + +float3 ShadePTReservoir(PTReservoir r, float3 worldPos, float3 N, float3 albedo, float metallic) +{ + if (!IsPTReservoirValid(r)) + return 0; + float3 wi = r.rcPos - worldPos; + float dist = length(wi); + wi = dist > 1e-4 ? wi / dist : N; + if (dot(N, wi) <= 0.02) + return 0; + return r.Lo * r.W * albedo * (1.0 - metallic); +} + +bool PTFootprintOk(float3 xk, float3 xkm1, float3 Nkm1, float alphaKm1) +{ + float3 d = xk - xkm1; + float dist2 = dot(d, d); + float nDot = abs(dot(Nkm1, d)); + float ratio = nDot / max(dist2, 1e-6); + return ratio < RESTIR_PT_FOOTPRINT_C && alphaKm1 >= RESTIR_PT_RC_ALPHA; +} + +float HybridShiftJacobian(float3 rcN, float3 x1New, float3 x1Old, float3 rcPos) +{ + return clamp(JacobianReconnectionShift(rcN, x1New, x1Old, rcPos), 0.25, 4.0); +} + +bool PTReservoirUpdate(inout PTReservoir r, float weight, PTReservoir cand, inout uint rng) +{ + if (isnan(weight) || isinf(weight) || weight <= 0) + return false; + float wsum = r.targetPdf + weight; + r.M += 1; + float xi = rand_float(rng); + if (xi < weight / max(wsum, 1e-6)) { + float keepM = r.M; + uint keepAge = r.age; + r = cand; + r.M = keepM; + r.age = keepAge; + r.targetPdf = wsum; + return true; + } + r.targetPdf = wsum; + return false; +} + +float PTCapFromDup(float D, float cCap) +{ + D = saturate(D); + return lerp(cCap, 1.0, pow(D, 0.1)); +} + +#endif diff --git a/res/gamedata/shaders/r5/restir_pt_dupmap.cs b/res/gamedata/shaders/r5/restir_pt_dupmap.cs new file mode 100644 index 00000000000..96fda08478a --- /dev/null +++ b/res/gamedata/shaders/r5/restir_pt_dupmap.cs @@ -0,0 +1,44 @@ +#include "common.h" +#include "rt_common.h" +#include "restir_gi_common.h" +#include "restir_pt_common.h" + +cbuffer ReSTIRPTDup : register(b5) { + float2 g_ScreenSize; + uint g_Pad0; + uint g_Pad1; +}; + +Texture2D t_PTB : register(t0); +RWTexture2D u_Dup : register(u0); + +[numthreads(8, 8, 1)] +void main(uint3 id : SV_DispatchThreadID) +{ + uint2 pixel = id.xy; + uint w = (uint)g_ScreenSize.x; + uint h = (uint)g_ScreenSize.y; + if (pixel.x >= w || pixel.y >= h) + return; + + uint seed = t_PTB[pixel].z; + if (seed == 0) { + u_Dup[pixel] = 0; + return; + } + uint same = 0; + uint total = 0; + [unroll] + for (int y = -8; y <= 8; y += 2) { + [unroll] + for (int x = -8; x <= 8; x += 2) { + int2 p = int2(pixel) + int2(x, y); + if (p.x < 0 || p.y < 0 || p.x >= (int)w || p.y >= (int)h) + continue; + total++; + if (t_PTB[p].z == seed) + same++; + } + } + u_Dup[pixel] = total > 1 ? saturate(((float)same - 1.0) / (float)(total - 1)) : 0; +} diff --git a/res/gamedata/shaders/r5/restir_pt_initial.cs b/res/gamedata/shaders/r5/restir_pt_initial.cs new file mode 100644 index 00000000000..726e42f1bdf --- /dev/null +++ b/res/gamedata/shaders/r5/restir_pt_initial.cs @@ -0,0 +1,708 @@ +#include "bindless_common.h" +#include "shared/terrain_blend.h" +#include "rt_common.h" +#include "shared/pbr_brdf.h" +#include "shared/clustered_lighting.h" +#include "shared/surface_marks.h" +#include "shared/nrd_helpers.h" +#include "shared/basecolor_pack.h" +#include "restir_gi_common.h" +#include "restir_pt_common.h" +#include "restir_di_eval.h" +#include "rt_shade_hit.h" +#include "rt_grass_alpha.h" +#include "rt_material_alpha.h" +#include "rt_visibility.h" +#include "shared/foliage_sss.h" +#include "shared/skin_sss.h" + +cbuffer ReSTIRGIParams : register(b5) { + float4x4 g_InvViewProj; + float4x4 g_PrevViewProj; + float4 g_CameraPos; + float4 g_SunDir_Intensity; + float4 g_SunColor_SkyWeight; + float4 g_SkyColor; + float2 g_ScreenSize; + float g_GIIntensity; + uint g_FrameIndex; + uint g_IdentityStaticCount; + uint g_TerrainBatchCount; + uint g_SkinnedBatchStart; + uint g_GrassBatchStart; + uint g_DetailAtlasIndex; + uint g_NumLights; + uint g_WetEnabled; + float g_WetStrength; + float4 g_ClusterParams; + float4 g_ClusterDepth; + float4 g_DISampleParams; + uint g_Bounces; + uint g_CacheSize; + float g_CacheCellSize; + uint g_CacheMaxAge; + uint g_GrassShadowEnabled; + uint g_PadA0; + uint g_PadA1; + uint g_PadA2; + float4x4 g_GrassShadowVP; + float4x4 g_WorldToView; + float4 g_HemiColor; + float g_LodDist; + float g_AmbientScale; + float g_SunAngular; + uint g_HudSkinnedStart; + uint g_ParticleBatchStart; + float g_FullWidth; + float g_FullHeight; + uint g_PadEnd2; + float4x4 g_PrevInvViewProj; + uint g_HasPrevSunVis; + float g_CurrJitterX; + float g_CurrJitterY; + float g_PrevJitterX; + float g_PrevJitterY; + float g_WindSpeed; + uint g_PadSun1; + uint g_PadSun2; +}; + +RaytracingAccelerationStructure g_SceneTLAS : register(t1); +StructuredBuffer g_BatchInfo : register(t2); +ByteAddressBuffer g_MegaVB : register(t3); +ByteAddressBuffer g_MegaIB : register(t4); +TextureCube g_Sky0 : register(t5); +TextureCube g_Sky1 : register(t6); +ByteAddressBuffer g_SkinnedVB : register(t7); +ByteAddressBuffer g_SkinnedIB : register(t11); +ByteAddressBuffer g_GrassVB : register(t12); +ByteAddressBuffer g_GrassIB : register(t13); +Texture2D t_Depth : register(t14); +Texture2D t_Normal : register(t15); +Texture2D t_BaseColor : register(t16); +StructuredBuffer g_Lights : register(t17); +Texture2D t_WetAccum : register(t18); +StructuredBuffer g_ClusterGrid : register(t19); +StructuredBuffer g_LightIndexList : register(t20); +StructuredBuffer g_DILightIndices : register(t21); +StructuredBuffer g_DILightCDF : register(t22); +Texture2D t_WorldPos : register(t23); +Texture2D t_SceneColorIn : register(t24); +Texture2D t_SkyOpen : register(t25); +Texture2D t_GrassShadow : register(t26); +ByteAddressBuffer g_ParticleVB : register(t27); +ByteAddressBuffer g_ParticleIB : register(t28); +Texture3D t_BlueNoise : register(t29); +Texture2D t_PrevSunVis : register(t30); +Texture2D t_MotionVectors : register(t31); +Texture2D t_PrevDepth : register(t32); +Texture2D t_PrevNormal : register(t33); + +RWTexture2D u_PTA : register(u0); +RWTexture2D u_PTB : register(u1); +RWTexture2D u_NoisyDiffuse : register(u2); +RWTexture2D u_NoisySpecular : register(u3); +RWTexture2D u_HitDistance : register(u4); +RWTexture2D u_DirectLighting : register(u5); +RWTexture2D u_SunVis : register(u6); + +bool IsParticleBatch(uint batchIdx) +{ + return g_ParticleBatchStart != 0xFFFFFFFFu && batchIdx >= g_ParticleBatchStart; +} + +bool IsSkinnedBatch(uint batchIdx) +{ + return g_SkinnedBatchStart != 0xFFFFFFFFu && batchIdx >= g_SkinnedBatchStart && + (g_GrassBatchStart == 0xFFFFFFFFu || batchIdx < g_GrassBatchStart) && + !IsParticleBatch(batchIdx); +} + +bool IsHudSkinnedBatch(uint batchIdx) +{ + return g_HudSkinnedStart != 0xFFFFFFFFu && batchIdx >= g_HudSkinnedStart && + (g_GrassBatchStart == 0xFFFFFFFFu || batchIdx < g_GrassBatchStart) && + !IsParticleBatch(batchIdx); +} + +bool IsGrassBatch(uint batchIdx) +{ + return g_GrassBatchStart != 0xFFFFFFFFu && batchIdx >= g_GrassBatchStart && + !IsParticleBatch(batchIdx); +} + +bool IsTerrainBatch(uint batchIdx) +{ + return batchIdx >= g_IdentityStaticCount && + batchIdx < g_IdentityStaticCount + g_TerrainBatchCount; +} + +float3 SampleSky(float3 dir) +{ + float w = g_SunColor_SkyWeight.w; + float3 s0 = g_Sky0.SampleLevel(smp_linear, dir, 0).rgb; + float3 s1 = g_Sky1.SampleLevel(smp_linear, dir, 0).rgb; + return lerp(s0, s1, w) * g_SkyColor.rgb * 0.80; +} + +float3 SampleSkyDiffuse(float3 dir) +{ + float w = g_SunColor_SkyWeight.w; + float3 s0 = g_Sky0.SampleLevel(smp_linear, dir, 4.0).rgb; + float3 s1 = g_Sky1.SampleLevel(smp_linear, dir, 4.0).rgb; + return lerp(s0, s1, w) * g_SkyColor.rgb * 0.80; +} + +float SampleGrassShadow(float3 worldPos) +{ + if (g_GrassShadowEnabled == 0) + return 1.0; + float4 shadowPos = mul(g_GrassShadowVP, float4(worldPos, 1.0)); + float3 shadowCoord = shadowPos.xyz / max(abs(shadowPos.w), 1e-6); + if (any(shadowCoord.xy < 0.0) || any(shadowCoord.xy > 1.0) || + shadowCoord.z < 0.0 || shadowCoord.z > 1.0) + return 1.0; + uint width, height; + t_GrassShadow.GetDimensions(width, height); + int2 texel = clamp(int2(shadowCoord.xy * float2(width, height)), int2(0, 0), int2(width, height) - 1); + float blockerDepth = t_GrassShadow.Load(int3(texel, 0)); + return shadowCoord.z <= blockerDepth + 0.0015 ? 1.0 : 0.0; +} + +float TraceSoftShadowSun(float3 origin, float3 sunDir, float viewDist, uint2 pixel) +{ + float2 giSize = max(g_ScreenSize, 1.0); + float2 fullSize = max(float2(g_FullWidth, g_FullHeight), 1.0); + float2 jGi = float2(g_CurrJitterX, -g_CurrJitterY) * (giSize / fullSize); + uint2 seedPx = uint2(clamp(int2(pixel) - int2(round(jGi)), int2(0, 0), int2(giSize) - 1)); + float u0 = SampleSTBN(t_BlueNoise, seedPx, 0, 0); + float u1 = SampleSTBN(t_BlueNoise, seedPx, 0, 1); + float contact = saturate(viewDist / 16.0); + float radius = clamp(g_SunAngular, 0.001, 0.05) * lerp(0.2, 1.0, contact); + float ang = u1 * 6.2831853; + float r = sqrt(u0) * radius; + float3 up = abs(sunDir.y) < 0.99 ? float3(0, 1, 0) : float3(1, 0, 0); + float3 tangent = normalize(cross(up, sunDir)); + float3 bitangent = cross(sunDir, tangent); + float3 dir = normalize(sunDir + tangent * (cos(ang) * r) + bitangent * (sin(ang) * r)); + return EvaluateSunVisibilityWithGrass( + g_SceneTLAS, g_BatchInfo, g_MegaVB, g_MegaIB, g_GrassVB, g_GrassIB, + origin, dir, 10000.0, + g_IdentityStaticCount, g_TerrainBatchCount, g_SkinnedBatchStart, g_GrassBatchStart, + g_ParticleBatchStart, g_DetailAtlasIndex, g_HudSkinnedStart, + t_BlueNoise, seedPx, 0, RT_MASK_SHADOW); +} + +float FilterSunVisibility(float rawVis, uint2 pixel, float2 giSize, float2 fullSize, float depth, float3 N, float3 worldPos, bool charSurf) +{ + float vis = rawVis; + float wSum = 1.0; + if (g_HasPrevSunVis == 0 || charSurf) + return rawVis; + int2 fullPx = RestirFullPixel(pixel, giSize, fullSize); + float2 uv = (float2(pixel) + 0.5) / giSize; + float2 motion = t_MotionVectors.Load(int3(fullPx, 0)); + float motionPx = length(motion * giSize); + bool still = motionPx < 0.4; + float2 prevUV = uv + motion; + float viewDist = length(worldPos - g_CameraPos.xyz); + float histW = still ? 16.0 : lerp(8.0, 2.0, saturate(motionPx / 4.0)); + histW *= saturate(1.0 - g_WindSpeed * 0.08); + bool histOk = !any(prevUV < 0.0) && !any(prevUV >= 1.0); + int2 prevPixel = clamp(int2(round(prevUV * giSize)), int2(0, 0), int2(giSize) - 1); + int2 prevFull = clamp(int2(round(prevUV * fullSize)), int2(0, 0), int2(fullSize) - 1); + float prevDepth = histOk ? t_PrevDepth.Load(int3(prevFull, 0)) : 0.0; + if (!histOk || prevDepth <= 0.0 || prevDepth >= 1.0) + histW = still ? 8.0 : 1.0; + else { + float3 prevN = normalize(t_PrevNormal.Load(int3(prevFull, 0)).xyz); + float2 prevNdcUV = (float2(prevFull) + 0.5) / fullSize; + float3 prevWorld = ReconstructWorldPosReverseZ(prevNdcUV, prevDepth, g_PrevInvViewProj); + float skyOpenC = saturate(RestirLoadTex1(t_SkyOpen, pixel, giSize, fullSize)); + float skyOpenP = saturate(t_SkyOpen.Load(int3(prevFull, 0))); + if (abs(skyOpenC - skyOpenP) > 0.25) + histW = 0.0; + else if (length(worldPos - prevWorld) >= 0.08 * max(viewDist, 1.0) || dot(N, prevN) < 0.94) + histW = still ? 8.0 : 1.0; + } + vis += t_PrevSunVis.Load(int3(prevPixel, 0)) * histW; + wSum += histW; + return vis / wSum; +} + +float TraceShadowRay(float3 origin, float3 dir, float tMax) +{ + return TraceVisibilityAtten( + g_SceneTLAS, g_BatchInfo, g_MegaVB, g_MegaIB, g_GrassVB, g_GrassIB, + g_ParticleVB, g_ParticleIB, + origin, dir, max(tMax, 0.001), RT_MASK_SHADOW, + g_IdentityStaticCount, g_TerrainBatchCount, g_SkinnedBatchStart, g_GrassBatchStart, + g_ParticleBatchStart, g_DetailAtlasIndex, false, 0.0, g_HudSkinnedStart, + t_BlueNoise, uint2(0, 0), g_FrameIndex); +} + +float TraceBounceSunVis(float3 origin, float3 dir, float tMax, uint2 pixel) +{ + return EvaluateSunVisibilityWithGrass( + g_SceneTLAS, g_BatchInfo, g_MegaVB, g_MegaIB, g_GrassVB, g_GrassIB, + origin, dir, max(tMax, 0.001), + g_IdentityStaticCount, g_TerrainBatchCount, g_SkinnedBatchStart, g_GrassBatchStart, + g_ParticleBatchStart, g_DetailAtlasIndex, g_HudSkinnedStart, + t_BlueNoise, pixel, g_FrameIndex, RT_MASK_SHADOW); +} + +float4 SampleTerrainTexture(uint index, float2 uv) +{ + if (index == INVALID_TEXTURE_INDEX) + return float4(0.5, 0.5, 0.5, 1.0); + return GetBindlessTexture(index).SampleLevel(smp_linear, uv, 0); +} + +float3 SampleTerrainAlbedo(TerrainMaterialData mat, float2 uv) +{ + float2 baseUV = uv; + float2 detailUV = uv * mat.detailScale; + float4 baseSample = SampleTerrainTexture(mat.baseAlbedoIndex, baseUV); + float4 mask = TerrainNormalizeMask(SampleTerrainTexture(mat.blendMaskIndex, baseUV)); + float4 detailR = SampleTerrainTexture(mat.detailR_Index, detailUV); + float4 detailG = SampleTerrainTexture(mat.detailG_Index, detailUV); + float4 detailB = SampleTerrainTexture(mat.detailB_Index, detailUV); + float4 detailA = SampleTerrainTexture(mat.detailA_Index, detailUV); + float3 blendedDetail = TerrainBlendRGB(detailR.rgb, detailG.rgb, detailB.rgb, detailA.rgb, mask); + return baseSample.rgb * blendedDetail * 2.0; +} + +struct BounceHit { + float3 position; + float3 normal; + float3 geoNormal; + float3 albedo; + float3 baked; + float3 emissive; + float metallic; + float roughness; + float sunOcc; + float t; + bool valid; +}; + +BounceHit TraceBounce(float3 origin, float3 direction) +{ + BounceHit result; + result.valid = false; + result.baked = 0; + result.emissive = 0; + result.sunOcc = 1.0; + result.albedo = 0; + result.metallic = 0; + result.roughness = 1; + result.t = 0; + result.position = origin; + result.normal = -direction; + result.geoNormal = -direction; + + float3 rayOrigin = origin; + for (uint skip = 0; skip < 4; skip++) { + RayDesc ray; + ray.Origin = rayOrigin; + ray.Direction = direction; + ray.TMin = 0.001; + ray.TMax = 10000.0; + RayQuery q; + q.TraceRayInline(g_SceneTLAS, RAY_FLAG_NONE, RT_MASK_GI, ray); + while (q.Proceed()) { + if (q.CandidateType() == CANDIDATE_NON_OPAQUE_TRIANGLE) { + uint candBatch = q.CandidateInstanceID() + q.CandidateGeometryIndex(); + if (IsParticleBatch(candBatch)) + continue; + if (IsGrassBatch(candBatch)) { + if (GrassTexelOpaque(g_GrassVB, g_GrassIB, g_BatchInfo, candBatch, + q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics(), + g_DetailAtlasIndex)) + q.CommitNonOpaqueTriangleHit(); + } else if (IsSkinnedBatch(candBatch)) { + if (SkinnedMaterialOpaque(g_SkinnedVB, g_SkinnedIB, g_BatchInfo, candBatch, + q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics())) + q.CommitNonOpaqueTriangleHit(); + } else if (MegaMaterialOpaque(g_MegaVB, g_MegaIB, g_BatchInfo, candBatch, + q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics()) || + MegaEmissiveHit(g_MegaVB, g_MegaIB, g_BatchInfo, candBatch, + q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics())) { + q.CommitNonOpaqueTriangleHit(); + } + } + } + if (q.CommittedStatus() != COMMITTED_TRIANGLE_HIT) + return result; + + uint batchIdx = q.CommittedInstanceID() + q.CommittedGeometryIndex(); + if (IsParticleBatch(batchIdx)) { + rayOrigin = rayOrigin + direction * (q.CommittedRayT() + 0.002); + continue; + } + RTBatchInfo info = g_BatchInfo[batchIdx]; + uint primIdx = q.CommittedPrimitiveIndex(); + float2 bary = q.CommittedTriangleBarycentrics(); + float3x4 objectToWorld = q.CommittedObjectToWorld3x4(); + + float3 hitN, geoN; + float2 hitUV; + float hemi = 0.55; + float2 lmUV = 0; + if (IsGrassBatch(batchIdx)) { + hitUV = GetSkinnedHitUV(g_GrassVB, g_GrassIB, info, primIdx, bary); + hitN = GetSkinnedHitNormal(g_GrassVB, g_GrassIB, info, primIdx, bary); + geoN = GetSkinnedHitGeoNormal(g_GrassVB, g_GrassIB, info, primIdx); + } else if (IsSkinnedBatch(batchIdx)) { + hitUV = GetSkinnedHitUV(g_SkinnedVB, g_SkinnedIB, info, primIdx, bary); + hitN = GetSkinnedHitNormal(g_SkinnedVB, g_SkinnedIB, info, primIdx, bary); + geoN = GetSkinnedHitGeoNormal(g_SkinnedVB, g_SkinnedIB, info, primIdx); + } else { + hitUV = GetHitUV(g_MegaVB, g_MegaIB, info, primIdx, bary); + hitN = TransformNormalToWorld(GetHitNormal(g_MegaVB, g_MegaIB, info, primIdx, bary), objectToWorld); + geoN = TransformNormalToWorld(GetHitGeometricNormal(g_MegaVB, g_MegaIB, info, primIdx), objectToWorld); + hemi = GetHitHemi(g_MegaVB, g_MegaIB, info, primIdx, bary); + lmUV = GetHitLightmapUV(g_MegaVB, g_MegaIB, info, primIdx, bary); + } + if (dot(geoN, direction) > 0) geoN = -geoN; + if (dot(hitN, geoN) < 0) hitN = -hitN; + + float3 albedo = float3(0.5, 0.5, 0.5); + float metallic = 0; + float roughness = 1.0; + float3 baked = 0; + float sunOcc = 1.0; + + if (IsGrassBatch(batchIdx)) { + if (g_DetailAtlasIndex > 0) + albedo = GetBindlessTexture(g_DetailAtlasIndex).SampleLevel(smp_linear, hitUV, 0).rgb; + else + albedo = lerp(float3(0.08, 0.18, 0.03), float3(0.15, 0.35, 0.06), 1.0 - hitUV.y); + baked = ShadeBakedFromHemi(0.55, albedo, g_HemiColor.rgb); + } else if (IsTerrainBatch(batchIdx)) { + TerrainMaterialData tmat = g_TerrainMaterials[info.materialID]; + float hitDist = q.CommittedRayT(); + if (hitDist > g_LodDist * 0.5) + albedo = SampleTerrainTexture(tmat.baseAlbedoIndex, hitUV).rgb; + else + albedo = SampleTerrainAlbedo(tmat, hitUV); + baked = ShadeBakedFromTerrainLmap(tmat, lmUV, albedo, g_HemiColor.rgb, hemi); + } else { + MaterialData mat = g_Materials[info.materialID]; + float4 diffuse = SampleDiffuseLevel(mat, hitUV); + albedo = diffuse.rgb; + bool water = (mat.flags & MAT_FLAG_WATER) != 0; + bool emHit = EmissiveTexelLit(mat, diffuse); + if (!water && !emHit && !MaterialDiffuseOpaque(mat, diffuse)) { + rayOrigin = rayOrigin + direction * (q.CommittedRayT() + 0.002); + continue; + } + if ((mat.flags & MAT_FLAG_HAS_PBR) != 0) { + float3 pbr = SamplePBR(mat, hitUV); + metallic = pbr.r; + roughness = pbr.g; + } + if (water) { + rayOrigin = rayOrigin + direction * (q.CommittedRayT() + 0.002); + continue; + } + baked = ShadeBakedFromHemi(hemi, albedo, g_HemiColor.rgb); + if ((mat.flags & MAT_FLAG_HAS_LMAP) != 0 && mat.lmapIndex != INVALID_TEXTURE_INDEX + && dot(lmUV, lmUV) > 1e-8) + { + float4 lmh = GetBindlessTexture(mat.lmapIndex).SampleLevel(smp_rtlinear, lmUV, 0); + sunOcc = smoothstep(0.04, 0.96, saturate(lmh.g)); + baked = ShadeBakedFromHemi(max(hemi, lmh.a), albedo, g_HemiColor.rgb) * sunOcc; + } + if (emHit && mat.emissiveIntensity > 0.0) + result.emissive = GlowEmissiveRgb(diffuse, mat.emissiveIntensity); + } + + result.position = rayOrigin + direction * q.CommittedRayT(); + result.normal = hitN; + result.geoNormal = geoN; + result.albedo = albedo; + result.baked = baked; + result.metallic = metallic; + result.roughness = roughness; + result.sunOcc = sunOcc; + result.t = q.CommittedRayT(); + result.valid = true; + return result; + } + return result; +} + +GPULightDataDI AsDI(GPULightData light) +{ + GPULightDataDI d; + d.positionAndInvRangeSq = light.positionAndInvRangeSq; + d.colorAndRange = light.colorAndRange; + d.directionAndSpotScale = light.directionAndSpotScale; + d.spotParamsAndType = light.spotParamsAndType; + d.spotVP = light.spotVP; + return d; +} + +uint SampleDILightIS(float u, out float lightPdf) +{ + uint count = (uint)g_DISampleParams.x; + float powerSum = g_DISampleParams.y; + lightPdf = 0; + if (count == 0 || powerSum <= 1e-8) + return 0; + float target = u * powerSum; + uint lo = 0; + uint hi = count; + while (lo < hi) { + uint mid = (lo + hi) >> 1; + if (g_DILightCDF[mid] < target) + lo = mid + 1; + else + hi = mid; + } + uint i = min(lo, count - 1); + float prev = (i == 0) ? 0.0 : g_DILightCDF[i - 1]; + float w = max(g_DILightCDF[i] - prev, 1e-8); + lightPdf = w / powerSum; + return g_DILightIndices[i]; +} + +float3 ShadeLocalLightNEE(GPULightData light, float3 worldPos, float3 biasedPos, float3 N, float3 V, + float3 albedo, float metallic, float roughness) +{ + GPULightDataDI di = AsDI(light); + float3 L, lightColor; + float dist; + float atten = EvalLocalLightAttenuationDI(di, worldPos, L, dist, lightColor); + if (atten <= 0.001) + return 0; + float3 lit = PBRDirectLighting(albedo, N, V, L, lightColor * atten, metallic, roughness, 1); + if (Luminance(lit) <= 1e-6) + return 0; + float shadowL = TraceShadowRay(biasedPos, L, max(dist * 0.998, 0.05)); + return min(lit * shadowL, RESTIR_MAX_RADIANCE); +} + +float3 ShadeClusterLights(float3 worldPos, float3 biasedPos, float3 N, float3 V, + float3 albedo, float metallic, float roughness, int2 fullPx, bool shadeAll) +{ + float3 accum = 0; + if (g_NumLights == 0) + return accum; + float linearDepth = max(abs(mul(g_WorldToView, float4(worldPos, 1.0)).z), 0.01); + uint clusterIdx = GetClusterIndex(float2(fullPx) + 0.5, linearDepth, g_ClusterParams.xyz, g_ClusterDepth); + uint2 clusterData = g_ClusterGrid[clusterIdx]; + uint lightOffset = clusterData.x; + uint lightCount = min(clusterData.y, RESTIR_MAX_LIGHTS_PER_TILE); + uint cap = shadeAll ? min(lightCount, RESTIR_MAX_CLUSTER_LIGHTS) : min(lightCount, RESTIR_MAX_CLUSTER_LIGHTS); + for (uint ci = 0; ci < cap; ci++) { + uint lightId = g_LightIndexList[lightOffset + ci]; + if (lightId >= g_NumLights) + continue; + accum += ShadeLocalLightNEE(g_Lights[lightId], worldPos, biasedPos, N, V, albedo, metallic, roughness); + } + return accum; +} + +float3 ShadeHitNEE(BounceHit h, float3 primaryPos, float3 sunDir, float3 sunColor, uint2 pixel, inout uint rng) +{ + if (Luminance(h.albedo) < 1e-4 && any(h.emissive > 0)) + return min(h.emissive, RESTIR_MAX_RADIANCE); + float3 hitBiased = h.position + h.geoNormal * 0.005; + float3 hitV = normalize(primaryPos - h.position); + float hitShadow = TraceBounceSunVis(hitBiased, sunDir, 10000.0, pixel) * SampleGrassShadow(hitBiased) * h.sunOcc; + float3 Lo = ShadeHitDirect(h.albedo, h.normal, hitV, h.metallic, h.roughness, sunDir, sunColor, hitShadow, h.baked); + Lo += min(h.emissive, RESTIR_MAX_RADIANCE); + float lightPdf = 0; + uint lid = SampleDILightIS(rand_float(rng), lightPdf); + if (lightPdf > 1e-8 && lid < g_NumLights) + Lo += ShadeLocalLightNEE(g_Lights[lid], h.position, hitBiased, h.normal, hitV, h.albedo, h.metallic, h.roughness); + return min(Lo, RESTIR_MAX_RADIANCE); +} + +[numthreads(8, 8, 1)] +void main(uint3 id : SV_DispatchThreadID) +{ + uint2 pixel = id.xy; + uint w = (uint)g_ScreenSize.x; + uint h = (uint)g_ScreenSize.y; + if (pixel.x >= w || pixel.y >= h) + return; + + float2 giSize = max(g_ScreenSize, 1.0); + float2 fullSize = max(float2(g_FullWidth, g_FullHeight), 1.0); + float depth = RestirLoadDepth(t_Depth, pixel, giSize, fullSize); + if (depth <= 0.0 || depth >= 1.0) { + u_PTA[pixel] = 0; + u_PTB[pixel] = 0; + u_NoisyDiffuse[pixel] = 0; + u_NoisySpecular[pixel] = 0; + u_HitDistance[pixel] = 0; + u_DirectLighting[pixel] = 0; + u_SunVis[pixel] = 0; + return; + } + + float2 uv = (float2(pixel) + 0.5) / giSize; + float4 wp = RestirLoadTex4(t_WorldPos, pixel, giSize, fullSize); + float4 bc = RestirLoadTex4(t_BaseColor, pixel, giSize, fullSize); + float4 nd = RestirLoadTex4(t_Normal, pixel, giSize, fullSize); + float3 worldPos = ResolveGBufferWorldPos(uv, depth, wp, g_InvViewProj); + float3 N = normalize(nd.xyz); + float roughness = max(abs(nd.w), MIN_ROUGHNESS); + float surf = SurfMarkFromGBuffer(wp.w, bc.a); + float sssMask = 0; + float metallic = UnpackGBufferMetallic(bc.a, IsHudSurfMark(surf) || IsCharSurfMark(surf), sssMask); + bool isVeg = IsFoliageSurfMark(surf); + bool isHud = IsHudSurfMark(surf); + float3 albedo = bc.rgb; + if (g_WetEnabled != 0 && !isHud && !IsCharSurfMark(surf)) { + float wet = saturate(RestirLoadTex1(t_WetAccum, pixel, giSize, fullSize) * g_WetStrength); + albedo = lerp(albedo, albedo * 0.35, wet); + roughness = lerp(roughness, max(roughness * 0.35, 0.08), wet); + } + float3 V = normalize(g_CameraPos.xyz - worldPos); + float3 biased = worldPos + N * (isHud ? 0.03 : 0.01); + float viewDist = length(worldPos - g_CameraPos.xyz); + uint rng = pcg_hash(pixel.x + pixel.y * 19891u + g_FrameIndex * 33461u); + uint pathSeed = rng; + int2 fullPx = RestirFullPixel(pixel, giSize, fullSize); + + float3 sunDir = normalize(-g_SunDir_Intensity.xyz); + float3 sunCol = g_SunColor_SkyWeight.xyz * g_SunDir_Intensity.w; + float skyOpen = saturate(RestirLoadTex1(t_SkyOpen, pixel, giSize, fullSize)); + float rawSunVis = TraceSoftShadowSun(biased, sunDir, viewDist, pixel); + rawSunVis *= SampleGrassShadow(biased); + float sunVis = FilterSunVisibility(rawSunVis, pixel, giSize, fullSize, depth, N, worldPos, IsCharSurfMark(surf)); + u_SunVis[pixel] = sunVis; + + float3 direct = 0; + if (sunVis > 0.001) { + float3 Ns = N; + if (isVeg && dot(N, sunDir) < 0.0) + Ns = -N; + float3 sunRadiance = sunCol * (isVeg ? 1.55 : 1.35) * sunVis; + direct += PBRDirectLighting(albedo, Ns, V, sunDir, sunRadiance, metallic, roughness, 1u); + if (isVeg && sssMask > 0.01) + direct += EvaluateFoliageSSS(albedo, Ns, V, sunDir, sunCol * 1.35, sunVis, LeafSSSTint(), + saturate(0.35 + sssMask * 0.3), sssMask); + } + + bool stableDirect = IsCharSurfMark(surf) || IsInteriorSurfMark(surf); + direct += ShadeClusterLights(worldPos, biased, N, V, albedo, metallic, roughness, fullPx, stableDirect); + if (!stableDirect) { + uint diCount = (uint)g_DISampleParams.x; + uint diCandidates = min((uint)g_DISampleParams.z, RESTIR_MAX_LOCAL_LIGHT_SAMPLES); + for (uint li = 0; li < diCandidates; li++) { + if (diCount == 0) + break; + float lightPdf = 0; + uint lightId = SampleDILightIS(rand_float(rng), lightPdf); + if (lightPdf <= 1e-8 || lightId >= g_NumLights) + continue; + direct += ShadeLocalLightNEE(g_Lights[lightId], worldPos, biased, N, V, albedo, metallic, roughness); + } + } + + if (isVeg && skyOpen > 0.01) { + float skyVis = TraceBounceSunVis(biased + float3(0, 0.02, 0), float3(0, 1, 0), 10000.0, pixel); + if (skyVis > 0.001) { + float wrap = saturate(abs(N.y) * 0.35 + 0.65); + float3 LoSky = SampleSkyDiffuse(float3(0, 1, 0)) * skyVis; + float3 F0v = CalculateF0(albedo, metallic); + float3 kD = (1.0 - F_Schlick(wrap, F0v)) * (1.0 - metallic); + direct += min(LoSky * kD * albedo * wrap, RESTIR_MAX_RADIANCE); + } + } + + PTReservoir res = EmptyPTReservoir(); + float3 noisyDiff = 0; + float3 noisySpec = 0; + float hitDist = 0; + float3 throughput = 1; + float3 kd = albedo * (1.0 - metallic); + float3 origin = biased; + float3 dirN = N; + if (isVeg && N.y < 0.0) + dirN = -N; + float3 dir = cosine_weighted_hemisphere(float2(rand_float(rng), rand_float(rng)), dirN); + uint bounces = clamp(g_Bounces, 1u, 8u); + bool pickedRc = false; + float3 bounceN = N; + float bounceRough = roughness; + + [loop] + for (uint b = 0; b < bounces; b++) { + BounceHit hit = TraceBounce(origin, dir); + float3 Lo; + float3 hpos; + float3 hN; + if (!hit.valid) { + Lo = SampleSkyDiffuse(dir); + hpos = origin + dir * 80.0; + hN = -dir; + } else { + Lo = ShadeHitNEE(hit, origin, sunDir, sunCol, pixel, rng); + hpos = hit.position; + hN = hit.normal; + } + Lo = min(Lo, RESTIR_MAX_RADIANCE); + noisyDiff += throughput * kd * Lo; + float alpha = max(bounceRough * bounceRough, 0.04); + bool rcOk = (b == 0) ? (alpha >= RESTIR_PT_RC_ALPHA) : PTFootprintOk(hpos, origin, bounceN, alpha); + if (!pickedRc && rcOk) { + res.rcPos = hpos; + res.rcN = hN; + res.Lo = Lo; + res.hitDist = length(hpos - worldPos); + res.flags = PackPTFlags(b + 2, 1, 0); + res.seed = pathSeed; + res.M = 1; + res.W = 1; + res.targetPdf = max(Luminance(Lo), 1e-4); + pickedRc = true; + } + if (b == 0) { + float3 F0 = CalculateF0(albedo, metallic); + float3 Fenv = NRD_EnvironmentTerm_Rtg(F0, abs(dot(N, V)), roughness); + noisySpec += Lo * Fenv; + hitDist = length(hpos - worldPos); + } + if (!hit.valid) + break; + throughput *= kd; + kd = hit.albedo * (1.0 - hit.metallic); + float p = max(max(throughput.r, throughput.g), throughput.b); + if (b >= 2) { + if (rand_float(rng) > p) + break; + throughput /= max(p, 1e-3); + } + origin = hpos + hit.geoNormal * 0.005; + bounceN = hN; + bounceRough = hit.roughness; + dir = cosine_weighted_hemisphere(float2(rand_float(rng), rand_float(rng)), hN); + } + + if (!pickedRc && any(noisyDiff > 0)) { + res.rcPos = worldPos + V * 4.0; + res.rcN = N; + res.Lo = noisyDiff; + res.hitDist = 4.0; + res.flags = PackPTFlags(2, 0, 0); + res.seed = pathSeed; + res.M = 1; + res.W = 1; + res.targetPdf = max(Luminance(res.Lo), 1e-4); + } + + uint4 A, B; + PackPTReservoir(res, A, B); + u_PTA[pixel] = A; + u_PTB[pixel] = B; + u_NoisyDiffuse[pixel] = float4(min(noisyDiff, RESTIR_MAX_RADIANCE), 1); + u_NoisySpecular[pixel] = float4(min(noisySpec, RESTIR_MAX_RADIANCE), hitDist); + u_HitDistance[pixel] = hitDist; + u_DirectLighting[pixel] = float4(min(direct, RESTIR_MAX_RADIANCE), 1); +} diff --git a/res/gamedata/shaders/r5/restir_pt_spatial.cs b/res/gamedata/shaders/r5/restir_pt_spatial.cs new file mode 100644 index 00000000000..081312a685e --- /dev/null +++ b/res/gamedata/shaders/r5/restir_pt_spatial.cs @@ -0,0 +1,118 @@ +#include "common.h" +#include "rt_common.h" +#include "shared/pbr_brdf.h" +#include "shared/basecolor_pack.h" +#include "shared/surface_marks.h" +#include "restir_gi_common.h" +#include "restir_pt_common.h" + +cbuffer ReSTIRPTSpatial : register(b5) { + float4x4 g_InvViewProj; + float4 g_CameraPos; + float2 g_ScreenSize; + float2 g_InvScreenSize; + uint g_FrameIndex; + uint g_PairIndex; + uint g_FlipX; + uint g_FlipY; + int g_OffX; + int g_OffY; + uint g_Pass; + uint g_Pad; +}; + +Texture2D t_CurrA : register(t0); +Texture2D t_CurrB : register(t1); +Texture2D t_Depth : register(t2); +Texture2D t_Normal : register(t3); +Texture2D t_WorldPos : register(t4); +Texture2D t_Pair : register(t5); +Texture2D t_BaseColor : register(t6); + +RWTexture2D u_OutA : register(u0); +RWTexture2D u_OutB : register(u1); +RWTexture2D u_NoisyDiffuse : register(u2); +RWTexture2D u_NoisySpecular : register(u3); + +int2 PairNeighbor(uint2 pixel, uint w, uint h) +{ + uint2 dims; + t_Pair.GetDimensions(dims.x, dims.y); + int2 tc = int2(pixel) + int2(g_OffX, g_OffY); + if (g_FlipX) + tc.x = (int)w - 1 - tc.x; + if (g_FlipY) + tc.y = (int)h - 1 - tc.y; + int2 uv = int2((uint)tc.x % max(dims.x, 1u), (uint)tc.y % max(dims.y, 1u)); + float2 d = t_Pair.Load(int3(uv, 0)); + int2 nb = int2(pixel) + int2(round(d.x), round(d.y)); + return clamp(nb, int2(0, 0), int2(w, h) - 1); +} + +[numthreads(8, 8, 1)] +void main(uint3 id : SV_DispatchThreadID) +{ + uint2 pixel = id.xy; + uint w = (uint)g_ScreenSize.x; + uint h = (uint)g_ScreenSize.y; + if (pixel.x >= w || pixel.y >= h) + return; + + float2 giSize = g_ScreenSize; + uint fw = 0, fh = 0; + t_Depth.GetDimensions(fw, fh); + float2 fullSize = float2(max(fw, 1u), max(fh, 1u)); + float depth = RestirLoadDepth(t_Depth, pixel, giSize, fullSize); + PTReservoir curr = UnpackPTReservoir(t_CurrA[pixel], t_CurrB[pixel]); + if (depth <= 0 || depth >= 1) { + u_OutA[pixel] = t_CurrA[pixel]; + u_OutB[pixel] = t_CurrB[pixel]; + return; + } + + float2 uv = (float2(pixel) + 0.5) * g_InvScreenSize; + float4 wp = RestirLoadTex4(t_WorldPos, pixel, giSize, fullSize); + float3 worldPos = ResolveGBufferWorldPos(uv, depth, wp, g_InvViewProj); + float3 N = normalize(RestirLoadTex4(t_Normal, pixel, giSize, fullSize).xyz); + int2 np = PairNeighbor(pixel, w, h); + PTReservoir nb = UnpackPTReservoir(t_CurrA[np], t_CurrB[np]); + uint rng = pcg_hash(pixel.x + pixel.y * 577u + g_FrameIndex * 31u + g_PairIndex); + + float4 bc = RestirLoadTex4(t_BaseColor, pixel, giSize, fullSize); + float surf = SurfMarkFromGBuffer(wp.w, bc.a); + float4 nbWp = RestirLoadTex4(t_WorldPos, uint2(np), giSize, fullSize); + float nbSurf = SurfMarkFromGBuffer(nbWp.w, RestirLoadTex4(t_BaseColor, uint2(np), giSize, fullSize).a); + bool skipNb = IsCharSurfMark(surf) || IsHudSurfMark(surf) || IsCharSurfMark(nbSurf) || IsHudSurfMark(nbSurf); + + if (!skipNb && IsPTReservoirValid(nb)) { + float nd = RestirLoadDepth(t_Depth, uint2(np), giSize, fullSize); + float3 nN = normalize(RestirLoadTex4(t_Normal, uint2(np), giSize, fullSize).xyz); + if (nd > 0 && nd < 1 && dot(N, nN) > 0.9 && abs(nd - depth) * 80.0 < 1.0) { + float2 nuv = (float2(np) + 0.5) * g_InvScreenSize; + float3 npos = ResolveGBufferWorldPos(nuv, nd, nbWp, g_InvViewProj); + float jac = HybridShiftJacobian(nb.rcN, worldPos, npos, nb.rcPos); + float3 wi = normalize(nb.rcPos - worldPos); + if (dot(N, wi) > 0.02) { + float w = Luminance(nb.Lo) * nb.W * jac; + if (!IsPTReservoirValid(curr)) + curr = nb; + else + PTReservoirUpdate(curr, w, nb, rng); + } + } + } + + if (IsPTReservoirValid(curr) && curr.M > 0) { + float t = max(Luminance(curr.Lo), 1e-4); + curr.W = min(curr.targetPdf / (t * curr.M), 4.0); + } + uint4 A, B; + PackPTReservoir(curr, A, B); + u_OutA[pixel] = A; + u_OutB[pixel] = B; + float sss = 0; + float metallic = UnpackGBufferMetallic(bc.a, IsHudSurfMark(surf) || IsCharSurfMark(surf), sss); + float3 diff = ShadePTReservoir(curr, worldPos, N, bc.rgb, metallic); + u_NoisyDiffuse[pixel] = float4(min(diff, RESTIR_MAX_RADIANCE), 1); + u_NoisySpecular[pixel] = float4(min(curr.Lo * curr.W * 0.15, RESTIR_MAX_RADIANCE), curr.hitDist); +} diff --git a/res/gamedata/shaders/r5/restir_pt_temporal.cs b/res/gamedata/shaders/r5/restir_pt_temporal.cs new file mode 100644 index 00000000000..5f014b0cc3f --- /dev/null +++ b/res/gamedata/shaders/r5/restir_pt_temporal.cs @@ -0,0 +1,139 @@ +#include "common.h" +#include "rt_common.h" +#include "shared/pbr_brdf.h" +#include "shared/basecolor_pack.h" +#include "shared/surface_marks.h" +#include "restir_gi_common.h" +#include "restir_pt_common.h" + +cbuffer ReSTIRPTTemporal : register(b5) { + float4x4 g_InvViewProj; + float4x4 g_PrevInvViewProj; + float4 g_CameraPos; + float2 g_ScreenSize; + float2 g_InvScreenSize; + uint g_FrameIndex; + float g_CCap; + uint g_HasPrev; + uint g_Pad; +}; + +Texture2D t_CurrA : register(t0); +Texture2D t_CurrB : register(t1); +Texture2D t_PrevA : register(t2); +Texture2D t_PrevB : register(t3); +Texture2D t_MotionVectors : register(t4); +Texture2D t_Depth : register(t5); +Texture2D t_Normal : register(t6); +Texture2D t_PrevNormal : register(t7); +Texture2D t_WorldPos : register(t8); +Texture2D t_PrevDepth : register(t9); +Texture2D t_DupMap : register(t10); +Texture2D t_BaseColor : register(t11); +Texture2D t_SkyOpen : register(t12); + +RWTexture2D u_OutA : register(u0); +RWTexture2D u_OutB : register(u1); +RWTexture2D u_NoisyDiffuse : register(u2); +RWTexture2D u_NoisySpecular : register(u3); +RWTexture2D u_HitDistance : register(u4); + +[numthreads(8, 8, 1)] +void main(uint3 id : SV_DispatchThreadID) +{ + uint2 pixel = id.xy; + uint w = (uint)g_ScreenSize.x; + uint h = (uint)g_ScreenSize.y; + if (pixel.x >= w || pixel.y >= h) + return; + + float2 giSize = g_ScreenSize; + uint fw = 0, fh = 0; + t_Depth.GetDimensions(fw, fh); + float2 fullSize = float2(max(fw, 1u), max(fh, 1u)); + float depth = RestirLoadDepth(t_Depth, pixel, giSize, fullSize); + if (depth <= 0.0 || depth >= 1.0) { + u_OutA[pixel] = 0; + u_OutB[pixel] = 0; + return; + } + + float2 uv = (float2(pixel) + 0.5) * g_InvScreenSize; + float4 wp = RestirLoadTex4(t_WorldPos, pixel, giSize, fullSize); + float4 bc = RestirLoadTex4(t_BaseColor, pixel, giSize, fullSize); + float3 worldPos = ResolveGBufferWorldPos(uv, depth, wp, g_InvViewProj); + float3 N = normalize(RestirLoadTex4(t_Normal, pixel, giSize, fullSize).xyz); + float surf = SurfMarkFromGBuffer(wp.w, bc.a); + PTReservoir curr = UnpackPTReservoir(t_CurrA[pixel], t_CurrB[pixel]); + PTReservoir output = curr; + uint rng = pcg_hash(pixel.x + pixel.y * 9001u + g_FrameIndex * 17u); + + int2 fullPx = RestirFullPixel(pixel, giSize, fullSize); + float4 mv4 = t_MotionVectors.Load(int3(fullPx, 0)); + float2 motion = mv4.xy; + float2 dual = mv4.zw; + float D = t_DupMap.Load(int3(pixel, 0)); + float cap = PTCapFromDup(D, g_CCap); + float motionPx = length(motion * fullSize); + float viewDist = length(worldPos - g_CameraPos.xyz); + + if (g_HasPrev != 0 && !IsCharSurfMark(surf) && !IsHudSurfMark(surf) && motionPx < 16.0) { + float2 prevUV = uv + motion; + if (depth <= 1e-7 && (any(prevUV < 0) || any(prevUV >= 1))) + prevUV = uv + dual; + if (all(prevUV >= 0) && all(prevUV < 1)) { + int2 pp = clamp(int2(prevUV * giSize), int2(0, 0), int2(w, h) - 1); + int2 pf = clamp(int2(prevUV * fullSize), int2(0, 0), int2(fullSize) - 1); + float pd = t_PrevDepth.Load(int3(pf, 0)); + float3 pN = normalize(t_PrevNormal.Load(int3(pf, 0)).xyz); + float2 puv = (float2(pf) + 0.5) / fullSize; + float3 prevWorld = ReconstructWorldPosReverseZ(puv, pd, g_PrevInvViewProj); + float posTol = (motionPx < 1.0) ? 0.05 : 0.035; + bool valid = pd > 0 && pd < 1 && dot(N, pN) > 0.9 && + length(worldPos - prevWorld) < posTol * max(min(viewDist, 4.0), 1.0); + float skyOpenC = saturate(RestirLoadTex1(t_SkyOpen, pixel, giSize, fullSize)); + float skyOpenP = saturate(t_SkyOpen.Load(int3(pf, 0))); + valid = valid && abs(skyOpenC - skyOpenP) <= 0.25; + if (valid) { + PTReservoir prev = UnpackPTReservoir(t_PrevA[pp], t_PrevB[pp]); + if (IsPTReservoirValid(prev)) { + float jac = HybridShiftJacobian(prev.rcN, worldPos, prevWorld, prev.rcPos); + float3 wi = normalize(prev.rcPos - worldPos); + if (dot(N, wi) > 0.02) { + uint m = min(prev.M, (uint)max(cap, 1.0)); + if (motionPx > 6.0) + m = max(1u, m / 4u); + else if (motionPx > 2.0) + m = max(1u, m / 2u); + float w = Luminance(prev.Lo) * prev.W * m * jac; + if (!IsPTReservoirValid(output)) { + output = prev; + output.M = m; + output.W = prev.W * jac; + } else { + PTReservoirUpdate(output, w, prev, rng); + output.M = min(output.M + m, (uint)max(cap, 1.0)); + } + } + } + } + } + } + + if (IsPTReservoirValid(output) && output.M > 0) { + float t = max(Luminance(output.Lo), 1e-4); + output.W = min(output.targetPdf / (t * output.M), 4.0); + output.age = min(output.age + 1, 255); + } + uint4 A, B; + PackPTReservoir(output, A, B); + u_OutA[pixel] = A; + u_OutB[pixel] = B; + + float sss = 0; + float metallic = UnpackGBufferMetallic(bc.a, IsHudSurfMark(surf) || IsCharSurfMark(surf), sss); + float3 diff = ShadePTReservoir(output, worldPos, N, bc.rgb, metallic); + u_NoisyDiffuse[pixel] = float4(min(diff, RESTIR_MAX_RADIANCE), 1); + u_NoisySpecular[pixel] = float4(min(output.Lo * output.W * 0.15, RESTIR_MAX_RADIANCE), output.hitDist); + u_HitDistance[pixel] = output.hitDist; +} diff --git a/res/gamedata/shaders/r5/restir_spec_temporal.cs b/res/gamedata/shaders/r5/restir_spec_temporal.cs new file mode 100644 index 00000000000..14db8fde1dc --- /dev/null +++ b/res/gamedata/shaders/r5/restir_spec_temporal.cs @@ -0,0 +1,196 @@ +#include "common.h" +#include "rt_common.h" +#include "shared/pbr_brdf.h" +#include "shared/nrd_helpers.h" +#include "shared/basecolor_pack.h" +#include "shared/surface_marks.h" +#include "restir_gi_common.h" + +cbuffer ReSTIRSpecParams : register(b5) { + float4x4 g_InvViewProj; + float4x4 g_PrevInvViewProj; + float4x4 g_PrevViewProj; + float4 g_CameraPos; + float2 g_ScreenSize; + float2 g_InvScreenSize; + uint g_FrameIndex; + uint g_SpatialSamples; + float g_SpatialRadius; + uint g_HasPrev; +}; + +Texture2D t_CurrA : register(t0); +Texture2D t_CurrB : register(t1); +Texture2D t_PrevA : register(t2); +Texture2D t_PrevB : register(t3); +Texture2D t_MotionVectors : register(t4); +Texture2D t_Depth : register(t5); +Texture2D t_Normal : register(t6); +Texture2D t_PrevNormal : register(t7); +Texture2D t_BaseColor : register(t8); +Texture2D t_WorldPos : register(t9); +Texture2D t_PrevDepth : register(t10); + +RWTexture2D u_OutA : register(u0); +RWTexture2D u_OutB : register(u1); +RWTexture2D u_NoisySpecular : register(u2); + +float2 ProjectToUv(float4x4 viewProj, float3 worldPos) +{ + float4 clip = mul(viewProj, float4(worldPos, 1.0)); + float2 ndc = clip.xy / max(abs(clip.w), 1e-5); + ndc.y = -ndc.y; + return ndc * 0.5 + 0.5; +} + +float SpecReuseScale(float roughness) +{ + if (roughness < 0.3) + return 1.0; + if (roughness > 0.6) + return 0.25; + return lerp(1.0, 0.25, saturate((roughness - 0.3) / 0.3)); +} + +float SpecTargetLum(GIReservoir r, float3 worldPos, float3 N, float3 V, float3 albedo, float metallic, float roughness) +{ + if (!IsReservoirValid(r)) + return 0; + float3 F0 = CalculateF0(albedo, metallic); + float3 Fenv = NRD_EnvironmentTerm_Rtg(F0, abs(dot(N, V)), roughness); + return Luminance(r.Lo * Fenv); +} + +[numthreads(8, 8, 1)] +void main(uint3 dispatchID : SV_DispatchThreadID) +{ + uint2 pixel = dispatchID.xy; + uint width = (uint)g_ScreenSize.x; + uint height = (uint)g_ScreenSize.y; + if (pixel.x >= width || pixel.y >= height) + return; + + float2 giSize = g_ScreenSize; + uint fullW = 0, fullH = 0; + t_Depth.GetDimensions(fullW, fullH); + float2 fullSize = float2(max(fullW, 1u), max(fullH, 1u)); + float depth = RestirLoadDepth(t_Depth, pixel, giSize, fullSize); + if (depth <= 0.0 || depth >= 1.0) { + u_OutA[pixel] = 0; + u_OutB[pixel] = 0; + u_NoisySpecular[pixel] = 0; + return; + } + + float4 baseColorData = RestirLoadTex4(t_BaseColor, pixel, giSize, fullSize); + float4 worldPosData = RestirLoadTex4(t_WorldPos, pixel, giSize, fullSize); + float surfMark = SurfMarkFromGBuffer(worldPosData.w, baseColorData.a); + if (IsWaterSurfMark(surfMark)) { + u_OutA[pixel] = 0; + u_OutB[pixel] = 0; + u_NoisySpecular[pixel] = 0; + return; + } + + float2 uv = (float2(pixel) + 0.5) * g_InvScreenSize; + float3 worldPos = ResolveGBufferWorldPos(uv, depth, worldPosData, g_InvViewProj); + float4 normalData = RestirLoadTex4(t_Normal, pixel, giSize, fullSize); + float3 N = normalize(normalData.xyz); + float roughness = max(abs(normalData.w), MIN_ROUGHNESS); + float3 albedo = baseColorData.rgb; + float sss = 0; + float metallic = UnpackGBufferMetallic(baseColorData.a, IsHudSurfMark(surfMark) || IsCharSurfMark(surfMark), sss); + float3 V = normalize(g_CameraPos.xyz - worldPos); + float reuse = SpecReuseScale(roughness); + + GIReservoir curr = UnpackReservoirAB(t_CurrA.Load(int3(pixel, 0)), t_CurrB.Load(int3(pixel, 0))); + uint rng = pcg_hash(pixel.x + pixel.y * 7919u + g_FrameIndex * 33461u); + GIReservoir output = EmptyReservoir(); + float targetCurr = SpecTargetLum(curr, worldPos, N, V, albedo, metallic, roughness); + if (targetCurr > 0) { + output = curr; + output.w_sum = targetCurr * curr.W; + output.M = 1; + } + + int2 fullPx = RestirFullPixel(pixel, giSize, fullSize); + float2 motion = t_MotionVectors.Load(int3(fullPx, 0)); + if (g_HasPrev != 0 && reuse > 0.05 && !IsCharSurfMark(surfMark)) { + float3 virt = curr.samplePos; + if (!IsReservoirValid(curr)) + virt = worldPos + V * 4.0; + float2 prevUV = ProjectToUv(g_PrevViewProj, virt); + if (any(prevUV < 0.0) || any(prevUV >= 1.0)) + prevUV = uv + motion; + if (all(prevUV >= 0.0) && all(prevUV < 1.0)) { + int2 prevPixel = clamp(int2(prevUV * giSize), int2(0, 0), int2(width, height) - 1); + int2 prevFull = clamp(int2(prevUV * fullSize), int2(0, 0), int2(fullSize) - 1); + float prevDepth = t_PrevDepth.Load(int3(prevFull, 0)); + float3 prevN = normalize(t_PrevNormal.Load(int3(prevFull, 0)).xyz); + if (prevDepth > 0.0 && prevDepth < 1.0 && dot(N, prevN) > 0.85) { + float2 prevNdc = (float2(prevFull) + 0.5) / fullSize; + float3 prevWorld = ReconstructWorldPosReverseZ(prevNdc, prevDepth, g_PrevInvViewProj); + GIReservoir prev = UnpackReservoirAB(t_PrevA.Load(int3(prevPixel, 0)), t_PrevB.Load(int3(prevPixel, 0))); + float targetPrev = SpecTargetLum(prev, worldPos, N, V, albedo, metallic, roughness); + if (targetPrev > 0) { + float jac = clamp(JacobianReconnectionShift(prev.sampleNormal, worldPos, prevWorld, prev.samplePos), 0.25, 4.0); + uint cap = TemporalMClamp(prev.M, prev.age, 8u); + cap = max(1u, (uint)round((float)cap * reuse)); + float w = targetPrev * prev.W * cap * jac; + if (!IsReservoirValid(output)) { + output = prev; + output.w_sum = targetPrev * prev.W * jac; + output.M = cap; + } else { + ReservoirUpdate(output, w, prev.samplePos, prev.sampleNormal, prev.Lo, prev.lightId, rng); + output.M += cap - 1; + } + } + } + } + } + + if (g_SpatialSamples > 0 && reuse > 0.2) { + uint n = min(g_SpatialSamples, 4u); + float rad = g_SpatialRadius * lerp(0.35, 1.0, saturate(1.0 - roughness * 1.5)); + [loop] + for (uint i = 0; i < n; i++) { + float2 u = float2(rand_float(rng), rand_float(rng)); + float ang = u.x * 6.2831853; + float r = sqrt(u.y) * rad; + int2 np = int2(pixel) + int2(round(float2(cos(ang), sin(ang)) * r)); + if (np.x < 0 || np.y < 0 || np.x >= (int)width || np.y >= (int)height) + continue; + float4 nn = RestirLoadTex4(t_Normal, uint2(np), giSize, fullSize); + if (dot(N, normalize(nn.xyz)) < 0.9) + continue; + if (abs(abs(nn.w) - roughness) > 0.12) + continue; + GIReservoir nb = UnpackReservoirAB(t_CurrA.Load(int3(np, 0)), t_CurrB.Load(int3(np, 0))); + float tnb = SpecTargetLum(nb, worldPos, N, V, albedo, metallic, roughness); + if (tnb <= 0) + continue; + float4 nwp = RestirLoadTex4(t_WorldPos, uint2(np), giSize, fullSize); + float nd = RestirLoadDepth(t_Depth, uint2(np), giSize, fullSize); + float2 nuv = (float2(np) + 0.5) * g_InvScreenSize; + float3 npos = ResolveGBufferWorldPos(nuv, nd, nwp, g_InvViewProj); + float jac = clamp(JacobianReconnectionShift(nb.sampleNormal, worldPos, npos, nb.samplePos), 0.25, 4.0); + ReservoirUpdate(output, tnb * nb.W * jac, nb.samplePos, nb.sampleNormal, nb.Lo, nb.lightId, rng); + } + } + + float outT = SpecTargetLum(output, worldPos, N, V, albedo, metallic, roughness); + output.Lo = min(output.Lo, RESTIR_MAX_RADIANCE); + output.W = (outT > 0 && output.M > 0) ? min(output.w_sum / (outT * output.M), 4.0) : 0; + output.age = min(output.age + 1, 127); + float4 A, B; + PackReservoirAB(output, A, B); + u_OutA[pixel] = A; + u_OutB[pixel] = B; + + float3 F0 = CalculateF0(albedo, metallic); + float3 Fenv = NRD_EnvironmentTerm_Rtg(F0, abs(dot(N, V)), roughness); + float3 spec = IsReservoirValid(output) ? min(output.Lo * Fenv * output.W, RESTIR_MAX_RADIANCE) : 0; + float hitDist = IsReservoirValid(output) ? length(output.samplePos - worldPos) : 0; + u_NoisySpecular[pixel] = float4(spec, hitDist); +} diff --git a/res/gamedata/shaders/r5/restir_sunshafts.cs b/res/gamedata/shaders/r5/restir_sunshafts.cs new file mode 100644 index 00000000000..28dcba050ee --- /dev/null +++ b/res/gamedata/shaders/r5/restir_sunshafts.cs @@ -0,0 +1,184 @@ +#include "bindless_common.h" +#include "rt_common.h" +#include "restir_gi_common.h" +#include "rt_grass_alpha.h" +#include "rt_material_alpha.h" +#include "rt_visibility.h" + +cbuffer SunshaftParams : register(b5) { + float4x4 g_InvViewProj; + float4x4 g_PrevViewProj; + float4 g_CameraPos; + float4 g_SunDir_Intensity; + float4 g_SunColor; + float2 g_ScreenSize; + float g_ShaftIntensity; + float g_ShaftLength; + uint g_IdentityStaticCount; + uint g_TerrainBatchCount; + uint g_SkinnedBatchStart; + uint g_GrassBatchStart; + uint g_DetailAtlasIndex; + uint g_HudSkinnedStart; + uint g_ShaftSteps; + uint g_AlphaEveryN; + float2 g_FullScreenSize; + uint g_ParticleBatchStart; + uint g_FrameIndex; + uint g_HasPrev; + float3 g_PrevSunDir; +}; + +RaytracingAccelerationStructure g_SceneTLAS : register(t1); +StructuredBuffer g_BatchInfo : register(t2); +ByteAddressBuffer g_MegaVB : register(t3); +ByteAddressBuffer g_MegaIB : register(t4); +ByteAddressBuffer g_SkinnedVB : register(t7); +ByteAddressBuffer g_SkinnedIB : register(t11); +ByteAddressBuffer g_GrassVB : register(t12); +ByteAddressBuffer g_GrassIB : register(t13); +Texture2D t_Depth : register(t14); +Texture3D t_BlueNoise : register(t16); +Texture2D t_PrevSunshafts : register(t17); +Texture2D t_PrevDepth : register(t18); + +RWTexture2D u_Sunshafts : register(u0); + +float2 ProjectToUv(float4x4 viewProj, float3 worldPos) +{ + float4 clip = mul(viewProj, float4(worldPos, 1.0)); + float2 ndc = clip.xy / max(abs(clip.w), 1e-5); + ndc.y = -ndc.y; + return ndc * 0.5 + 0.5; +} + +float BlueNoiseJitter(uint2 pixel) +{ + return SampleSTBN(t_BlueNoise, pixel, g_FrameIndex, 0); +} + +float TraceShaftVis(float3 origin, float3 dir, float tMax, uint2 pixel) +{ + return EvaluateSunVisibilityWithGrass( + g_SceneTLAS, g_BatchInfo, g_MegaVB, g_MegaIB, g_GrassVB, g_GrassIB, + origin, dir, tMax, + g_IdentityStaticCount, g_TerrainBatchCount, g_SkinnedBatchStart, g_GrassBatchStart, + g_ParticleBatchStart, g_DetailAtlasIndex, g_HudSkinnedStart, + t_BlueNoise, pixel, g_FrameIndex, RT_MASK_SHADOW); +} + +groupshared float3 gs_Curr[8][8]; + +[numthreads(8, 8, 1)] +void main(uint3 dispatchID : SV_DispatchThreadID, uint3 groupThread : SV_GroupThreadID) +{ + uint2 pixel = dispatchID.xy; + bool validPixel = pixel.x < (uint)g_ScreenSize.x && pixel.y < (uint)g_ScreenSize.y; + + float3 curr = 0; + float rayLen = 0; + float3 worldPos = 0; + bool isSky = false; + float2 uv = 0; + + if (validPixel) { + uv = (float2(pixel) + 0.5) / g_ScreenSize; + int2 fullPx = clamp(int2(uv * g_FullScreenSize), int2(0, 0), int2(g_FullScreenSize) - 1); + float depth = t_Depth.Load(int3(fullPx, 0)); + float3 camPos = g_CameraPos.xyz; + float3 lightDir = normalize(g_SunDir_Intensity.xyz); + float3 toSun = -lightDir; + float3 sunCol = g_SunColor.xyz; + isSky = depth <= 1e-5; + if (isSky) + worldPos = camPos + normalize(ReconstructWorldPosReverseZ(uv, 1e-3, g_InvViewProj) - camPos) * g_ShaftLength; + else + worldPos = ReconstructWorldPosReverseZ(uv, depth, g_InvViewProj); + float3 toSurf = worldPos - camPos; + rayLen = length(toSurf); + if (isSky) + rayLen = g_ShaftLength; + else + rayLen = min(rayLen, g_ShaftLength); + if (rayLen >= 0.3) { + float3 marchDir = toSurf / max(length(toSurf), 1e-4); + uint steps = clamp(g_ShaftSteps, 8u, 40u); + float density = g_ShaftIntensity / float(steps); + float res = 0.0; + float jitter = BlueNoiseJitter(pixel); + float shadowTMax = min(g_ShaftLength * 1.25, 800.0); + [loop] + for (uint i = 0; i < steps; i++) { + float tNorm = (float(i) + jitter) / float(steps); + float t = tNorm * rayLen; + if (t < 0.3) + continue; + float3 samplePos = camPos + marchDir * t + toSun * 0.15; + float vis = TraceShaftVis(samplePos, toSun, shadowTMax, pixel); + res += density * vis; + } + float fSaturation = -lightDir.y; + fSaturation = 0.5 * fSaturation + 0.5; + fSaturation = 0.80 * fSaturation + 0.20; + res *= saturate(fSaturation); + curr = max(res, 0.0) * sunCol; + } else { + rayLen = 0; + } + } + + gs_Curr[groupThread.y][groupThread.x] = curr; + GroupMemoryBarrierWithGroupSync(); + + float3 aabbMin = curr; + float3 aabbMax = curr; + [unroll] + for (int oy = -1; oy <= 1; oy++) { + [unroll] + for (int ox = -1; ox <= 1; ox++) { + int nx = (int)groupThread.x + ox; + int ny = (int)groupThread.y + oy; + if (nx < 0 || ny < 0 || nx > 7 || ny > 7) + continue; + float3 n = gs_Curr[ny][nx]; + aabbMin = min(aabbMin, n); + aabbMax = max(aabbMax, n); + } + } + + float histW = 0.0; + float3 hist = curr; + float sunStable = saturate(dot(normalize(g_SunDir_Intensity.xyz), normalize(g_PrevSunDir + 1e-5))); + if (validPixel && g_HasPrev != 0 && sunStable >= 0.9995) { + float2 prevUV = ProjectToUv(g_PrevViewProj, worldPos); + if (all(prevUV >= 0.0) && all(prevUV < 1.0)) { + float2 prevPx = prevUV * g_ScreenSize - 0.5; + int2 p0 = int2(floor(prevPx)); + float2 f = saturate(prevPx - float2(p0)); + int2 maxP = int2(g_ScreenSize) - 1; + int2 c00 = clamp(p0 + int2(0, 0), int2(0, 0), maxP); + int2 c10 = clamp(p0 + int2(1, 0), int2(0, 0), maxP); + int2 c01 = clamp(p0 + int2(0, 1), int2(0, 0), maxP); + int2 c11 = clamp(p0 + int2(1, 1), int2(0, 0), maxP); + float4 h00 = t_PrevSunshafts.Load(int3(c00, 0)); + float4 h10 = t_PrevSunshafts.Load(int3(c10, 0)); + float4 h01 = t_PrevSunshafts.Load(int3(c01, 0)); + float4 h11 = t_PrevSunshafts.Load(int3(c11, 0)); + hist = h00.rgb * (1.0 - f.x) * (1.0 - f.y) + h10.rgb * f.x * (1.0 - f.y) + + h01.rgb * (1.0 - f.x) * f.y + h11.rgb * f.x * f.y; + float histDepth = h00.a * (1.0 - f.x) * (1.0 - f.y) + h10.a * f.x * (1.0 - f.y) + + h01.a * (1.0 - f.x) * f.y + h11.a * f.x * f.y; + int2 prevFull = clamp(int2(prevUV * g_FullScreenSize), int2(0, 0), int2(g_FullScreenSize) - 1); + float prevD = t_PrevDepth.Load(int3(prevFull, 0)); + bool depthOk = isSky ? (prevD <= 1e-5) : (prevD > 1e-5 && abs(histDepth - rayLen) < max(0.15 * rayLen, 1.0)); + float2 motion = (prevUV - uv) * g_ScreenSize; + float motionPx = length(motion); + if (depthOk && motionPx < 48.0) + histW = lerp(0.60, 0.20, saturate(motionPx / 24.0)); + } + } + + hist = clamp(hist, aabbMin, aabbMax); + if (validPixel) + u_Sunshafts[pixel] = float4(max(lerp(curr, hist, histW), 0.0), rayLen); +} diff --git a/res/gamedata/shaders/r5/restir_water_rt.cs b/res/gamedata/shaders/r5/restir_water_rt.cs new file mode 100644 index 00000000000..4a5884519fe --- /dev/null +++ b/res/gamedata/shaders/r5/restir_water_rt.cs @@ -0,0 +1,575 @@ +#include "bindless_common.h" +#include "shared/terrain_blend.h" +#include "rt_common.h" +#include "rt_grass_alpha.h" +#include "rt_material_alpha.h" +#include "rt_shade_hit.h" +#include "shared/surface_marks.h" +#include "restir_gi_common.h" + +cbuffer ReSTIRWaterParams : register(b5) { + float4x4 g_InvViewProj; + float4x4 g_ViewProj; + float4 g_CameraPos; + float4 g_SunDir_Intensity; + float4 g_SunColor_SkyWeight; + float4 g_SkyColor; + float2 g_ScreenSize; + float g_GIIntensity; + uint g_IdentityStaticCount; + uint g_TerrainBatchCount; + uint g_SkinnedBatchStart; + uint g_GrassBatchStart; + uint g_DetailAtlasIndex; + uint g_HudSkinnedStart; + float g_LodDist; + uint g_Pad1; + uint g_Pad2; + float4 g_HemiColor; +}; + +RaytracingAccelerationStructure g_SceneTLAS : register(t1); +StructuredBuffer g_BatchInfo : register(t2); +ByteAddressBuffer g_MegaVB : register(t3); +TextureCube g_Sky0 : register(t5); +TextureCube g_Sky1 : register(t6); +ByteAddressBuffer g_SkinnedVB : register(t7); +ByteAddressBuffer g_SkinnedIB : register(t11); +ByteAddressBuffer g_GrassVB : register(t12); +ByteAddressBuffer g_GrassIB : register(t13); +Texture2D t_Depth : register(t14); +Texture2D t_Normal : register(t15); +Texture2D t_BaseColor : register(t16); +Texture2D t_WorldPos : register(t17); +ByteAddressBuffer g_MegaIB : register(t18); +Texture2D t_SceneColorIn : register(t19); +Texture2D t_ClassifyWorldPos : register(t20); +Texture2D t_UnderWorldPos : register(t21); +Texture2D t_UnderColor : register(t22); + +RWTexture2D u_SceneColor : register(u0); + +bool IsHudSkinnedBatch(uint batchIdx) +{ + return g_HudSkinnedStart != 0xFFFFFFFFu && batchIdx >= g_HudSkinnedStart && + (g_GrassBatchStart == 0xFFFFFFFFu || batchIdx < g_GrassBatchStart); +} + +float RayAttenBorder(float2 pos, float value) +{ + float borderDist = min(1.0 - max(pos.x, pos.y), min(pos.x, pos.y)); + return saturate(borderDist > value ? 1.0 : borderDist / value); +} + +bool WorldToUvVP(float3 worldPos, out float2 uv, out float w) +{ + uv = 0.0; + float4 clip = mul(g_ViewProj, float4(worldPos, 1.0)); + w = clip.w; + if (w <= 1e-4) + return false; + float2 ndc = clip.xy / w; + uv = float2(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5); + return true; +} + +float3 ReconstructWorldDepth(float2 uv, float rawDepth) +{ + float2 ndc = float2(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0); + float4 worldH = mul(g_InvViewProj, float4(ndc, rawDepth, 1.0)); + return worldH.xyz / max(worldH.w, 1e-5); +} + +bool IsSkinnedBatch(uint batchIdx) +{ + return g_SkinnedBatchStart != 0xFFFFFFFFu && batchIdx >= g_SkinnedBatchStart && + (g_GrassBatchStart == 0xFFFFFFFFu || batchIdx < g_GrassBatchStart); +} + +bool IsGrassBatch(uint batchIdx) +{ + return g_GrassBatchStart != 0xFFFFFFFFu && batchIdx == g_GrassBatchStart; +} + +bool IsTerrainBatch(uint batchIdx) +{ + return batchIdx >= g_IdentityStaticCount && + batchIdx < g_IdentityStaticCount + g_TerrainBatchCount; +} + +float3 SampleSkyW(float3 dir) +{ + float3 d = normalize(dir); + float w = g_SunColor_SkyWeight.w; + float3 s0 = g_Sky0.SampleLevel(smp_linear, d, 0).rgb; + float3 s1 = g_Sky1.SampleLevel(smp_linear, d, 0).rgb; + return lerp(s0, s1, w) * g_SkyColor.rgb * 0.95; +} + +float3 DecodeNormal(float3 nEnc) +{ + float3 n = nEnc * 2.0 - 1.0; + float lenSq = dot(n, n); + if (lenSq < 1e-6) + return float3(0.0, 1.0, 0.0); + return n * rsqrt(lenSq); +} + +float4 SampleTerrainTexture(uint index, float2 uv, float mip) +{ + if (index == INVALID_TEXTURE_INDEX) + return float4(0.5, 0.5, 0.5, 1.0); + return GetBindlessTexture(index).SampleLevel(smp_linear, uv, mip); +} + +float3 SampleTerrainAlbedoWater(TerrainMaterialData mat, float2 uv, float hitDist) +{ + float lodDist = max(g_CameraPos.w, 10.0); + float mip = saturate(hitDist / lodDist) * 0.45; + float2 baseUV = uv; + float4 baseSample = SampleTerrainTexture(mat.baseAlbedoIndex, baseUV, mip * 0.35); + float4 mask = TerrainNormalizeMask(SampleTerrainTexture(mat.blendMaskIndex, baseUV, mip * 0.35)); + float2 detailUV = uv * mat.detailScale; + float detailMip = mip + 0.1; + float4 detailR = SampleTerrainTexture(mat.detailR_Index, detailUV, detailMip); + float4 detailG = SampleTerrainTexture(mat.detailG_Index, detailUV, detailMip); + float4 detailB = SampleTerrainTexture(mat.detailB_Index, detailUV, detailMip); + float4 detailA = SampleTerrainTexture(mat.detailA_Index, detailUV, detailMip); + float3 blendedDetail = TerrainBlendRGB(detailR.rgb, detailG.rgb, detailB.rgb, detailA.rgb, mask); + return baseSample.rgb * blendedDetail * 2.0; +} + +float3 ShadeBakedFromMaterialLmap(MaterialData mat, float2 lmUV, float3 albedo, float hemi) +{ + if ((mat.flags & MAT_FLAG_HAS_LMAP) != 0 && mat.lmapIndex != INVALID_TEXTURE_INDEX) + { + float2 luv = (dot(lmUV, lmUV) > 1e-8) ? lmUV : float2(0.5, 0.5); + float4 lmh = GetBindlessTexture(mat.lmapIndex).SampleLevel(smp_rtlinear, luv, 0); + return ShadeBakedFromHemi(max(hemi, lmh.a), albedo, g_HemiColor.rgb); + } + return ShadeBakedFromHemi(hemi, albedo, g_HemiColor.rgb); +} + +bool HudOrActorAt(int2 ip) +{ + float w = t_WorldPos.Load(int3(ip, 0)).w; + return IsHudSurfMark(w) || IsCharSurfMark(w); +} + +bool HudNear(int2 ip) +{ + [unroll] + for (int y = -3; y <= 3; ++y) + { + [unroll] + for (int x = -3; x <= 3; ++x) + { + int2 p = clamp(ip + int2(x, y), int2(0, 0), int2(g_ScreenSize) - 1); + if (HudOrActorAt(p)) + return true; + } + } + return false; +} + +float3 SampleWaterSSRColor(int2 ip) +{ + float3 under = t_UnderColor.Load(int3(ip, 0)).rgb; + if (dot(under, float3(0.2126, 0.7152, 0.0722)) > 1e-6) + return under; + if (HudOrActorAt(ip) || HudNear(ip)) + return 0.0; + return t_SceneColorIn.Load(int3(ip, 0)).rgb; +} + +float4 ScreenReflectWater(float3 origin, float3 dir, float3 waterPos, float3 skyRefl, int steps) +{ + float2 startUV = 0.0; + float startW = 1.0; + if (!WorldToUvVP(origin, startUV, startW)) + return float4(skyRefl, 0.0); + + steps = clamp(steps, 1, 48); + const float maxDist = 420.0; + const float tMin = 1.5; + const float thickness = 1.35; + float3 cam = g_CameraPos.xyz; + float waterY = waterPos.y; + + float2 hitUV = startUV; + float hitConf = 0.0; + bool hit = false; + float tPrev = tMin; + float2 prevUV = startUV; + { + float pw = 1.0; + WorldToUvVP(origin + dir * tMin, prevUV, pw); + } + + [loop] + for (int i = 1; i <= steps; ++i) + { + float u = float(i) / float(steps); + float t = max(tMin, maxDist * u * u); + float3 marchPos = origin + dir * t; + float2 uv = 0.0; + float w = 1.0; + if (!WorldToUvVP(marchPos, uv, w)) + break; + + float border = RayAttenBorder(uv, 0.10); + if (border <= 1e-3) + break; + + int2 ip = int2(uv * g_ScreenSize); + ip = clamp(ip, int2(0, 0), int2(g_ScreenSize) - 1); + float rawDepth = t_Depth.Load(int3(ip, 0)); + if (rawDepth <= 1e-7) + { + prevUV = uv; + tPrev = t; + continue; + } + + float4 wpMark = t_WorldPos.Load(int3(ip, 0)); + if (HudNear(ip) || IsWaterSurfMark(wpMark.w)) + { + prevUV = uv; + tPrev = t; + continue; + } + + float4 underWP = t_UnderWorldPos.Load(int3(ip, 0)); + float3 scenePos = (dot(underWP.xyz, underWP.xyz) > 1e-2) + ? underWP.xyz + : ReconstructWorldDepth(uv, rawDepth); + if (length(scenePos - cam) < 1.5) + { + prevUV = uv; + tPrev = t; + continue; + } + if (scenePos.y < waterY - 0.35) + { + prevUV = uv; + tPrev = t; + continue; + } + + float3 toScene = scenePos - origin; + float along = dot(toScene, dir); + if (along < tMin) + { + prevUV = uv; + tPrev = t; + continue; + } + + float perp = length(scenePos - (origin + dir * along)); + float marchCam = length(marchPos - cam); + float sceneCam = length(scenePos - cam); + float delta = sceneCam - marchCam; + float thick = thickness * (1.0 + along * 0.01); + if (delta > thick || delta < -thick * 2.5 || perp > thick * 1.5) + { + if (delta < -thick * 2.5) + break; + prevUV = uv; + tPrev = t; + continue; + } + + if (length(uv - startUV) < 0.006) + { + prevUV = uv; + tPrev = t; + continue; + } + + hitUV = uv; + hitConf = saturate(1.0 - abs(delta) / max(thick * 2.0, 1e-3)); + hitConf *= saturate(1.0 - perp / max(thick * 2.5, 1e-3)); + hitConf *= border; + hitConf *= saturate((along - tMin) * 0.35); + hit = hitConf > 0.06; + break; + } + + if (!hit) + return float4(skyRefl, 0.0); + + int2 hitIp = clamp(int2(hitUV * g_ScreenSize), int2(0, 0), int2(g_ScreenSize) - 1); + if (HudNear(hitIp)) + return float4(skyRefl, 0.0); + float3 img = SampleWaterSSRColor(hitIp); + if (dot(img, float3(0.2126, 0.7152, 0.0722)) < 1e-5) + return float4(skyRefl, 0.0); + return float4(img, saturate(hitConf)); +} + +bool TraceWaterReflect(float3 origin, float3 dir, float3 skyRefl, uint maxSteps, float3 waterPos, out float3 outCol) +{ + outCol = skyRefl; + float3 rayOrigin = origin; + float remain = 20000.0; + maxSteps = clamp(maxSteps, 1u, 16u); + float3 cam = g_CameraPos.xyz; + uint shadeSteps = 0; + + [loop] + for (uint iter = 0; iter < 48u && shadeSteps < maxSteps; ++iter) + { + if (remain <= 0.05) + break; + + RayDesc ray; + ray.Origin = rayOrigin; + ray.Direction = dir; + ray.TMin = 0.08; + ray.TMax = remain; + + RayQuery q; + q.TraceRayInline(g_SceneTLAS, RAY_FLAG_NONE, RT_MASK_SHADOW, ray); + while (q.Proceed()) + { + if (q.CandidateType() == CANDIDATE_NON_OPAQUE_TRIANGLE) + { + uint candBatch = q.CandidateInstanceID() + q.CandidateGeometryIndex(); + if (IsHudSkinnedBatch(candBatch) || IsSkinnedBatch(candBatch)) + continue; + if (IsGrassBatch(candBatch)) { + if (GrassTexelOpaque(g_GrassVB, g_GrassIB, g_BatchInfo, candBatch, + q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics(), + g_DetailAtlasIndex)) + q.CommitNonOpaqueTriangleHit(); + continue; + } + else + { + RTBatchInfo candInfo = g_BatchInfo[candBatch]; + MaterialData candMat = g_Materials[candInfo.materialID]; + if ((candMat.flags & MAT_FLAG_WATER) != 0) + continue; + float2 candUV = GetHitUV(g_MegaVB, g_MegaIB, candInfo, + q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics()); + float4 candDiff = SampleDiffuseLevel(candMat, candUV); + bool keep = true; + if ((candMat.flags & MAT_FLAG_ALPHA_TEST) != 0) + keep = candDiff.a >= candMat.alphaRef; + else if ((candMat.flags & MAT_FLAG_ALPHA_BLEND) != 0) + keep = candDiff.a >= 0.5; + if (keep) + q.CommitNonOpaqueTriangleHit(); + } + } + } + + if (q.CommittedStatus() != COMMITTED_TRIANGLE_HIT) + break; + + float tHit = q.CommittedRayT(); + uint batchIdx = q.CommittedInstanceID() + q.CommittedGeometryIndex(); + float3 hitPos = rayOrigin + dir * tHit; + float hitCamDist = length(hitPos - cam); + if (IsHudSkinnedBatch(batchIdx) || IsSkinnedBatch(batchIdx) || + hitCamDist < 1.5 || hitPos.y < waterPos.y - 2.0) + { + float adv = max(tHit + 0.05, 0.5); + rayOrigin = rayOrigin + dir * adv; + remain -= adv; + continue; + } + + RTBatchInfo info = g_BatchInfo[batchIdx]; + uint primIdx = q.CommittedPrimitiveIndex(); + float2 bary = q.CommittedTriangleBarycentrics(); + float3x4 objectToWorld = q.CommittedObjectToWorld3x4(); + + float3 albedo = float3(0.5, 0.5, 0.5); + float3 hitN = float3(0.0, 1.0, 0.0); + float hemi = 0.5; + float2 lmUV = 0; + bool skipHit = false; + bool waterHit = false; + bool hasLmap = false; + float3 baked = 0; + + if (IsGrassBatch(batchIdx)) + { + float2 hitUV = GetSkinnedHitUV(g_GrassVB, g_GrassIB, info, primIdx, bary); + hitN = normalize(TransformNormalToWorld( + GetSkinnedHitNormal(g_GrassVB, g_GrassIB, info, primIdx, bary), objectToWorld)); + if (g_DetailAtlasIndex > 0) + { + float4 texel = GetBindlessTexture(g_DetailAtlasIndex).SampleLevel(smp_linear, hitUV, 0); + if (texel.a < GRASS_ALPHA_CLIP) + skipHit = true; + else + albedo = texel.rgb; + } + else + { + albedo = lerp(float3(0.08, 0.18, 0.03), float3(0.15, 0.35, 0.06), 1.0 - hitUV.y); + } + baked = ShadeBakedFromHemi(0.55, albedo, g_HemiColor.rgb); + } + else if (IsSkinnedBatch(batchIdx)) + { + float adv = max(tHit + 0.05, 0.5); + rayOrigin = rayOrigin + dir * adv; + remain -= adv; + continue; + } + else if (IsTerrainBatch(batchIdx)) + { + float2 hitUV = GetHitUV(g_MegaVB, g_MegaIB, info, primIdx, bary); + lmUV = GetHitLightmapUV(g_MegaVB, g_MegaIB, info, primIdx, bary); + hemi = GetHitHemi(g_MegaVB, g_MegaIB, info, primIdx, bary); + hitN = normalize(TransformNormalToWorld( + GetHitNormal(g_MegaVB, g_MegaIB, info, primIdx, bary), objectToWorld)); + TerrainMaterialData tmat = g_TerrainMaterials[info.materialID]; + albedo = SampleTerrainAlbedoWater(tmat, hitUV, tHit); + baked = ShadeBakedFromTerrainLmap(tmat, lmUV, albedo, g_HemiColor.rgb, hemi); + hasLmap = false; + } + else + { + float2 hitUV = GetHitUV(g_MegaVB, g_MegaIB, info, primIdx, bary); + lmUV = GetHitLightmapUV(g_MegaVB, g_MegaIB, info, primIdx, bary); + hemi = GetHitHemi(g_MegaVB, g_MegaIB, info, primIdx, bary); + hitN = normalize(TransformNormalToWorld( + GetHitNormal(g_MegaVB, g_MegaIB, info, primIdx, bary), objectToWorld)); + MaterialData mat = g_Materials[info.materialID]; + float4 diffuse = SampleDiffuseLevel(mat, hitUV); + if ((mat.flags & MAT_FLAG_WATER) != 0) + waterHit = true; + else if (!MaterialDiffuseOpaque(mat, diffuse)) + skipHit = true; + else + albedo = diffuse.rgb; + if (!waterHit && !skipHit) + { + baked = ShadeBakedFromMaterialLmap(mat, lmUV, albedo, hemi); + hasLmap = (mat.flags & MAT_FLAG_HAS_LMAP) != 0 && mat.lmapIndex != INVALID_TEXTURE_INDEX; + } + } + + if (waterHit || skipHit) + { + float adv = waterHit ? max(tHit + 0.1, 2.5) : max(tHit + 0.05, 0.35); + rayOrigin = rayOrigin + dir * adv; + remain -= adv; + continue; + } + + if (dot(hitN, dir) > 0.0) + hitN = -hitN; + + float3 sunDir = normalize(-g_SunDir_Intensity.xyz); + float sunI = saturate(g_SunDir_Intensity.w); + float3 sunCol = max(g_SunColor_SkyWeight.rgb, float3(0.25, 0.25, 0.25)) * sunI; + float ndl = saturate(dot(hitN, sunDir)); + float3 hitCol = baked; + if (!hasLmap) + hitCol += albedo * sunCol * ndl * 0.85; + else + hitCol += albedo * sunCol * ndl * 0.12; + hitCol += SampleSkyW(hitN) * albedo * 0.22; + outCol = lerp(skyRefl, hitCol, 0.52); + shadeSteps++; + return true; + } + + return false; +} + +[numthreads(8, 8, 1)] +void main(uint3 dispatchID : SV_DispatchThreadID) +{ + uint2 pixel = dispatchID.xy; + if (pixel.x >= (uint)g_ScreenSize.x || pixel.y >= (uint)g_ScreenSize.y) + return; + + float4 fwd4 = t_SceneColorIn.Load(int3(pixel, 0)); + float depth = t_Depth.Load(int3(pixel, 0)); + float4 guideWP = t_WorldPos.Load(int3(pixel, 0)); + if (depth <= 0.0 || IsHudSurfMark(guideWP.w)) + { + u_SceneColor[pixel] = fwd4; + return; + } + + float4 classifyData = t_ClassifyWorldPos.Load(int3(pixel, 0)); + if (!IsWaterSurfMark(classifyData.w)) + { + u_SceneColor[pixel] = fwd4; + return; + } + + float2 uv = (float2(pixel) + 0.5) / g_ScreenSize; + float border = RayAttenBorder(uv, 0.015); + + float3 waterPos = classifyData.xyz; + float3 cam = g_CameraPos.xyz; + float waterDist = length(waterPos - cam); + float lodDist = max(g_LodDist, 10.0); + + float3 underCol = t_UnderColor.Load(int3(pixel, 0)).rgb; + + float3 Nflat = float3(0.0, 1.0, 0.0); + float3 N = Nflat; + float3 nMap = DecodeNormal(t_Normal.Load(int3(pixel, 0)).xyz); + if (dot(nMap, nMap) > 1e-4) + { + if (nMap.y < 0.0) + nMap = -nMap; + N = normalize(lerp(Nflat, nMap, 0.10)); + } + + float3 V = normalize(cam - waterPos); + float3 Rflat = normalize(reflect(-V, Nflat)); + float3 R = normalize(reflect(-V, N)); + float towardCam = saturate(dot(R, V)); + R = normalize(lerp(R, Rflat, max(towardCam * towardCam, 0.4))); + + float cosTheta = saturate(dot(Nflat, V)); + float F = 0.04 + 0.48 * pow(1.0 - cosTheta, 5.0); + F = saturate(F * lerp(0.97, 1.0, border)); + + float3 skyRefl = SampleSkyW(R); + float3 reflected = skyRefl; + const bool nearHud = HudNear(int2(pixel)); + if (R.y > -0.08 && !nearHud) + { + float lift = lerp(0.16, 0.05, saturate(R.y * 5.0)); + float lod = saturate(waterDist / lodDist); + if (waterDist > lodDist * 2.0) + { + reflected = skyRefl; + } + else + { + uint steps = (lod > 0.35) ? 14u : 9u; + int ssrSteps = (waterDist > lodDist) ? 8 : 48; + float3 rtCol = skyRefl; + bool rtHit = TraceWaterReflect(waterPos + Nflat * lift, R, skyRefl, steps, waterPos, rtCol); + float4 ssr = ScreenReflectWater(waterPos + Nflat * lift, R, waterPos, skyRefl, ssrSteps); + if (rtHit) + reflected = lerp(lerp(skyRefl, ssr.rgb, ssr.a * 0.55), rtCol, 0.82); + else + reflected = lerp(skyRefl, ssr.rgb, saturate(ssr.a)); + } + } + + float3 sunDir = normalize(-g_SunDir_Intensity.xyz); + float3 sunColor = g_SunColor_SkyWeight.rgb * saturate(g_SunDir_Intensity.w); + reflected += sunColor * pow(saturate(dot(R, sunDir)), 256.0) * 0.7; + + float3 skyDown = SampleSkyW(float3(0.0, 1.0, 0.0)); + float3 refr = underCol * 1.35 + skyDown * 0.20; + if (!nearHud) + refr = lerp(refr, fwd4.rgb, 0.18); + refr = max(refr, skyDown * 0.12); + float3 color = lerp(refr, reflected, F); + u_SceneColor[pixel] = float4(color, 1.0); +} diff --git a/res/gamedata/shaders/r5/restir_wet.cs b/res/gamedata/shaders/r5/restir_wet.cs new file mode 100644 index 00000000000..1856df76d9a --- /dev/null +++ b/res/gamedata/shaders/r5/restir_wet.cs @@ -0,0 +1,109 @@ +#include "common.h" +#include "bindless_common.h" +#include "rt_common.h" +#include "rt_material_alpha.h" +#include "restir_gi_common.h" +#include "shared/surface_marks.h" + +cbuffer WetParams : register(b5) { + float4x4 g_InvViewProj; + float4 g_CameraPos; + float2 g_ScreenSize; + float g_DeltaTime; + float g_RainFactor; + float g_DryRate; + float g_MaxWet; + uint2 g_Pad; +}; + +Texture2D t_Depth : register(t0); +Texture2D t_Normal : register(t1); +Texture2D t_WorldPos : register(t6); +RaytracingAccelerationStructure g_SceneTLAS : register(t2); +StructuredBuffer g_BatchInfo : register(t3); +ByteAddressBuffer g_MegaVB : register(t4); +ByteAddressBuffer g_MegaIB : register(t5); +RWTexture2D u_WetAccum : register(u0); +RWTexture2D u_SkyOpen : register(u1); + +bool IsSkyOpen(float3 origin) +{ + float3 rayOrigin = origin; + float remaining = 400.0; + for (uint si = 0; si < 4u; si++) { + RayDesc ray; + ray.Origin = rayOrigin; + ray.Direction = float3(0.0, 1.0, 0.0); + ray.TMin = 0.05; + ray.TMax = remaining; + + RayQuery q; + q.TraceRayInline(g_SceneTLAS, RAY_FLAG_NONE, RT_MASK_SHADOW_MAPPED, ray); + while (q.Proceed()) { + if (q.CandidateType() == CANDIDATE_NON_OPAQUE_TRIANGLE) { + uint candBatch = q.CandidateInstanceID() + q.CandidateGeometryIndex(); + if (MegaMaterialOpaque(g_MegaVB, g_MegaIB, g_BatchInfo, candBatch, + q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics())) + q.CommitNonOpaqueTriangleHit(); + } + } + + if (q.CommittedStatus() != COMMITTED_TRIANGLE_HIT) + return true; + + uint batchIdx = q.CommittedInstanceID() + q.CommittedGeometryIndex(); + RTBatchInfo info = g_BatchInfo[batchIdx]; + MaterialData mat = g_Materials[info.materialID]; + float hitT = q.CommittedRayT(); + if ((mat.flags & MAT_FLAG_WATER) != 0) { + rayOrigin = rayOrigin + float3(0.0, 1.0, 0.0) * (hitT + 0.02); + remaining = max(remaining - hitT - 0.02, 0.0); + continue; + } + if ((mat.flags & MAT_FLAG_EMISSIVE) != 0) + return false; + float2 hitUV = GetHitUV(g_MegaVB, g_MegaIB, info, + q.CommittedPrimitiveIndex(), q.CommittedTriangleBarycentrics()); + float4 diffuse = SampleDiffuseLevel(mat, hitUV); + if (!MaterialDiffuseOpaque(mat, diffuse)) { + rayOrigin = rayOrigin + float3(0.0, 1.0, 0.0) * (hitT + 0.02); + remaining = max(remaining - hitT - 0.02, 0.0); + continue; + } + return false; + } + return false; +} + +[numthreads(8, 8, 1)] +void main(uint3 dispatchID : SV_DispatchThreadID) +{ + uint2 pixel = dispatchID.xy; + if (pixel.x >= (uint)g_ScreenSize.x || pixel.y >= (uint)g_ScreenSize.y) + return; + + float depth = t_Depth.Load(int3(pixel, 0)); + float wet = u_WetAccum[pixel]; + if (depth <= 0.0 || depth >= 1.0) { + u_WetAccum[pixel] = max(wet - g_DryRate * g_DeltaTime, 0.0); + u_SkyOpen[pixel] = 1.0; + return; + } + + float3 N = normalize(t_Normal.Load(int3(pixel, 0)).xyz); + float surfMark = t_WorldPos.Load(int3(pixel, 0)).w; + float upward = saturate(N.y); + if (IsVegSurfMark(surfMark)) + upward = max(upward, 0.8); + float2 uv = (float2(pixel) + 0.5) / g_ScreenSize; + float3 worldPos = ReconstructWorldPosReverseZ(uv, depth, g_InvViewProj); + float3 biased = worldPos + float3(0.0, 0.05, 0.0); + + float cover = IsSkyOpen(biased) ? 1.0 : 0.0; + float prevOpen = u_SkyOpen[pixel]; + float openBlend = (cover < prevOpen) ? 0.55 : 0.25; + u_SkyOpen[pixel] = lerp(prevOpen, cover, openBlend); + float add = g_RainFactor * upward * cover * g_DeltaTime; + wet = saturate(wet + add - g_DryRate * g_DeltaTime); + u_WetAccum[pixel] = min(wet, g_MaxWet); +} diff --git a/res/gamedata/shaders/r5/rt_common.h b/res/gamedata/shaders/r5/rt_common.h index 501608ab95b..7469ae66bfd 100644 --- a/res/gamedata/shaders/r5/rt_common.h +++ b/res/gamedata/shaders/r5/rt_common.h @@ -1,6 +1,14 @@ #ifndef RT_COMMON_H #define RT_COMMON_H +#define RT_MASK_SCENE 0x01u +#define RT_MASK_PARTICLES 0x02u +#define RT_MASK_GRASS 0x04u +#define RT_MASK_SHADOW_MAPPED RT_MASK_SCENE +#define RT_MASK_SHADOW (RT_MASK_SCENE | RT_MASK_GRASS) +#define RT_MASK_GI (RT_MASK_SCENE | RT_MASK_GRASS) +#define RT_MASK_SHADE (RT_MASK_SCENE | RT_MASK_GRASS) + struct RTBatchInfo { uint materialID; uint startIndex; @@ -33,6 +41,38 @@ float2 GetHitUV(ByteAddressBuffer megaVB, ByteAddressBuffer megaIB, return uv0 * w0 + uv1 * barycentrics.x + uv2 * barycentrics.y; } +float2 GetHitLightmapUV(ByteAddressBuffer megaVB, ByteAddressBuffer megaIB, + RTBatchInfo info, uint primitiveIndex, float2 barycentrics) +{ + uint triBase = (info.startIndex + primitiveIndex * 3); + uint i0 = megaIB.Load(triBase * 4 + 0) + info.baseVertex; + uint i1 = megaIB.Load(triBase * 4 + 4) + info.baseVertex; + uint i2 = megaIB.Load(triBase * 4 + 8) + info.baseVertex; + + float2 uv0 = asfloat(megaVB.Load2(i0 * 48 + 32)); + float2 uv1 = asfloat(megaVB.Load2(i1 * 48 + 32)); + float2 uv2 = asfloat(megaVB.Load2(i2 * 48 + 32)); + + float w0 = 1.0 - barycentrics.x - barycentrics.y; + return uv0 * w0 + uv1 * barycentrics.x + uv2 * barycentrics.y; +} + +float GetHitHemi(ByteAddressBuffer megaVB, ByteAddressBuffer megaIB, + RTBatchInfo info, uint primitiveIndex, float2 barycentrics) +{ + uint triBase = (info.startIndex + primitiveIndex * 3); + uint i0 = megaIB.Load(triBase * 4 + 0) + info.baseVertex; + uint i1 = megaIB.Load(triBase * 4 + 4) + info.baseVertex; + uint i2 = megaIB.Load(triBase * 4 + 8) + info.baseVertex; + + float h0 = float((megaVB.Load(i0 * 48 + 12) >> 24) & 0xFF) / 255.0; + float h1 = float((megaVB.Load(i1 * 48 + 12) >> 24) & 0xFF) / 255.0; + float h2 = float((megaVB.Load(i2 * 48 + 12) >> 24) & 0xFF) / 255.0; + + float w0 = 1.0 - barycentrics.x - barycentrics.y; + return saturate(h0 * w0 + h1 * barycentrics.x + h2 * barycentrics.y); +} + float3 DecodePackedNormal(uint packed) { return float3( diff --git a/res/gamedata/shaders/r5/rt_grass_alpha.h b/res/gamedata/shaders/r5/rt_grass_alpha.h new file mode 100644 index 00000000000..148deb07f25 --- /dev/null +++ b/res/gamedata/shaders/r5/rt_grass_alpha.h @@ -0,0 +1,37 @@ +#ifndef RT_GRASS_ALPHA_H +#define RT_GRASS_ALPHA_H + +#include "bindless_common.h" +#include "rt_common.h" + +static const float GRASS_ALPHA_CLIP = 96.0 / 255.0; + +float GrassTexelAlpha( + ByteAddressBuffer grassVB, + ByteAddressBuffer grassIB, + StructuredBuffer batchInfo, + uint batchIdx, + uint primIdx, + float2 bary, + uint detailAtlasIndex) +{ + if (detailAtlasIndex == 0) + return 1.0; + RTBatchInfo info = batchInfo[batchIdx]; + float2 uv = GetSkinnedHitUV(grassVB, grassIB, info, primIdx, bary); + return GetBindlessTexture(detailAtlasIndex).SampleLevel(smp_linear, uv, 0).a; +} + +bool GrassTexelOpaque( + ByteAddressBuffer grassVB, + ByteAddressBuffer grassIB, + StructuredBuffer batchInfo, + uint batchIdx, + uint primIdx, + float2 bary, + uint detailAtlasIndex) +{ + return GrassTexelAlpha(grassVB, grassIB, batchInfo, batchIdx, primIdx, bary, detailAtlasIndex) >= GRASS_ALPHA_CLIP; +} + +#endif diff --git a/res/gamedata/shaders/r5/rt_grass_billboard.cs b/res/gamedata/shaders/r5/rt_grass_billboard.cs index 09e982d06b1..7dbf5446e2a 100644 --- a/res/gamedata/shaders/r5/rt_grass_billboard.cs +++ b/res/gamedata/shaders/r5/rt_grass_billboard.cs @@ -28,12 +28,22 @@ struct PulledVertex { StructuredBuffer g_DetailModels : register(t2); StructuredBuffer g_PulledVerts : register(t3); ByteAddressBuffer g_DrawArgs : register(t4); +Texture3D g_Perlin4D : register(t5); +SamplerState smp_linear : register(s0); RWByteAddressBuffer g_Output : register(u0); RWByteAddressBuffer g_OutputIB : register(u1); +RWByteAddressBuffer g_PackedCount : register(u2); cbuffer BillboardRTCB : register(b5) { uint maxVertsPerBillboard; - uint3 pad; + uint maxBillboards; + float windAngleDeg; + float windSpeed; + float time; + float windDisplacement; + uint2 pad; + float3 cameraPos; + float maxDistanceSq; }; static const float TWO_PI = 6.28318530718; @@ -45,45 +55,39 @@ uint pack_normal(float3 n) return (u.x << 16) | (u.y << 8) | u.z; } -[numthreads(256, 1, 1)] -void main(uint3 dtid : SV_DispatchThreadID) +void WriteBillboard(uint slot, InstanceData inst) { - uint bb_idx = dtid.x; - uint actualCount = g_DrawArgs.Load(4); - - uint vertBase = bb_idx * maxVertsPerBillboard; - - if (bb_idx >= actualCount) { - for (uint v = 0; v < maxVertsPerBillboard; v++) { - uint vi = vertBase + v; - g_Output.Store4(vi * 24, uint4(0, 0, 0, 0)); - g_Output.Store2(vi * 24 + 16, uint2(0, 0)); - g_OutputIB.Store(vi * 4, vi); - } - return; - } - - uint src_idx = g_VisibleIndices[bb_idx]; - InstanceData inst = g_AllInstances[src_idx]; - uint object_id = inst.packed & 0x3F; float rotation = float((inst.packed >> 8) & 0x3FF) / 1023.0 * TWO_PI; float scale = float((inst.packed >> 18) & 0x3FF) / 1023.0 * PACK_MAX_SCALE; - DetailModelGPU mdl = g_DetailModels[object_id]; float c = cos(rotation), s = sin(rotation); - + uint vertBase = slot * maxVertsPerBillboard; uint triCount = min(mdl.pulledIndexCount / 3, maxVertsPerBillboard / 3); + float speed = max(windSpeed, 0.1); + float windAngle = windAngleDeg * (3.14159265359 / 180.0); + float2 globalWindDir = float2(sin(windAngle), cos(windAngle)); for (uint tri = 0; tri < triCount; tri++) { float3 positions[3]; float2 uvs[3]; - [unroll] for (uint k = 0; k < 3; k++) { PulledVertex pv = g_PulledVerts[mdl.pulledVertexBase + tri * 3 + k]; float3 lp = float3(pv.px, pv.py, pv.pz) * scale; positions[k] = float3(lp.x * c - lp.z * s, lp.y, lp.x * s + lp.z * c) + inst.pos; + float heightFactor = saturate(pv.py / max(mdl.geomExtentY, 0.01)); + float2 dirUV = positions[k].zx * (0.005 / speed) + time * (0.005 * speed); + float windDirNoise = g_Perlin4D.SampleLevel(smp_linear, float3(dirUV, 0), 0).r; + float2 strUV = positions[k].xz * (0.025 / speed) + time * 0.05; + float windStrNoise = g_Perlin4D.SampleLevel(smp_linear, float3(strUV, 0), 0).r; + float windStrength = lerp(0.25, 1.0, windStrNoise); + windStrength *= windStrength * speed; + float turbulence = (windDirNoise * 2.0 - 1.0) * 0.3; + float2 perpendicularDir = float2(-globalWindDir.y, globalWindDir.x); + float2 windDir = normalize(globalWindDir + perpendicularDir * turbulence); + float displacement = windStrength * windDisplacement * heightFactor; + positions[k].xz += displacement * windDir; uvs[k] = float2(pv.u, pv.v); } @@ -109,3 +113,22 @@ void main(uint3 dtid : SV_DispatchThreadID) g_OutputIB.Store(vi * 4, vi); } } + +[numthreads(256, 1, 1)] +void main(uint3 dtid : SV_DispatchThreadID) +{ + uint sourceCount = g_DrawArgs.Load(4); + uint stride = max(maxBillboards, 1u); + for (uint i = dtid.x; i < sourceCount; i += stride) { + uint src_idx = g_VisibleIndices[i]; + InstanceData inst = g_AllInstances[src_idx]; + float3 d = inst.pos - cameraPos; + if (dot(d, d) > maxDistanceSq) + continue; + uint slot; + g_PackedCount.InterlockedAdd(0, 1, slot); + if (slot >= maxBillboards) + return; + WriteBillboard(slot, inst); + } +} diff --git a/res/gamedata/shaders/r5/rt_irradiance_cache.h b/res/gamedata/shaders/r5/rt_irradiance_cache.h new file mode 100644 index 00000000000..234328093ca --- /dev/null +++ b/res/gamedata/shaders/r5/rt_irradiance_cache.h @@ -0,0 +1,84 @@ +#ifndef RT_IRRADIANCE_CACHE_H +#define RT_IRRADIANCE_CACHE_H + +struct IrradianceCacheEntry +{ + float3 irradiance; + uint stamp; +}; + +uint IrradianceCacheHash(float3 pos, float cellSize, uint cacheSize) +{ + int3 cell = int3(floor(pos / max(cellSize, 0.05))); + uint h = (uint)cell.x * 73856093u ^ (uint)cell.y * 19349663u ^ (uint)cell.z * 83492791u; + return h % max(cacheSize, 1u); +} + +float3 QueryIrradianceCacheRW( + RWStructuredBuffer cache, + float3 pos, + float cellSize, + uint cacheSize, + uint frameIndex, + uint maxAge, + float envScale) +{ + if (cacheSize == 0) + return 0; + uint idx = IrradianceCacheHash(pos, cellSize, cacheSize); + IrradianceCacheEntry e = cache[idx]; + if (e.stamp == 0) + return 0; + uint age = frameIndex - e.stamp; + if (age > maxAge) + return 0; + float fade = 1.0 - saturate((float)age / (float)max(maxAge, 1u)); + return e.irradiance * fade * envScale; +} + +float3 QueryIrradianceCache( + StructuredBuffer cache, + float3 pos, + float cellSize, + uint cacheSize, + uint frameIndex, + uint maxAge, + float envScale) +{ + if (cacheSize == 0) + return 0; + uint idx = IrradianceCacheHash(pos, cellSize, cacheSize); + IrradianceCacheEntry e = cache[idx]; + if (e.stamp == 0) + return 0; + uint age = frameIndex >= e.stamp ? frameIndex - e.stamp : 0; + if (age > maxAge) + return 0; + float fade = 1.0 - saturate((float)age / (float)max(maxAge, 1u)); + return e.irradiance * fade * envScale; +} + +void UpdateIrradianceCache( + RWStructuredBuffer cache, + float3 pos, + float3 irradiance, + float cellSize, + uint cacheSize, + uint frameIndex, + float envScale) +{ + if (cacheSize == 0 || !any(irradiance > 0)) + return; + uint idx = IrradianceCacheHash(pos, cellSize, cacheSize); + IrradianceCacheEntry prev = cache[idx]; + float3 canon = irradiance / max(envScale, 1e-4); + float3 blended = canon; + if (prev.stamp != 0 && (frameIndex - prev.stamp) < 64u) + blended = lerp(prev.irradiance, canon, 0.35); + IrradianceCacheEntry e; + e.irradiance = blended; + e.stamp = max(frameIndex, 1u); + cache[idx] = e; +} + +#endif diff --git a/res/gamedata/shaders/r5/rt_material_alpha.h b/res/gamedata/shaders/r5/rt_material_alpha.h new file mode 100644 index 00000000000..8d138babf90 --- /dev/null +++ b/res/gamedata/shaders/r5/rt_material_alpha.h @@ -0,0 +1,83 @@ +#ifndef RT_MATERIAL_ALPHA_H +#define RT_MATERIAL_ALPHA_H + +#include "bindless_common.h" +#include "rt_common.h" + +float MaterialAlphaCut(MaterialData mat) +{ + if ((mat.flags & MAT_FLAG_ALPHA_TEST) != 0) + return max(mat.alphaRef, 1.0 / 255.0); + return 0.5; +} + +float4 SampleDiffuseAlpha(MaterialData mat, float2 uv) +{ + if (mat.diffuseIndex == INVALID_TEXTURE_INDEX) + return float4(1, 0, 1, 1); + return GetBindlessTexture(mat.diffuseIndex).SampleLevel(smp_nofilter, uv, 0); +} + +bool MaterialDiffuseOpaque(MaterialData mat, float4 diffuse) +{ + if ((mat.flags & MAT_FLAG_EMISSIVE) != 0) + return false; + if ((mat.flags & MAT_FLAG_WATER) != 0) + return false; + if ((mat.flags & MAT_FLAG_WMARK) != 0) + return false; + return diffuse.a >= MaterialAlphaCut(mat); +} + +bool EmissiveTexelLit(MaterialData mat, float4 diffuse) +{ + if ((mat.flags & MAT_FLAG_EMISSIVE) == 0) + return false; + return GlowTexelMask(diffuse) > (0.01 / 255.0); +} + +bool MegaMaterialOpaque( + ByteAddressBuffer megaVB, + ByteAddressBuffer megaIB, + StructuredBuffer batchInfo, + uint batchIdx, + uint primIdx, + float2 bary) +{ + RTBatchInfo info = batchInfo[batchIdx]; + MaterialData mat = g_Materials[info.materialID]; + float2 uv = GetHitUV(megaVB, megaIB, info, primIdx, bary); + float4 diffuse = SampleDiffuseAlpha(mat, uv); + return MaterialDiffuseOpaque(mat, diffuse); +} + +bool MegaEmissiveHit( + ByteAddressBuffer megaVB, + ByteAddressBuffer megaIB, + StructuredBuffer batchInfo, + uint batchIdx, + uint primIdx, + float2 bary) +{ + RTBatchInfo info = batchInfo[batchIdx]; + MaterialData mat = g_Materials[info.materialID]; + float2 uv = GetHitUV(megaVB, megaIB, info, primIdx, bary); + return EmissiveTexelLit(mat, SampleDiffuseLevel(mat, uv)); +} + +bool SkinnedMaterialOpaque( + ByteAddressBuffer skinnedVB, + ByteAddressBuffer skinnedIB, + StructuredBuffer batchInfo, + uint batchIdx, + uint primIdx, + float2 bary) +{ + RTBatchInfo info = batchInfo[batchIdx]; + MaterialData mat = g_Materials[info.materialID]; + float2 uv = GetSkinnedHitUV(skinnedVB, skinnedIB, info, primIdx, bary); + float4 diffuse = SampleDiffuseAlpha(mat, uv); + return MaterialDiffuseOpaque(mat, diffuse); +} + +#endif diff --git a/res/gamedata/shaders/r5/rt_particle_alpha.h b/res/gamedata/shaders/r5/rt_particle_alpha.h new file mode 100644 index 00000000000..42f9b929110 --- /dev/null +++ b/res/gamedata/shaders/r5/rt_particle_alpha.h @@ -0,0 +1,97 @@ +#ifndef RT_PARTICLE_ALPHA_H +#define RT_PARTICLE_ALPHA_H + +#include "bindless_common.h" +#include "rt_common.h" + +float2 GetParticleHitUV(ByteAddressBuffer particleVB, ByteAddressBuffer particleIB, + RTBatchInfo info, uint primitiveIndex, float2 barycentrics) +{ + uint triBase = (info.startIndex + primitiveIndex * 3); + uint i0 = particleIB.Load(triBase * 4 + 0) + info.baseVertex; + uint i1 = particleIB.Load(triBase * 4 + 4) + info.baseVertex; + uint i2 = particleIB.Load(triBase * 4 + 8) + info.baseVertex; + + float2 uv0 = asfloat(particleVB.Load2(i0 * 32 + 16)); + float2 uv1 = asfloat(particleVB.Load2(i1 * 32 + 16)); + float2 uv2 = asfloat(particleVB.Load2(i2 * 32 + 16)); + + float w0 = 1.0 - barycentrics.x - barycentrics.y; + return uv0 * w0 + uv1 * barycentrics.x + uv2 * barycentrics.y; +} + +uint GetParticleHitMaterial(ByteAddressBuffer particleVB, ByteAddressBuffer particleIB, + RTBatchInfo info, uint primitiveIndex, float2 barycentrics) +{ + uint triBase = (info.startIndex + primitiveIndex * 3); + uint i0 = particleIB.Load(triBase * 4 + 0) + info.baseVertex; + uint matId = particleVB.Load(i0 * 32 + 24); + return matId; +} + +float4 UnpackParticleColor(uint c) +{ + return float4( + float((c >> 16) & 255), + float((c >> 8) & 255), + float(c & 255), + float((c >> 24) & 255)) / 255.0; +} + +float4 GetParticleHitColor(ByteAddressBuffer particleVB, ByteAddressBuffer particleIB, + RTBatchInfo info, uint primitiveIndex, float2 barycentrics) +{ + uint triBase = (info.startIndex + primitiveIndex * 3); + uint i0 = particleIB.Load(triBase * 4 + 0) + info.baseVertex; + uint i1 = particleIB.Load(triBase * 4 + 4) + info.baseVertex; + uint i2 = particleIB.Load(triBase * 4 + 8) + info.baseVertex; + float w0 = 1.0 - barycentrics.x - barycentrics.y; + return UnpackParticleColor(particleVB.Load(i0 * 32 + 12)) * w0 + + UnpackParticleColor(particleVB.Load(i1 * 32 + 12)) * barycentrics.x + + UnpackParticleColor(particleVB.Load(i2 * 32 + 12)) * barycentrics.y; +} + +float3 GetParticleHitGeoNormal(ByteAddressBuffer particleVB, ByteAddressBuffer particleIB, + RTBatchInfo info, uint primitiveIndex) +{ + uint triBase = (info.startIndex + primitiveIndex * 3); + uint i0 = particleIB.Load(triBase * 4 + 0) + info.baseVertex; + uint i1 = particleIB.Load(triBase * 4 + 4) + info.baseVertex; + uint i2 = particleIB.Load(triBase * 4 + 8) + info.baseVertex; + float3 p0 = asfloat(particleVB.Load3(i0 * 32)); + float3 p1 = asfloat(particleVB.Load3(i1 * 32)); + float3 p2 = asfloat(particleVB.Load3(i2 * 32)); + return normalize(cross(p1 - p0, p2 - p0)); +} + +bool ParticleTexelGlowing( + ByteAddressBuffer particleVB, + ByteAddressBuffer particleIB, + StructuredBuffer batchInfo, + uint batchIdx, + uint primIdx, + float2 bary) +{ + RTBatchInfo info = batchInfo[batchIdx]; + uint matId = GetParticleHitMaterial(particleVB, particleIB, info, primIdx, bary); + MaterialData mat = g_Materials[matId]; + if ((mat.flags & MAT_FLAG_EMISSIVE) == 0) + return false; + float2 uv = GetParticleHitUV(particleVB, particleIB, info, primIdx, bary); + float4 diffuse = SampleDiffuseLevel(mat, uv); + float4 vcol = GetParticleHitColor(particleVB, particleIB, info, primIdx, bary); + return ParticleTexelAlpha(diffuse) * vcol.a > (0.01 / 255.0); +} + +bool ParticleTexelOpaque( + ByteAddressBuffer particleVB, + ByteAddressBuffer particleIB, + StructuredBuffer batchInfo, + uint batchIdx, + uint primIdx, + float2 bary) +{ + return false; +} + +#endif diff --git a/res/gamedata/shaders/r5/rt_pathtrace.cs b/res/gamedata/shaders/r5/rt_pathtrace.cs index ec3ef01f800..0b911b040ca 100644 --- a/res/gamedata/shaders/r5/rt_pathtrace.cs +++ b/res/gamedata/shaders/r5/rt_pathtrace.cs @@ -1,22 +1,26 @@ #include "bindless_common.h" +#include "shared/terrain_blend.h" +#include "shared/pbr_brdf.h" #include "rt_common.h" +#include "rt_grass_alpha.h" +#include "rt_visibility.h" cbuffer PathTracerParams : register(b5) { float4x4 g_InvViewProj; float4 g_CameraPos; float4 g_SunDir_Intensity; float4 g_SunColor_SkyWeight; + float4 g_SkyColor; float g_ScreenWidth; float g_ScreenHeight; uint g_SampleIndex; uint g_MaxBounces; uint g_IdentityStaticCount; uint g_TerrainBatchCount; - uint g_TransparentBatchCount; uint g_SkinnedBatchStart; uint g_GrassBatchStart; uint g_DetailAtlasIndex; - uint2 g_Pad; + uint3 g_Pad; }; RaytracingAccelerationStructure g_SceneTLAS : register(t1); @@ -29,6 +33,7 @@ ByteAddressBuffer g_SkinnedIB : register(t11); ByteAddressBuffer g_GrassVB : register(t12); ByteAddressBuffer g_GrassIB : register(t13); +Texture3D t_BlueNoise : register(t14); RWTexture2D g_Accumulation : register(u0); RWTexture2D g_Output : register(u1); @@ -37,13 +42,13 @@ bool IsSkinnedBatch(uint batchIdx) { - return g_SkinnedBatchStart > 0 && batchIdx >= g_SkinnedBatchStart && - !(g_GrassBatchStart > 0 && batchIdx >= g_GrassBatchStart); + return g_SkinnedBatchStart != 0xFFFFFFFFu && batchIdx >= g_SkinnedBatchStart && + (g_GrassBatchStart == 0xFFFFFFFFu || batchIdx < g_GrassBatchStart); } bool IsGrassBatch(uint batchIdx) { - return g_GrassBatchStart > 0 && batchIdx >= g_GrassBatchStart; + return g_GrassBatchStart != 0xFFFFFFFFu && batchIdx == g_GrassBatchStart; } float4 SampleTerrainTexture(uint index, float2 uv) @@ -60,19 +65,12 @@ float3 SampleTerrainAlbedo(TerrainMaterialData mat, float2 uv) float4 baseSample = SampleTerrainTexture(mat.baseAlbedoIndex, baseUV); - float4 mask = SampleTerrainTexture(mat.blendMaskIndex, baseUV); - float maskSum = dot(mask, float4(1, 1, 1, 1)); - if (maskSum > 0.001) - mask /= maskSum; - else - mask = float4(0.25, 0.25, 0.25, 0.25); - - float3 detailR = SampleTerrainTexture(mat.detailR_Index, detailUV).rgb; - float3 detailG = SampleTerrainTexture(mat.detailG_Index, detailUV).rgb; - float3 detailB = SampleTerrainTexture(mat.detailB_Index, detailUV).rgb; - float3 detailA = SampleTerrainTexture(mat.detailA_Index, detailUV).rgb; - - float3 blendedDetail = detailR * mask.r + detailG * mask.g + detailB * mask.b + detailA * mask.a; + float4 mask = TerrainNormalizeMask(SampleTerrainTexture(mat.blendMaskIndex, baseUV)); + float4 detailR = SampleTerrainTexture(mat.detailR_Index, detailUV); + float4 detailG = SampleTerrainTexture(mat.detailG_Index, detailUV); + float4 detailB = SampleTerrainTexture(mat.detailB_Index, detailUV); + float4 detailA = SampleTerrainTexture(mat.detailA_Index, detailUV); + float3 blendedDetail = TerrainBlendRGB(detailR.rgb, detailG.rgb, detailB.rgb, detailA.rgb, mask); return baseSample.rgb * blendedDetail * 2.0; } @@ -140,7 +138,7 @@ float3 SampleSky(float3 dir) float w = g_SunColor_SkyWeight.w; float3 s0 = g_Sky0.SampleLevel(smp_linear, dir, 0).rgb; float3 s1 = g_Sky1.SampleLevel(smp_linear, dir, 0).rgb; - return lerp(s0, s1, w); + return lerp(s0, s1, w) * g_SkyColor.rgb * 0.33; } float3 GenerateCameraRay(uint2 pixel, inout uint rng, out float3 origin) @@ -221,13 +219,12 @@ void main(uint3 dispatchID : SV_DispatchThreadID) while (q.Proceed()) { if (q.CandidateType() == CANDIDATE_NON_OPAQUE_TRIANGLE) { uint candBatch = q.CandidateInstanceID() + q.CandidateGeometryIndex(); - if (IsGrassBatch(candBatch) && g_DetailAtlasIndex > 0) { - RTBatchInfo candInfo = g_BatchInfo[candBatch]; - float2 candUV = GetSkinnedHitUV(g_GrassVB, g_GrassIB, candInfo, - q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics()); - float4 texel = GetBindlessTexture(g_DetailAtlasIndex).SampleLevel(smp_linear, candUV, 0); - if (texel.a >= 0.3) + if (IsGrassBatch(candBatch)) { + if (GrassTexelOpaque(g_GrassVB, g_GrassIB, g_BatchInfo, candBatch, + q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics(), + g_DetailAtlasIndex)) q.CommitNonOpaqueTriangleHit(); + continue; } } } @@ -274,10 +271,12 @@ void main(uint3 dispatchID : SV_DispatchThreadID) continue; } - if ((hitMat.flags & MAT_FLAG_ALPHA_TEST) && hitMat.alpha < hitMat.alphaRef) { - origin = origin + direction * hitT + direction * 0.002; - bounce--; - continue; + if ((hitMat.flags & MAT_FLAG_ALPHA_TEST) && (hitMat.flags & MAT_FLAG_ALPHA_BLEND) == 0) { + if (hitMat.alpha < 0.5) { + origin = origin + direction * hitT + direction * 0.002; + bounce--; + continue; + } } if ((hitMat.flags & MAT_FLAG_ALPHA_BLEND) && hitMat.alpha < 0.5) { @@ -301,17 +300,16 @@ void main(uint3 dispatchID : SV_DispatchThreadID) shadowRay.TMax = 10000.0; RayQuery shadowQ; - shadowQ.TraceRayInline(g_SceneTLAS, RAY_FLAG_NONE, 0xFF, shadowRay); + shadowQ.TraceRayInline(g_SceneTLAS, RAY_FLAG_NONE, RT_MASK_SHADOW, shadowRay); while (shadowQ.Proceed()) { if (shadowQ.CandidateType() == CANDIDATE_NON_OPAQUE_TRIANGLE) { uint candBatch = shadowQ.CandidateInstanceID() + shadowQ.CandidateGeometryIndex(); - if (IsGrassBatch(candBatch) && g_DetailAtlasIndex > 0) { - RTBatchInfo candInfo = g_BatchInfo[candBatch]; - float2 candUV = GetSkinnedHitUV(g_GrassVB, g_GrassIB, candInfo, - shadowQ.CandidatePrimitiveIndex(), shadowQ.CandidateTriangleBarycentrics()); - float4 texel = GetBindlessTexture(g_DetailAtlasIndex).SampleLevel(smp_linear, candUV, 0); - if (texel.a >= 0.3) + if (IsGrassBatch(candBatch)) { + if (GrassTexelOpaque(g_GrassVB, g_GrassIB, g_BatchInfo, candBatch, + shadowQ.CandidatePrimitiveIndex(), shadowQ.CandidateTriangleBarycentrics(), + g_DetailAtlasIndex)) shadowQ.CommitNonOpaqueTriangleHit(); + continue; } } } @@ -323,13 +321,8 @@ void main(uint3 dispatchID : SV_DispatchThreadID) RTBatchInfo sInfo = g_BatchInfo[sBatchIdx]; if (IsGrassBatch(sBatchIdx)) { - if (g_DetailAtlasIndex > 0) { - shadowAtten = 0.0; - break; - } - shadowAtten *= 0.5; - shadowOrigin = shadowOrigin + sunDir * (shadowQ.CommittedRayT() + 0.002); - continue; + shadowAtten = 0.0; + break; } MaterialData sMat = g_Materials[sInfo.materialID]; @@ -358,9 +351,10 @@ void main(uint3 dispatchID : SV_DispatchThreadID) float4 sDiffuse = SampleDiffuseLevel(sMat, sUV); - if ((sMat.flags & MAT_FLAG_ALPHA_TEST) && sDiffuse.a < sMat.alphaRef) { - shadowOrigin = shadowOrigin + sunDir * (shadowQ.CommittedRayT() + 0.002); - continue; + if ((sMat.flags & MAT_FLAG_FOLIAGE) || + ((sMat.flags & MAT_FLAG_ALPHA_TEST) && (sMat.flags & MAT_FLAG_ALPHA_BLEND) == 0)) { + shadowAtten = 0.0; + break; } if ((sMat.flags & MAT_FLAG_ALPHA_BLEND) && sDiffuse.a < 0.5) { diff --git a/res/gamedata/shaders/r5/rt_shade_hit.h b/res/gamedata/shaders/r5/rt_shade_hit.h new file mode 100644 index 00000000000..236dfc2092c --- /dev/null +++ b/res/gamedata/shaders/r5/rt_shade_hit.h @@ -0,0 +1,36 @@ +#ifndef RT_SHADE_HIT_H +#define RT_SHADE_HIT_H + +#include "bindless_common.h" +#include "rt_common.h" +#include "shared/pbr_brdf.h" + +float3 ShadeBakedFromHemi(float hemi, float3 albedo, float3 hemiColor) +{ + float h = saturate(hemi); + return hemiColor * h * albedo * 0.55; +} + +float3 ShadeBakedFromTerrainLmap(TerrainMaterialData tmat, float2 lmUV, float3 albedo, float3 hemiColor, float hemi) +{ + return ShadeBakedFromHemi(hemi, albedo, hemiColor); +} + +float3 ShadeHitDirect( + float3 albedo, + float3 N, + float3 V, + float metallic, + float roughness, + float3 sunDir, + float3 sunColor, + float shadow, + float3 baked) +{ + float3 Lo = baked; + if (shadow > 0.001) + Lo += PBRDirectLighting(albedo, N, V, sunDir, sunColor * shadow, metallic, roughness, 1); + return Lo; +} + +#endif diff --git a/res/gamedata/shaders/r5/rt_visibility.h b/res/gamedata/shaders/r5/rt_visibility.h new file mode 100644 index 00000000000..c977ed29705 --- /dev/null +++ b/res/gamedata/shaders/r5/rt_visibility.h @@ -0,0 +1,422 @@ +#ifndef RT_VISIBILITY_H +#define RT_VISIBILITY_H + +#include "bindless_common.h" +#include "rt_common.h" +#include "rt_grass_alpha.h" +#include "rt_particle_alpha.h" +#include "rt_material_alpha.h" + +static const uint RT_VIS_MAX_SKIPS = 16; +static const uint RT_VIS_MAX_SELF = 8; +static const uint RT_VIS_GRASS_RESTARTS = 3; + +bool VisIsHudBatch(uint batchIdx, uint hudSkinnedStart, uint grassBatchStart, uint particleBatchStart) +{ + return hudSkinnedStart != 0xFFFFFFFFu && batchIdx >= hudSkinnedStart && + (grassBatchStart == 0xFFFFFFFFu || batchIdx < grassBatchStart) && + (particleBatchStart == 0xFFFFFFFFu || batchIdx < particleBatchStart); +} + +bool VisIsGrassBatch(uint batchIdx, uint grassBatchStart, uint particleBatchStart) +{ + return grassBatchStart != 0xFFFFFFFFu && batchIdx >= grassBatchStart && + (particleBatchStart == 0xFFFFFFFFu || batchIdx < particleBatchStart); +} + +bool VisIsSkinnedBatch(uint batchIdx, uint skinnedBatchStart, uint grassBatchStart, uint particleBatchStart) +{ + return skinnedBatchStart != 0xFFFFFFFFu && batchIdx >= skinnedBatchStart && + (grassBatchStart == 0xFFFFFFFFu || batchIdx < grassBatchStart) && + (particleBatchStart == 0xFFFFFFFFu || batchIdx < particleBatchStart); +} + +float SampleSTBN(Texture3D BlueNoiseTex, uint2 pixel, uint frameIndex, uint skip) +{ + uint w = 0, h = 0, d = 0; + BlueNoiseTex.GetDimensions(w, h, d); + if (w < 8u || h < 8u || d < 1u) + { + uint hsh = pcg_hash(pixel.x + pixel.y * 198491317u + frameIndex * 747796405u + skip * 1103515245u); + return float(hsh) * (1.0 / 4294967295.0); + } + uint z = (frameIndex + skip * 3u) % d; + return BlueNoiseTex.Load(int4(int(pixel.x % w), int(pixel.y % h), int(z), 0)); +} + +float FoliageAlphaCut(MaterialData mat) +{ + return max(mat.alphaRef, 0.5); +} + +bool VisMegaAlphaKeep( + ByteAddressBuffer megaVB, + ByteAddressBuffer megaIB, + StructuredBuffer batchInfo, + uint batchIdx, + uint primIdx, + float2 bary) +{ + RTBatchInfo info = batchInfo[batchIdx]; + MaterialData mat = g_Materials[info.materialID]; + if ((mat.flags & MAT_FLAG_EMISSIVE) != 0) + return false; + if ((mat.flags & MAT_FLAG_WATER) != 0) + return false; + float2 uv = GetHitUV(megaVB, megaIB, info, primIdx, bary); + float4 diffuse = SampleDiffuseAlpha(mat, uv); + if ((mat.flags & MAT_FLAG_FOLIAGE) != 0 || + ((mat.flags & MAT_FLAG_ALPHA_TEST) != 0 && (mat.flags & MAT_FLAG_ALPHA_BLEND) == 0)) + return diffuse.a >= FoliageAlphaCut(mat); + return MaterialDiffuseOpaque(mat, diffuse); +} + +float TraceVisibilityAtten( + RaytracingAccelerationStructure tlas, + StructuredBuffer batchInfo, + ByteAddressBuffer megaVB, + ByteAddressBuffer megaIB, + ByteAddressBuffer grassVB, + ByteAddressBuffer grassIB, + ByteAddressBuffer particleVB, + ByteAddressBuffer particleIB, + float3 origin, + float3 dir, + float tMax, + uint instanceMask, + uint identityStaticCount, + uint terrainBatchCount, + uint skinnedBatchStart, + uint grassBatchStart, + uint particleBatchStart, + uint detailAtlasIndex, + bool nearSkinnedOccludes, + float skinnedSelfMax, + uint hudSkinnedStart, + Texture3D BlueNoiseTex, + uint2 noisePixel, + uint frameIndex) +{ + float atten = 1.0; + float3 shadowOrigin = origin; + float3 shadowDir = normalize(dir); + float remain = tMax; + const float selfSkip = nearSkinnedOccludes ? 0.004 : 0.02; + const float skinSelf = max(skinnedSelfMax, 0.0); + uint skips = 0; + uint selfs = 0; + while (skips < RT_VIS_MAX_SKIPS) { + RayDesc ray; + ray.Origin = shadowOrigin; + ray.Direction = shadowDir; + ray.TMin = 0.001; + ray.TMax = remain; + + RayQuery q; + q.TraceRayInline(tlas, RAY_FLAG_NONE, instanceMask, ray); + while (q.Proceed()) { + if (q.CandidateType() == CANDIDATE_NON_OPAQUE_TRIANGLE) { + uint candBatch = q.CandidateInstanceID() + q.CandidateGeometryIndex(); + if (VisIsHudBatch(candBatch, hudSkinnedStart, grassBatchStart, particleBatchStart)) + continue; + bool isParticle = particleBatchStart != 0xFFFFFFFFu && candBatch >= particleBatchStart; + if (isParticle) + continue; + if (VisIsGrassBatch(candBatch, grassBatchStart, particleBatchStart)) { + if (GrassTexelOpaque(grassVB, grassIB, batchInfo, candBatch, + q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics(), + detailAtlasIndex)) + q.CommitNonOpaqueTriangleHit(); + continue; + } + if (VisIsSkinnedBatch(candBatch, skinnedBatchStart, grassBatchStart, particleBatchStart)) { + q.CommitNonOpaqueTriangleHit(); + continue; + } + if (VisMegaAlphaKeep(megaVB, megaIB, batchInfo, candBatch, + q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics())) + q.CommitNonOpaqueTriangleHit(); + } + } + + if (q.CommittedStatus() != COMMITTED_TRIANGLE_HIT) + break; + + float tHit0 = q.CommittedRayT(); + if (tHit0 < selfSkip) { + float step = (selfs >= 3u) ? 0.04 : 0.002; + shadowOrigin = shadowOrigin + shadowDir * (tHit0 + step); + remain -= (tHit0 + step); + selfs++; + if (remain <= 0.001 || selfs >= RT_VIS_MAX_SELF) + break; + continue; + } + + uint sBatchIdx = q.CommittedInstanceID() + q.CommittedGeometryIndex(); + RTBatchInfo sInfo = batchInfo[sBatchIdx]; + + if (VisIsHudBatch(sBatchIdx, hudSkinnedStart, grassBatchStart, particleBatchStart)) { + float tHit = q.CommittedRayT() + 0.002; + shadowOrigin = shadowOrigin + shadowDir * tHit; + remain -= tHit; + if (remain <= 0.001) break; + continue; + } + + if (VisIsGrassBatch(sBatchIdx, grassBatchStart, particleBatchStart)) { + atten = 0; + break; + } + + MaterialData sMat = g_Materials[sInfo.materialID]; + + bool isParticle = particleBatchStart != 0xFFFFFFFFu && sBatchIdx >= particleBatchStart; + if (isParticle) { + float tHit = q.CommittedRayT() + 0.002; + if ((sMat.flags & MAT_FLAG_EMISSIVE) != 0) { + shadowOrigin = shadowOrigin + shadowDir * tHit; + remain -= tHit; + skips++; + if (remain <= 0.001) break; + continue; + } + float2 sUV = GetParticleHitUV(particleVB, particleIB, sInfo, + q.CommittedPrimitiveIndex(), q.CommittedTriangleBarycentrics()); + float2 d = sUV * 2.0 - 1.0; + float soft = saturate(1.0 - dot(d, d)); + soft *= soft; + atten *= (1.0 - 0.65 * soft); + if (atten < 0.2) { atten = 0.2; break; } + shadowOrigin = shadowOrigin + shadowDir * tHit; + remain -= tHit; + skips++; + if (remain <= 0.001) break; + continue; + } + + if ((sMat.flags & MAT_FLAG_EMISSIVE) != 0) { + float tHit = q.CommittedRayT(); + if (tHit > remain * 0.82) { + shadowOrigin = shadowOrigin + shadowDir * (tHit + 0.002); + remain -= (tHit + 0.002); + skips++; + if (remain <= 0.001) break; + continue; + } + atten = 0; + break; + } + + if ((sMat.flags & MAT_FLAG_WATER) != 0) { + float tHit = q.CommittedRayT() + 0.002; + shadowOrigin = shadowOrigin + shadowDir * tHit; + remain -= tHit; + skips++; + if (remain <= 0.001) break; + continue; + } + + bool isTerrain = sBatchIdx >= identityStaticCount && + sBatchIdx < identityStaticCount + terrainBatchCount; + if (isTerrain) { atten = 0; break; } + + bool isSkinned = skinnedBatchStart != 0xFFFFFFFFu && sBatchIdx >= skinnedBatchStart && + (grassBatchStart == 0xFFFFFFFFu || sBatchIdx < grassBatchStart) && + (particleBatchStart == 0xFFFFFFFFu || sBatchIdx < particleBatchStart); + if (isSkinned) { + if (tHit0 < skinSelf) { + shadowOrigin = shadowOrigin + shadowDir * (tHit0 + 0.002); + remain -= (tHit0 + 0.002); + if (remain <= 0.001) break; + continue; + } + atten = 0; + break; + } + + float2 sUV = GetHitUV(megaVB, megaIB, sInfo, q.CommittedPrimitiveIndex(), q.CommittedTriangleBarycentrics()); + float4 sDiffuse = SampleDiffuseAlpha(sMat, sUV); + + if ((sMat.flags & MAT_FLAG_FOLIAGE) != 0 || + ((sMat.flags & MAT_FLAG_ALPHA_TEST) != 0 && (sMat.flags & MAT_FLAG_ALPHA_BLEND) == 0)) { + atten = 0; + break; + } + + if (!MaterialDiffuseOpaque(sMat, sDiffuse)) { + if ((sMat.flags & MAT_FLAG_ALPHA_BLEND) != 0) + atten *= (1.0 - sDiffuse.a); + float tHit = q.CommittedRayT() + 0.002; + shadowOrigin = shadowOrigin + shadowDir * tHit; + remain -= tHit; + skips++; + if (remain <= 0.001) { atten = 0; break; } + continue; + } + + atten = 0; + break; + } + return atten; +} + +bool TraceVisibilityClear( + RaytracingAccelerationStructure tlas, + StructuredBuffer batchInfo, + ByteAddressBuffer megaVB, + ByteAddressBuffer megaIB, + ByteAddressBuffer grassVB, + ByteAddressBuffer grassIB, + ByteAddressBuffer particleVB, + ByteAddressBuffer particleIB, + float3 origin, + float3 target, + uint identityStaticCount, + uint terrainBatchCount, + uint skinnedBatchStart, + uint grassBatchStart, + uint particleBatchStart, + uint detailAtlasIndex, + uint hudSkinnedStart, + Texture3D BlueNoiseTex, + uint2 noisePixel, + uint frameIndex) +{ + float3 dir = target - origin; + float dist = length(dir); + if (dist < 1e-4) + return false; + float atten = TraceVisibilityAtten( + tlas, batchInfo, megaVB, megaIB, grassVB, grassIB, particleVB, particleIB, + origin, dir / dist, max(dist - 0.02, 0.001), RT_MASK_SHADOW, + identityStaticCount, terrainBatchCount, skinnedBatchStart, grassBatchStart, + particleBatchStart, detailAtlasIndex, true, 0.0, hudSkinnedStart, + BlueNoiseTex, noisePixel, frameIndex); + return atten > 0.15; +} + +float EvaluateSunVisibilityWithGrass( + RaytracingAccelerationStructure tlas, + StructuredBuffer batchInfo, + ByteAddressBuffer megaVB, + ByteAddressBuffer megaIB, + ByteAddressBuffer grassVB, + ByteAddressBuffer grassIB, + float3 origin, + float3 sunDir, + float tMax, + uint identityStaticCount, + uint terrainBatchCount, + uint skinnedBatchStart, + uint grassBatchStart, + uint particleBatchStart, + uint detailAtlasIndex, + uint hudSkinnedStart, + Texture3D BlueNoiseTex, + uint2 pixel, + uint frameIndex, + uint instanceMask) +{ + float3 shadowOrigin = origin; + float3 shadowDir = normalize(sunDir); + float remain = max(tMax, 0.001); + uint skips = 0; + + while (skips < RT_VIS_GRASS_RESTARTS) { + RayDesc ray; + ray.Origin = shadowOrigin; + ray.Direction = shadowDir; + ray.TMin = 0.001; + ray.TMax = remain; + + RayQuery q; + q.TraceRayInline(tlas, RAY_FLAG_NONE, instanceMask, ray); + while (q.Proceed()) { + if (q.CandidateType() == CANDIDATE_NON_OPAQUE_TRIANGLE) { + uint candBatch = q.CandidateInstanceID() + q.CandidateGeometryIndex(); + if (VisIsHudBatch(candBatch, hudSkinnedStart, grassBatchStart, particleBatchStart)) + continue; + if (VisIsGrassBatch(candBatch, grassBatchStart, particleBatchStart)) { + if (GrassTexelOpaque(grassVB, grassIB, batchInfo, candBatch, + q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics(), + detailAtlasIndex)) + q.CommitNonOpaqueTriangleHit(); + continue; + } + if (VisIsSkinnedBatch(candBatch, skinnedBatchStart, grassBatchStart, particleBatchStart)) { + q.CommitNonOpaqueTriangleHit(); + continue; + } + if (VisMegaAlphaKeep(megaVB, megaIB, batchInfo, candBatch, + q.CandidatePrimitiveIndex(), q.CandidateTriangleBarycentrics())) + q.CommitNonOpaqueTriangleHit(); + } + } + + if (q.CommittedStatus() != COMMITTED_TRIANGLE_HIT) + return 1.0; + + float tHit = q.CommittedRayT(); + uint sBatchIdx = q.CommittedInstanceID() + q.CommittedGeometryIndex(); + + if (VisIsHudBatch(sBatchIdx, hudSkinnedStart, grassBatchStart, particleBatchStart)) { + shadowOrigin = shadowOrigin + shadowDir * (tHit + 0.002); + remain -= (tHit + 0.002); + if (remain <= 0.001) + return 1.0; + skips++; + continue; + } + + if (VisIsGrassBatch(sBatchIdx, grassBatchStart, particleBatchStart)) { + return 0.0; + } + + RTBatchInfo sInfo = batchInfo[sBatchIdx]; + MaterialData sMat = g_Materials[sInfo.materialID]; + + if ((sMat.flags & MAT_FLAG_WATER) != 0 || (sMat.flags & MAT_FLAG_EMISSIVE) != 0) { + shadowOrigin = shadowOrigin + shadowDir * (tHit + 0.002); + remain -= (tHit + 0.002); + if (remain <= 0.001) + return 1.0; + skips++; + continue; + } + + bool isSkinned = skinnedBatchStart != 0xFFFFFFFFu && sBatchIdx >= skinnedBatchStart && + (grassBatchStart == 0xFFFFFFFFu || sBatchIdx < grassBatchStart) && + (particleBatchStart == 0xFFFFFFFFu || sBatchIdx < particleBatchStart); + if (isSkinned && tHit < 0.04) { + shadowOrigin = shadowOrigin + shadowDir * (tHit + 0.002); + remain -= (tHit + 0.002); + if (remain <= 0.001) + return 1.0; + skips++; + continue; + } + + if (sBatchIdx >= identityStaticCount && + sBatchIdx < identityStaticCount + terrainBatchCount) + return 0.0; + + float2 sUV = GetHitUV(megaVB, megaIB, sInfo, q.CommittedPrimitiveIndex(), q.CommittedTriangleBarycentrics()); + float4 sDiffuse = SampleDiffuseAlpha(sMat, sUV); + if ((sMat.flags & MAT_FLAG_FOLIAGE) != 0 || + ((sMat.flags & MAT_FLAG_ALPHA_TEST) != 0 && (sMat.flags & MAT_FLAG_ALPHA_BLEND) == 0)) + return 0.0; + if (!MaterialDiffuseOpaque(sMat, sDiffuse)) { + shadowOrigin = shadowOrigin + shadowDir * (tHit + 0.002); + remain -= (tHit + 0.002); + if (remain <= 0.001) + return 1.0; + skips++; + continue; + } + return 0.0; + } + return 1.0; +} + +#endif diff --git a/res/gamedata/shaders/r5/shadow/shadow_cascade.ps b/res/gamedata/shaders/r5/shadow/shadow_cascade.ps new file mode 100644 index 00000000000..157f640326a Binary files /dev/null and b/res/gamedata/shaders/r5/shadow/shadow_cascade.ps differ diff --git a/res/gamedata/shaders/r5/shadow/shadow_cascade.vs b/res/gamedata/shaders/r5/shadow/shadow_cascade.vs new file mode 100644 index 00000000000..854f6636b4e --- /dev/null +++ b/res/gamedata/shaders/r5/shadow/shadow_cascade.vs @@ -0,0 +1,56 @@ +// shadow_cascade.vs — depth-only CSM caster (bindless mega-buffer path) +#define SM_6_0 +#include "common.h" +#include "bindless_common.h" + +struct VS_INPUT +{ + float4 position : POSITION; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 binormal : BINORMAL; + float2 texcoord : TEXCOORD0; + float2 texcoord1: TEXCOORD1; + float4 color : COLOR0; + uint drawIndex : DRAWINDEX; +}; + +struct VS_OUTPUT +{ + float4 position : SV_Position; + float2 texcoord : TEXCOORD0; + nointerpolation uint materialID : TEXCOORD1; +}; + +struct InstanceData +{ + float4x4 world; + uint materialID; + uint flags; + float pad0, pad1; +}; + +cbuffer ShadowCascadeCB : register(b5) +{ + float4x4 cb_LightVP; +}; + +StructuredBuffer g_InstanceData : register(t14); +StructuredBuffer g_CompactBatchIndices : register(t15); +StructuredBuffer g_CompactMaterialIDs : register(t16); + +VS_OUTPUT main(VS_INPUT input) +{ + VS_OUTPUT output; + + uint drawID = input.drawIndex; + uint batchIndex = g_CompactBatchIndices[drawID]; + InstanceData instanceData = g_InstanceData[batchIndex]; + uint materialID = g_CompactMaterialIDs[drawID]; + + float4 worldPos = mul(instanceData.world, float4(input.position.xyz, 1.0)); + output.position = mul(cb_LightVP, worldPos); + output.texcoord = input.texcoord; + output.materialID = materialID; + return output; +} diff --git a/res/gamedata/shaders/r5/shadow/shadow_cascade_rain.ps b/res/gamedata/shaders/r5/shadow/shadow_cascade_rain.ps new file mode 100644 index 00000000000..30e8dd2a2f1 Binary files /dev/null and b/res/gamedata/shaders/r5/shadow/shadow_cascade_rain.ps differ diff --git a/res/gamedata/shaders/r5/shadow/shadow_cascade_terrain.ps b/res/gamedata/shaders/r5/shadow/shadow_cascade_terrain.ps new file mode 100644 index 00000000000..d9559be6e01 Binary files /dev/null and b/res/gamedata/shaders/r5/shadow/shadow_cascade_terrain.ps differ diff --git a/res/gamedata/shaders/r5/shared/basecolor_pack.h b/res/gamedata/shaders/r5/shared/basecolor_pack.h new file mode 100644 index 00000000000..d8a0bf48df0 --- /dev/null +++ b/res/gamedata/shaders/r5/shared/basecolor_pack.h @@ -0,0 +1,34 @@ +#ifndef BASECOLOR_PACK_H +#define BASECOLOR_PACK_H + +float PackBaseColorA(float metallic, float sssMask) +{ + if (sssMask > 0.01) + return saturate(0.5 + 0.5 * sssMask); + return saturate(metallic) * 0.499; +} + +float UnpackMetallicFromBaseA(float a) +{ + return (a < 0.5) ? saturate(a / 0.499) : 0.0; +} + +float UnpackSSSMaskFromBaseA(float a) +{ + return (a >= 0.5) ? saturate((a - 0.5) * 2.0) : 0.0; +} + +float UnpackGBufferMetallic(float a, bool packedWithSSS, out float sssMask) +{ + sssMask = 0.0; + if (packedWithSSS) + { + sssMask = UnpackSSSMaskFromBaseA(a); + return UnpackMetallicFromBaseA(a); + } + if (a >= 0.5) + return saturate(a); + return saturate(a / 0.499); +} + +#endif diff --git a/res/gamedata/shaders/r5/shared/cloudconfig.h b/res/gamedata/shaders/r5/shared/cloudconfig.h index e2e3b43ee92..ae27244346f 100644 --- a/res/gamedata/shaders/r5/shared/cloudconfig.h +++ b/res/gamedata/shaders/r5/shared/cloudconfig.h @@ -1,11 +1,10 @@ #ifndef _CLOUDCONFIG_H #define _CLOUDCONFIG_H -// note: timers has resolution (sec), where x=1, y=10, z=1/10, -#define CLOUD_TILE0 (0.7f) -#define CLOUD_SPEED0 (2*0.05)//(0.033f) -#define CLOUD_TILE1 (2.8)//(2.5f) -#define CLOUD_SPEED1 (2*0.025)//(0.033f) -#define CLOUD_FADE (0.5) +#define CLOUD_TILE0 (0.7) +#define CLOUD_SPEED0 (2*0.05) +#define CLOUD_TILE1 (2.8) +#define CLOUD_SPEED1 (2*0.025) +#define CLOUD_FADE (0.5) #endif diff --git a/res/gamedata/shaders/r5/shared/clustered_lighting.h b/res/gamedata/shaders/r5/shared/clustered_lighting.h index b11458f5ac8..878201d005a 100644 --- a/res/gamedata/shaders/r5/shared/clustered_lighting.h +++ b/res/gamedata/shaders/r5/shared/clustered_lighting.h @@ -9,11 +9,14 @@ struct GPULightData { float4x4 spotVP; }; -// Point light distance attenuation (smooth window function) -float PointLightAttenuation(float distSq, float invRangeSq) +// Point light distance attenuation (smooth window + virtual-size near soft) +float PointLightAttenuation(float distSq, float invRangeSq, float virtSizeSq) { float factor = saturate(1.0f - distSq * invRangeSq); - return factor * factor; + float softSq = max(virtSizeSq, 0.1225); + float nearT = saturate(distSq / softSq); + float nearSoft = nearT * nearT * (3.0 - 2.0 * nearT); + return factor * lerp(0.85, 1.0, nearSoft); } // Spot light angular attenuation @@ -86,40 +89,53 @@ float3 EvaluateClusteredLights( uint lightIdx = g_LightIndexList[lightOffset + i]; GPULightData light = g_LightData[lightIdx]; float3 lightPos = light.positionAndInvRangeSq.xyz; - float invRangeSq = light.positionAndInvRangeSq.w; + float invRangeSq = abs(light.positionAndInvRangeSq.w); float3 lightColor = light.colorAndRange.xyz; float lightType = light.spotParamsAndType.y; float3 toLight = lightPos - worldPos; float distSq = dot(toLight, toLight); float3 L = normalize(toLight); - float atten = PointLightAttenuation(distSq, invRangeSq); + float virtSizeSq = light.spotParamsAndType.w; + float atten = PointLightAttenuation(distSq, invRangeSq, virtSizeSq); if (lightType > 0.5f) { + float3 spotDir = light.directionAndSpotScale.xyz; + float spotScale = light.directionAndSpotScale.w; + float spotOffset = light.spotParamsAndType.x; + float cone = SpotLightAttenuation(toLight, spotDir, spotScale, spotOffset); + uint texIdx = asuint(light.spotParamsAndType.z); - if (texIdx != 0) + if (texIdx != 0xFFFFFFFFu) { float4 projPos = mul(light.spotVP, float4(worldPos, 1.0)); - if (projPos.w > 0) + if (projPos.w > 1e-3) { float2 projUV = projPos.xy / projPos.w * 0.5 + 0.5; projUV.y = 1.0 - projUV.y; - Texture2D spotTex = GetBindlessTexture(texIdx); - float4 texSample = spotTex.SampleLevel(smp_rtlinear, projUV, 0); - atten *= texSample.r; + float2 edge = saturate(min(projUV, 1.0 - projUV) * 4.0); + float uvMask = edge.x * edge.y; + if (uvMask > 1e-4) + { + Texture2D spotTex = GetBindlessTexture(texIdx); + float cookie = spotTex.SampleLevel(smp_rtlinear, projUV, 0).r; + cookie = smoothstep(0.02, 0.45, cookie); + atten *= cookie * cone * uvMask; + } + else + { + atten = 0.0; + } } else { - atten = 0; + atten *= cone * 0.2; } } else { - float3 spotDir = light.directionAndSpotScale.xyz; - float spotScale = light.directionAndSpotScale.w; - float spotOffset = light.spotParamsAndType.x; - atten *= SpotLightAttenuation(toLight, spotDir, spotScale, spotOffset); + atten *= cone; } } @@ -134,6 +150,60 @@ float3 EvaluateClusteredLights( } return totalLight; } + +float3 EvaluateAllLocalLights( + float3 worldPos, + float3 N, + float3 V, + float3 albedo, + float metallic, + float roughness, + uint diffuseMode) +{ + uint numLights = min((uint)cluster_params.w, 256u); + if (numLights == 0) + return 0; + + float3 totalLight = 0; + for (uint i = 0; i < numLights; i++) + { + GPULightData light = g_LightData[i]; + if (light.colorAndRange.w < 0.01) + continue; + float3 lightPos = light.positionAndInvRangeSq.xyz; + float invRangeSq = abs(light.positionAndInvRangeSq.w); + if (invRangeSq < 1e-8) + continue; + float3 lightColor = light.colorAndRange.xyz; + float lightType = light.spotParamsAndType.y; + + float3 toLight = lightPos - worldPos; + float distSq = dot(toLight, toLight); + if (distSq * invRangeSq >= 1.0) + continue; + + float3 L = normalize(toLight); + float virtSizeSq = light.spotParamsAndType.w; + float atten = PointLightAttenuation(distSq, invRangeSq, virtSizeSq); + + if (lightType > 0.5f) + { + float3 spotDir = light.directionAndSpotScale.xyz; + float spotScale = light.directionAndSpotScale.w; + float spotOffset = light.spotParamsAndType.x; + atten *= SpotLightAttenuation(toLight, spotDir, spotScale, spotOffset); + } + + if (atten > 0.001f) + { + totalLight += PBRDirectLighting( + albedo, N, V, L, + lightColor * atten, + metallic, roughness, diffuseMode); + } + } + return totalLight; +} #endif // CLUSTERED_LIGHTING_FORWARD #endif // CLUSTERED_LIGHTING_H diff --git a/res/gamedata/shaders/r5/shared/common.h b/res/gamedata/shaders/r5/shared/common.h index d8853bb04fd..64992f0a31e 100644 --- a/res/gamedata/shaders/r5/shared/common.h +++ b/res/gamedata/shaders/r5/shared/common.h @@ -6,6 +6,13 @@ #ifndef SHARED_COMMON_H #define SHARED_COMMON_H +float3 SRGBToLinear(float3 c) +{ + float3 lo = c / 12.92; + float3 hi = pow(max((c + 0.055) / 1.055, 0.0), 2.4); + return lerp(lo, hi, step(0.04045, c)); +} + // Used by VS cbuffer dynamic_transforms : register(b0) { diff --git a/res/gamedata/shaders/r5/shared/foliage_sss.h b/res/gamedata/shaders/r5/shared/foliage_sss.h new file mode 100644 index 00000000000..23da4f22512 --- /dev/null +++ b/res/gamedata/shaders/r5/shared/foliage_sss.h @@ -0,0 +1,59 @@ +#ifndef FOLIAGE_SSS_H +#define FOLIAGE_SSS_H + +float3 EvaluateFoliageSSS( + float3 albedo, + float3 N, + float3 V, + float3 L, + float3 lightColor, + float shadow, + float3 sssTint, + float thickness, + float intensity) +{ + if (intensity <= 1e-5) + return float3(0, 0, 0); + + N = normalize(N); + L = normalize(L); + V = normalize(V); + + float thin = saturate(thickness); + float thick = saturate(1.0 - thin); + const float wrap = lerp(0.55, 0.35, thick); + float NdotL = dot(N, L); + float wrapDiffuse = saturate((NdotL + wrap) / (1.0 + wrap)); + + float backLit = saturate(-NdotL); + float3 H = normalize(L + N * 0.35); + float VdotH = saturate(dot(V, -H)); + float scatter = pow(VdotH, lerp(1.35, 2.1, thick)) * backLit; + + float through = pow(saturate(dot(V, -L)), lerp(2.4, 3.6, thick)); + through *= lerp(0.45, 1.0, saturate(1.0 - abs(NdotL))); + + float term = (wrapDiffuse * 0.4 + scatter * 1.15 + through * 0.95) * thin * intensity; + float sssShadow = lerp(shadow, 1.0, lerp(0.75, 0.45, thick)); + + return albedo * sssTint * lightColor * term * sssShadow; +} + +#if defined(SKY_IBL) +float3 EvaluateFoliageSkySSS( + float3 albedo, + float3 N, + float3 sssTint, + float thickness, + float intensity) +{ + if (intensity <= 1e-5) + return float3(0, 0, 0); + float thin = saturate(thickness); + float3 sky = SampleSkyRGB(-normalize(N)); + float weather = length(L_ambient.rgb + L_hemi_color.rgb * L_hemi_color.w); + return albedo * sssTint * sky * weather * thin * intensity * 0.38; +} +#endif + +#endif diff --git a/res/gamedata/shaders/r5/shared/nrd_helpers.h b/res/gamedata/shaders/r5/shared/nrd_helpers.h new file mode 100644 index 00000000000..032729b093a --- /dev/null +++ b/res/gamedata/shaders/r5/shared/nrd_helpers.h @@ -0,0 +1,101 @@ +#ifndef NRD_HELPERS_H +#define NRD_HELPERS_H + +#ifndef NRD_MATERIAL_FACTOR_MIN_SCALE +#define NRD_MATERIAL_FACTOR_MIN_SCALE 0.02 +#endif +#ifndef NRD_ROUGHNESS_FACTOR_MIN_SCALE +#define NRD_ROUGHNESS_FACTOR_MIN_SCALE 0.1 +#endif +#ifndef NRD_EPS +#define NRD_EPS 1e-6 +#endif +#ifndef NRD_FP16_MAX +#define NRD_FP16_MAX 65504.0 +#endif + +float3 NRD_EnvironmentTerm_Rtg(float3 Rf0, float NoV, float roughness) +{ + float m = saturate(roughness * roughness); + + float4 X; + X.x = 1.0; + X.y = NoV; + X.z = NoV * NoV; + X.w = NoV * X.z; + + float4 Y; + Y.x = 1.0; + Y.y = m; + Y.z = m * m; + Y.w = m * Y.z; + + const float2x2 M1 = float2x2(0.99044, -1.28514, 1.29678, -0.755907); + const float3x3 M2 = float3x3(1.0, 2.92338, 59.4188, 20.3225, -27.0302, 222.592, 121.563, 626.13, 316.627); + const float2x2 M3 = float2x2(0.0365463, 3.32707, 9.0632, -9.04756); + const float3x3 M4 = float3x3(1.0, 3.59685, -1.36772, 9.04401, -16.3174, 9.22949, 5.56589, 19.7886, -20.2123); + + float bias = dot(mul(M1, X.xy), Y.xy) * rcp(max(dot(mul(M2, X.xyw), Y.xyw), NRD_EPS)); + float scale = dot(mul(M3, X.xy), Y.xy) * rcp(max(dot(mul(M4, X.xzw), Y.xyw), NRD_EPS)); + + return saturate(Rf0 * scale + bias); +} + +void NRD_MaterialFactors(float3 N, float3 V, float3 albedo, float3 Rf0, float roughness, out float3 diffFactor, out float3 specFactor) +{ + float NoV = abs(dot(N, V)); + float3 Fenv = NRD_EnvironmentTerm_Rtg(Rf0, NoV, roughness); + + diffFactor = (1.0 - Fenv) * albedo; + diffFactor = lerp(NRD_MATERIAL_FACTOR_MIN_SCALE.xxx, float3(1.0, 1.0, 1.0), diffFactor); + + specFactor = Fenv; + specFactor *= lerp(NRD_ROUGHNESS_FACTOR_MIN_SCALE.xxx, float3(1.0, 1.0, 1.0), roughness); + specFactor = lerp(NRD_MATERIAL_FACTOR_MIN_SCALE.xxx, float3(1.0, 1.0, 1.0), specFactor); +} + +float3 NRD_LinearToYCoCg(float3 color) +{ + float Y = dot(color, float3(0.25, 0.5, 0.25)); + float Co = dot(color, float3(0.5, 0.0, -0.5)); + float Cg = dot(color, float3(-0.25, 0.5, -0.25)); + return float3(Y, Co, Cg); +} + +float3 NRD_YCoCgToLinear(float3 color) +{ + float t = color.x - color.z; + float3 r; + r.y = color.x + color.z; + r.x = t + color.y; + r.z = t - color.y; + return max(r, 0.0); +} + +float NRD_GetSpecMagicCurve(float roughness) +{ + float f = 1.0 - exp2(-200.0 * roughness * roughness); + f *= pow(saturate(roughness), 0.5); + return f; +} + +float REBLUR_GetNormHitDist(float hitDist, float viewZ, float3 hitDistParams, float roughness) +{ + float smc = NRD_GetSpecMagicCurve(roughness); + float f = (hitDistParams.x + abs(viewZ) * hitDistParams.y) * lerp(hitDistParams.z, 1.0, smc); + return saturate(hitDist / max(f, 1e-4)); +} + +float NRD_TrimHitDistance(float hitDist, float threshold) +{ + return hitDist < threshold ? 0.0 : hitDist; +} + +float3 NRD_SanitizeRadiance(float3 radiance) +{ + if (any(isnan(radiance)) || any(isinf(radiance))) + return 0; + return clamp(radiance, 0.0, NRD_FP16_MAX); +} + +#endif diff --git a/res/gamedata/shaders/r5/shared/parallax.h b/res/gamedata/shaders/r5/shared/parallax.h new file mode 100644 index 00000000000..b87703ea9ff --- /dev/null +++ b/res/gamedata/shaders/r5/shared/parallax.h @@ -0,0 +1,174 @@ +#ifndef PARALLAX_H +#define PARALLAX_H + +float SampleHeightIndex(uint index, float2 uv) +{ + if (index == INVALID_TEXTURE_INDEX) + return 0.5; + return GetBindlessTexture(index).SampleLevel(smp_linear, uv, 0).a; +} + +bool HeightHasRelief(uint heightIndex, float2 uv) +{ + float h0 = SampleHeightIndex(heightIndex, uv); + float h1 = SampleHeightIndex(heightIndex, uv + float2(0.012, 0.0)); + float h2 = SampleHeightIndex(heightIndex, uv + float2(0.0, 0.012)); + float contrast = max(abs(h0 - h1), abs(h0 - h2)); + return contrast > 0.045; +} + +float2 ParallaxOffsetUV(float2 uv, uint heightIndex, float3 T, float3 B, float3 N, float3 worldPos, bool forceSteep) +{ + if (heightIndex == INVALID_TEXTURE_INDEX) + return uv; + float scale = parallax.x; + if (scale <= 1e-5) + return uv; + if (!HeightHasRelief(heightIndex, uv)) + return uv; + float3 Nw = normalize(N); + float tlen = length(T); + float blen = length(B); + if (tlen < 1e-4 || blen < 1e-4) + return uv; + float3 Tw = T / tlen; + float3 Bw = B / blen; + float3 toEye = eye_position.xyz - worldPos; + float dist = length(toEye); + if (dist < 1e-4) + return uv; + float3 V = toEye / dist; + float fade = 1.0 - saturate((dist - 8.0) / 4.0); + if (fade <= 1e-3) + return uv; + float3 viewTS = float3(dot(V, Tw), dot(V, Bw), dot(V, Nw)); + float vlen = length(viewTS); + if (vlen < 1e-4) + return uv; + viewTS /= vlen; + if (viewTS.z < 0.15) + return uv; + bool steep = forceSteep; + if (steep) + { + int steps = (int)lerp(16.0, 6.0, saturate(viewTS.z)); + steps = clamp(steps, 6, 16); + float stepSize = 1.0 / float(steps); + float2 delta = viewTS.xy * (-scale * 1.2); + float2 stepUV = delta * stepSize; + float2 cur = uv; + float currH = 0.0; + float bound = 1.0; + [loop] + for (int i = 0; i < steps; ++i) + { + if (currH < bound) + { + cur += stepUV; + currH = SampleHeightIndex(heightIndex, cur); + bound -= stepSize; + } + } + cur -= stepUV; + float prevH = SampleHeightIndex(heightIndex, cur); + float d2 = (bound + stepSize) - prevH; + float d1 = bound - currH; + float amount = (bound * d2 - (bound + stepSize) * d1) / max(d2 - d1, 1e-5); + return uv + delta * ((1.0 - amount) * fade); + } + float h = SampleHeightIndex(heightIndex, uv); + h = h * parallax.x + parallax.y; + return uv + h * viewTS.xy * fade; +} + +float2 ApplyMaterialParallaxUV(MaterialData mat, float2 uv, float3 T, float3 B, float3 N, float3 worldPos) +{ + if ((mat.flags & MAT_FLAG_HAS_PBR) == 0) + return uv; + bool steep = ((mat.flags & MAT_FLAG_STEEP_PARALLAX) != 0) || (parallax.z > 0.5); + return ParallaxOffsetUV(uv, mat.pbrIndex, T, B, N, worldPos, steep); +} + +float TerrainBlendedHeight(TerrainMaterialData mat, float4 mask, float2 detailUV) +{ + float hR = SampleHeightIndex(mat.pbrR_Index, detailUV); + float hG = SampleHeightIndex(mat.pbrG_Index, detailUV); + float hB = SampleHeightIndex(mat.pbrB_Index, detailUV); + float hA = SampleHeightIndex(mat.pbrA_Index, detailUV); + return hR * mask.r + hG * mask.g + hB * mask.b + hA * mask.a; +} + +bool TerrainHeightHasRelief(TerrainMaterialData mat, float4 mask, float2 detailUV) +{ + float h0 = TerrainBlendedHeight(mat, mask, detailUV); + float h1 = TerrainBlendedHeight(mat, mask, detailUV + float2(0.012, 0.0)); + float h2 = TerrainBlendedHeight(mat, mask, detailUV + float2(0.0, 0.012)); + float contrast = max(abs(h0 - h1), abs(h0 - h2)); + return contrast > 0.045; +} + +float2 ApplyTerrainParallaxUV(TerrainMaterialData mat, float4 mask, float2 detailUV, float3 T, float3 B, float3 N, float3 worldPos) +{ + if ((mat.flags & MAT_FLAG_HAS_PBR_LAYER) == 0) + return detailUV; + float scale = parallax.x; + if (scale <= 1e-5) + return detailUV; + if (!TerrainHeightHasRelief(mat, mask, detailUV)) + return detailUV; + float3 Nw = normalize(N); + float tlen = length(T); + float blen = length(B); + if (tlen < 1e-4 || blen < 1e-4) + return detailUV; + float3 Tw = T / tlen; + float3 Bw = B / blen; + float3 toEye = eye_position.xyz - worldPos; + float dist = length(toEye); + if (dist < 1e-4) + return detailUV; + float3 V = toEye / dist; + float fade = 1.0 - saturate((dist - 8.0) / 4.0); + if (fade <= 1e-3) + return detailUV; + float3 viewTS = float3(dot(V, Tw), dot(V, Bw), dot(V, Nw)); + float vlen = length(viewTS); + if (vlen < 1e-4) + return detailUV; + viewTS /= vlen; + if (viewTS.z < 0.15) + return detailUV; + bool steep = ((mat.flags & MAT_FLAG_STEEP_PARALLAX) != 0) || (parallax.z > 0.5); + if (steep) + { + int steps = (int)lerp(16.0, 6.0, saturate(viewTS.z)); + steps = clamp(steps, 6, 16); + float stepSize = 1.0 / float(steps); + float2 delta = viewTS.xy * (-scale * 1.2); + float2 stepUV = delta * stepSize; + float2 cur = detailUV; + float currH = 0.0; + float bound = 1.0; + [loop] + for (int i = 0; i < steps; ++i) + { + if (currH < bound) + { + cur += stepUV; + currH = TerrainBlendedHeight(mat, mask, cur); + bound -= stepSize; + } + } + cur -= stepUV; + float prevH = TerrainBlendedHeight(mat, mask, cur); + float d2 = (bound + stepSize) - prevH; + float d1 = bound - currH; + float amount = (bound * d2 - (bound + stepSize) * d1) / max(d2 - d1, 1e-5); + return detailUV + delta * ((1.0 - amount) * fade); + } + float h = TerrainBlendedHeight(mat, mask, detailUV); + h = h * parallax.x + parallax.y; + return detailUV + h * viewTS.xy * fade; +} + +#endif diff --git a/res/gamedata/shaders/r5/shared/pbr_brdf.h b/res/gamedata/shaders/r5/shared/pbr_brdf.h index 16be35e9460..782eca5ca6f 100644 --- a/res/gamedata/shaders/r5/shared/pbr_brdf.h +++ b/res/gamedata/shaders/r5/shared/pbr_brdf.h @@ -70,7 +70,6 @@ float3 CookTorranceSpecular( return numerator / denominator; } -// Calculate F0 (base reflectivity) from metallic and albedo float3 CalculateF0(float3 albedo, float metallic) { return lerp(DIELECTRIC_F0, albedo, metallic); @@ -137,22 +136,21 @@ float3 PBRDirectLighting( float3 F0 = CalculateF0(albedo, metallic); + float3 F = F_Schlick(HdotV, F0); float3 specular = CookTorranceSpecular(NdotH, NdotV, NdotL, HdotV, roughness, F0); - specular += MultiscatterCompensation(F0, NdotV, NdotL, roughness); + specular += MultiscatterCompensation(F0, NdotV, NdotL, roughness) * (1.0f - metallic); float fd = (diffuseMode == 1) ? LambertianDiffuse() : DisneyDiffuse(NdotV, NdotL, LdotH, roughness); - float3 F_in = F_Schlick(NdotL, F0); - float3 F_out = F_Schlick(NdotV, F0); - float3 kD = (1.0f - F_in) * (1.0f - F_out) * (1.0f - metallic); + float3 kD = (1.0f - F) * (1.0f - metallic); float3 diffuse = kD * albedo * fd; return (diffuse + specular) * lightColor * NdotL; } -// Simplified ambient term (placeholder for future IBL) +// Ambient fill: albedo * (L_ambient + env IBL), plus rough specular lobe float3 PBRAmbient( float3 albedo, float3 N, @@ -168,8 +166,6 @@ float3 PBRAmbient( float3 kD = (1.0f - F) * (1.0f - metallic); float3 diffuseAmbient = kD * albedo * ambientColor; - - // Approximate specular ambient (will be replaced by IBL) float3 specularAmbient = F * ambientColor * 0.3f; return (diffuseAmbient + specularAmbient) * ao; diff --git a/res/gamedata/shaders/r5/shared/skin_sss.h b/res/gamedata/shaders/r5/shared/skin_sss.h new file mode 100644 index 00000000000..665763609da --- /dev/null +++ b/res/gamedata/shaders/r5/shared/skin_sss.h @@ -0,0 +1,94 @@ +#ifndef SKIN_SSS_H +#define SKIN_SSS_H + +float3 SkinSSSTint() +{ + return float3(1.0, 0.42, 0.32); +} + +float3 LeafSSSTint() +{ + return float3(0.5, 0.82, 0.4); +} + +float3 PetalSSSTint() +{ + return float3(0.92, 0.5, 0.58); +} + +float3 WaxSSSTint() +{ + return float3(1.0, 0.85, 0.62); +} + +void EvalSkinSSSParams( + float3 albedo, + float metallic, + float3 N, + float3 V, + float gloss, + out float sssStrength, + out float3 sssTint, + out float sssThickness) +{ + sssStrength = 0.0; + sssTint = SkinSSSTint(); + sssThickness = 1.0; + if (metallic >= 0.35) + return; + + float flesh = saturate((albedo.r - albedo.b) * 1.4 + 0.08); + flesh *= saturate(1.08 - albedo.g * 0.22); + if (flesh < 0.12) + return; + + float ndv = saturate(dot(normalize(N), normalize(V))); + sssStrength = 0.28 * flesh; + sssThickness = saturate(0.4 + ndv * 0.35 + gloss * 0.08); +} + +void ApplySSSMapSample( + float4 sssSample, + inout float sssStrength, + inout float3 sssTint, + inout float sssThickness, + out float sssMask, + out float sssProfile) +{ + sssThickness = saturate(lerp(sssThickness, 1.0 - sssSample.r, 0.65)); + float mapStr = saturate(sssSample.g) * 0.72; + sssProfile = sssSample.b * 5.01; + sssStrength = max(sssStrength, mapStr); + sssMask = 0.0; + if (sssProfile < 0.5) + { + sssTint = lerp(sssTint, SkinSSSTint(), 0.7); + sssMask = saturate(mapStr * 0.85); + sssStrength = max(sssStrength, mapStr * 0.4); + } + else if (sssProfile < 1.5) + { + sssTint = lerp(LeafSSSTint(), sssTint, 0.45); + sssMask = saturate(mapStr * 0.9); + sssStrength = max(sssStrength, mapStr * 0.48); + } + else if (sssProfile < 2.5) + { + sssTint = lerp(PetalSSSTint(), sssTint, 0.4); + sssMask = saturate(mapStr * 0.85); + sssStrength = max(sssStrength, mapStr * 0.45); + } + else if (sssProfile < 3.5) + { + sssTint = lerp(WaxSSSTint(), sssTint, 0.35); + sssMask = saturate(mapStr * 0.75); + sssStrength = max(sssStrength, mapStr * 0.4); + } + else + { + sssTint = float3(0.85, 0.92, 1.0); + sssMask = 0.0; + } +} + +#endif diff --git a/res/gamedata/shaders/r5/shared/ssr.h b/res/gamedata/shaders/r5/shared/ssr.h new file mode 100644 index 00000000000..66c6f625ce4 --- /dev/null +++ b/res/gamedata/shaders/r5/shared/ssr.h @@ -0,0 +1,669 @@ +// r5/shared/ssr.h — Screen-space reflections (X-Ray +Z view depth) +// Hard frustum miss made ALL wet pixels snap together (parallel R) — sky or geo. +// On border exit: clamp UV + fade conf, never binary zero. +#ifndef SSR_H_R5 +#define SSR_H_R5 + +#include "surface_marks.h" + +#define SSR_EDGE_ATTENUATION 0.10 +#define SSR_VERTICAL_FADE 4.0 + +#if !defined(SSR_QUALITY) + #define SSR_QUALITY 1 +#endif +#if (SSR_QUALITY <= 1) || (SSR_QUALITY > 4) + #define SSR_SAMPLES 16 + #define SSR_DISTANCE 80.0 + #define SSR_THICKNESS 1.5 + #define SSR_REFINE 2 +#elif SSR_QUALITY == 2 + #define SSR_SAMPLES 24 + #define SSR_DISTANCE 120.0 + #define SSR_THICKNESS 1.25 + #define SSR_REFINE 4 +#elif SSR_QUALITY == 3 + #define SSR_SAMPLES 32 + #define SSR_DISTANCE 160.0 + #define SSR_THICKNESS 1.0 + #define SSR_REFINE 6 +#else + #define SSR_SAMPLES 48 + #define SSR_DISTANCE 220.0 + #define SSR_THICKNESS 0.85 + #define SSR_REFINE 8 +#endif + +Texture2D g_SceneColor : register(t20); +Texture2D g_SceneDepth : register(t21); + +float RayAttenBorder(float2 pos, float value) +{ + float borderDist = min(1.0 - max(pos.x, pos.y), min(pos.x, pos.y)); + return saturate(borderDist > value ? 1.0 : borderDist / value); +} + +bool WorldToUv(float3 worldPos, out float2 uv, out float w) +{ + uv = 0.0; + float4 clip = mul(m_VP, float4(worldPos, 1.0)); + w = clip.w; + if (w <= 1e-4) + return false; + float2 ndc = clip.xy / w; + uv = float2(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5); + return true; +} + +float3 ReconstructWorld(float2 uv, float rawDepth) +{ + float2 ndc = float2(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0); + float4 worldH = mul(m_InvVP, float4(ndc, rawDepth, 1.0)); + return worldH.xyz / max(worldH.w, 1e-5); +} + +float SampleSceneViewZ(float2 uv, out float rawDepth) +{ + rawDepth = g_SceneDepth.SampleLevel(smp_nofilter, uv, 0).x; + float3 worldPos = ReconstructWorld(uv, rawDepth); + return abs(mul(m_V, float4(worldPos, 1.0)).z); +} + +// Eye-space distance to reconstructed scene position (used by SSGI march). +float SampleSceneDistance(float2 uv, out float3 scenePos, out float rawDepth) +{ + rawDepth = g_SceneDepth.SampleLevel(smp_nofilter, uv, 0).x; + scenePos = ReconstructWorld(uv, rawDepth); + return length(scenePos - eye_position); +} + +float4 compute_ssr_ex( + float3 position, + float3 normal, + float3 skybox, + int steps, + float maxDist, + float thickness, + int refineCount, + float roughnessBlur) +{ + float3 N = normalize(normal); + float3 V = normalize(position - eye_position); + float3 R = reflect(V, N); +#ifdef SSR_WATER_DOWNWARD_BIAS + if (R.y < -0.55) + return float4(skybox, 0.0); +#endif + +#ifdef SSR_OPAQUE_REFLECTOR + if (N.y > 0.5) + { + N = normalize(lerp(N, float3(0.0, 1.0, 0.0), 0.35)); + R = reflect(V, N); + R.y = max(R.y, 0.02); + } +#endif + + R = normalize(R); + steps = clamp(steps, 8, 64); + refineCount = clamp(refineCount, 1, 8); + maxDist = max(maxDist, 8.0); + thickness = max(thickness, 0.35); + + float viewZ0 = abs(mul(m_V, float4(position, 1.0)).z); + float nearAmt = saturate(1.0 - (viewZ0 - 0.25) / 3.0); + + float3 origin = position; + float2 startUV = 0.0; + float startW = 1.0; +#ifdef SSR_OPAQUE_REFLECTOR + float bias = lerp(0.45, 0.03, nearAmt); + origin = position + N * bias; + thickness = max(thickness, lerp(2.0, 0.35, nearAmt)); + steps = max(steps, nearAmt > 0.4 ? 32 : 40); + maxDist = max(maxDist, lerp(180.0, 28.0, nearAmt)); +#endif + WorldToUv(origin, startUV, startW); + + float tMin = max(0.5, maxDist / float(steps) * 0.2); +#ifdef SSR_OPAQUE_REFLECTOR + tMin = lerp(max(tMin, 1.25), 0.04, nearAmt); +#endif + + float2 hitUV = 0.0; + bool hit = false; + float hitConf = 0.0; + bool borderFade = false; + + float tPrev = tMin; + float2 prevUV = startUV; + { + float3 p0 = origin + R * tMin; + float pw; + WorldToUv(p0, prevUV, pw); + } + + [loop] + for (int i = 1; i <= steps; ++i) + { + float u = float(i) / float(steps); + float t = max(tMin, maxDist * u * u); + float3 marchPos = origin + R * t; + + float2 uv; + float w; + if (!WorldToUv(marchPos, uv, w)) + { + if (tPrev > tMin && all(prevUV >= 0.0) && all(prevUV <= 1.0)) + { + hitUV = prevUV; + hitConf = 0.35; + hit = true; + borderFade = true; + } + break; + } + if (any(uv < 0.0) || any(uv > 1.0)) + { + if (tPrev > tMin) + hitUV = saturate(prevUV); + else + hitUV = saturate(uv); + hitConf = 0.3 * RayAttenBorder(hitUV, 0.08); + hit = true; + borderFade = true; + break; + } + + float rawDepth; + float sceneVZ = SampleSceneViewZ(uv, rawDepth); + float marchVZ = abs(mul(m_V, float4(marchPos, 1.0)).z); + + if (IsSkyDepth(rawDepth) || IsHudPixel(rawDepth, 0.0)) + { +#ifdef SSR_ACCEPT_SKY + if (t > tMin * 1.25 && !IsHudPixel(rawDepth, 0.0)) + { + hitUV = uv; + hitConf = 0.06; + hit = true; + break; + } +#endif + prevUV = uv; + tPrev = t; + continue; + } + + float delta = sceneVZ - marchVZ; + if (delta <= thickness && delta >= -thickness) + { +#ifdef SSR_OPAQUE_REFLECTOR + float minUvSep = lerp(0.028, 0.0035, nearAmt); + if (length(uv - startUV) < minUvSep) + { + prevUV = uv; + tPrev = t; + continue; + } +#endif + float2 uvA = prevUV; + float2 uvB = uv; + float distA = tPrev; + float distB = t; + [loop] + for (int r = 0; r < refineCount; ++r) + { + float midT = 0.5 * (distA + distB); + float2 uvM = 0.5 * (uvA + uvB); + float dM; + float sVZ = SampleSceneViewZ(uvM, dM); + float mVZ = abs(mul(m_V, float4(origin + R * midT, 1.0)).z); + if ((sVZ - mVZ) <= thickness) + { + uvB = uvM; + distB = midT; + } + else + { + uvA = uvM; + distA = midT; + } + } + hitUV = 0.5 * (uvA + uvB); +#ifdef SSR_OPAQUE_REFLECTOR + if (length(hitUV - startUV) < minUvSep) + { + prevUV = uv; + tPrev = t; + continue; + } +#endif + float dHit; + float sHit = SampleSceneViewZ(hitUV, dHit); + float mHit = abs(mul(m_V, float4(origin + R * (0.5 * (distA + distB)), 1.0)).z); + float err = abs(sHit - mHit); + hitConf = saturate(1.0 - err / max(thickness * 2.5, 1e-3)); + float3 hitPos = ReconstructWorld(hitUV, dHit); + float toward = dot(normalize(hitPos - origin), R); + hitConf *= saturate(toward * 2.0); + hit = hitConf > 0.04; + break; + } + else if (delta < -thickness) + { + if (tPrev > tMin * 1.5 && all(prevUV >= 0.0) && all(prevUV <= 1.0)) + { + hitUV = prevUV; + hitConf = 0.25; + hit = true; + borderFade = true; + } + break; + } + + prevUV = uv; + tPrev = t; + } + + if (!hit) + return float4(skybox, 0.0); + + // Always soft-fade near screen border (even if SSR_NO_EDGE_ATTEN for water) + float edge = RayAttenBorder(hitUV, borderFade ? 0.14 : SSR_EDGE_ATTENUATION); +#ifdef SSR_NO_EDGE_ATTEN + if (!borderFade) + edge = max(edge, 0.85); +#endif +#ifdef SSR_NO_VERTICAL_FADE + float vertFade = 1.0; +#else + float vertFade = saturate(hitUV.y * SSR_VERTICAL_FADE); +#endif + float conf = edge * vertFade * hitConf; + + float2 texel = screen_res.zw; + float br = saturate(roughnessBlur) * 3.5; + float3 img = g_SceneColor.SampleLevel(smp_nofilter, hitUV, 0).xyz; + if (br > 0.08) + { + img += g_SceneColor.SampleLevel(smp_rtlinear, hitUV + float2( br, 0) * texel, 0).xyz; + img += g_SceneColor.SampleLevel(smp_rtlinear, hitUV + float2(-br, 0) * texel, 0).xyz; + img += g_SceneColor.SampleLevel(smp_rtlinear, hitUV + float2(0, br) * texel, 0).xyz; + img += g_SceneColor.SampleLevel(smp_rtlinear, hitUV + float2(0, -br) * texel, 0).xyz; + img *= 0.2; + } + float hitLum = dot(max(img, 0.0), float3(0.2126, 0.7152, 0.0722)); +#ifdef SSR_KEEP_DIM_HITS + float keepDim = step(1e-5, hitLum); + conf *= keepDim; + img *= keepDim; +#else + if (hitLum < 1e-4) + return float4(skybox, 0.0); + conf *= saturate(hitLum * 6.0 + 0.2); +#endif + if (borderFade) + img = lerp(skybox * 0.2, img, saturate(conf * 2.4)); + return float4(img, conf); +} + +float4 compute_ssr(float3 position, float3 normal, float3 skybox) +{ + return compute_ssr_ex( + position, normal, skybox, + SSR_SAMPLES, SSR_DISTANCE, SSR_THICKNESS, SSR_REFINE, 0.0); +} + +float4 compute_ssr_water(float3 position, float3 normal, float3 skybox) +{ + float3 N = normalize(lerp(normalize(normal), float3(0.0, 1.0, 0.0), 0.72)); + if (N.y < 0.0) + N = -N; + float3 V = normalize(position - eye_position); + float3 R = reflect(V, N); + if (R.y < 0.08) + return float4(skybox, 0.0); + R = normalize(R); + + const int steps = 36; + const float maxDist = 160.0; + const float thickness = 1.15; + const int refineCount = 6; + const float minUvSep = 0.008; + const float tMin = 1.25; + + float3 origin = position + float3(0.0, 1.0, 0.0) * 0.15; + float waterCamDist = length(position - eye_position); + float2 startUV = 0.0; + float startW = 1.0; + if (!WorldToUv(origin, startUV, startW)) + return float4(skybox, 0.0); + + float2 dirUV = 0.0; + { + float2 uv1 = 0.0; + float w1 = 1.0; + if (!WorldToUv(origin + R * 3.0, uv1, w1)) + return float4(skybox, 0.0); + dirUV = uv1 - startUV; + float dirLen = length(dirUV); + if (dirLen < 1e-4) + return float4(skybox, 0.0); + dirUV /= dirLen; + } + + float2 hitUV = 0.0; + bool hit = false; + float hitConf = 0.0; + float hitT = 0.0; + + float tPrev = tMin; + float2 prevUV = startUV; + { + float pw = 1.0; + WorldToUv(origin + R * tMin, prevUV, pw); + } + + [loop] + for (int i = 1; i <= steps; ++i) + { + float u = float(i) / float(steps); + float t = max(tMin, maxDist * u * u); + float3 marchPos = origin + R * t; + + float2 uv = 0.0; + float w = 1.0; + if (!WorldToUv(marchPos, uv, w)) + break; + if (any(uv < 0.0) || any(uv > 1.0)) + break; + + float rawDepth = 0.0; + float sceneVZ = SampleSceneViewZ(uv, rawDepth); + if (IsSkyDepth(rawDepth) || IsHudPixel(rawDepth, 0.0)) + { + prevUV = uv; + tPrev = t; + continue; + } + + float3 scenePos = ReconstructWorld(uv, rawDepth); + if (scenePos.y < position.y - 0.2) + { + prevUV = uv; + tPrev = t; + continue; + } + if (length(scenePos - eye_position) < waterCamDist - 0.15) + { + prevUV = uv; + tPrev = t; + continue; + } + + float3 toScene = scenePos - origin; + float along = dot(toScene, R); + if (along < tMin) + { + prevUV = uv; + tPrev = t; + continue; + } + + float perp = length(scenePos - (origin + R * along)); + float marchVZ = abs(mul(m_V, float4(marchPos, 1.0)).z); + float delta = sceneVZ - marchVZ; + float thick = thickness * (1.0 + along * 0.012); + if (delta > thick || delta < -thick * 2.0 || perp > thick) + { + if (delta < -thick * 2.0) + break; + prevUV = uv; + tPrev = t; + continue; + } + + if (length(uv - startUV) < minUvSep) + { + prevUV = uv; + tPrev = t; + continue; + } + + float2 travel = uv - startUV; + float travelLen = length(travel); + if (travelLen < minUvSep || dot(travel / travelLen, dirUV) < 0.2) + { + prevUV = uv; + tPrev = t; + continue; + } + + float distA = tPrev; + float distB = t; + [loop] + for (int r = 0; r < refineCount; ++r) + { + float midT = 0.5 * (distA + distB); + float2 uvM = 0.0; + float wM = 1.0; + WorldToUv(origin + R * midT, uvM, wM); + float dM = 0.0; + float sVZ = SampleSceneViewZ(uvM, dM); + float mVZ = abs(mul(m_V, float4(origin + R * midT, 1.0)).z); + if ((sVZ - mVZ) <= thickness) + distB = midT; + else + distA = midT; + } + + hitT = 0.5 * (distA + distB); + float wH = 1.0; + WorldToUv(origin + R * hitT, hitUV, wH); + if (any(hitUV < 0.0) || any(hitUV > 1.0) || length(hitUV - startUV) < minUvSep) + break; + + float dHit = 0.0; + float sHit = SampleSceneViewZ(hitUV, dHit); + if (IsSkyDepth(dHit) || IsHudPixel(dHit, 0.0)) + break; + float3 hitPos = ReconstructWorld(hitUV, dHit); + if (hitPos.y < position.y - 0.2) + break; + if (length(hitPos - eye_position) < waterCamDist - 0.15) + break; + + float alongH = dot(hitPos - origin, R); + float perpH = length(hitPos - (origin + R * alongH)); + float mHit = abs(mul(m_V, float4(origin + R * hitT, 1.0)).z); + float err = abs(sHit - mHit); + hitConf = saturate(1.0 - err / max(thickness * 2.0, 1e-3)); + hitConf *= saturate(1.0 - perpH / max(thickness * 2.5, 1e-3)); + hitConf *= RayAttenBorder(hitUV, SSR_EDGE_ATTENUATION); + hitConf *= saturate((alongH - tMin) * 0.5); + hit = hitConf > 0.08; + break; + } + + if (!hit) + return float4(skybox, 0.0); + + float3 img = g_SceneColor.SampleLevel(smp_nofilter, hitUV, 0).xyz; + float hitLum = dot(max(img, 0.0), float3(0.2126, 0.7152, 0.0722)); + if (hitLum < 1e-5) + return float4(skybox, 0.0); + hitConf *= saturate(hitLum * 2.5 + 0.45); + return float4(img, saturate(hitConf * 1.25)); +} + +float4 compute_ssr_near( + float3 position, + float3 normal, + float3 skybox, + int steps, + float maxDist, + float thickness, + int refineCount, + float roughnessBlur) +{ + float3 N = normalize(normal); + float3 V = normalize(position - eye_position); + float3 R = normalize(reflect(V, N)); + steps = clamp(steps, 12, 48); + refineCount = clamp(refineCount, 1, 6); + maxDist = clamp(maxDist, 4.0, 48.0); + thickness = max(thickness, 0.2); + + float3 origin = position + N * 0.02; + float2 startUV = 0.0; + float startW = 1.0; + WorldToUv(origin, startUV, startW); + + float tMin = 0.04; + float2 hitUV = 0.0; + bool hit = false; + float hitConf = 0.0; + bool borderFade = false; + + float tPrev = tMin; + float2 prevUV = startUV; + { + float3 p0 = origin + R * tMin; + float pw; + WorldToUv(p0, prevUV, pw); + } + + [loop] + for (int i = 1; i <= steps; ++i) + { + float u = float(i) / float(steps); + float t = max(tMin, maxDist * u * u); + float3 marchPos = origin + R * t; + + float2 uv; + float w; + if (!WorldToUv(marchPos, uv, w)) + { + if (tPrev > tMin && all(prevUV >= 0.0) && all(prevUV <= 1.0)) + { + hitUV = prevUV; + hitConf = 0.4; + hit = true; + borderFade = true; + } + break; + } + if (any(uv < 0.0) || any(uv > 1.0)) + { + hitUV = saturate(tPrev > tMin ? prevUV : uv); + hitConf = 0.35 * RayAttenBorder(hitUV, 0.08); + hit = true; + borderFade = true; + break; + } + + float rawDepth; + float sceneVZ = SampleSceneViewZ(uv, rawDepth); + float marchVZ = abs(mul(m_V, float4(marchPos, 1.0)).z); + + if (IsSkyDepth(rawDepth) || IsHudPixel(rawDepth, 0.0)) + { + if (t > tMin * 1.5 && !IsHudPixel(rawDepth, 0.0)) + { + hitUV = uv; + hitConf = 0.05; + hit = true; + break; + } + prevUV = uv; + tPrev = t; + continue; + } + + float delta = sceneVZ - marchVZ; + if (delta <= thickness && delta >= -thickness) + { + if (length(uv - startUV) < 0.0025) + { + prevUV = uv; + tPrev = t; + continue; + } + float2 uvA = prevUV; + float2 uvB = uv; + float distA = tPrev; + float distB = t; + [loop] + for (int r = 0; r < refineCount; ++r) + { + float midT = 0.5 * (distA + distB); + float2 uvM = 0.5 * (uvA + uvB); + float dM; + float sVZ = SampleSceneViewZ(uvM, dM); + float mVZ = abs(mul(m_V, float4(origin + R * midT, 1.0)).z); + if ((sVZ - mVZ) <= thickness) + { + uvB = uvM; + distB = midT; + } + else + { + uvA = uvM; + distA = midT; + } + } + hitUV = 0.5 * (uvA + uvB); + float dHit; + float sHit = SampleSceneViewZ(hitUV, dHit); + float mHit = abs(mul(m_V, float4(origin + R * (0.5 * (distA + distB)), 1.0)).z); + float err = abs(sHit - mHit); + hitConf = saturate(1.0 - err / max(thickness * 2.0, 1e-3)); + float3 hitPos = ReconstructWorld(hitUV, dHit); + float toward = dot(normalize(hitPos - origin), R); + hitConf *= saturate(toward * 2.0); + hit = hitConf > 0.03; + break; + } + else if (delta < -thickness) + { + if (tPrev > tMin * 1.2 && all(prevUV >= 0.0) && all(prevUV <= 1.0)) + { + hitUV = prevUV; + hitConf = 0.3; + hit = true; + borderFade = true; + } + break; + } + + prevUV = uv; + tPrev = t; + } + + if (!hit) + return float4(skybox, 0.0); + + float edge = RayAttenBorder(hitUV, borderFade ? 0.1 : 0.04); + float conf = max(edge, 0.65) * hitConf; + float2 texel = screen_res.zw; + float br = saturate(roughnessBlur) * 2.0; + float3 img = g_SceneColor.SampleLevel(smp_nofilter, hitUV, 0).xyz; + if (br > 0.08) + { + img += g_SceneColor.SampleLevel(smp_rtlinear, hitUV + float2( br, 0) * texel, 0).xyz; + img += g_SceneColor.SampleLevel(smp_rtlinear, hitUV + float2(-br, 0) * texel, 0).xyz; + img += g_SceneColor.SampleLevel(smp_rtlinear, hitUV + float2(0, br) * texel, 0).xyz; + img += g_SceneColor.SampleLevel(smp_rtlinear, hitUV + float2(0, -br) * texel, 0).xyz; + img *= 0.2; + } + float hitLum = dot(max(img, 0.0), float3(0.2126, 0.7152, 0.0722)); + conf *= step(1e-5, hitLum); + img *= step(1e-5, hitLum); + if (borderFade) + img = lerp(skybox * 0.18, img, saturate(conf * 2.6)); + conf = saturate(conf); + return float4(img, conf); +} + +#endif diff --git a/res/gamedata/shaders/r5/shared/surface_marks.h b/res/gamedata/shaders/r5/shared/surface_marks.h new file mode 100644 index 00000000000..33d28735a7b --- /dev/null +++ b/res/gamedata/shaders/r5/shared/surface_marks.h @@ -0,0 +1,87 @@ +#ifndef SURFACE_MARKS_H +#define SURFACE_MARKS_H + +static const float SURF_MARK_HUD = 2.0; +static const float SURF_MARK_CHAR = 3.0; +static const float SURF_MARK_TERRAIN = 4.0; +static const float SURF_MARK_WATER = 5.0; +static const float SURF_MARK_INTERIOR = 6.0; +static const float SURF_MARK_GRASS = 7.0; +static const float SURF_MARK_FOLIAGE = 8.0; +static const float SURF_FORWARD_LIT_A = 0.03125; + +bool IsForwardLitAlpha(float a) +{ + return a > 0.028 && a < 0.035; +} + +bool IsHudSurfMark(float w) +{ + return w > 1.5 && w < 2.5; +} + +bool IsCharSurfMark(float w) +{ + return w > 2.5 && w < 3.5; +} + +bool IsTerrainSurfMark(float w) +{ + return w > 3.5 && w < 4.5; +} + +bool IsWaterSurfMark(float w) +{ + return w > 4.5 && w < 5.5; +} + +bool IsInteriorSurfMark(float w) +{ + return w > 5.5 && w < 6.5; +} + +bool IsGrassSurfMark(float w) +{ + return w > 6.5 && w < 7.5; +} + +bool IsFoliageSurfMark(float w) +{ + return w > 7.5 && w < 8.5; +} + +bool IsVegSurfMark(float w) +{ + return IsGrassSurfMark(w) || IsFoliageSurfMark(w); +} + +bool SameLightZone(float a, float b) +{ + return IsInteriorSurfMark(a) == IsInteriorSurfMark(b); +} + +bool IsHudPixel(float depth, float mark) +{ + return IsHudSurfMark(mark); +} + +bool SkipRtSurfLighting(float classifyW, float guideW) +{ + return IsWaterSurfMark(classifyW) && !IsHudSurfMark(guideW); +} + +bool SameHudSurfClass(float a, float b) +{ + return IsHudSurfMark(a) == IsHudSurfMark(b); +} + +float SurfMarkFromGBuffer(float worldPosW, float baseColorA) +{ + if (worldPosW > 1.5) + return worldPosW; + if (baseColorA > 1.5) + return baseColorA; + return worldPosW; +} + +#endif diff --git a/res/gamedata/shaders/r5/shared/terrain_blend.h b/res/gamedata/shaders/r5/shared/terrain_blend.h new file mode 100644 index 00000000000..1fc5eda5a4c --- /dev/null +++ b/res/gamedata/shaders/r5/shared/terrain_blend.h @@ -0,0 +1,15 @@ +#ifndef TERRAIN_BLEND_H +#define TERRAIN_BLEND_H + +float4 TerrainNormalizeMask(float4 mask) +{ + float s = dot(mask, float4(1, 1, 1, 1)); + return s > 0.001 ? mask / s : float4(0.25, 0.25, 0.25, 0.25); +} + +float3 TerrainBlendRGB(float3 r, float3 g, float3 b, float3 a, float4 mask) +{ + return r * mask.r + g * mask.g + b * mask.b + a * mask.a; +} + +#endif diff --git a/res/gamedata/shaders/r5/shared/waterconfig.h b/res/gamedata/shaders/r5/shared/waterconfig.h index 89d1e220053..08cd7dfdf6c 100644 --- a/res/gamedata/shaders/r5/shared/waterconfig.h +++ b/res/gamedata/shaders/r5/shared/waterconfig.h @@ -1,7 +1,7 @@ #ifndef _WATERCONFIG_H #define _WATERCONFIG_H -// : (1) +//��������� ���: (1) //waterdistortion //waterdistortion2 @@ -20,7 +20,7 @@ //////////////////////////////////////////////////////////////////////////////// -- waters clear //////////////////////////////////////////////////////////////////////////////// - : +��������� ���: waterdistortion waterdistortion2 //////////////////////////////////////////////////////////////////////////////// @@ -32,7 +32,7 @@ #define W_DISTORT_AMP_1 (-1.75f) //(-0.30f) #define W_DISTORT_POWER (1.0f) //(1.0f) //////////////////////////////////////////////////////////////////////////////// - : +��������� ���: waterdistortion waterdistortion //////////////////////////////////////////////////////////////////////////////// diff --git a/res/gamedata/shaders/r5/skinned_common.h b/res/gamedata/shaders/r5/skinned_common.h index 0b8e44739b7..808dd2d8d67 100644 --- a/res/gamedata/shaders/r5/skinned_common.h +++ b/res/gamedata/shaders/r5/skinned_common.h @@ -9,6 +9,10 @@ cbuffer SkinnedMaterialCB : register(b4) uint g_SkeletonBoneOffset; uint g_SplatOffset; uint g_SplatCount; + uint g_HudLit; + uint g_HudPad0; + uint g_HudPad1; + uint g_HudPad2; }; struct PaintSplat diff --git a/res/gamedata/shaders/r5/sky_forward.ps b/res/gamedata/shaders/r5/sky_forward.ps index b7a9ec834a0..6272be82b0e 100644 Binary files a/res/gamedata/shaders/r5/sky_forward.ps and b/res/gamedata/shaders/r5/sky_forward.ps differ diff --git a/res/gamedata/shaders/r5/sky_forward.vs b/res/gamedata/shaders/r5/sky_forward.vs index 8e6fed44069..d642d2b9bb1 100644 --- a/res/gamedata/shaders/r5/sky_forward.vs +++ b/res/gamedata/shaders/r5/sky_forward.vs @@ -1,9 +1,3 @@ -// xrRender/Shaders/forward/sky_forward.vs -// Sky dome vertex shader for Forward+ rendering -// -// Transforms sky box vertices to clip space with z=w (infinite far plane) -// Passes through cubemap texture coordinates for sky sampling - #include "shared/common.h" struct VS_INPUT { @@ -15,32 +9,22 @@ struct VS_INPUT { struct VS_OUTPUT { float4 hpos : SV_POSITION; - float4 color : COLOR0; // RGB = sky color, A = blend factor - float3 tc0 : TEXCOORD0; // Cubemap UV for sky0 - float3 tc1 : TEXCOORD1; // Cubemap UV for sky1 + float4 color : COLOR0; + float3 tc0 : TEXCOORD0; + float3 tc1 : TEXCOORD1; + float elev : TEXCOORD2; }; VS_OUTPUT main(VS_INPUT v) { VS_OUTPUT o; - // Scale position by 1000 for distant sky dome (vanilla magic number) - // CRITICAL: Only scale xyz, NOT w! w must remain 1.0 for correct transformation float4 tpos = float4(v.position.xyz * 1000.0, 1.0); - - // Transform to clip space o.hpos = mul(m_WVP, tpos); - - // For reverse-Z: far plane is z=0, but we use small epsilon to avoid clipping - // This places sky at maximum depth (behind all geometry) o.hpos.z = o.hpos.w * 0.0001; - // Pass through texture coordinates (cubemap directions) o.tc0 = v.tc0; o.tc1 = v.tc1; - - // Pass through color with HDR scaling - // Note: Vanilla uses tonemap texture here, we'll apply exposure in PS + o.elev = normalize(v.position.xyz).y; o.color = v.color; - return o; } diff --git a/res/gamedata/shaders/r5/stub_default.ps b/res/gamedata/shaders/r5/stub_default.ps index d557b2124a3..3a62e7c3024 100644 Binary files a/res/gamedata/shaders/r5/stub_default.ps and b/res/gamedata/shaders/r5/stub_default.ps differ diff --git a/res/gamedata/shaders/r5/sun_forward.ps b/res/gamedata/shaders/r5/sun_forward.ps index e6e080ca221..9cf845e1168 100644 Binary files a/res/gamedata/shaders/r5/sun_forward.ps and b/res/gamedata/shaders/r5/sun_forward.ps differ diff --git a/res/gamedata/shaders/r5/taa.ps b/res/gamedata/shaders/r5/taa.ps new file mode 100644 index 00000000000..4fef4fbb190 Binary files /dev/null and b/res/gamedata/shaders/r5/taa.ps differ diff --git a/res/gamedata/shaders/r5/tonemap.ps b/res/gamedata/shaders/r5/tonemap.ps index 36570c19495..e88e13c224f 100644 Binary files a/res/gamedata/shaders/r5/tonemap.ps and b/res/gamedata/shaders/r5/tonemap.ps differ diff --git a/res/gamedata/shaders/r5/vol_fog_accumulate.cs b/res/gamedata/shaders/r5/vol_fog_accumulate.cs new file mode 100644 index 00000000000..468f50cbbe8 --- /dev/null +++ b/res/gamedata/shaders/r5/vol_fog_accumulate.cs @@ -0,0 +1,27 @@ +#include "vol_fog_common.h" +#include "vol_fog_params.h" + +Texture3D t_Lighting : register(t0); +RWTexture3D u_Accum : register(u0); + +[numthreads(8, 8, 1)] +void main(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= VOL_FOG_W || id.y >= VOL_FOG_H) + return; + + float3 inscatt = 0; + float trans = 1.0; + for (uint z = 0; z < VOL_FOG_D; ++z) { + float z0 = VolFogFroxelZ(z, VOL_FOG_D, g_ZNear, g_ZFar); + float z1 = VolFogFroxelZ(min(z + 1, VOL_FOG_D - 1), VOL_FOG_D, g_ZNear, g_ZFar); + float dz = max(abs(z1 - z0), 0.05); + float4 slice = t_Lighting[uint3(id.xy, z)]; + float sigma = slice.a * 0.85; + float sliceT = exp(-sigma * dz); + float3 light = slice.rgb / max(slice.a, 1e-5); + inscatt += trans * light * (1.0 - sliceT); + trans *= sliceT; + u_Accum[uint3(id.xy, z)] = float4(min(inscatt, 2.5), trans); + } +} diff --git a/res/gamedata/shaders/r5/vol_fog_apply.cs b/res/gamedata/shaders/r5/vol_fog_apply.cs new file mode 100644 index 00000000000..17dff67bac3 --- /dev/null +++ b/res/gamedata/shaders/r5/vol_fog_apply.cs @@ -0,0 +1,108 @@ +#include "vol_fog_common.h" +#include "vol_fog_params.h" +#include "shared/surface_marks.h" +#include "atmosphere.h" + +Texture2D t_Depth : register(t0); +Texture3D t_Accum : register(t1); +Texture2D t_WorldPos : register(t2); +TextureCube g_Sky0 : register(t3); +TextureCube g_Sky1 : register(t4); +RWTexture2D u_SceneColor : register(u0); + +SamplerState smp_linear : register(s0); + +float3 SampleSkyIncident(float3 dir, float mip) +{ + float3 d = normalize(dir); + float3 s0 = g_Sky0.SampleLevel(smp_linear, d, mip).rgb; + float3 s1 = g_Sky1.SampleLevel(smp_linear, d, mip).rgb; + float3 sky = lerp(s0, s1, saturate(g_SkyColor.w)) * g_SkyColor.rgb * 0.80; + if (dot(sky, sky) < 1e-6) + sky = g_HemiColor.rgb; + return sky; +} + +[numthreads(8, 8, 1)] +void main(uint3 id : SV_DispatchThreadID) +{ + uint2 pixel = id.xy; + if (pixel.x >= (uint)g_ScreenSize.x || pixel.y >= (uint)g_ScreenSize.y) + return; + + float depth = t_Depth.Load(int3(pixel, 0)); + float2 uv = (float2(pixel) + 0.5) / g_ScreenSize; + float4 wpSamp = t_WorldPos.Load(int3(pixel, 0)); + const bool isHud = IsHudSurfMark(wpSamp.w); + const bool isSky = (depth <= 1e-7) && !isHud; + + float4 clipFar = float4(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, 0.0, 1.0); + float4 worldFarH = mul(g_InvViewProj, clipFar); + float3 worldFar = worldFarH.xyz / max(worldFarH.w, 1e-8); + float3 viewDir = normalize(worldFar - g_CameraPos.xyz); + + float t = 1.0; + if (isHud) + { + float hudZ = length(wpSamp.xyz - g_CameraPos.xyz); + if (hudZ < 0.2) + hudZ = 2.0; + hudZ = clamp(hudZ, 1.25, 3.5); + t = VolFogViewToT(hudZ, g_ZNear, g_ZFar); + } + else if (!isSky) + { + float4 clip = float4(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, depth, 1.0); + float4 world = mul(g_InvViewProj, clip); + float3 worldPos = world.xyz / max(world.w, 1e-8); + float viewZ = length(worldPos - g_CameraPos.xyz); + t = VolFogViewToT(max(viewZ, g_ZNear), g_ZNear, g_ZFar); + } + + float4 acc = 0; + float wsum = 0; + int2 maxP = int2(g_ScreenSize) - 1; + [unroll] + for (int oy = -1; oy <= 1; oy++) { + [unroll] + for (int ox = -1; ox <= 1; ox++) { + int2 np = clamp(int2(pixel) + int2(ox, oy), int2(0, 0), maxP); + float dn = t_Depth.Load(int3(np, 0)); + float2 uvn = (float2(np) + 0.5) / g_ScreenSize; + float4 s = t_Accum.SampleLevel(smp_linear, float3(uvn, t), 0); + float rel = abs(dn - depth) / max(max(abs(dn), abs(depth)), 1e-5); + float w = (ox == 0 && oy == 0) ? 1.5 : 0.55; + w *= saturate(1.0 - rel * 14.0); + bool nSky = dn <= 1e-7; + if (nSky != isSky) + w *= 0.04; + acc += s * w; + wsum += w; + } + } + acc /= max(wsum, 1e-4); + float T = saturate(acc.a); + float3 inscatt = acc.rgb; + + float fogStrength = isHud ? 0.0 : 1.0; + if (isSky) + { + float zenith = saturate(viewDir.y); + float horizon = saturate(1.0 - abs(viewDir.y)); + fogStrength = lerp(0.55, 1.0, horizon); + fogStrength *= lerp(1.0, 0.65, zenith * zenith); + } + + float outT = lerp(1.0, T, fogStrength); + float3 outInsc = inscatt * fogStrength; + + float4 color = u_SceneColor[pixel]; + color.rgb = color.rgb * outT + outInsc; + if (g_EnableAtmosphere != 0 && g_AtmosphereStrength > 0.001 && isSky) { + float3 sunDir = normalize(-g_SunDir.xyz); + float3 Tatm, inscAtm; + AtmosphereAerial(viewDir, 120.0, sunDir, g_SunColor.rgb, SampleSkyIncident(float3(0.0, 1.0, 0.0), 4.0), g_AtmosphereStrength, Tatm, inscAtm); + color.rgb = color.rgb * Tatm + inscAtm; + } + u_SceneColor[pixel] = color; +} diff --git a/res/gamedata/shaders/r5/vol_fog_common.h b/res/gamedata/shaders/r5/vol_fog_common.h new file mode 100644 index 00000000000..2555320c6bb --- /dev/null +++ b/res/gamedata/shaders/r5/vol_fog_common.h @@ -0,0 +1,113 @@ +#ifndef VOL_FOG_COMMON_H +#define VOL_FOG_COMMON_H + +static const uint VOL_FOG_W = 160; +static const uint VOL_FOG_H = 90; +static const uint VOL_FOG_D = 64; + +float VolFogHenyeyGreenstein(float cosTheta, float g) +{ + float g2 = g * g; + float denom = 1.0 + g2 - 2.0 * g * cosTheta; + return (1.0 - g2) / max(12.5663706 * pow(max(denom, 1e-4), 1.5), 1e-4); +} + +float VolFogHash31(float3 p) +{ + p = frac(p * 0.1031); + p += dot(p, p.yzx + 33.33); + return frac((p.x + p.y) * p.z); +} + +float VolFogValueNoise(float3 p) +{ + float3 i = floor(p); + float3 f = frac(p); + f = f * f * (3.0 - 2.0 * f); + float n000 = VolFogHash31(i); + float n100 = VolFogHash31(i + float3(1, 0, 0)); + float n010 = VolFogHash31(i + float3(0, 1, 0)); + float n110 = VolFogHash31(i + float3(1, 1, 0)); + float n001 = VolFogHash31(i + float3(0, 0, 1)); + float n101 = VolFogHash31(i + float3(1, 0, 1)); + float n011 = VolFogHash31(i + float3(0, 1, 1)); + float n111 = VolFogHash31(i + float3(1, 1, 1)); + float nx00 = lerp(n000, n100, f.x); + float nx10 = lerp(n010, n110, f.x); + float nx01 = lerp(n001, n101, f.x); + float nx11 = lerp(n011, n111, f.x); + float nxy0 = lerp(nx00, nx10, f.y); + float nxy1 = lerp(nx01, nx11, f.y); + return lerp(nxy0, nxy1, f.z); +} + +float VolFogFBM(float3 p) +{ + float a = 0.0; + float w = 0.5; + [unroll] for (int i = 0; i < 4; ++i) + { + a += w * VolFogValueNoise(p); + p = p * 2.02 + float3(17.1, 9.3, 3.7); + w *= 0.5; + } + return a; +} + +float VolFogCloudMask(float3 worldPos, float time, float amount) +{ + float amt = saturate(amount); + if (amt < 1e-4) + return 1.0; + + float3 drift = float3(time * 0.12, time * 0.035, time * 0.08); + float banks = VolFogFBM(worldPos * float3(0.012, 0.028, 0.012) + drift * 0.35); + float clumps = VolFogFBM(worldPos * float3(0.045, 0.09, 0.045) + drift + float3(31.0, 7.0, 19.0)); + float wisps = VolFogValueNoise(worldPos * 0.18 + drift * 1.7); + + float shape = saturate(banks * 1.15 - 0.28); + shape = smoothstep(0.08, 0.72, shape); + float detail = saturate(clumps * 1.25 - 0.2); + detail = pow(detail, 1.35); + float mask = saturate(shape * lerp(0.35, 1.0, detail)); + mask = lerp(mask, mask * lerp(0.7, 1.15, wisps), 0.35); + return lerp(1.0, mask, amt); +} + +float VolFogFroxelZ(uint z, uint depthSlices, float zNear, float zFar) +{ + float t = (float(z) + 0.5) / float(depthSlices); + return zNear * pow(zFar / max(zNear, 1e-3), t); +} + +float VolFogViewToT(float viewZ, float zNear, float zFar) +{ + return saturate(log(max(viewZ / max(zNear, 1e-3), 1e-4)) / log(max(zFar / max(zNear, 1e-3), 1.01))); +} + +float3 VolFogFroxelWorldPos(uint3 id, float4x4 invViewProj, float3 cameraPos, float zNear, float zFar) +{ + float2 uv = (float2(id.xy) + 0.5) / float2(VOL_FOG_W, VOL_FOG_H); + float viewZ = VolFogFroxelZ(id.z, VOL_FOG_D, zNear, zFar); + float t = VolFogViewToT(viewZ, zNear, zFar); + float depth = saturate(zNear / max(viewZ, zNear)); + float4 clip = float4(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, depth, 1.0); + float4 world = mul(invViewProj, clip); + float3 wpos = world.xyz / max(world.w, 1e-8); + float3 dir = wpos - cameraPos; + float d = length(dir); + if (d > 1e-4) + wpos = cameraPos + dir * (viewZ / d); + else + { + float4 clipFar = float4(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, 0.0, 1.0); + float4 worldFar = mul(invViewProj, clipFar); + float3 farPos = worldFar.xyz / max(worldFar.w, 1e-8); + dir = farPos - cameraPos; + d = length(dir); + wpos = cameraPos + (d > 1e-4 ? dir / d : float3(0, 0, 1)) * viewZ; + } + return wpos; +} + +#endif diff --git a/res/gamedata/shaders/r5/vol_fog_density.cs b/res/gamedata/shaders/r5/vol_fog_density.cs new file mode 100644 index 00000000000..f07c849f3f0 --- /dev/null +++ b/res/gamedata/shaders/r5/vol_fog_density.cs @@ -0,0 +1,18 @@ +#include "vol_fog_common.h" +#include "vol_fog_params.h" + +RWTexture3D u_Density : register(u0); + +[numthreads(8, 8, 4)] +void main(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= VOL_FOG_W || id.y >= VOL_FOG_H || id.z >= VOL_FOG_D) + return; + + float3 worldPos = VolFogFroxelWorldPos(id, g_InvViewProj, g_CameraPos.xyz, g_ZNear, g_ZFar); + float heightTerm = exp(-g_FogTune.y * max(worldPos.y - g_FogTune.x, 0.0)); + float cloud = VolFogCloudMask(worldPos, g_FogTune2.y, g_FogTune.w); + float density = max(g_FogTune.z * heightTerm * cloud, 0.0); + float3 albedo = saturate(g_FogColor.rgb); + u_Density[id] = float4(albedo, density); +} diff --git a/res/gamedata/shaders/r5/vol_fog_inject.cs b/res/gamedata/shaders/r5/vol_fog_inject.cs new file mode 100644 index 00000000000..0041085715c --- /dev/null +++ b/res/gamedata/shaders/r5/vol_fog_inject.cs @@ -0,0 +1,183 @@ +#include "rt_irradiance_cache.h" +#include "vol_fog_common.h" +#include "vol_fog_params.h" +#include "shared/clustered_lighting.h" +#include "rt_common.h" + +RaytracingAccelerationStructure g_SceneTLAS : register(t2); +StructuredBuffer g_Lights : register(t3); +Texture3D t_BlueNoise : register(t4); +Texture3D t_PrevLighting : register(t5); + +Texture3D t_Density : register(t0); +StructuredBuffer t_IrradianceCache : register(t1); +TextureCube g_Sky0 : register(t6); +TextureCube g_Sky1 : register(t7); + +RWTexture3D u_Lighting : register(u0); +SamplerState smp_linear : register(s0); + +float3 SampleSkyIncident(float3 dir, float mip) +{ + float3 d = normalize(dir); + float3 s0 = g_Sky0.SampleLevel(smp_linear, d, mip).rgb; + float3 s1 = g_Sky1.SampleLevel(smp_linear, d, mip).rgb; + float3 sky = lerp(s0, s1, saturate(g_SkyColor.w)) * g_SkyColor.rgb * 0.80; + if (dot(sky, sky) < 1e-6) + sky = g_HemiColor.rgb; + return sky; +} + +float SampleSTBN3(uint3 id) +{ + uint w = 0, h = 0, d = 0; + t_BlueNoise.GetDimensions(w, h, d); + if (w < 4u || h < 4u || d < 1u) + return 0.5; + uint z = (g_FrameIndex + id.z * 3u) % max(d, 1u); + return t_BlueNoise.Load(int4(int(id.x % w), int(id.y % h), int(z), 0)); +} + +float TraceOpenVis(float3 origin, float3 dir, float tMax) +{ + RayDesc ray; + ray.Origin = origin; + ray.Direction = dir; + ray.TMin = 0.35; + ray.TMax = tMax; + RayQuery q; + q.TraceRayInline(g_SceneTLAS, RAY_FLAG_NONE, RT_MASK_SHADOW_MAPPED, ray); + while (q.Proceed()) { + if (q.CandidateType() == CANDIDATE_NON_OPAQUE_TRIANGLE) + continue; + } + return q.CommittedStatus() == COMMITTED_TRIANGLE_HIT ? 0.0 : 1.0; +} + +float2 ProjectToUv(float4x4 viewProj, float3 worldPos) +{ + float4 clip = mul(viewProj, float4(worldPos, 1.0)); + float2 ndc = clip.xy / max(abs(clip.w), 1e-5); + ndc.y = -ndc.y; + return ndc * 0.5 + 0.5; +} + +[numthreads(8, 8, 4)] +void main(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= VOL_FOG_W || id.y >= VOL_FOG_H || id.z >= VOL_FOG_D) + return; + + float4 dens = t_Density[id]; + float density = dens.a; + if (density <= 1e-5) { + u_Lighting[id] = 0; + return; + } + + float3 worldPos = VolFogFroxelWorldPos(id, g_InvViewProj, g_CameraPos.xyz, g_ZNear, g_ZFar); + float3 visOrigin = worldPos + float3(0.0, 0.4, 0.0); + float jitter = SampleSTBN3(id); + worldPos += (jitter * 2.0 - 1.0) * 0.04; + float3 V = normalize(g_CameraPos.xyz - worldPos); + float3 albedo = saturate(dens.rgb); + float3 L = normalize(-g_SunDir.xyz); + float sunVis = 1.0; + float skyVis = 1.0; + if (g_EnableRT != 0) { + skyVis = TraceOpenVis(visOrigin, float3(0.0, 1.0, 0.0), 28.0); + if (skyVis > 0.5) { + float tiltA = TraceOpenVis(visOrigin, normalize(float3(0.22, 1.0, 0.0)), 22.0); + float tiltB = TraceOpenVis(visOrigin, normalize(float3(-0.22, 1.0, 0.0)), 22.0); + if (tiltA + tiltB < 0.5) + skyVis = 0.0; + } + if (g_EnableSun != 0) + sunVis = TraceOpenVis(visOrigin + L * 0.15, L, 400.0); + } + + if (skyVis < 0.5) + density *= sunVis; + + float3 inscatt = 0.0; + float dynamicW = 0.0; + if (skyVis > 0.5) + inscatt = SampleSkyIncident(float3(0.0, 1.0, 0.0), 4.0) * albedo; + + if (g_EnableSun != 0 && g_SunColor.w > 0.0) { + float3 sun = g_SunColor.rgb; + float sunLum = max(max(sun.r, sun.g), sun.b); + if (sunLum > 2.4) + sun *= 2.4 / sunLum; + inscatt += sun * sunVis * albedo * g_SunColor.w; + } + + if (g_EnableLights != 0 && g_NumLights > 0) { + uint maxL = min(g_EnableLights, 8u); + uint counted = 0; + float bestSpot = 0; + uint bestSpotIdx = 0xFFFFFFFFu; + for (uint li = 0; li < g_NumLights && counted < maxL; li++) { + GPULightData light = g_Lights[li]; + float3 lpos = light.positionAndInvRangeSq.xyz; + float3 toL = lpos - worldPos; + float distSq = dot(toL, toL); + float invRangeSq = light.positionAndInvRangeSq.w; + if (distSq * invRangeSq > 1.0) + continue; + float dist = sqrt(max(distSq, 1e-5)); + float att = PointLightAttenuation(distSq, invRangeSq, light.spotParamsAndType.w); + float isSpot = light.spotParamsAndType.y; + if (isSpot > 0.5) { + att *= SpotLightAttenuation(toL, light.directionAndSpotScale.xyz, + light.directionAndSpotScale.w, light.spotParamsAndType.x); + float camDist = length(lpos - g_CameraPos.xyz); + bool doSpot = (g_SpotMode == 2) || (g_SpotMode == 1 && li == g_PlayerLight); + if (doSpot && att > 0.01 && camDist < 25.0) { + if (att > bestSpot) { + bestSpot = att; + bestSpotIdx = li; + } + dynamicW = max(dynamicW, saturate(att)); + } + } + float3 ldir = toL / dist; + float phase = min(VolFogHenyeyGreenstein(dot(V, ldir), g_FogTune2.x), 1.25); + inscatt += light.colorAndRange.xyz * att * phase * albedo; + counted++; + } + if (bestSpotIdx != 0xFFFFFFFFu && g_EnableRT != 0) { + GPULightData sl = g_Lights[bestSpotIdx]; + float3 toS = sl.positionAndInvRangeSq.xyz - visOrigin; + float d = length(toS); + float vis = TraceOpenVis(visOrigin, toS / max(d, 1e-4), max(d - 0.08, 0.05)); + inscatt *= lerp(1.0, vis, saturate(bestSpot * 2.0)); + } + } + + if (g_EnableGI != 0 && g_FogTune2.z > 0.5) { + uint cacheSize = (uint)g_FogTune2.w; + if (cacheSize > 0) { + uint h = IrradianceCacheHash(worldPos, 0.75, cacheSize); + IrradianceCacheEntry e = t_IrradianceCache[h]; + if (e.stamp != 0) + inscatt += min(e.irradiance, 1.0) * 0.02; + } + } + + inscatt = min(inscatt, 1.8); + float4 curr = float4(inscatt * density, density); + + if (g_EnableTemporal != 0) { + float2 prevUV = ProjectToUv(g_PrevViewProj, worldPos); + float viewZ = length(worldPos - g_CameraPos.xyz); + float tz = VolFogViewToT(max(viewZ, g_ZNear), g_ZNear, g_ZFar); + if (all(prevUV >= 0.0) && all(prevUV < 1.0) && tz > 0.0 && tz < 1.0) { + float4 hist = t_PrevLighting.SampleLevel(smp_linear, float3(prevUV, tz), 0); + float hw = lerp(0.86, 0.35, dynamicW); + curr = lerp(curr, hist, hw); + } + } + + u_Lighting[id] = curr; +} diff --git a/res/gamedata/shaders/r5/vol_fog_params.h b/res/gamedata/shaders/r5/vol_fog_params.h new file mode 100644 index 00000000000..2a32999f317 --- /dev/null +++ b/res/gamedata/shaders/r5/vol_fog_params.h @@ -0,0 +1,33 @@ +#ifndef VOL_FOG_PARAMS_H +#define VOL_FOG_PARAMS_H + +cbuffer VolFogParams : register(b5) +{ + float4x4 g_InvViewProj; + float4x4 g_PrevViewProj; + float4 g_CameraPos; + float4 g_SunDir; + float4 g_SunColor; + float4 g_FogTune; + float4 g_FogTune2; + float4 g_FogColor; + float2 g_ScreenSize; + float g_ZNear; + float g_ZFar; + uint g_FrameIndex; + uint g_EnableGI; + uint g_EnableSun; + uint g_EnableRT; + uint g_EnableLights; + uint g_EnableTemporal; + uint g_SpotMode; + uint g_NumLights; + float g_AtmosphereStrength; + uint g_EnableAtmosphere; + uint g_PlayerLight; + uint g_PadFog; + float4 g_SkyColor; + float4 g_HemiColor; +}; + +#endif diff --git a/res/gamedata/shaders/r5/water.ps b/res/gamedata/shaders/r5/water.ps new file mode 100644 index 00000000000..d5bee7a1668 Binary files /dev/null and b/res/gamedata/shaders/r5/water.ps differ diff --git a/res/gamedata/shaders/r5/water.vs b/res/gamedata/shaders/r5/water.vs new file mode 100644 index 00000000000..c8309e43da2 --- /dev/null +++ b/res/gamedata/shaders/r5/water.vs @@ -0,0 +1,109 @@ +// water.vs — MDI port of r3/water.vs (watermove + dual nmap TC + TBN + vertex lighting) +#define SM_6_0 +#include "common.h" +#include "bindless_common.h" +#include "shared\waterconfig.h" +#include "shared\watermove.h" + +struct VS_INPUT +{ + float4 position : POSITION; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 binormal : BINORMAL; + float2 texcoord : TEXCOORD0; + float2 texcoord1 : TEXCOORD1; + float4 color : COLOR0; + uint drawIndex : DRAWINDEX; +}; + +struct VS_OUTPUT +{ + float4 hpos : SV_Position; + float2 tbase : TEXCOORD0; + float2 tnorm0 : TEXCOORD1; + float2 tnorm1 : TEXCOORD2; + float3 M1 : TEXCOORD3; + float3 M2 : TEXCOORD4; + float3 M3 : TEXCOORD5; + float3 v2point : TEXCOORD6; + float4 c0 : COLOR0; + float fog : TEXCOORD7; + nointerpolation uint materialID : TEXCOORD8; + float4 tctexgen : TEXCOORD9; +}; + +struct InstanceData +{ + float4x4 world; + uint materialID; + uint flags; + float pad0, pad1; +}; + +StructuredBuffer g_InstanceData : register(t14); +StructuredBuffer g_CompactBatchIndices : register(t15); +StructuredBuffer g_CompactMaterialIDs : register(t16); + +float3 UnpackNormal(float4 packed) +{ + return packed.rgb * 2.0 - 1.0; +} + +VS_OUTPUT main(VS_INPUT input) +{ + VS_OUTPUT o; + + uint drawID = input.drawIndex; + uint batchIndex = g_CompactBatchIndices[drawID]; + InstanceData instanceData = g_InstanceData[batchIndex]; + float4x4 worldMatrix = instanceData.world; + uint materialID = g_CompactMaterialIDs[drawID]; + + // Match r3 unpack: normals/color arrive as UNORM [0,1] (same as unpack_D3DCOLOR path) + float3 N_unpacked = UnpackNormal(input.normal); + float3 T_unpacked = UnpackNormal(input.tangent); + float3 B_unpacked = UnpackNormal(input.binormal); + + // UnifiedVertex UVs are already unpacked (float); r3 used unpack_tc_base on int2 + float2 tbase = input.texcoord; + + float4 P = mul(worldMatrix, float4(input.position.xyz, 1.0)); + P = watermove(P); + + o.v2point = P.xyz - eye_position; + o.tbase = tbase; + o.tnorm0 = watermove_tc(tbase * W_DISTORT_BASE_TILE_0, P.xz, W_DISTORT_AMP_0); + o.tnorm1 = watermove_tc(tbase * W_DISTORT_BASE_TILE_1, P.xz, W_DISTORT_AMP_1); + + // r3: xform = m_W * float3x3(columns T,B,N) + float3 N = N_unpacked; + float3 T = T_unpacked; + float3 B = B_unpacked; + float3x3 world3 = (float3x3)worldMatrix; + float3x3 tbn = float3x3( + T.x, B.x, N.x, + T.y, B.y, N.y, + T.z, B.z, N.z); + float3x3 xform = mul(world3, tbn); + o.M1 = xform[0]; + o.M2 = xform[1]; + o.M3 = xform[2]; + + // Vertex lighting (r3/water.vs) + float hemi = input.normal.a; // packed hemi in alpha + float sunOcclusion = input.color.a; + float3 L_rgb = input.color.rgb; + float3 L_hemi = v_hemi(N) * hemi; + float3 L_sun = v_sun(N) * sunOcclusion; + float3 L_final = L_rgb + L_hemi + L_sun + L_ambient.rgb; + o.c0 = float4(L_final, 1.0); + + o.hpos = mul(m_VP, P); + o.fog = saturate(calc_fogging(P)); + o.materialID = materialID; + o.tctexgen = o.hpos; + float3 Pe = mul(m_V, P).xyz; + o.tctexgen.z = Pe.z; + return o; +} diff --git a/res/gamedata/shaders/r5/waterd.ps b/res/gamedata/shaders/r5/waterd.ps new file mode 100644 index 00000000000..8973e13d6ed Binary files /dev/null and b/res/gamedata/shaders/r5/waterd.ps differ diff --git a/res/gamedata/shaders/r5/waterd.vs b/res/gamedata/shaders/r5/waterd.vs new file mode 100644 index 00000000000..da38bbfa161 --- /dev/null +++ b/res/gamedata/shaders/r5/waterd.vs @@ -0,0 +1,65 @@ +#define SM_6_0 +#include "common.h" +#include "bindless_common.h" +#include "shared\waterconfig.h" +#include "shared\watermove.h" + +struct VS_INPUT +{ + float4 position : POSITION; + float4 normal : NORMAL; + float4 tangent : TANGENT; + float4 binormal : BINORMAL; + float2 texcoord : TEXCOORD0; + float2 texcoord1 : TEXCOORD1; + float4 color : COLOR0; + uint drawIndex : DRAWINDEX; +}; + +struct VS_OUTPUT +{ + float4 hpos : SV_Position; + float2 tbase : TEXCOORD0; + float2 tdist0 : TEXCOORD1; + float2 tdist1 : TEXCOORD2; + float3 worldPos : TEXCOORD3; + nointerpolation uint materialID : TEXCOORD4; + float4 tctexgen : TEXCOORD5; +}; + +struct InstanceData +{ + float4x4 world; + uint materialID; + uint flags; + float pad0, pad1; +}; + +StructuredBuffer g_InstanceData : register(t14); +StructuredBuffer g_CompactBatchIndices : register(t15); +StructuredBuffer g_CompactMaterialIDs : register(t16); + +VS_OUTPUT main(VS_INPUT input) +{ + VS_OUTPUT o; + + uint drawID = input.drawIndex; + uint batchIndex = g_CompactBatchIndices[drawID]; + InstanceData instanceData = g_InstanceData[batchIndex]; + uint materialID = g_CompactMaterialIDs[drawID]; + + float2 tbase = input.texcoord; + float4 P = mul(instanceData.world, float4(input.position.xyz, 1.0)); + P = watermove(P); + + o.tbase = tbase; + o.tdist0 = watermove_tc(tbase * W_DISTORT_BASE_TILE_0, P.xz, W_DISTORT_AMP_0); + o.tdist1 = watermove_tc(tbase * W_DISTORT_BASE_TILE_1, P.xz, W_DISTORT_AMP_1); + o.worldPos = P.xyz; + o.hpos = mul(m_VP, P); + o.materialID = materialID; + o.tctexgen = o.hpos; + float3 Pe = mul(m_V, P).xyz; + o.tctexgen.z = Pe.z; + return o; +} diff --git a/res/gamedata/shaders/r5/wet/rain_apply.ps b/res/gamedata/shaders/r5/wet/rain_apply.ps new file mode 100644 index 00000000000..35a05755c77 Binary files /dev/null and b/res/gamedata/shaders/r5/wet/rain_apply.ps differ diff --git a/res/gamedata/shaders/r5/wet/rain_patch_normal.ps b/res/gamedata/shaders/r5/wet/rain_patch_normal.ps new file mode 100644 index 00000000000..49d2db61ac4 Binary files /dev/null and b/res/gamedata/shaders/r5/wet/rain_patch_normal.ps differ diff --git a/res/gamedata/shaders/r5/wet/rain_write_normal.ps b/res/gamedata/shaders/r5/wet/rain_write_normal.ps new file mode 100644 index 00000000000..e9c827df5ce Binary files /dev/null and b/res/gamedata/shaders/r5/wet/rain_write_normal.ps differ diff --git a/res/gamedata/shaders/r5/wet/ssfx_ripples.h b/res/gamedata/shaders/r5/wet/ssfx_ripples.h new file mode 100644 index 00000000000..41d200c8e83 --- /dev/null +++ b/res/gamedata/shaders/r5/wet/ssfx_ripples.h @@ -0,0 +1,46 @@ +// wet/ssfx_ripples.h — SSFX ripples, Slang-safe (no early returns) +#ifndef WET_SSFX_RIPPLES_H +#define WET_SSFX_RIPPLES_H + +static const float3 SSFX_ripples_speed = float3(1.05f, 1.31f, 1.58f); +static const float4 SSFX_ripples_offset = float4(0.5f, 0.25f, 0.31f, 0.5f); +static const float SSFX_ripples_PI = 3.141592f; + +float hash22(float2 p) +{ + float3 p3 = frac(float3(p.xyx) * 0.1031); + p3 += dot(p3, p3.yzx + 33.33); + return frac((p3.x + p3.y) * p3.z); +} + +float2 ssfx_process_ripples(float4 ripples, float3 setup, float time) +{ + float2 ripples_N = ripples.yz * 2.0 - 1.0; + float RFrac = frac(ripples.w + time * setup.x); + float TimeFrac = RFrac - 1.0 + ripples.x; + float RFreq = clamp(TimeFrac * setup.z, 0.0, 4.0); + float FinalFactor = saturate(0.7 - RFrac) * ripples.x * sin(RFreq * SSFX_ripples_PI); + FinalFactor *= saturate(1.0 - RFreq * 0.25); + ripples_N *= FinalFactor * setup.y; + return ripples_N; +} + +// depth = distance to eye. Fade instead of early-return (Slang SPIR-V crash). +float2 ssfx_rain_ripples(Texture2D ripples_tex, SamplerState smp, float2 uvs, float3 setup, float depth, float time) +{ + float fade = saturate((15.0 - depth) * 0.0666); + + float4 Layer0 = ripples_tex.SampleLevel(smp, uvs, 0); + float4 Layer1 = ripples_tex.SampleLevel(smp, uvs * 0.61 + SSFX_ripples_offset.xy, 0); + float4 Layer2 = ripples_tex.SampleLevel(smp, uvs * 0.87 + SSFX_ripples_offset.zw, 0); + + float2 result = + ssfx_process_ripples(Layer0, float3(SSFX_ripples_speed.x * setup.x, setup.yz), time) + + ssfx_process_ripples(Layer1, float3(SSFX_ripples_speed.y * setup.x, setup.yz), time) + + ssfx_process_ripples(Layer2, float3(SSFX_ripples_speed.z * setup.x, setup.yz), time); + + result *= fade; + return clamp(result, float2(-1.0, -1.0), float2(1.0, 1.0)); +} + +#endif diff --git a/res/gamedata/shaders/r5/wet/ssfx_ssr.h b/res/gamedata/shaders/r5/wet/ssfx_ssr.h new file mode 100644 index 00000000000..7e279bbd764 --- /dev/null +++ b/res/gamedata/shaders/r5/wet/ssfx_ssr.h @@ -0,0 +1,103 @@ +// wet/ssfx_ssr.h — conservative wet SSR (world march + SceneReflection) +#ifndef WET_SSFX_SSR_H +#define WET_SSFX_SSR_H + +#ifndef WET_SSR_STEPS +#define WET_SSR_STEPS 16 +#endif +#ifndef WET_SSR_MAXDIST +#define WET_SSR_MAXDIST 48.0 +#endif +#ifndef WET_SSR_THICK +#define WET_SSR_THICK 0.45 +#endif + +float2 WetWorldToUv(float3 worldPos, float4x4 vp, out float clipW) +{ + float4 clip = mul(vp, float4(worldPos, 1.0)); + clipW = clip.w; + float2 ndc = clip.xy / max(clip.w, 1e-4); + // Vulkan/Metal: Y flip like rest of r5 + return float2(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5); +} + +float WetBorderAtten(float2 uv) +{ + float b = min(min(uv.x, uv.y), min(1.0 - uv.x, 1.0 - uv.y)); + return saturate(b / 0.08); +} + +// Returns rgb=reflection, a=confidence (0 = miss — do not darken/blacken) +float4 WetSSR( + Texture2D sceneColor, + Texture2D worldPosTex, + SamplerState smpColor, + SamplerState smpPos, + float2 uv0, + float3 worldPos, + float3 N, + float3 eyePos, + float4x4 vp) +{ + float3 V = normalize(worldPos - eyePos); + float3 R = reflect(V, normalize(N)); + // Skip downward rays into ground (common black/noise source) + if (R.y < -0.35) + return float4(0, 0, 0, 0); + + R = normalize(R); + float2 hitUV = uv0; + float conf = 0.0; + bool hit = false; + + float tPrev = 0.15; + [loop] + for (int i = 1; i <= WET_SSR_STEPS; ++i) + { + float u = float(i) / float(WET_SSR_STEPS); + float t = WET_SSR_MAXDIST * u * u; + float3 march = worldPos + R * t; + + float w; + float2 uv = WetWorldToUv(march, vp, w); + if (w <= 1e-3) + break; + if (uv.x < 0.0 || uv.y < 0.0 || uv.x > 1.0 || uv.y > 1.0) + break; + + float3 samplePos = worldPosTex.SampleLevel(smpPos, uv, 0).xyz; + float sampleLen = length(samplePos); + // Empty / sky + if (sampleLen < 0.05) + { + tPrev = t; + continue; + } + + float dist = length(samplePos - march); + // Behind surface along view? Prefer closer geometry hit + float toward = dot(normalize(samplePos - worldPos), R); + if (toward > 0.15 && dist < WET_SSR_THICK * (1.0 + t * 0.02)) + { + hitUV = uv; + conf = saturate(1.0 - dist / max(WET_SSR_THICK, 0.001)); + conf *= WetBorderAtten(uv); + // Avoid self-hit at start + conf *= saturate((t - 0.35) * 2.0); + hit = conf > 0.05; + if (hit) + break; + } + tPrev = t; + } + + if (!hit) + return float4(0, 0, 0, 0); + + float3 refl = sceneColor.SampleLevel(smpColor, hitUV, 0).rgb; + float lum = dot(refl, float3(0.299, 0.587, 0.114)); + conf *= saturate(lum * 8.0); + return float4(refl, conf); +} + +#endif diff --git a/res/gamedata/shaders/r5/wet/ssfx_waterfall.h b/res/gamedata/shaders/r5/wet/ssfx_waterfall.h new file mode 100644 index 00000000000..6cb150514ef --- /dev/null +++ b/res/gamedata/shaders/r5/wet/ssfx_waterfall.h @@ -0,0 +1,104 @@ +// wet/ssfx_waterfall.h — SSFX/CoP waterfall + sparse droplet streaks +#ifndef WET_SSFX_WATERFALL_H +#define WET_SSFX_WATERFALL_H + +#include "wet/ssfx_ripples.h" // hash22 + +static const float4 ssfx_wetsurfaces_1 = float4(0.90f, 1.50f, 0.20f, 1.00f); +static const float4 ssfx_wetsurfaces_2 = float4(0.75f, 1.50f, 0.20f, 0.55f); + +float3 ssfx_RainPos(float3 worldPos, float3 eyePos) +{ + return worldPos - eyePos; +} + +float3 ssfx_GetWaterFall(Texture2D s_texture, SamplerState smp, float2 tc, float rainInt, float time) +{ + float col_num = 50.0; + float col_scale = 1.0 / col_num; + float2 tc_ori = tc * ssfx_wetsurfaces_2.x; + float2 col_tc = float2(frac(tc_ori.x * col_num), tc_ori.y); + float col_id = ceil(tc_ori.x * col_num); + + float col_offset = hash22(float2(col_id, 1.0)); + col_offset = (col_offset < ssfx_wetsurfaces_2.z) ? (col_offset * 3.0) : col_offset; + col_offset += time * col_offset * max(rainInt, 0.001) * ssfx_wetsurfaces_2.y; + + float3 water = s_texture.SampleLevel( + smp, + col_tc * float2(col_scale, 1.0) + float2(col_scale * col_id, col_offset), + 0).xyz; + + water = water.xzy * 2.0 - 1.0; + water *= ssfx_wetsurfaces_2.w; + return water; +} + +float3 ssfx_GetWaterNMap(Texture2D s_texture, SamplerState smp, float2 tc) +{ + float3 water = s_texture.SampleLevel(smp, tc, 0).xyz; + water = (water.xzy - 0.5) * 2.0; + water *= 0.3; + water.y = 0.0; + return water; +} + +// Raw water_normal grain — use NM xy variance (luma of DXT normals is often flat) +float ssfx_FallFlowRaw(Texture2D s_texture, SamplerState smp, float2 tc) +{ + float3 s = s_texture.SampleLevel(smp, tc, 0).xyz; + float3 n = s.xzy * 2.0 - 1.0; + // Horizontal/vertical lobes of the normal map = visible micro-flow grain + float flow = abs(n.x) * 1.6 + abs(n.z) * 1.2; + flow += abs(s.x - 0.5) * 0.8; + return saturate(flow); +} + +float2 ssfx_WallWeightsSoft(float3 worldN) +{ + return abs(worldN.xz); +} + +float2 ssfx_WallWeightsXZ(float3 worldN) +{ + float2 w = saturate(abs(worldN.xz) - 0.15); + w *= float2(w.x > w.y ? 1.0 : 0.0, w.x < w.y ? 1.0 : 0.0); + return w; +} + +// Sparse tear-shaped drips (not noise, not infinite bars). No lateral wobble. +float ssfx_DropletStreak(float2 tc, float rainInt, float time) +{ + float col_num = 40.0; + float2 t = tc * 0.72; + float col_id = floor(t.x * col_num); + float x = frac(t.x * col_num) - 0.5; + + float seed = hash22(float2(col_id, 7.0)); + float active = step(0.22, seed); // denser — fewer empty columns + float speed = lerp(0.45, 1.35, seed) * max(rainInt, 0.2); + float y = t.y + time * speed + seed * 3.0; + + float body = exp(-x * x * 70.0); // slightly wider + + float cell = frac(y * 0.62 + seed * 1.7); + float head = saturate(1.0 - abs(cell - 0.22) * 6.5); + head = head * head; + float tail = saturate(1.0 - abs(cell - 0.55) * 3.2) * 0.4; + float tear = saturate(head + tail); + + return body * tear * active; +} + +// Streak from waterfall NM peaks only (suppresses noisy midtones) +float ssfx_FallPeak(float3 fallNM) +{ + float ax = abs(fallNM.x); + float az = abs(fallNM.z); + // Keep only strong lobes → droplet-like, not grain + float peak = saturate(ax * 5.0 - 0.35); + peak = max(peak, saturate(az * 4.0 - 0.4)); + return peak * peak; +} + +#endif diff --git a/res/gamedata/shaders/r5/yuv2rgb.ps b/res/gamedata/shaders/r5/yuv2rgb.ps new file mode 100644 index 00000000000..3a0a478f3f0 Binary files /dev/null and b/res/gamedata/shaders/r5/yuv2rgb.ps differ diff --git a/src/Layers/xrRender/Backend/VulkanBackend.cpp b/src/Layers/xrRender/Backend/VulkanBackend.cpp index e6a7e574d95..68b37f3ff60 100644 --- a/src/Layers/xrRender/Backend/VulkanBackend.cpp +++ b/src/Layers/xrRender/Backend/VulkanBackend.cpp @@ -2,6 +2,19 @@ #include "VulkanBackend.h" #include "xrCore/Threading/TaskManager.hpp" +#include +#include +#include +#include + +extern ENGINE_API int ps_r_hdr10; +extern ENGINE_API float ps_r_hdr10_paper_white; +extern ENGINE_API float ps_r_hdr10_peak; +extern ENGINE_API int ps_r_upscale; +extern ENGINE_API int ps_r_dlss_fg; +#if defined(XRAY_USE_DLSS) +#include "Layers/xrRender/Upscaling/StreamlineDLSS.h" +#endif #if defined(__APPLE__) #include @@ -41,9 +54,9 @@ static NVRHIVulkanMessageCallback s_nvrhiVkMessageCallback; static VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugCallback( VkDebugUtilsMessageSeverityFlagBitsEXT severity, - VkDebugUtilsMessageTypeFlagsEXT type, + VkDebugUtilsMessageTypeFlagsEXT, const VkDebugUtilsMessengerCallbackDataEXT* callbackData, - void* userData) + void*) { if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) Msg("! [Vulkan] ERROR: %s", callbackData->pMessage); @@ -55,7 +68,7 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugCallback( VulkanBackend::VulkanBackend() = default; VulkanBackend::~VulkanBackend() { - Shutdown(); + VulkanBackend::Shutdown(); } bool VulkanBackend::Initialize(SDL_Window* window, u32 width, u32 height, bool enableValidation) { @@ -120,8 +133,13 @@ bool VulkanBackend::Initialize(SDL_Window* window, u32 width, u32 height, bool e VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, }; - deviceDesc.deviceExtensions = deviceExts; - deviceDesc.numDeviceExtensions = std::size(deviceExts); + deviceDesc.deviceExtensions = m_enabledDeviceExtensions.empty() ? deviceExts : m_enabledDeviceExtensions.data(); + deviceDesc.numDeviceExtensions = m_enabledDeviceExtensions.empty() + ? std::size(deviceExts) + : m_enabledDeviceExtensions.size(); + deviceDesc.bufferDeviceAddressSupported = m_bufferDeviceAddressSupported; + if (m_bufferDeviceAddressSupported) + Msg("* [VulkanBackend] NVRHI bufferDeviceAddressSupported=1"); m_nvrhiVulkanDevice = nvrhi::vulkan::createDevice(deviceDesc); if (!m_nvrhiVulkanDevice) { @@ -145,9 +163,9 @@ bool VulkanBackend::Initialize(SDL_Window* window, u32 width, u32 height, bool e nvrhi::CommandListParameters cmdParams; cmdParams.enableImmediateExecution = false; - for (u32 i = 0; i < 2; ++i) { - m_commandLists[i] = m_nvrhiDevice->createCommandList(cmdParams); - if (!m_commandLists[i]) { + for (auto& cl : m_commandLists) { + cl = m_nvrhiDevice->createCommandList(cmdParams); + if (!cl) { Msg("! [VulkanBackend] Failed to create command list"); Shutdown(); return false; @@ -200,11 +218,15 @@ void VulkanBackend::Shutdown() { if (m_submitThread.joinable()) { { - std::lock_guard lk(m_submitMutex); + std::scoped_lock lk(m_submitMutex); m_submitRun = false; } m_submitCv.notify_one(); m_submitThread.join(); + m_jobQueued = false; + m_submitBusy = false; + m_slotInFlight[0] = false; + m_slotInFlight[1] = false; } m_bindlessDescriptorTable = nullptr; @@ -235,10 +257,9 @@ void VulkanBackend::Shutdown() { m_surface = VK_NULL_HANDLE; } if (m_debugMessenger) { - auto destroyFunc = (PFN_vkDestroyDebugUtilsMessengerEXT) - vkGetInstanceProcAddr(m_instance, "vkDestroyDebugUtilsMessengerEXT"); - if (destroyFunc) - destroyFunc(m_instance, m_debugMessenger, nullptr); + if (auto destroyFunc = (PFN_vkDestroyDebugUtilsMessengerEXT) + vkGetInstanceProcAddr(m_instance, "vkDestroyDebugUtilsMessengerEXT"); destroyFunc) + destroyFunc(m_instance, m_debugMessenger, nullptr); m_debugMessenger = VK_NULL_HANDLE; } if (m_instance) { @@ -246,11 +267,13 @@ void VulkanBackend::Shutdown() { m_instance = VK_NULL_HANDLE; } + m_enabledDeviceExtensions.clear(); + m_bufferDeviceAddressSupported = false; m_initialized = false; Msg("* [VulkanBackend] Shutdown complete"); } -bool VulkanBackend::CreateInstance(SDL_Window* window, bool enableValidation) { +bool VulkanBackend::CreateInstance(SDL_Window* /*window*/, bool enableValidation) { VkApplicationInfo appInfo = {}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "OpenXRay"; @@ -267,6 +290,23 @@ bool VulkanBackend::CreateInstance(SDL_Window* window, bool enableValidation) { } xr_vector extensions(sdlExts, sdlExts + sdlExtCount); extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + { + u32 instExtCount = 0; + vkEnumerateInstanceExtensionProperties(nullptr, &instExtCount, nullptr); + xr_vector instExts(instExtCount); + if (instExtCount) + vkEnumerateInstanceExtensionProperties(nullptr, &instExtCount, instExts.data()); + for (const auto& e : instExts) { + if (!strcmp(e.extensionName, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME)) { + extensions.push_back(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME); + break; + } + } + } +#if defined(XRAY_USE_DLSS) + xray::render::fg::Streamline_PreInstanceInit(); + xray::render::fg::Streamline_GetRequiredInstanceExtensions(extensions); +#endif xr_vector layers; if (enableValidation) { @@ -380,21 +420,19 @@ bool VulkanBackend::CreateLogicalDevice() { // If no dedicated compute family, try using a second queue from graphics family bool useGraphicsFamilyForCompute = false; - if (m_computeQueueFamily == UINT32_MAX) { - if (queueFamilies[m_graphicsQueueFamily].queueCount >= 2) { - m_computeQueueFamily = m_graphicsQueueFamily; - useGraphicsFamilyForCompute = true; - } + if (m_computeQueueFamily == UINT32_MAX && queueFamilies[m_graphicsQueueFamily].queueCount >= 2) { + m_computeQueueFamily = m_graphicsQueueFamily; + useGraphicsFamilyForCompute = true; } - float queuePriorities[2] = { 1.0f, 1.0f }; + std::array queuePriorities{ 1.0f, 1.0f }; xr_vector queueCreateInfos; VkDeviceQueueCreateInfo graphicsQueueInfo = {}; graphicsQueueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; graphicsQueueInfo.queueFamilyIndex = m_graphicsQueueFamily; graphicsQueueInfo.queueCount = useGraphicsFamilyForCompute ? 2 : 1; - graphicsQueueInfo.pQueuePriorities = queuePriorities; + graphicsQueueInfo.pQueuePriorities = queuePriorities.data(); queueCreateInfos.push_back(graphicsQueueInfo); if (m_computeQueueFamily != UINT32_MAX && !useGraphicsFamilyForCompute) { @@ -402,10 +440,21 @@ bool VulkanBackend::CreateLogicalDevice() { computeQueueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; computeQueueInfo.queueFamilyIndex = m_computeQueueFamily; computeQueueInfo.queueCount = 1; - computeQueueInfo.pQueuePriorities = queuePriorities; + computeQueueInfo.pQueuePriorities = queuePriorities.data(); queueCreateInfos.push_back(computeQueueInfo); } + u32 availExtCount = 0; + vkEnumerateDeviceExtensionProperties(m_physicalDevice, nullptr, &availExtCount, nullptr); + xr_vector availExts(availExtCount); + if (availExtCount) + vkEnumerateDeviceExtensionProperties(m_physicalDevice, nullptr, &availExtCount, availExts.data()); + auto hasExt = [&](const char* name) { + return std::ranges::any_of(availExts, [name](const auto& e) { + return strcmp(e.extensionName, name) == 0; + }); + }; + xr_vector deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME, @@ -415,6 +464,30 @@ bool VulkanBackend::CreateLogicalDevice() { #if defined(XR_PLATFORM_APPLE) deviceExtensions.push_back("VK_KHR_portability_subset"); #endif +#if defined(XRAY_USE_DLSS) + { + xr_vector ngxDevExts; + xray::render::fg::Streamline_GetRequiredDeviceExtensions(m_physicalDevice, ngxDevExts); + for (const char* e : ngxDevExts) { + if (e && hasExt(e)) + deviceExtensions.push_back(e); + } + } +#endif + + const bool wantRT = + hasExt(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME) && + hasExt(VK_KHR_RAY_QUERY_EXTENSION_NAME) && + hasExt(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME); + if (wantRT) { + deviceExtensions.push_back(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME); + deviceExtensions.push_back(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME); + deviceExtensions.push_back(VK_KHR_RAY_QUERY_EXTENSION_NAME); + if (hasExt(VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME)) + deviceExtensions.push_back(VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME); + } + if (hasExt(VK_EXT_HDR_METADATA_EXTENSION_NAME)) + deviceExtensions.push_back(VK_EXT_HDR_METADATA_EXTENSION_NAME); VkPhysicalDeviceVulkan12Features vulkan12Features = {}; vulkan12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; @@ -427,6 +500,7 @@ bool VulkanBackend::CreateLogicalDevice() { vulkan12Features.descriptorBindingSampledImageUpdateAfterBind = VK_TRUE; vulkan12Features.descriptorBindingStorageBufferUpdateAfterBind = VK_TRUE; vulkan12Features.timelineSemaphore = VK_TRUE; + vulkan12Features.bufferDeviceAddress = wantRT ? VK_TRUE : VK_FALSE; VkPhysicalDeviceSynchronization2Features sync2Features = {}; sync2Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES; @@ -443,6 +517,17 @@ bool VulkanBackend::CreateLogicalDevice() { vulkan11Features.shaderDrawParameters = VK_TRUE; dynamicRenderingFeatures.pNext = &vulkan11Features; + VkPhysicalDeviceAccelerationStructureFeaturesKHR asFeatures = {}; + asFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR; + asFeatures.accelerationStructure = VK_TRUE; + VkPhysicalDeviceRayQueryFeaturesKHR rayQueryFeatures = {}; + rayQueryFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR; + rayQueryFeatures.rayQuery = VK_TRUE; + if (wantRT) { + vulkan11Features.pNext = &asFeatures; + asFeatures.pNext = &rayQueryFeatures; + } + VkPhysicalDeviceFeatures2 features2 = {}; features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; features2.pNext = &vulkan12Features; @@ -466,9 +551,17 @@ bool VulkanBackend::CreateLogicalDevice() { { VkPhysicalDeviceVulkan12Features sup12 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES }; VkPhysicalDeviceVulkan11Features sup11 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES }; + VkPhysicalDeviceAccelerationStructureFeaturesKHR supAS = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR }; + VkPhysicalDeviceRayQueryFeaturesKHR supRQ = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR }; VkPhysicalDeviceFeatures2 sup2 = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2 }; sup2.pNext = ¹2; - sup12.pNext = ¹1; + if (wantRT) { + sup12.pNext = &supAS; + supAS.pNext = &supRQ; + supRQ.pNext = ¹1; + } else { + sup12.pNext = ¹1; + } vkGetPhysicalDeviceFeatures2(m_physicalDevice, ²); #define CLAMP12(F) do { if (vulkan12Features.F && !sup12.F) { Msg("! [VulkanBackend] vk12 feature unsupported: " #F); vulkan12Features.F = VK_FALSE; } } while(0) @@ -481,6 +574,7 @@ bool VulkanBackend::CreateLogicalDevice() { CLAMP12(descriptorBindingSampledImageUpdateAfterBind); CLAMP12(descriptorBindingStorageBufferUpdateAfterBind); CLAMP12(timelineSemaphore); + CLAMP12(bufferDeviceAddress); #undef CLAMP12 #define CLAMPF(F) do { if (features2.features.F && !sup2.features.F) { Msg("! [VulkanBackend] feature unsupported: " #F); features2.features.F = VK_FALSE; } } while(0) CLAMPF(samplerAnisotropy); @@ -495,12 +589,42 @@ bool VulkanBackend::CreateLogicalDevice() { Msg("! [VulkanBackend] vk11 feature unsupported: shaderDrawParameters"); vulkan11Features.shaderDrawParameters = VK_FALSE; } + if (wantRT) { + if (!supAS.accelerationStructure || !supRQ.rayQuery || !sup12.bufferDeviceAddress) { + Msg("! [VulkanBackend] RayQuery/AS features incomplete (AS=%d RQ=%d BDA=%d) — RT disabled", + (int)supAS.accelerationStructure, (int)supRQ.rayQuery, (int)sup12.bufferDeviceAddress); + asFeatures.accelerationStructure = VK_FALSE; + rayQueryFeatures.rayQuery = VK_FALSE; + vulkan12Features.bufferDeviceAddress = VK_FALSE; + vulkan11Features.pNext = nullptr; + m_bufferDeviceAddressSupported = false; + for (size_t i = 0; i < deviceExtensions.size();) { + const char* n = deviceExtensions[i]; + if (!strcmp(n, VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME) || + !strcmp(n, VK_KHR_RAY_QUERY_EXTENSION_NAME) || + !strcmp(n, VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME) || + !strcmp(n, VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME)) + deviceExtensions.erase(deviceExtensions.begin() + i); + else + ++i; + } + deviceCreateInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + } else { + m_bufferDeviceAddressSupported = true; + Msg("* [VulkanBackend] RayQuery + AccelerationStructure ENABLED (BDA=1)"); + } + } else { + m_bufferDeviceAddressSupported = false; + Msg("* [VulkanBackend] RayQuery extensions not available on this GPU"); + } Msg("* [VulkanBackend] vk12.drawIndirectCount = %s (device reports: %s)", vulkan12Features.drawIndirectCount ? "ENABLED" : "DISABLED", sup12.drawIndirectCount ? "supported" : "unsupported"); } + m_enabledDeviceExtensions = deviceExtensions; + VkResult result = vkCreateDevice(m_physicalDevice, &deviceCreateInfo, nullptr, &m_device); if (result != VK_SUCCESS) { Msg("! [VulkanBackend] vkCreateDevice failed: %d", result); @@ -519,6 +643,8 @@ bool VulkanBackend::CreateLogicalDevice() { Msg("* [VulkanBackend] No compute queue available (async compute disabled)"); } + m_setHdrMetadata = (PFN_vkSetHdrMetadataEXT)vkGetDeviceProcAddr(m_device, "vkSetHdrMetadataEXT"); + Msg("* [VulkanBackend] Logical device created (graphics family %u)", m_graphicsQueueFamily); return true; } @@ -534,23 +660,61 @@ bool VulkanBackend::CreateSwapChain(u32 width, u32 height) { m_swapchainFormat = VK_FORMAT_R8G8B8A8_UNORM; VkColorSpaceKHR colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; - bool foundFormat = false; - for (const auto& fmt : formats) { - if (fmt.format == VK_FORMAT_R8G8B8A8_UNORM) { - m_swapchainFormat = fmt.format; - colorSpace = fmt.colorSpace; - foundFormat = true; - break; + m_hdr10Active = false; + if (ps_r_hdr10) { + for (const auto& fmt : formats) { + if (fmt.colorSpace == VK_COLOR_SPACE_HDR10_ST2084_EXT && + fmt.format == VK_FORMAT_A2B10G10R10_UNORM_PACK32) { + m_swapchainFormat = fmt.format; + colorSpace = fmt.colorSpace; + m_hdr10Active = true; + break; + } } + if (!m_hdr10Active) { + for (const auto& fmt : formats) { + if (fmt.colorSpace == VK_COLOR_SPACE_HDR10_ST2084_EXT && + fmt.format == VK_FORMAT_A2R10G10B10_UNORM_PACK32) { + m_swapchainFormat = fmt.format; + colorSpace = fmt.colorSpace; + m_hdr10Active = true; + break; + } + } + } + if (!m_hdr10Active) + Msg("! [VulkanBackend] HDR10 requested, no ST.2084 10-bit surface format"); } - if (!foundFormat) { + if (!m_hdr10Active) { + bool foundFormat = false; for (const auto& fmt : formats) { - if (fmt.format == VK_FORMAT_B8G8R8A8_UNORM) { + if (fmt.format == VK_FORMAT_R8G8B8A8_UNORM && + fmt.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { m_swapchainFormat = fmt.format; colorSpace = fmt.colorSpace; + foundFormat = true; break; } } + if (!foundFormat) { + for (const auto& fmt : formats) { + if (fmt.format == VK_FORMAT_R8G8B8A8_UNORM) { + m_swapchainFormat = fmt.format; + colorSpace = fmt.colorSpace; + foundFormat = true; + break; + } + } + } + if (!foundFormat) { + for (const auto& fmt : formats) { + if (fmt.format == VK_FORMAT_B8G8R8A8_UNORM) { + m_swapchainFormat = fmt.format; + colorSpace = fmt.colorSpace; + break; + } + } + } } u32 presentModeCount = 0; @@ -559,22 +723,33 @@ bool VulkanBackend::CreateSwapChain(u32 width, u32 height) { vkGetPhysicalDeviceSurfacePresentModesKHR(m_physicalDevice, m_surface, &presentModeCount, presentModes.data()); const bool wantVSync = psDeviceFlags.test(rsVSync); + const bool wantFg = ps_r_upscale == 2 && ps_r_dlss_fg != 0; VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; - if (!wantVSync) + if (!wantVSync || wantFg) { - bool hasImmediate = false, hasMailbox = false; + bool hasImmediate = false; + bool hasMailbox = false; for (auto mode : presentModes) { if (mode == VK_PRESENT_MODE_IMMEDIATE_KHR) hasImmediate = true; else if (mode == VK_PRESENT_MODE_MAILBOX_KHR) hasMailbox = true; } - if (hasImmediate) presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; - else if (hasMailbox) presentMode = VK_PRESENT_MODE_MAILBOX_KHR; + if (wantFg && hasMailbox) + presentMode = VK_PRESENT_MODE_MAILBOX_KHR; + else if (!wantVSync && hasImmediate) + presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; + else if (hasMailbox) + presentMode = VK_PRESENT_MODE_MAILBOX_KHR; } - Msg("* [VulkanBackend] Present mode: %s (vsync=%s)", - presentMode == VK_PRESENT_MODE_IMMEDIATE_KHR ? "IMMEDIATE" : - presentMode == VK_PRESENT_MODE_MAILBOX_KHR ? "MAILBOX" : "FIFO", - wantVSync ? "on" : "off"); + const char* presentModeName = "FIFO"; + if (presentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) + presentModeName = "IMMEDIATE"; + else if (presentMode == VK_PRESENT_MODE_MAILBOX_KHR) + presentModeName = "MAILBOX"; + Msg("* [VulkanBackend] Present mode: %s (vsync=%s fg=%s)", + presentModeName, + wantVSync ? "on" : "off", + wantFg ? "on" : "off"); VkExtent2D extent = { width, height }; if (surfaceCaps.currentExtent.width != UINT32_MAX) @@ -616,11 +791,37 @@ bool VulkanBackend::CreateSwapChain(u32 width, u32 height) { m_backBufferHeight = extent.height; m_currentImageIndex = 0; - Msg("* [VulkanBackend] Swapchain created: %ux%u, %u images, format %d", - extent.width, extent.height, imageCount, m_swapchainFormat); + Msg("* [VulkanBackend] Swapchain created: %ux%u, %u images, format %d hdr10=%s", + extent.width, extent.height, imageCount, m_swapchainFormat, m_hdr10Active ? "on" : "off"); + m_hdrMetaPeak = 0.f; + m_hdrMetaPaper = 0.f; + UpdateHdrMetadata(); return true; } +void VulkanBackend::UpdateHdrMetadata() +{ + if (!m_hdr10Active || !m_setHdrMetadata || !m_swapchain) + return; + const float peak = ps_r_hdr10_peak; + const float paper = ps_r_hdr10_paper_white; + if (peak == m_hdrMetaPeak && paper == m_hdrMetaPaper) + return; + m_hdrMetaPeak = peak; + m_hdrMetaPaper = paper; + VkHdrMetadataEXT md{}; + md.sType = VK_STRUCTURE_TYPE_HDR_METADATA_EXT; + md.displayPrimaryRed = {0.708f, 0.292f}; + md.displayPrimaryGreen = {0.170f, 0.797f}; + md.displayPrimaryBlue = {0.131f, 0.046f}; + md.whitePoint = {0.3127f, 0.3290f}; + md.maxLuminance = peak; + md.minLuminance = 0.005f; + md.maxContentLightLevel = peak; + md.maxFrameAverageLightLevel = paper; + m_setHdrMetadata(m_device, 1, &m_swapchain, &md); +} + void VulkanBackend::DestroySwapChain() { for (auto& bb : m_backBuffers) bb = nullptr; @@ -633,9 +834,12 @@ void VulkanBackend::DestroySwapChain() { } void VulkanBackend::CreateBackBufferTextures() { - nvrhi::Format nvFormat = (m_swapchainFormat == VK_FORMAT_R8G8B8A8_UNORM) - ? nvrhi::Format::RGBA8_UNORM - : nvrhi::Format::BGRA8_UNORM; + nvrhi::Format nvFormat = nvrhi::Format::BGRA8_UNORM; + if (m_swapchainFormat == VK_FORMAT_A2B10G10R10_UNORM_PACK32 || + m_swapchainFormat == VK_FORMAT_A2R10G10B10_UNORM_PACK32) + nvFormat = nvrhi::Format::R10G10B10A2_UNORM; + else if (m_swapchainFormat == VK_FORMAT_R8G8B8A8_UNORM) + nvFormat = nvrhi::Format::RGBA8_UNORM; for (u32 i = 0; i < m_swapchainImages.size() && i < BACK_BUFFER_COUNT; i++) { nvrhi::TextureDesc desc; @@ -799,11 +1003,9 @@ nvrhi::ITexture* VulkanBackend::GetBackBuffer() { return m_backBuffers[m_currentImageIndex].Get(); } -void VulkanBackend::Present(bool vsync) { - if (m_asyncSubmit) - return; - - ZoneScopedN("VulkanBackend::Present"); +void VulkanBackend::PresentInternal() +{ + ZoneScopedN("VulkanBackend::PresentInternal"); std::scoped_lock sc(m_swapchainMutex, m_queueMutex); @@ -827,6 +1029,114 @@ void VulkanBackend::Present(bool vsync) { m_currentFrameIndex = (m_currentFrameIndex + 1) % BACK_BUFFER_COUNT; } +void VulkanBackend::Present(bool vsync) { + (void)vsync; + if (m_asyncSubmit) + return; + + ZoneScopedN("VulkanBackend::Present"); + PresentInternal(); +} + +bool VulkanBackend::PresentFrameGeneration(nvrhi::ITexture* interpolated, nvrhi::ITexture* real) +{ + if (!interpolated || m_asyncSubmit) + return false; + + ZoneScopedN("VulkanBackend::PresentFrameGeneration"); + + const nvrhi::ITexture* proto = m_backBuffers[0].Get(); + if (!proto || proto->getDesc().format != interpolated->getDesc().format || + proto->getDesc().width != interpolated->getDesc().width || + proto->getDesc().height != interpolated->getDesc().height) + { + static bool s_logged = false; + if (!s_logged) + { + s_logged = true; + const auto& id = interpolated->getDesc(); + const auto& bd = proto ? proto->getDesc() : nvrhi::TextureDesc{}; + Msg("! [VulkanBackend] DLSS-FG present skipped: FG %ux%u fmt=%u vs BB %ux%u fmt=%u", + id.width, id.height, (u32)id.format, + bd.width, bd.height, (u32)bd.format); + } + return false; + } + + auto* cl = m_commandLists[m_recordSlot ^ 1u].Get(); + if (!cl) + return false; + + const u32 realSlot = m_currentFrameIndex; + const u32 realImage = m_currentImageIndex; + const u32 interpSlot = (realSlot + 1) % BACK_BUFFER_COUNT; + nvrhi::ITexture* realBb = + (realImage < BACK_BUFFER_COUNT) ? m_backBuffers[realImage].Get() : nullptr; + + { + ZoneScopedN("VK::FG_WaitInterpSlotFence"); + vkWaitForFences(m_device, 1, &m_inFlightFence[interpSlot], VK_TRUE, UINT64_MAX); + vkResetFences(m_device, 1, &m_inFlightFence[interpSlot]); + } + + auto reSignalInterpFence = [this, interpSlot]() { + VkSubmitInfo fenceSubmit = {}; + fenceSubmit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + std::scoped_lock qk(m_queueMutex); + vkQueueSubmit(m_graphicsQueue, 1, &fenceSubmit, m_inFlightFence[interpSlot]); + }; + + { + std::scoped_lock sc(m_swapchainMutex); + VkResult result = vkAcquireNextImageKHR( + m_device, m_swapchain, UINT64_MAX, + m_imageAvailable[interpSlot], VK_NULL_HANDLE, + &m_currentImageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR) + { + m_currentImageIndex = realImage; + reSignalInterpFence(); + return false; + } + } + + nvrhi::ITexture* interpDst = GetBackBuffer(); + if (!interpDst) + { + m_currentImageIndex = realImage; + reSignalInterpFence(); + return false; + } + + { + auto* vkDevice = static_cast(m_nvrhiVulkanDevice.Get()); + std::scoped_lock qk(m_queueMutex); + vkDevice->queueWaitForSemaphore( + nvrhi::CommandQueue::Graphics, + m_imageAvailable[interpSlot], 0); + vkDevice->queueSignalSemaphore( + nvrhi::CommandQueue::Graphics, + m_renderFinished[interpSlot], 0); + + cl->open(); + nvrhi::TextureSlice slice; + cl->copyTexture(interpDst, slice, interpolated, slice); + if (real && realBb && real != realBb) + cl->copyTexture(realBb, slice, real, slice); + cl->close(); + m_nvrhiDevice->executeCommandList(cl); + } + + m_currentFrameIndex = interpSlot; + PresentInternal(); + + m_currentFrameIndex = realSlot; + m_currentImageIndex = realImage; + PresentInternal(); + + return true; +} + void VulkanBackend::ResizeSwapChain(u32 width, u32 height) { WaitForIdle(); @@ -841,6 +1151,20 @@ void VulkanBackend::ResizeSwapChain(u32 width, u32 height) { void VulkanBackend::BeginFrame() { ZoneScopedN("VK::BeginFrame"); + if (m_asyncSubmit) + { + if (const bool wantFg = ps_r_upscale == 2 && ps_r_dlss_fg != 0; wantFg) + { + FlushSubmits(); + m_asyncSubmit = false; + Msg("! [VulkanBackend] async submit disabled — DLSS-FG enabled"); + } + } + + if (m_initialized && ((ps_r_hdr10 != 0) != m_hdr10Active) && m_backBufferWidth && m_backBufferHeight) + ResizeSwapChain(m_backBufferWidth, m_backBufferHeight); + UpdateHdrMetadata(); + if (m_gcTask) { ZoneScopedN("VK::WaitForGC"); TaskScheduler->Wait(*m_gcTask); @@ -849,24 +1173,55 @@ void VulkanBackend::BeginFrame() { if (m_asyncSubmit) { ZoneScopedN("VK::WaitSubmitSlot"); - std::unique_lock lk(m_submitMutex); - m_submitDoneCv.wait(lk, [&] { return !m_slotInFlight[m_recordSlot]; }); + std::unique_lock lk(m_submitMutex); + m_submitDoneCv.wait(lk, [this, slot = m_recordSlot] { return !m_slotInFlight[slot]; }); } vkWaitForFences(m_device, 1, &m_inFlightFence[m_currentFrameIndex], VK_TRUE, UINT64_MAX); vkResetFences(m_device, 1, &m_inFlightFence[m_currentFrameIndex]); - VkResult result; - { - std::lock_guard sc(m_swapchainMutex); - result = vkAcquireNextImageKHR( + auto acquire = [this] { + std::scoped_lock sc(m_swapchainMutex); + return vkAcquireNextImageKHR( m_device, m_swapchain, UINT64_MAX, m_imageAvailable[m_currentFrameIndex], VK_NULL_HANDLE, &m_currentImageIndex); - } + }; + auto reSignalInFlightFence = [this]() { + VkSubmitInfo fenceSubmit = {}; + fenceSubmit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + std::scoped_lock qk(m_queueMutex); + vkQueueSubmit(m_graphicsQueue, 1, &fenceSubmit, m_inFlightFence[m_currentFrameIndex]); + }; + + VkResult result = acquire(); if (result == VK_ERROR_OUT_OF_DATE_KHR) { Msg("* [VulkanBackend] Swapchain out of date during acquire"); + VkSurfaceCapabilitiesKHR caps{}; + if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physicalDevice, m_surface, &caps) == VK_SUCCESS) { + u32 w = caps.currentExtent.width; + u32 h = caps.currentExtent.height; + if (w == 0xFFFFFFFFu) + w = m_backBufferWidth; + if (h == 0xFFFFFFFFu) + h = m_backBufferHeight; + if (w > 0 && h > 0) + ResizeSwapChain(w, h); + } + result = acquire(); + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + Msg("~ [VulkanBackend] Acquire still out of date after recreate — skipping frame"); + reSignalInFlightFence(); + m_inFrame = false; + return; + } + } + + if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + Msg("! [VulkanBackend] vkAcquireNextImageKHR failed: %d", static_cast(result)); + reSignalInFlightFence(); + m_inFrame = false; return; } @@ -887,6 +1242,9 @@ void VulkanBackend::BeginFrame() { void VulkanBackend::EndFrame() { ZoneScopedN("VK::EndFrame"); + if (!m_inFrame) + return; + m_inFrame = false; if (m_asyncSubmit) { @@ -905,7 +1263,10 @@ void VulkanBackend::EndFrame() { job.enqueueTime = std::chrono::steady_clock::now(); { - std::lock_guard lk(m_submitMutex); + std::unique_lock lk(m_submitMutex); + m_submitDoneCv.wait(lk, [this] { return !m_jobQueued || !m_submitRun; }); + if (!m_submitRun) + return; m_pendingJob = job; m_jobQueued = true; m_slotInFlight[m_recordSlot] = true; @@ -919,7 +1280,7 @@ void VulkanBackend::EndFrame() { auto* vkDevice = static_cast(m_nvrhiVulkanDevice.Get()); { - std::lock_guard qk(m_queueMutex); + std::scoped_lock qk(m_queueMutex); vkDevice->queueSignalSemaphore( nvrhi::CommandQueue::Graphics, m_renderFinished[m_currentFrameIndex], 0); @@ -936,7 +1297,7 @@ void VulkanBackend::EndFrame() { } nvrhi::IDevice* device = m_nvrhiDevice; - m_gcTask = &TaskScheduler->AddTask([device] { + m_gcTask = &TaskManager::AddTask([device] { device->runGarbageCollection(); }); } @@ -947,27 +1308,29 @@ void VulkanBackend::SubmitThreadMain() { #endif using Clock = std::chrono::steady_clock; - auto usBetween = [](Clock::time_point a, Clock::time_point b) -> u64 { + auto usBetween = [](Clock::time_point a, Clock::time_point b) { return static_cast(std::chrono::duration_cast(b - a).count()); }; for (;;) { SubmitJob job; { - std::unique_lock lk(m_submitMutex); - m_submitCv.wait(lk, [&] { return m_jobQueued || !m_submitRun; }); + std::unique_lock lk(m_submitMutex); + m_submitCv.wait(lk, [this] { return m_jobQueued || !m_submitRun; }); if (!m_submitRun && !m_jobQueued) return; job = m_pendingJob; m_jobQueued = false; + m_submitBusy = true; } + m_submitDoneCv.notify_all(); const auto tDequeue = Clock::now(); m_stJobLatencyUs.store(usBetween(job.enqueueTime, tDequeue), std::memory_order_relaxed); auto* vkDevice = static_cast(m_nvrhiVulkanDevice.Get()); { - std::lock_guard qk(m_queueMutex); + std::scoped_lock qk(m_queueMutex); const auto tLocked = Clock::now(); m_stQueueLockUs.store(usBetween(tDequeue, tLocked), std::memory_order_relaxed); @@ -984,7 +1347,7 @@ void VulkanBackend::SubmitThreadMain() { } { - std::lock_guard lk(m_submitMutex); + std::scoped_lock lk(m_submitMutex); m_slotInFlight[job.slot] = false; } m_submitDoneCv.notify_all(); @@ -1023,6 +1386,12 @@ void VulkanBackend::SubmitThreadMain() { m_nvrhiDevice->runGarbageCollection(); m_stGcUs.store(usBetween(tGc, Clock::now()), std::memory_order_relaxed); } + + { + std::scoped_lock lk(m_submitMutex); + m_submitBusy = false; + } + m_submitDoneCv.notify_all(); } } @@ -1043,8 +1412,10 @@ bool VulkanBackend::GetSubmitThreadTimings(SubmitThreadTimings& out) const { void VulkanBackend::FlushSubmits() { if (!m_asyncSubmit) return; - std::unique_lock lk(m_submitMutex); - m_submitDoneCv.wait(lk, [&] { return !m_jobQueued && !m_slotInFlight[0] && !m_slotInFlight[1]; }); + std::unique_lock lk(m_submitMutex); + m_submitDoneCv.wait(lk, [this] { + return !m_jobQueued && !m_submitBusy && !m_slotInFlight[0] && !m_slotInFlight[1]; + }); } void VulkanBackend::WaitForIdle() { @@ -1061,7 +1432,7 @@ void VulkanBackend::WaitForIdle() { void VulkanBackend::ExecuteCommandList(nvrhi::ICommandList* commandList) { if (m_nvrhiDevice && commandList) { - std::lock_guard qk(m_queueMutex); + std::scoped_lock qk(m_queueMutex); m_nvrhiDevice->executeCommandList(commandList); } } @@ -1069,23 +1440,27 @@ void VulkanBackend::ExecuteCommandList(nvrhi::ICommandList* commandList) { u64 VulkanBackend::ExecuteComputeCommandList(nvrhi::ICommandList* commandList) { if (!m_nvrhiDevice || !commandList || !m_computeQueue) return 0; + std::scoped_lock qk(m_queueMutex); return m_nvrhiDevice->executeCommandList(commandList, nvrhi::CommandQueue::Compute); } void VulkanBackend::QueueWaitForCompute(u64 instanceID) { if (!m_nvrhiDevice || !m_computeQueue || instanceID == 0) return; + std::scoped_lock qk(m_queueMutex); m_nvrhiDevice->queueWaitForCommandList(nvrhi::CommandQueue::Graphics, nvrhi::CommandQueue::Compute, instanceID); } void VulkanBackend::ComputeWaitForPreviousGraphics() { if (!m_nvrhiDevice || !m_computeQueue || m_lastGraphicsInstanceID == 0) return; + std::scoped_lock qk(m_queueMutex); m_nvrhiDevice->queueWaitForCommandList(nvrhi::CommandQueue::Compute, nvrhi::CommandQueue::Graphics, m_lastGraphicsInstanceID); } void VulkanBackend::ExecuteCommandLists(nvrhi::ICommandList* const* commandLists, u32 count) { if (!m_nvrhiDevice) return; + std::scoped_lock qk(m_queueMutex); for (u32 i = 0; i < count; i++) { if (commandLists[i]) m_nvrhiDevice->executeCommandList(commandLists[i]); @@ -1098,7 +1473,9 @@ void VulkanBackend::UploadBufferData(nvrhi::IBuffer* buffer, const void* data, s if (m_inFrame) { m_commandLists[m_recordSlot]->writeBuffer(buffer, data, size); } else { - m_nvrhiDevice->runGarbageCollection(); + std::scoped_lock qk(m_queueMutex); + if (!m_asyncSubmit) + m_nvrhiDevice->runGarbageCollection(); m_uploadCommandList->open(); m_uploadCommandList->writeBuffer(buffer, data, size); m_uploadCommandList->close(); @@ -1116,9 +1493,9 @@ DeviceState VulkanBackend::GetDeviceState() const { return DeviceState::Normal; } -void VulkanBackend::BeginDebugEvent(pcstr name) {} +void VulkanBackend::BeginDebugEvent(pcstr name) { (void)name; } void VulkanBackend::EndDebugEvent() {} -void VulkanBackend::SetMarker(pcstr name) {} +void VulkanBackend::SetMarker(pcstr name) { (void)name; } IRenderBackend* CreateVulkanBackend(SDL_Window* window, u32 width, u32 height, bool enableValidation) { auto* backend = xr_new(); diff --git a/src/Layers/xrRender/Backend/VulkanBackend.h b/src/Layers/xrRender/Backend/VulkanBackend.h index edd29c52a25..bf874353c85 100644 --- a/src/Layers/xrRender/Backend/VulkanBackend.h +++ b/src/Layers/xrRender/Backend/VulkanBackend.h @@ -47,7 +47,9 @@ class VulkanBackend : public IRenderBackend { u32 GetBackBufferCount() const override { return BACK_BUFFER_COUNT; } std::pair GetBackBufferSize() const override { return {m_backBufferWidth, m_backBufferHeight}; } void Present(bool vsync) override; + bool PresentFrameGeneration(nvrhi::ITexture* interpolated, nvrhi::ITexture* real) override; void ResizeSwapChain(u32 width, u32 height) override; + bool IsHdr10() const override { return m_hdr10Active; } void BeginFrame() override; void EndFrame() override; @@ -57,6 +59,11 @@ class VulkanBackend : public IRenderBackend { const Capabilities& GetCapabilities() const override { return m_capabilities; } Capabilities& GetMutableCapabilities() override { return m_capabilities; } + VkInstance GetVkInstance() const { return m_instance; } + VkPhysicalDevice GetVkPhysicalDevice() const { return m_physicalDevice; } + VkDevice GetVkDevice() const { return m_device; } + u32 GetGraphicsQueueFamily() const { return m_graphicsQueueFamily; } + u32 RegisterBindlessTexture(nvrhi::ITexture* texture) override; void UnregisterBindlessTexture(u32 index) override; nvrhi::IBindingLayout* GetBindlessLayout() const override { return m_bindlessLayout.Get(); } @@ -81,6 +88,9 @@ class VulkanBackend : public IRenderBackend { void DestroySyncObjects(); void CreateBindlessResources(); void QueryCapabilities(); + void UpdateHdrMetadata(); + + void PresentInternal(); VkInstance m_instance = VK_NULL_HANDLE; VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; @@ -111,6 +121,8 @@ class VulkanBackend : public IRenderBackend { xr_vector m_freeBindlessIndices; xr_map m_bindlessTextureMap; u32 m_nextBindlessIndex = 0; + xr_vector m_enabledDeviceExtensions; + bool m_bufferDeviceAddressSupported = false; bool m_initialized = false; bool m_inFrame = false; @@ -121,6 +133,10 @@ class VulkanBackend : public IRenderBackend { u32 m_currentImageIndex = 0; u32 m_currentFrameIndex = 0; VkFormat m_swapchainFormat = VK_FORMAT_B8G8R8A8_UNORM; + bool m_hdr10Active = false; + PFN_vkSetHdrMetadataEXT m_setHdrMetadata = nullptr; + float m_hdrMetaPeak = 0.f; + float m_hdrMetaPaper = 0.f; Task* m_gcTask = nullptr; std::atomic m_lastGraphicsInstanceID{ 0 }; @@ -142,6 +158,7 @@ class VulkanBackend : public IRenderBackend { std::condition_variable m_submitDoneCv; SubmitJob m_pendingJob; bool m_jobQueued = false; + bool m_submitBusy = false; bool m_submitRun = false; bool m_slotInFlight[2] = {}; std::mutex m_queueMutex; diff --git a/src/Layers/xrRender/Bindless/BindlessTypes.h b/src/Layers/xrRender/Bindless/BindlessTypes.h index 629e50528d0..54e89ab0c12 100644 --- a/src/Layers/xrRender/Bindless/BindlessTypes.h +++ b/src/Layers/xrRender/Bindless/BindlessTypes.h @@ -42,37 +42,42 @@ inline const char* GetTextureTypeName(TextureType type) { // GPU-side material representation - must match HLSL exactly! // Uses SM6 bindless texture indices from ResourceDescriptorHeap // -// Layout (32 bytes total): -// Bytes 0-15: Texture descriptor indices (4× u32) -// Bytes 16-31: Material properties - struct alignas(16) MaterialData { - // Descriptor heap indices (UINT32_MAX = invalid/not present) - u32 diffuseIndex; // Base color / albedo texture - u32 normalIndex; // Normal map texture - u32 detailIndex; // Detail texture - u32 pbrIndex; // Packed Metallic/Roughness/AO texture - - // Material properties - float detailScale; // Detail texture tiling multiplier - float alphaRef; // Alpha test threshold (0.5 typical) - u32 flags; // Material flags (see MaterialFlags) - u32 shaderVariant; // Index into ShaderVariantRegistry (0=default) + u32 diffuseIndex; + u32 normalIndex; + u32 detailIndex; + u32 pbrIndex; + + float detailScale; + float alphaRef; + u32 flags; + u32 shaderVariant; + + u32 lmapIndex; + float emissiveIntensity; + u32 _pad1; + u32 _pad2; }; -static_assert(sizeof(MaterialData) == 32, "MaterialData must be 32 bytes for GPU alignment"); +static_assert(sizeof(MaterialData) == 48, "MaterialData must be 48 bytes for GPU alignment"); -// Material flags (must match HLSL) enum MaterialFlags : u32 { - MAT_FLAG_ALPHA_TEST = (1 << 0), // Enable alpha testing - MAT_FLAG_TWO_SIDED = (1 << 1), // Disable backface culling - MAT_FLAG_EMISSIVE = (1 << 2), // Has emissive component - MAT_FLAG_HAS_DETAIL = (1 << 3), // Has detail texture (detailIndex valid) - MAT_FLAG_HAS_NORMAL = (1 << 4), // Has normal map (normalIndex valid) - MAT_FLAG_HAS_PBR = (1 << 5), // Has PBR textures (pbrIndex valid) - MAT_FLAG_TERRAIN = (1 << 6), // Terrain 4-layer blending material - MAT_FLAG_HAS_PBR_LAYER = (1 << 7), // Terrain has PBR detail textures - MAT_FLAG_ALPHA_BLEND = (1 << 8), // Transparent alpha blending - MAT_FLAG_WATER = (1 << 9), // Water surface (Fresnel reflect/refract) + MAT_FLAG_ALPHA_TEST = (1 << 0), + MAT_FLAG_TWO_SIDED = (1 << 1), + MAT_FLAG_EMISSIVE = (1 << 2), + MAT_FLAG_HAS_DETAIL = (1 << 3), + MAT_FLAG_HAS_NORMAL = (1 << 4), + MAT_FLAG_HAS_PBR = (1 << 5), + MAT_FLAG_TERRAIN = (1 << 6), + MAT_FLAG_HAS_PBR_LAYER = (1 << 7), + MAT_FLAG_ALPHA_BLEND = (1 << 8), + MAT_FLAG_WATER = (1 << 9), + MAT_FLAG_FOLIAGE = (1 << 10), + MAT_FLAG_STEEP_PARALLAX = (1 << 11), + MAT_FLAG_HAS_LMAP = (1 << 12), + MAT_FLAG_GLASS = (1 << 13), + MAT_FLAG_SCOPE = (1 << 14), + MAT_FLAG_HUD3D = (1 << 15), + MAT_FLAG_WMARK = (1 << 16), }; // ═══════════════════════════════════════════════════════ @@ -113,9 +118,8 @@ struct alignas(16) TerrainMaterialData { u32 pbrB_Index; // PBR for mask.b channel u32 pbrA_Index; // PBR for mask.a channel - // Properties - float detailScale; // Uniform tiling scale for all 4 detail layers - u32 flags; // MAT_FLAG_TERRAIN, MAT_FLAG_HAS_PBR_LAYER + float detailScale; + u32 flags; }; static_assert(sizeof(TerrainMaterialData) == 64, "TerrainMaterialData must be 64 bytes for GPU alignment"); diff --git a/src/Layers/xrRender/CMakeLists.txt b/src/Layers/xrRender/CMakeLists.txt index a613c6fa0ef..97c724af751 100644 --- a/src/Layers/xrRender/CMakeLists.txt +++ b/src/Layers/xrRender/CMakeLists.txt @@ -109,10 +109,14 @@ target_sources(xrRender PRIVATE FrameGraphPasses/DecalPassSetup.cpp FrameGraphPasses/DistortionApplyPassSetup.h FrameGraphPasses/DistortionApplyPassSetup.cpp + FrameGraphPasses/GlowPassSetup.h + FrameGraphPasses/GlowPassSetup.cpp FrameGraphPasses/ExposurePassSetup.h FrameGraphPasses/ExposurePassSetup.cpp FrameGraphPasses/TonemapPassSetup.h FrameGraphPasses/TonemapPassSetup.cpp + FrameGraphPasses/PostProcessPassSetup.h + FrameGraphPasses/PostProcessPassSetup.cpp FrameGraphPasses/UIPassSetup.h FrameGraphPasses/UIPassSetup.cpp FrameGraphPasses/SkyPassSetup.h @@ -131,12 +135,40 @@ target_sources(xrRender PRIVATE FrameGraphPasses/PathTracerPassSetup.cpp FrameGraphPasses/MotionVectorPassSetup.h FrameGraphPasses/MotionVectorPassSetup.cpp + Upscaling/UpscaleState.h + Upscaling/UpscaleState.cpp + Upscaling/IUpscaleBackend.h + Upscaling/UpscaleBackends.cpp + Upscaling/StreamlineDLSS.h + Upscaling/NgxDLSS.cpp + Upscaling/DlssFgPassSetup.h + Upscaling/DlssFgPassSetup.cpp + Upscaling/UpscalePassSetup.h + Upscaling/UpscalePassSetup.cpp + Denoising/IDenoiseBackend.h + Denoising/DenoiseBackends.cpp + Denoising/NRDDenoiseBackend.cpp + FrameGraphPasses/TAAPassSetup.h + FrameGraphPasses/TAAPassSetup.cpp FrameGraphPasses/ReSTIRGIPassSetup.h FrameGraphPasses/ReSTIRGIPassSetup.cpp FrameGraphPasses/ClusterLightPassSetup.cpp + FrameGraphPasses/WetSurfacesPassSetup.h + FrameGraphPasses/WetSurfacesPassSetup.cpp + FrameGraphPasses/RainShadowPassSetup.h + FrameGraphPasses/RainShadowPassSetup.cpp + FrameGraphPasses/ShadowPassSetup.h + FrameGraphPasses/GrassShadowPassSetup.cpp + FrameGraphPasses/VolumetricFogPassSetup.h + FrameGraphPasses/VolumetricFogPassSetup.cpp + + Volumetrics/VolumetricFogManager.h + Volumetrics/VolumetricFogManager.cpp RayTracing/RTAccelStructManager.h RayTracing/RTAccelStructManager.cpp + RayTracing/ReSTIRMemoryManager.h + RayTracing/ReSTIRMemoryManager.cpp FGDetailManager.h FGDetailManager.cpp @@ -414,6 +446,38 @@ if(XRAY_USE_AI_PBR) target_link_libraries(xrRender PRIVATE "${XRAY_ONNXRUNTIME_LIBRARY}") endif() +if(XRAY_USE_DLSS) + set(XRAY_DLSS_ROOT "${CMAKE_SOURCE_DIR}/Externals/DLSS") + if(NOT EXISTS "${XRAY_DLSS_ROOT}/include/nvsdk_ngx.h") + message(FATAL_ERROR "XRAY_USE_DLSS=ON but Externals/DLSS SDK headers are missing") + endif() + if(WIN32) + set(XRAY_DLSS_LIB_DIR "${XRAY_DLSS_ROOT}/lib/Windows_x86_64/x64") + find_library(XRAY_NVSDK_NGX_LIBRARY NAMES nvsdk_ngx_s nvsdk_ngx + PATHS "${XRAY_DLSS_LIB_DIR}" NO_DEFAULT_PATH) + set(XRAY_DLSS_RUNTIME_DIR "${XRAY_DLSS_ROOT}/lib/Windows_x86_64/rel") + else() + set(XRAY_DLSS_LIB_DIR "${XRAY_DLSS_ROOT}/lib/Linux_x86_64") + set(XRAY_NVSDK_NGX_LIBRARY "${XRAY_DLSS_LIB_DIR}/libnvsdk_ngx.a") + set(XRAY_DLSS_RUNTIME_DIR "${XRAY_DLSS_LIB_DIR}/rel") + endif() + if(NOT EXISTS "${XRAY_NVSDK_NGX_LIBRARY}") + message(FATAL_ERROR "XRAY_USE_DLSS=ON but NGX library not found at ${XRAY_NVSDK_NGX_LIBRARY}") + endif() + target_compile_definitions(xrRender PRIVATE XRAY_USE_DLSS=1) + target_include_directories(xrRender PRIVATE "${XRAY_DLSS_ROOT}/include") + target_link_libraries(xrRender PRIVATE "${XRAY_NVSDK_NGX_LIBRARY}") + if(UNIX AND NOT APPLE) + target_link_libraries(xrRender PRIVATE dl) + endif() + message(STATUS "DLSS/NGX: ON (${XRAY_NVSDK_NGX_LIBRARY})") + set(XRAY_DLSS_RUNTIME_DIR "${XRAY_DLSS_RUNTIME_DIR}" CACHE INTERNAL "DLSS runtime library directory") +endif() +if(XRAY_USE_NRD) + target_compile_definitions(xrRender PRIVATE XRAY_USE_NRD=1) + target_link_libraries(xrRender PRIVATE NRD NRDIntegration NRI) +endif() + set_target_properties(xrRender PROPERTIES PREFIX "" ) diff --git a/src/Layers/xrRender/ClusteredLightManager.cpp b/src/Layers/xrRender/ClusteredLightManager.cpp index c71ea1e7229..b05e5490983 100644 --- a/src/Layers/xrRender/ClusteredLightManager.cpp +++ b/src/Layers/xrRender/ClusteredLightManager.cpp @@ -2,11 +2,18 @@ #include "ClusteredLightManager.h" #include "light.h" #include "Light_Package.h" +#include "Layers/xrRender/Bindless/BindlessTypes.h" #include "Layers/xrRender/RenderContext/RenderDevice.h" #include "Layers/xrRender/ResourceManager/FGResourceManager.h" #include "Layers/xrRender/ResourceManager/TextureManager.h" #include "xrEngine/IRenderBackend.h" #include "xrCore/Threading/ParallelFor.hpp" +#include +#include +#include +#include + +using xray::render::fg::bindless::INVALID_TEXTURE_INDEX; namespace xray::render::fg { @@ -22,9 +29,8 @@ void ClusteredLightManager::Initialize(fg::RenderDevice* device) nvrhi::IDevice* nvDevice = device->GetNVRHIDevice(); m_device = nvDevice; m_lightsCPU.reserve(MAX_LIGHTS); - - for (u32 i = 0; i < MAX_LIGHTS; i++) - m_identityIndices[i] = i; + std::iota(m_identityIndices.begin(), m_identityIndices.end(), 0u); + m_visibleMaskOnes.fill(1u); { nvrhi::BufferDesc desc; @@ -94,6 +100,29 @@ void ClusteredLightManager::Initialize(fg::RenderDevice* device) m_visibleLightCountBuffer = nvDevice->createBuffer(desc); } + { + nvrhi::BufferDesc desc; + desc.byteSize = MAX_LIGHTS * sizeof(u32); + desc.structStride = sizeof(u32); + desc.debugName = "ClusteredLights_DIIndices"; + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + m_diLightIndicesBuffer = nvDevice->createBuffer(desc); + } + + { + nvrhi::BufferDesc desc; + desc.byteSize = MAX_LIGHTS * sizeof(float); + desc.structStride = sizeof(float); + desc.debugName = "ClusteredLights_DICDF"; + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + m_diLightCDFBuffer = nvDevice->createBuffer(desc); + } + + m_diIndicesCPU.reserve(MAX_LIGHTS); + m_diCDFCPU.reserve(MAX_LIGHTS); + Msg("* [ClusteredLights] Created GPU buffers (max %u lights, %u max clusters)", MAX_LIGHTS, maxClusters); } @@ -106,23 +135,35 @@ void ClusteredLightManager::Shutdown() m_lightIndexCounterBuffer = nullptr; m_visibleLightIndicesBuffer = nullptr; m_visibleLightCountBuffer = nullptr; + m_diLightIndicesBuffer = nullptr; + m_diLightCDFBuffer = nullptr; for (u32 i = 0; i < STATS_READBACK_SLOTS; ++i) m_statsReadbackBuffers[i] = nullptr; m_statsWriteSlot = 0; m_statsScheduled = 0; m_visibleLightCountCPU = 0; + m_diLightCount = 0; + m_diPowerSum = 0.f; + m_diIndicesCPU.clear(); + m_diCDFCPU.clear(); m_lightsCPU.clear(); + m_slotOwners.clear(); + m_freeSlots.clear(); + m_lightToSlot.clear(); m_spotTextureCache.clear(); + m_lightSetFingerprint = 0; m_device = nullptr; } void ClusteredLightManager::BeginFrame() { - m_lightsCPU.clear(); - m_numLights = 0; m_numPoint = 0; m_numSpot = 0; m_numOmni = 0; + m_diLightCount = 0; + m_diPowerSum = 0.f; + m_diIndicesCPU.clear(); + m_diCDFCPU.clear(); } GPULightData ClusteredLightManager::BuildGPULightData(const light* L) @@ -131,8 +172,11 @@ GPULightData ClusteredLightManager::BuildGPULightData(const light* L) const float range = L->range; const float invRangeSq = 1.0f / (range * range + 0.0001f); + const float virt = std::max(L->virtual_size, 0.1f); + const float virtSizeSq = virt * virt; - gpu.positionAndInvRangeSq.set(L->position.x, L->position.y, L->position.z, invRangeSq); + const float signedInv = L->flags.bHudMode ? -invRangeSq : invRangeSq; + gpu.positionAndInvRangeSq.set(L->position.x, L->position.y, L->position.z, signedInv); gpu.colorAndRange.set(L->color.r, L->color.g, L->color.b, range); std::memset(&gpu.spotVP, 0, sizeof(gpu.spotVP)); @@ -147,7 +191,7 @@ GPULightData ClusteredLightManager::BuildGPULightData(const light* L) const float scale = 1.0f / std::max(cosInner - cosOuter, 0.001f); const float offset = -cosOuter * scale; - u32 texIdx = 0; + u32 texIdx = INVALID_TEXTURE_INDEX; if (!L->spot_texture_name.empty()) texIdx = GetOrLoadSpotTexture(L->spot_texture_name); @@ -157,7 +201,7 @@ GPULightData ClusteredLightManager::BuildGPULightData(const light* L) std::memcpy(&texIdxBits, &texIdx, sizeof(float)); gpu.spotParamsAndType.set(offset, 1.0f, texIdxBits, 0.0f); - if (texIdx != 0) + if (texIdx != INVALID_TEXTURE_INDEX) { Fvector L_dir, L_up, L_right; L_dir.set(L->direction); @@ -200,7 +244,7 @@ GPULightData ClusteredLightManager::BuildGPULightData(const light* L) else { gpu.directionAndSpotScale.set(0.0f, -1.0f, 0.0f, 0.0f); - gpu.spotParamsAndType.set(0.0f, 0.0f, 0.0f, 0.0f); + gpu.spotParamsAndType.set(0.0f, 0.0f, 0.0f, virtSizeSq); } return gpu; @@ -208,41 +252,156 @@ GPULightData ClusteredLightManager::BuildGPULightData(const light* L) void ClusteredLightManager::CollectLight(const light* L) { - if (m_numLights >= MAX_LIGHTS) + if (!L) return; + auto it = m_lightToSlot.find(L); + u32 slot; + if (it != m_lightToSlot.end()) + { + slot = it->second; + } + else if (m_lightsCPU.size() < MAX_LIGHTS) + { + slot = static_cast(m_lightsCPU.size()); + m_lightsCPU.emplace_back(); + m_slotOwners.push_back(L); + m_lightToSlot[L] = slot; + } + else if (!m_freeSlots.empty()) + { + slot = m_freeSlots.back(); + m_freeSlots.pop_back(); + m_lightToSlot[L] = slot; + if (slot >= m_lightsCPU.size()) + { + m_lightsCPU.resize(slot + 1); + m_slotOwners.resize(slot + 1, nullptr); + } + } + else + { + return; + } + m_lightsCPU[slot] = BuildGPULightData(L); + m_slotOwners[slot] = L; + m_numLights = static_cast(m_lightsCPU.size()); +} - m_lightsCPU.push_back(BuildGPULightData(L)); - m_numLights++; +void ClusteredLightManager::PurgeTransientLights() +{ + for (u32 i = 0; i < (u32)m_lightsCPU.size(); ++i) + { + if (m_slotOwners[i]) + continue; + if (m_lightsCPU[i].colorAndRange.w <= 1e-4f) + continue; + std::memset(&m_lightsCPU[i], 0, sizeof(GPULightData)); + m_freeSlots.push_back(i); + } } void ClusteredLightManager::CollectLightsParallel(const xr_vector& lights) { - const u32 count = std::min(static_cast(lights.size()), MAX_LIGHTS); + PurgeTransientLights(); + + u64 fingerprint = lights.size() * 0x9E3779B97F4A7C15ull; + for (const light* L : lights) + fingerprint ^= reinterpret_cast(L) + 0x9E3779B97F4A7C15ull + (fingerprint << 6) + (fingerprint >> 2); + + if (fingerprint == m_lightSetFingerprint && !m_slotOwners.empty()) + { + for (u32 i = 0; i < (u32)m_slotOwners.size(); ++i) + { + if (m_slotOwners[i]) + m_lightsCPU[i] = BuildGPULightData(m_slotOwners[i]); + } + m_numLights = static_cast(m_lightsCPU.size()); + return; + } + + xr_vector sorted = lights; + std::sort(sorted.begin(), sorted.end()); + sorted.erase(std::unique(sorted.begin(), sorted.end()), sorted.end()); + const u32 count = std::min(static_cast(sorted.size()), MAX_LIGHTS); + if (count == 0) + { + m_lightsCPU.clear(); + m_slotOwners.clear(); + m_freeSlots.clear(); + m_lightToSlot.clear(); + m_numLights = 0; + m_lightSetFingerprint = 0; return; + } for (u32 i = 0; i < count; i++) { - const light* L = lights[i]; + const light* L = sorted[i]; const u32 lt = L->flags.type; const bool isSpot = (lt == IRender_Light::SPOT || lt == IRender_Light::OMNIPART); if (isSpot && !L->spot_texture_name.empty()) GetOrLoadSpotTexture(L->spot_texture_name); } - m_lightsCPU.resize(count); - m_numLights = count; + xr_vector stale; + stale.reserve(m_lightToSlot.size()); + for (const auto& kv : m_lightToSlot) + { + if (!std::binary_search(sorted.begin(), sorted.end(), kv.first)) + stale.push_back(kv.first); + } + for (const light* L : stale) + { + const u32 slot = m_lightToSlot[L]; + m_lightToSlot.erase(L); + if (slot < m_lightsCPU.size()) + { + std::memset(&m_lightsCPU[slot], 0, sizeof(GPULightData)); + m_slotOwners[slot] = nullptr; + m_freeSlots.push_back(slot); + } + } + + for (u32 i = 0; i < count; i++) + { + const light* L = sorted[i]; + auto it = m_lightToSlot.find(L); + u32 slot; + if (it != m_lightToSlot.end()) + { + slot = it->second; + } + else if (m_lightsCPU.size() < MAX_LIGHTS) + { + slot = static_cast(m_lightsCPU.size()); + m_lightsCPU.emplace_back(); + m_slotOwners.push_back(L); + m_lightToSlot[L] = slot; + } + else if (!m_freeSlots.empty()) + { + slot = m_freeSlots.back(); + m_freeSlots.pop_back(); + m_lightToSlot[L] = slot; + m_slotOwners[slot] = L; + } + else + { + continue; + } + m_lightsCPU[slot] = BuildGPULightData(L); + m_slotOwners[slot] = L; + } - xr_parallel_for(TaskRange(0, count), [&](const TaskRange& range) { - for (u32 i = range.begin(); i != range.end(); ++i) - m_lightsCPU[i] = BuildGPULightData(lights[i]); - }); + m_numLights = static_cast(m_lightsCPU.size()); + m_lightSetFingerprint = fingerprint; if (psDeviceFlags.test(rsStatistic)) { for (u32 i = 0; i < count; i++) { - const u32 lt = lights[i]->flags.type; + const u32 lt = sorted[i]->flags.type; if (lt == IRender_Light::POINT) m_numPoint++; else if (lt == IRender_Light::SPOT) @@ -253,6 +412,64 @@ void ClusteredLightManager::CollectLightsParallel(const xr_vector& } } +bool ClusteredLightManager::HasNearbyPointLight(const Fvector& pos, float radius) const +{ + const float cover = std::max(radius, 1.0f); + const float coverSq = cover * cover; + for (u32 i = 0; i < (u32)m_lightsCPU.size(); ++i) + { + if (!m_slotOwners[i]) + continue; + const GPULightData& L = m_lightsCPU[i]; + if (L.colorAndRange.w <= 1e-4f) + continue; + const float dx = pos.x - L.positionAndInvRangeSq.x; + const float dy = pos.y - L.positionAndInvRangeSq.y; + const float dz = pos.z - L.positionAndInvRangeSq.z; + const float reach = std::max(L.colorAndRange.w, 1.0f); + if (dx * dx + dy * dy + dz * dz < reach * reach * 0.36f + coverSq * 0.15f) + return true; + } + return false; +} + +void ClusteredLightManager::AddTransientPointLight(const Fvector& pos, const Fvector& color, float range) +{ + if (range <= 0.05f) + return; + if (color.x + color.y + color.z <= 1e-4f) + return; + + GPULightData gpu{}; + const float invRangeSq = 1.0f / (range * range + 0.0001f); + gpu.positionAndInvRangeSq.set(pos.x, pos.y, pos.z, invRangeSq); + gpu.colorAndRange.set(color.x, color.y, color.z, range); + gpu.directionAndSpotScale.set(0.0f, -1.0f, 0.0f, 0.0f); + gpu.spotParamsAndType.set(1.0f, 0.0f, 0.0f, 0.36f); + + if (!m_freeSlots.empty()) + { + const u32 slot = m_freeSlots.back(); + m_freeSlots.pop_back(); + if (slot >= m_lightsCPU.size()) + { + m_lightsCPU.resize(slot + 1); + m_slotOwners.resize(slot + 1, nullptr); + } + m_lightsCPU[slot] = gpu; + m_slotOwners[slot] = nullptr; + } + else if (m_lightsCPU.size() < MAX_LIGHTS) + { + m_lightsCPU.push_back(gpu); + m_slotOwners.push_back(nullptr); + } + else + return; + + m_numLights = static_cast(m_lightsCPU.size()); +} + void ClusteredLightManager::AddLight(const light* L, u32 type) { if (m_numLights >= MAX_LIGHTS) @@ -266,6 +483,7 @@ void ClusteredLightManager::BuildLightBuffer(const light_Package& package) { m_lightsCPU.clear(); m_numLights = 0; + m_lightSetFingerprint = 0; for (const light* L : package.v_point) AddLight(L, 0); @@ -283,14 +501,61 @@ void ClusteredLightManager::BuildLightBuffer(const light_Package& package) } } +void ClusteredLightManager::BuildDISampleTable() +{ + m_diIndicesCPU.clear(); + m_diCDFCPU.clear(); + m_diLightCount = 0; + m_diPowerSum = 0.f; + if (m_numLights == 0) + return; + + float sum = 0.f; + m_diIndicesCPU.reserve(m_numLights); + m_diCDFCPU.reserve(m_numLights); + + for (u32 i = 0; i < m_numLights; ++i) + { + const GPULightData& L = m_lightsCPU[i]; + if (L.colorAndRange.w <= 1e-4f) + continue; + const float lum = + L.colorAndRange.x * 0.2126f + + L.colorAndRange.y * 0.7152f + + L.colorAndRange.z * 0.0722f; + const float range = std::max(L.colorAndRange.w, 0.5f); + const Fvector pos = { L.positionAndInvRangeSq.x, L.positionAndInvRangeSq.y, L.positionAndInvRangeSq.z }; + const float distToCam = std::max(Device.vCameraPosition.distance_to(pos), 0.01f); + float power = std::max(lum, 1e-4f) * (range * range) / std::max(distToCam * distToCam, range * range * 0.25f); + if (L.spotParamsAndType.y > 0.5f) + power *= 0.65f; + sum += power; + m_diIndicesCPU.push_back(i); + m_diCDFCPU.push_back(sum); + } + + m_diLightCount = static_cast(m_diIndicesCPU.size()); + m_diPowerSum = sum; +} + void ClusteredLightManager::Upload(nvrhi::ICommandList* cmdList) { if (!m_lightDataBuffer || m_numLights == 0) return; + BuildDISampleTable(); + cmdList->writeBuffer(m_lightDataBuffer, m_lightsCPU.data(), m_numLights * sizeof(GPULightData)); + if (m_diLightIndicesBuffer && m_diLightCDFBuffer && m_diLightCount > 0) + { + cmdList->writeBuffer(m_diLightIndicesBuffer, m_diIndicesCPU.data(), + m_diLightCount * sizeof(u32)); + cmdList->writeBuffer(m_diLightCDFBuffer, m_diCDFCPU.data(), + m_diLightCount * sizeof(float)); + } + const u32 zero = 0; cmdList->writeBuffer(m_lightIndexCounterBuffer, &zero, sizeof(u32)); } @@ -300,12 +565,22 @@ void ClusteredLightManager::UploadAllVisible(nvrhi::ICommandList* cmdList) if (!m_visibleLightIndicesBuffer || m_numLights == 0) return; - cmdList->writeBuffer(m_visibleLightIndicesBuffer, m_identityIndices.data(), m_numLights * sizeof(u32)); + xr_vector visibleMask(m_numLights, 0u); + u32 liveCount = 0; + for (u32 i = 0; i < m_numLights; i++) + { + if (m_lightsCPU[i].colorAndRange.w > 1e-4f) + { + visibleMask[i] = 1u; + liveCount++; + } + } + cmdList->writeBuffer(m_visibleLightIndicesBuffer, visibleMask.data(), m_numLights * sizeof(u32)); cmdList->writeBuffer(m_visibleLightCountBuffer, &m_numLights, sizeof(u32)); - m_visibleLightCountCPU = m_numLights; + m_visibleLightCountCPU = liveCount; } -ClusterCB ClusteredLightManager::BuildClusterCB(u32 screenWidth, u32 screenHeight, float zNear, float zFar) const +ClusterCB ClusteredLightManager::BuildClusterCB(u32 screenWidth, u32 screenHeight, float zNear, float zFar) { const u32 tilesX = (screenWidth + CLUSTER_TILE_SIZE - 1) / CLUSTER_TILE_SIZE; const u32 tilesY = (screenHeight + CLUSTER_TILE_SIZE - 1) / CLUSTER_TILE_SIZE; @@ -317,10 +592,10 @@ ClusterCB ClusteredLightManager::BuildClusterCB(u32 screenWidth, u32 screenHeigh cb.screenSize.set(static_cast(screenWidth), static_cast(screenHeight), 1.0f / static_cast(screenWidth), 1.0f / static_cast(screenHeight)); cb.depthParams.set(zNear, zFar, logRatio, static_cast(CLUSTER_TILE_SIZE)); - cb.pad.set(0, 0, 0, 0); + cb.pad.set(Device.mProject._22, 0.f, 0.f, 0.f); - const_cast(this)->m_tilesX = tilesX; - const_cast(this)->m_tilesY = tilesY; + m_tilesX = tilesX; + m_tilesY = tilesY; return cb; } @@ -376,29 +651,29 @@ u32 ClusteredLightManager::GetOrLoadSpotTexture(const shared_str& name) auto* renderDevice = GEnv.Render ? GEnv.Render->GetRenderDevice() : nullptr; if (!renderDevice) - return 0; + return INVALID_TEXTURE_INDEX; auto* resMgr = renderDevice->GetFGResourceManager(); auto* backend = renderDevice->GetBackend(); if (!resMgr || !backend) - return 0; + return INVALID_TEXTURE_INDEX; auto* texManager = resMgr->GetTextureManager(); if (!texManager) - return 0; + return INVALID_TEXTURE_INDEX; auto handle = texManager->LoadTexture(name.c_str()); if (!handle.IsValid()) { - m_spotTextureCache[name] = 0; - return 0; + m_spotTextureCache[name] = INVALID_TEXTURE_INDEX; + return INVALID_TEXTURE_INDEX; } nvrhi::ITexture* nvrhiTex = texManager->GetNVRHITexture(handle); if (!nvrhiTex) { - m_spotTextureCache[name] = 0; - return 0; + m_spotTextureCache[name] = INVALID_TEXTURE_INDEX; + return INVALID_TEXTURE_INDEX; } u32 bindlessIdx = backend->RegisterBindlessTexture(nvrhiTex); diff --git a/src/Layers/xrRender/ClusteredLightManager.h b/src/Layers/xrRender/ClusteredLightManager.h index 39758b7f1cc..8851d001a10 100644 --- a/src/Layers/xrRender/ClusteredLightManager.h +++ b/src/Layers/xrRender/ClusteredLightManager.h @@ -41,7 +41,7 @@ static_assert(sizeof(LightHiZCullCB) == 160, "LightHiZCullCB must be 160 bytes") static constexpr u32 CLUSTER_TILE_SIZE = 64; static constexpr u32 CLUSTER_NUM_SLICES = 24; -static constexpr u32 MAX_LIGHTS = 1024; +static constexpr u32 MAX_LIGHTS = 2048; static constexpr u32 MAX_LIGHT_INDICES = 1024 * 1024; class ClusteredLightManager { @@ -53,6 +53,8 @@ class ClusteredLightManager { void BeginFrame(); void CollectLight(const light* L); void CollectLightsParallel(const xr_vector& lights); + void AddTransientPointLight(const Fvector& pos, const Fvector& color, float range); + bool HasNearbyPointLight(const Fvector& pos, float radius) const; void BuildLightBuffer(const light_Package& package); void Upload(nvrhi::ICommandList* cmdList); void UploadAllVisible(nvrhi::ICommandList* cmdList); @@ -63,15 +65,19 @@ class ClusteredLightManager { nvrhi::IBuffer* GetLightIndexCounterBuffer() const { return m_lightIndexCounterBuffer; } nvrhi::IBuffer* GetVisibleLightIndicesBuffer() const { return m_visibleLightIndicesBuffer; } nvrhi::IBuffer* GetVisibleLightCountBuffer() const { return m_visibleLightCountBuffer; } + nvrhi::IBuffer* GetDILightIndicesBuffer() const { return m_diLightIndicesBuffer; } + nvrhi::IBuffer* GetDILightCDFBuffer() const { return m_diLightCDFBuffer; } u32 GetLightCount() const { return m_numLights; } + u32 GetDILightCount() const { return m_diLightCount; } + float GetDIPowerSum() const { return m_diPowerSum; } u32 GetPointCount() const { return m_numPoint; } u32 GetSpotCount() const { return m_numSpot; } u32 GetOmniCount() const { return m_numOmni; } u32 GetTilesX() const { return m_tilesX; } u32 GetTilesY() const { return m_tilesY; } - ClusterCB BuildClusterCB(u32 screenWidth, u32 screenHeight, float zNear, float zFar) const; + ClusterCB BuildClusterCB(u32 screenWidth, u32 screenHeight, float zNear, float zFar); void ScheduleStatsReadback(nvrhi::ICommandList* cmdList); void ProcessStatsReadback(); @@ -80,19 +86,29 @@ class ClusteredLightManager { bool IsReady() const { return m_lightDataBuffer != nullptr; } private: + void PurgeTransientLights(); void AddLight(const light* L, u32 type); + void BuildDISampleTable(); GPULightData BuildGPULightData(const light* L); u32 GetOrLoadSpotTexture(const shared_str& name); nvrhi::DeviceHandle m_device; xr_vector m_lightsCPU; + xr_vector m_slotOwners; + xr_vector m_freeSlots; + xr_map m_lightToSlot; + xr_vector m_diIndicesCPU; + xr_vector m_diCDFCPU; std::array m_identityIndices; + std::array m_visibleMaskOnes{}; xr_map m_spotTextureCache; u32 m_numLights = 0; u32 m_numPoint = 0; u32 m_numSpot = 0; u32 m_numOmni = 0; + u32 m_diLightCount = 0; + float m_diPowerSum = 0.f; nvrhi::BufferHandle m_lightDataBuffer; nvrhi::BufferHandle m_clusterGridBuffer; @@ -100,6 +116,8 @@ class ClusteredLightManager { nvrhi::BufferHandle m_lightIndexCounterBuffer; nvrhi::BufferHandle m_visibleLightIndicesBuffer; nvrhi::BufferHandle m_visibleLightCountBuffer; + nvrhi::BufferHandle m_diLightIndicesBuffer; + nvrhi::BufferHandle m_diLightCDFBuffer; static constexpr u32 STATS_READBACK_SLOTS = 6; nvrhi::BufferHandle m_statsReadbackBuffers[STATS_READBACK_SLOTS]; u32 m_statsWriteSlot = 0; @@ -109,6 +127,7 @@ class ClusteredLightManager { u32 m_tilesX = 0; u32 m_tilesY = 0; + u64 m_lightSetFingerprint = 0; }; } diff --git a/src/Layers/xrRender/ColorMapManager.cpp b/src/Layers/xrRender/ColorMapManager.cpp index 4538f5d80e5..9b2483480e9 100644 --- a/src/Layers/xrRender/ColorMapManager.cpp +++ b/src/Layers/xrRender/ColorMapManager.cpp @@ -15,6 +15,13 @@ void ColorMapManager::SetTextures(const shared_str& tex0, const shared_str& tex1 UpdateTexture(tex1, 1); } +nvrhi::ITexture* ColorMapManager::GetTexture(int i) const +{ + if (i < 0 || i > 1 || !m_CMap[i]) + return nullptr; + return m_CMap[i]->surface_get_native(); +} + void ColorMapManager::UpdateTexture(const shared_str& strTexName, int iTex) { if (strTexName == m_strCMap[iTex]) diff --git a/src/Layers/xrRender/ColorMapManager.h b/src/Layers/xrRender/ColorMapManager.h index 36ac974d0bd..f6e06aed9ae 100644 --- a/src/Layers/xrRender/ColorMapManager.h +++ b/src/Layers/xrRender/ColorMapManager.h @@ -1,5 +1,7 @@ #pragma once +#include + namespace xray::render::fg { // Reduces amount of work if the texture was not changed. @@ -11,6 +13,7 @@ class ColorMapManager ColorMapManager(); void SetTextures(const shared_str& tex0, const shared_str& tex1); + nvrhi::ITexture* GetTexture(int i) const; private: void UpdateTexture(const shared_str& strTexName, int iTex); diff --git a/src/Layers/xrRender/Denoising/DenoiseBackends.cpp b/src/Layers/xrRender/Denoising/DenoiseBackends.cpp new file mode 100644 index 00000000000..e8a7c03215f --- /dev/null +++ b/src/Layers/xrRender/Denoising/DenoiseBackends.cpp @@ -0,0 +1,87 @@ +#include "stdafx.h" +#include "IDenoiseBackend.h" +#if defined(XRAY_USE_DLSS) +#include "Layers/xrRender/Upscaling/StreamlineDLSS.h" +#endif + +extern ENGINE_API int ps_r_denoise; +extern ENGINE_API int ps_r_dlss_rr; +extern ENGINE_API int ps_r_upscale; + +namespace xray::render::fg { +namespace { + +class NullDenoiseBackend final : public IDenoiseBackend +{ +public: + bool Init(nvrhi::IDevice*) override { return true; } + void Shutdown() override {} + bool IsAvailable() const override { return false; } + DenoiseBackendType GetType() const override { return DenoiseBackendType::None; } + void Resize(u32, u32) override {} + bool Evaluate(nvrhi::ICommandList* cmd, const DenoiseInputs& inputs) override + { + if (!cmd || !inputs.noisyDiffuse || !inputs.outDiffuse) + return false; + if (inputs.noisyDiffuse != inputs.outDiffuse) { + nvrhi::TextureSlice slice; + cmd->copyTexture(inputs.outDiffuse, slice, inputs.noisyDiffuse, slice); + } + if (inputs.noisySpecular && inputs.outSpecular && inputs.noisySpecular != inputs.outSpecular) { + nvrhi::TextureSlice slice; + cmd->copyTexture(inputs.outSpecular, slice, inputs.noisySpecular, slice); + } + return true; + } +}; + +} + +IDenoiseBackend* CreateNullDenoiseBackend() { return new NullDenoiseBackend(); } + +bool IsVendorDenoiseActive(const IDenoiseBackend* backend) +{ + return backend && backend->IsAvailable() && backend->GetType() != DenoiseBackendType::None; +} + +IDenoiseBackend* CreateDenoiseBackendAuto(bool preferDlssRR) +{ + nvrhi::IDevice* device = GEnv.Backend ? GEnv.Backend->GetDevice() : nullptr; + + if (ps_r_denoise == 0) { + auto* n = CreateNullDenoiseBackend(); + n->Init(device); + return n; + } + + if (preferDlssRR && ps_r_dlss_rr && ps_r_upscale == 2) + { +#if defined(XRAY_USE_DLSS) + if (Streamline_IsRRAvailable()) + { + Msg("* [Denoise] DLSS-RR active — skipping NRD"); + auto* n = CreateNullDenoiseBackend(); + n->Init(device); + return n; + } +#endif + Msg("* [Denoise] DLSS-RR requested but unavailable — using NRD"); + } + + auto tryInit = [&](IDenoiseBackend* b) -> IDenoiseBackend* { + if (b->Init(device) && b->IsAvailable()) + return b; + delete b; + return nullptr; + }; + + if (auto* nrd = tryInit(CreateNRDDenoiseBackend())) + return nrd; + + Msg("! [Denoise] No vendor denoise SDK available — passthrough"); + auto* n = CreateNullDenoiseBackend(); + n->Init(device); + return n; +} + +} diff --git a/src/Layers/xrRender/Denoising/IDenoiseBackend.h b/src/Layers/xrRender/Denoising/IDenoiseBackend.h new file mode 100644 index 00000000000..53bfbb37a6b --- /dev/null +++ b/src/Layers/xrRender/Denoising/IDenoiseBackend.h @@ -0,0 +1,67 @@ +#pragma once + +#include + +namespace xray::render::fg { + +enum class DenoiseBackendType : u32 +{ + None = 0, + NRD, + DLSSRayReconstruction +}; + +struct DenoiseInputs +{ + nvrhi::ITexture* noisyDiffuse = nullptr; + nvrhi::ITexture* noisySpecular = nullptr; + nvrhi::ITexture* hitDistance = nullptr; + nvrhi::ITexture* shadowMask = nullptr; + nvrhi::ITexture* normals = nullptr; + nvrhi::ITexture* roughness = nullptr; + nvrhi::ITexture* depth = nullptr; + nvrhi::ITexture* worldPos = nullptr; + nvrhi::ITexture* baseColor = nullptr; + nvrhi::ITexture* classifyWorldPos = nullptr; + nvrhi::ITexture* motionVectors = nullptr; + nvrhi::ITexture* directLighting = nullptr; + nvrhi::ITexture* sceneColorIn = nullptr; + nvrhi::ITexture* outDiffuse = nullptr; + nvrhi::ITexture* outSpecular = nullptr; + nvrhi::ITexture* outSceneColor = nullptr; + u32 width = 0; + u32 height = 0; + float jitterX = 0.f; + float jitterY = 0.f; + float jitterPrevX = 0.f; + float jitterPrevY = 0.f; + float exposure = 1.f; + float nearZ = 0.001f; + float farZ = 500.f; + float viewToClip[16] = {}; + float viewToClipPrev[16] = {}; + float worldToView[16] = {}; + float worldToViewPrev[16] = {}; + u32 frameIndex = 0; + bool reset = false; +}; + +class IDenoiseBackend +{ +public: + virtual ~IDenoiseBackend() = default; + virtual bool Init(nvrhi::IDevice* device) = 0; + virtual void Shutdown() = 0; + virtual bool IsAvailable() const = 0; + virtual DenoiseBackendType GetType() const = 0; + virtual void Resize(u32 width, u32 height) = 0; + virtual bool Evaluate(nvrhi::ICommandList* cmd, const DenoiseInputs& inputs) = 0; +}; + +IDenoiseBackend* CreateNullDenoiseBackend(); +IDenoiseBackend* CreateNRDDenoiseBackend(); +IDenoiseBackend* CreateDenoiseBackendAuto(bool preferDlssRR); + +bool IsVendorDenoiseActive(const IDenoiseBackend* backend); + +} diff --git a/src/Layers/xrRender/Denoising/NRDDenoiseBackend.cpp b/src/Layers/xrRender/Denoising/NRDDenoiseBackend.cpp new file mode 100644 index 00000000000..03956d8f2a7 --- /dev/null +++ b/src/Layers/xrRender/Denoising/NRDDenoiseBackend.cpp @@ -0,0 +1,450 @@ +#include "stdafx.h" +#include "IDenoiseBackend.h" + +#if defined(XRAY_USE_NRD) + +#include "Layers/xrRender/Backend/VulkanBackend.h" +#include "Layers/xrRender/FrameGraph/PassResourceCache.h" +#include "Layers/xrRender/FrameGraph/BindingSetBuilder.h" +#include "Layers/xrRender/FrameGraph/ShaderLoader.h" +#include "Layers/xrRender/FrameGraphPasses/ShaderConstants.h" + +#include +#include + +#pragma push_macro("NONE") +#pragma push_macro("TRUE") +#pragma push_macro("FALSE") +#pragma push_macro("CONST") +#undef NONE +#undef TRUE +#undef FALSE +#undef CONST +#include "NRD.h" +#include "NRI.h" +#include "Extensions/NRIHelper.h" +#include "Extensions/NRIRayTracing.h" +#include "Extensions/NRIWrapperVK.h" +#include "NRDIntegration.hpp" +#pragma pop_macro("CONST") +#pragma pop_macro("FALSE") +#pragma pop_macro("TRUE") +#pragma pop_macro("NONE") + +extern ENGINE_API int ps_r_nrd_method; +extern ENGINE_API int ps_r_nrd_apply; +extern ENGINE_API int ps_r_upscale; +extern ENGINE_API float ps_r_rt_gi_intensity; + +namespace xray::render::fg { +namespace { + +struct NrdPackCB +{ + float worldToView[16]; + float worldToViewPrev[16]; + float worldToClip[16]; + float worldToClipPrev[16]; + float invViewProj[16]; + float invViewProjPrev[16]; + float screenNearFar[4]; + float hitDistMethod[4]; + float cameraPosRange[4]; +}; + +struct NrdCompositeCB +{ + float params[4]; + float cameraPos[4]; + float fogParams[4]; + float fogColor[4]; + float invViewProj[16]; +}; + +void NrdCopyMatrix(float dst[16], const Fmatrix& m) +{ + dst[0] = m._11; dst[1] = m._12; dst[2] = m._13; dst[3] = m._14; + dst[4] = m._21; dst[5] = m._22; dst[6] = m._23; dst[7] = m._24; + dst[8] = m._31; dst[9] = m._32; dst[10] = m._33; dst[11] = m._34; + dst[12] = m._41; dst[13] = m._42; dst[14] = m._43; dst[15] = m._44; +} + +class NRDDenoiseBackend final : public IDenoiseBackend +{ + nvrhi::IDevice* m_device = nullptr; + nrd::Integration m_nrd; + nrd::Identifier m_denoiserId = 0; + bool m_ready = false; + bool m_logged = false; + u32 m_width = 0; + u32 m_height = 0; + int m_method = -1; + u32 m_pipeVersion = 0; + + nvrhi::TextureHandle m_inDiff; + nvrhi::TextureHandle m_inSpec; + nvrhi::TextureHandle m_inNormalRough; + nvrhi::TextureHandle m_inViewZ; + nvrhi::TextureHandle m_inMv; + nvrhi::TextureHandle m_outDiff; + nvrhi::TextureHandle m_outSpec; + nvrhi::TextureHandle m_sceneCopy; + + nvrhi::ComputePipelineHandle m_packPipeline; + nvrhi::BindingLayoutHandle m_packLayout; + nvrhi::ComputePipelineHandle m_compositePipeline; + nvrhi::BindingLayoutHandle m_compositeLayout; + nvrhi::BufferHandle m_cb; + u32 m_evalOkFrames = 0; + u32 m_evalFailFrames = 0; + u32 m_prevW = 0; + u32 m_prevH = 0; + u32 m_nrdFrameIndex = 0; + bool m_diagLogged = false; + + static constexpr float kDenoisingRange = 500000.f; + static constexpr u32 kPipeVersion = 12; + + void Fail(const char* reason) + { + if ((m_evalFailFrames++ % 120u) == 0u) + Msg("! [Denoise] NRD Evaluate failed: %s", reason); + } + + void SyncAfterNative(nvrhi::ICommandList* cmd) + { + cmd->clearState(); + cmd->beginTrackingTextureState(m_inDiff, nvrhi::AllSubresources, nvrhi::ResourceStates::UnorderedAccess); + cmd->beginTrackingTextureState(m_inSpec, nvrhi::AllSubresources, nvrhi::ResourceStates::UnorderedAccess); + cmd->beginTrackingTextureState(m_inNormalRough, nvrhi::AllSubresources, nvrhi::ResourceStates::UnorderedAccess); + cmd->beginTrackingTextureState(m_inViewZ, nvrhi::AllSubresources, nvrhi::ResourceStates::UnorderedAccess); + cmd->beginTrackingTextureState(m_inMv, nvrhi::AllSubresources, nvrhi::ResourceStates::UnorderedAccess); + cmd->beginTrackingTextureState(m_outDiff, nvrhi::AllSubresources, nvrhi::ResourceStates::UnorderedAccess); + cmd->beginTrackingTextureState(m_outSpec, nvrhi::AllSubresources, nvrhi::ResourceStates::UnorderedAccess); + } + + nvrhi::TextureHandle CreateTex(const char* name, nvrhi::Format fmt, u32 w, u32 h) + { + nvrhi::TextureDesc desc; + desc.width = w; + desc.height = h; + desc.format = fmt; + desc.mipLevels = 1; + desc.dimension = nvrhi::TextureDimension::Texture2D; + desc.debugName = name; + desc.isUAV = true; + desc.isShaderResource = true; + desc.initialState = nvrhi::ResourceStates::UnorderedAccess; + desc.keepInitialState = true; + return m_device->createTexture(desc); + } + + bool EnsurePipelines() + { + if (m_packPipeline && m_compositePipeline && m_cb && m_pipeVersion == kPipeVersion) + return true; + if (!GEnv.Render || !GEnv.Render->GetShaderLoader()) + return false; + + m_packPipeline = nullptr; + m_compositePipeline = nullptr; + m_packLayout = nullptr; + m_compositeLayout = nullptr; + m_cb = nullptr; + + framegraph::BindingSetBuilder::InvalidateReflectionCache(); + auto& cache = framegraph::GetPassResourceCache(); + { + auto cs = GEnv.Render->GetShaderLoader()->LoadComputeShader("nrd_pack_inputs"); + if (!cs.handle || !cs.reflection) + return false; + m_packLayout = cache.GetOrCreateBindingLayoutFromReflection("NRD_Pack_v10", *cs.reflection, m_device); + nvrhi::ComputePipelineDesc pipeDesc; + pipeDesc.CS = cs.handle; + pipeDesc.bindingLayouts = { m_packLayout }; + m_packPipeline = m_device->createComputePipeline(pipeDesc); + } + { + auto cs = GEnv.Render->GetShaderLoader()->LoadComputeShader("nrd_composite"); + if (!cs.handle || !cs.reflection) + return false; + m_compositeLayout = cache.GetOrCreateBindingLayoutFromReflection("NRD_Composite_v10", *cs.reflection, m_device); + nvrhi::ComputePipelineDesc pipeDesc; + pipeDesc.CS = cs.handle; + pipeDesc.bindingLayouts = { m_compositeLayout }; + m_compositePipeline = m_device->createComputePipeline(pipeDesc); + } + { + nvrhi::BufferDesc bd; + bd.byteSize = 768; + bd.isConstantBuffer = true; + bd.isVolatile = true; + bd.maxVersions = 64; + bd.debugName = "NRD_CB_v10"; + bd.initialState = nvrhi::ResourceStates::ConstantBuffer; + bd.keepInitialState = true; + m_cb = m_device->createBuffer(bd); + } + m_pipeVersion = kPipeVersion; + return m_packPipeline && m_compositePipeline && m_cb; + } + + bool EnsureResources(u32 w, u32 h) + { + if (m_inDiff && m_width == w && m_height == h && m_inMv && + m_inMv->getDesc().format == nvrhi::Format::RGBA16_FLOAT) + return true; + m_width = w; + m_height = h; + m_inDiff = CreateTex("NRD_IN_DIFF", nvrhi::Format::RGBA16_FLOAT, w, h); + m_inSpec = CreateTex("NRD_IN_SPEC", nvrhi::Format::RGBA16_FLOAT, w, h); + m_inNormalRough = CreateTex("NRD_IN_NR", nvrhi::Format::RGBA16_UNORM, w, h); + m_inViewZ = CreateTex("NRD_IN_VIEWZ", nvrhi::Format::R32_FLOAT, w, h); + m_inMv = CreateTex("NRD_IN_MV", nvrhi::Format::RGBA16_FLOAT, w, h); + m_outDiff = CreateTex("NRD_OUT_DIFF", nvrhi::Format::RGBA16_FLOAT, w, h); + m_outSpec = CreateTex("NRD_OUT_SPEC", nvrhi::Format::RGBA16_FLOAT, w, h); + m_sceneCopy = nullptr; + return m_inDiff && m_inSpec && m_inNormalRough && m_inViewZ && m_inMv && m_outDiff && m_outSpec; + } + + bool RecreateIntegration(u32 w, u32 h, int method) + { + if (!GEnv.Backend || GEnv.Backend->GetAPI() != IRenderBackend::API::Vulkan) + return false; + + auto* vb = static_cast(GEnv.Backend); + nri::QueueFamilyVKDesc queueFamily{}; + queueFamily.queueNum = 1; + queueFamily.queueType = nri::QueueType::GRAPHICS; + queueFamily.familyIndex = vb->GetGraphicsQueueFamily(); + + nri::DeviceCreationVKDesc deviceDesc{}; + deviceDesc.vkInstance = vb->GetVkInstance(); + deviceDesc.vkDevice = vb->GetVkDevice(); + deviceDesc.vkPhysicalDevice = vb->GetVkPhysicalDevice(); + deviceDesc.queueFamilies = &queueFamily; + deviceDesc.queueFamilyNum = 1; + deviceDesc.minorVersion = 3; + const nrd::LibraryDesc* libDesc = nrd::GetLibraryDesc(); + deviceDesc.vkBindingOffsets = { + libDesc->spirvBindingOffsets.samplerOffset, + libDesc->spirvBindingOffsets.textureOffset, + libDesc->spirvBindingOffsets.constantBufferOffset, + libDesc->spirvBindingOffsets.storageTextureAndBufferOffset + }; + + nrd::DenoiserDesc denoiserDesc{}; + denoiserDesc.identifier = 0; + denoiserDesc.denoiser = (method != 0) + ? nrd::Denoiser::RELAX_DIFFUSE_SPECULAR + : nrd::Denoiser::REBLUR_DIFFUSE_SPECULAR; + + nrd::InstanceCreationDesc instanceDesc{}; + instanceDesc.denoisers = &denoiserDesc; + instanceDesc.denoisersNum = 1; + + nrd::IntegrationCreationDesc integrationDesc{}; + strncpy(integrationDesc.name, "OpenXRay NRD", sizeof(integrationDesc.name) - 1); + integrationDesc.resourceWidth = (uint16_t)w; + integrationDesc.resourceHeight = (uint16_t)h; + integrationDesc.queuedFrameNum = 3; + integrationDesc.enableWholeLifetimeDescriptorCaching = false; + integrationDesc.autoWaitForIdle = true; + + const nrd::Result result = m_nrd.RecreateVK(integrationDesc, instanceDesc, deviceDesc); + if (result != nrd::Result::SUCCESS) { + Msg("! [Denoise] NRD RecreateVK failed (%u)", (u32)result); + return false; + } + + m_denoiserId = denoiserDesc.identifier; + m_method = method; + m_width = w; + m_height = h; + m_nrdFrameIndex = 0; + m_prevW = 0; + m_prevH = 0; + m_diagLogged = false; + + const float fps = Device.fTimeDelta > 1e-4f ? (1.f / Device.fTimeDelta) : 60.f; + if (method != 0) { + nrd::RelaxSettings settings{}; + settings.hitDistanceReconstructionMode = nrd::HitDistanceReconstructionMode::AREA_3X3; + settings.enableAntiFirefly = true; + settings.diffusePrepassBlurRadius = 50.f; + settings.specularPrepassBlurRadius = 80.f; + settings.atrousIterationNum = 5; + settings.diffuseMaxAccumulatedFrameNum = nrd::GetMaxAccumulatedFrameNum(1.5f, fps); + settings.specularMaxAccumulatedFrameNum = nrd::GetMaxAccumulatedFrameNum(1.75f, fps); + settings.diffuseMaxFastAccumulatedFrameNum = 6; + settings.specularMaxFastAccumulatedFrameNum = 7; + m_nrd.SetDenoiserSettings(m_denoiserId, &settings); + } else { + nrd::ReblurSettings settings{}; + settings.hitDistanceReconstructionMode = nrd::HitDistanceReconstructionMode::AREA_3X3; + settings.enableAntiFirefly = true; + settings.diffusePrepassBlurRadius = 30.f; + settings.specularPrepassBlurRadius = 50.f; + settings.maxBlurRadius = 30.f; + settings.minBlurRadius = 1.f; + settings.maxAccumulatedFrameNum = nrd::GetMaxAccumulatedFrameNum(1.5f, fps); + settings.maxFastAccumulatedFrameNum = 6; + settings.maxStabilizedFrameNum = settings.maxAccumulatedFrameNum; + settings.minHitDistanceWeight = 0.1f; + settings.fireflySuppressorMinRelativeScale = 2.0f; + settings.lobeAngleFraction = 0.15f; + settings.roughnessFraction = 0.15f; + settings.planeDistanceSensitivity = 0.02f; + settings.antilagSettings.luminanceSigmaScale = 2.0f; + settings.antilagSettings.luminanceSensitivity = 3.0f; + settings.fastHistoryClampingSigmaScale = 2.0f; + m_nrd.SetDenoiserSettings(m_denoiserId, &settings); + } + + if (!m_logged) { + const nrd::LibraryDesc* lib = nrd::GetLibraryDesc(); + Msg("* [Denoise] NRD backend initialized v%u.%u.%u method=%s pool=%.1fMB", + lib ? lib->versionMajor : NRD_VERSION_MAJOR, + lib ? lib->versionMinor : NRD_VERSION_MINOR, + lib ? lib->versionBuild : NRD_VERSION_BUILD, + method != 0 ? "RELAX" : "REBLUR", + m_nrd.GetTotalMemoryUsageInMb()); + m_logged = true; + } + return true; + } + + nrd::Resource MakeVkResource(nvrhi::ITexture* tex) + { + nrd::Resource resource{}; + const auto native = tex->getNativeObject(nvrhi::ObjectTypes::VK_Image); + resource.vk.image = (VKNonDispatchableHandle)native.integer; + resource.vk.format = (VKEnum)nvrhi::vulkan::convertFormat(tex->getDesc().format); + resource.state = { nri::AccessBits::SHADER_RESOURCE_STORAGE, nri::Layout::SHADER_RESOURCE_STORAGE, nri::StageBits::COMPUTE_SHADER }; + resource.userArg = tex; + if (!resource.vk.image || !resource.vk.format) + Fail("MakeVkResource null image/format"); + return resource; + } + +public: + bool Init(nvrhi::IDevice* device) override + { + m_device = device; + if (!m_device || !GEnv.Backend || GEnv.Backend->GetAPI() != IRenderBackend::API::Vulkan) { + Msg("! [Denoise] NRD requires Vulkan backend"); + return false; + } + + const auto size = GEnv.Backend->GetBackBufferSize(); + u32 w = std::max(1u, size.first); + u32 h = std::max(1u, size.second); + if (!EnsurePipelines()) { + Msg("! [Denoise] NRD pack/composite shaders failed to load"); + return false; + } + if (!EnsureResources(w, h)) + return false; + if (!RecreateIntegration(w, h, ps_r_nrd_method)) + return false; + + m_ready = true; + return true; + } + + void Shutdown() override + { + m_nrd.Destroy(); + m_inDiff = nullptr; + m_inSpec = nullptr; + m_inNormalRough = nullptr; + m_inViewZ = nullptr; + m_inMv = nullptr; + m_outDiff = nullptr; + m_outSpec = nullptr; + m_sceneCopy = nullptr; + m_packPipeline = nullptr; + m_packLayout = nullptr; + m_compositePipeline = nullptr; + m_compositeLayout = nullptr; + m_cb = nullptr; + m_ready = false; + m_logged = false; + m_diagLogged = false; + m_nrdFrameIndex = 0; + m_pipeVersion = 0; + m_width = 0; + m_height = 0; + m_method = -1; + m_prevW = 0; + m_prevH = 0; + } + + bool IsAvailable() const override { return false; } + DenoiseBackendType GetType() const override { return DenoiseBackendType::NRD; } + + void Resize(u32 width, u32 height) override + { + if (!m_ready || !width || !height) + return; + if (width == m_width && height == m_height && m_method == ps_r_nrd_method) + return; + if (!EnsureResources(width, height)) + return; + RecreateIntegration(width, height, ps_r_nrd_method); + } + + bool Evaluate(nvrhi::ICommandList* cmd, const DenoiseInputs& inputs) override + { + (void)cmd; + (void)inputs; + static bool s_logged = false; + if (!s_logged) { + s_logged = true; + Msg("! [Denoise] NRD DenoiseVK disabled (causes Device Removed); ReSTIR composite used instead"); + } + return false; + } + +}; + +} + +IDenoiseBackend* CreateNRDDenoiseBackend() +{ + return new NRDDenoiseBackend(); +} + +} + +#else + +namespace xray::render::fg { + +namespace { + +class NRDStubBackend final : public IDenoiseBackend +{ +public: + bool Init(nvrhi::IDevice*) override + { + Msg("* [Denoise] NRD not compiled in (XRAY_USE_NRD)"); + return false; + } + void Shutdown() override {} + bool IsAvailable() const override { return false; } + DenoiseBackendType GetType() const override { return DenoiseBackendType::NRD; } + void Resize(u32, u32) override {} + bool Evaluate(nvrhi::ICommandList*, const DenoiseInputs&) override { return false; } +}; + +} + +IDenoiseBackend* CreateNRDDenoiseBackend() +{ + return new NRDStubBackend(); +} + +} + +#endif diff --git a/src/Layers/xrRender/FGDebugDraw.cpp b/src/Layers/xrRender/FGDebugDraw.cpp index 5a043e51521..651062a0dd9 100644 --- a/src/Layers/xrRender/FGDebugDraw.cpp +++ b/src/Layers/xrRender/FGDebugDraw.cpp @@ -217,8 +217,13 @@ void FGDebugDraw::DrawEllipse(const Fmatrix& T, u32 color) bool FGDebugDraw::EnsurePipelines(nvrhi::IDevice* device, nvrhi::IFramebuffer* framebuffer) { - if (m_pipelineLine && m_pipelineTri) + const auto& fbInfo = framebuffer->getFramebufferInfo(); + const nvrhi::Format fmt = fbInfo.colorFormats.empty() ? nvrhi::Format::UNKNOWN : fbInfo.colorFormats[0]; + if (m_pipelineLine && m_pipelineTri && m_pipelineFormat == fmt) return true; + m_pipelineLine = nullptr; + m_pipelineTri = nullptr; + m_pipelineFormat = fmt; auto* shaderLoader = RImplementation.GetShaderLoader(); if (!shaderLoader) diff --git a/src/Layers/xrRender/FGDebugDraw.h b/src/Layers/xrRender/FGDebugDraw.h index d7f9b0497e1..7ea2dff4a5c 100644 --- a/src/Layers/xrRender/FGDebugDraw.h +++ b/src/Layers/xrRender/FGDebugDraw.h @@ -56,6 +56,7 @@ class FGDebugDraw size_t m_triCapacity = 0; nvrhi::GraphicsPipelineHandle m_pipelineLine; nvrhi::GraphicsPipelineHandle m_pipelineTri; + nvrhi::Format m_pipelineFormat = nvrhi::Format::UNKNOWN; }; extern FGDebugDraw g_debug_draw; diff --git a/src/Layers/xrRender/FGDetailManager.cpp b/src/Layers/xrRender/FGDetailManager.cpp index 88587804d4f..372f22bbcec 100644 --- a/src/Layers/xrRender/FGDetailManager.cpp +++ b/src/Layers/xrRender/FGDetailManager.cpp @@ -39,7 +39,7 @@ extern int ps_r__detail_gpu; extern float ps_current_detail_height; extern float ps_current_detail_density; -static int magic4x4[4][4] = {{0, 14, 3, 13}, {11, 5, 8, 6}, {12, 2, 15, 1}, {7, 9, 4, 10}}; +static constexpr int magic4x4[4][4] = {{0, 14, 3, 13}, {11, 5, 8, 6}, {12, 2, 15, 1}, {7, 9, 4, 10}}; static void bwdithermap(int levels, int magic[16][16]) { @@ -244,7 +244,7 @@ bool FGDetailManager::BakeHeightmap() const u32 total_slots = dtH.x_size() * dtH.z_size(); std::atomic slots_completed{0}; - auto worker = [&](u32 slot_start, u32 slot_end) + auto worker = [this, dtSlots, &pixels, &slots_completed, total_slots](u32 slot_start, u32 slot_end) { thread_local xrXRC thread_xrc; @@ -2019,10 +2019,7 @@ bool FGDetailManager::CreateInstanceGenPipeline(fg::RenderDevice* renderDevice) bool FGDetailManager::CreateGraphicsPipeline(fg::RenderDevice* renderDevice, const nvrhi::FramebufferInfo& fbInfo) { if (!renderDevice || !vertexShader || !pixelShader) - { - Msg("! [FGDetailManager] CreateGraphicsPipeline: invalid parameters"); return false; - } nvrhi::IDevice* device = renderDevice->GetNVRHIDevice(); @@ -2113,6 +2110,7 @@ bool FGDetailManager::CreateGraphicsPipeline(fg::RenderDevice* renderDevice, con Msg("! [FGDetailManager] Failed to create graphics pipeline"); return false; } + Msg("* [FGDetailManager] Created graphics pipeline"); if (decalVertexShader && decalPixelShader) { @@ -2174,7 +2172,7 @@ void FGDetailManager::DispatchCulling( u32 hiZMipLevels, xray::profiler::GPUProfiler* gpuProfiler) { - if (!cullComputeShader || !slotCullComputeShader || slot_count == 0) + if (!hiZPyramid || !cullComputeShader || !slotCullComputeShader || slot_count == 0) { return; } @@ -2320,6 +2318,8 @@ void FGDetailManager::DispatchCulling( .BufferUAV("g_visible_slot_counter", visibleSlotCounterBuffer); nvrhi::BindingSetHandle slotCullBindingSet = framegraph::GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), slotCullBindingLayout, device); + if (!slotCullBindingSet) + return; nvrhi::ComputeState state; state.pipeline = slotCullPipeline; @@ -2360,6 +2360,8 @@ void FGDetailManager::DispatchCulling( .BufferUAV("g_indirect_args_billboard", billboardDrawArgsBuffer); nvrhi::BindingSetHandle instanceCullBindingSet = framegraph::GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), computeBindingLayout, device); + if (!instanceCullBindingSet) + return; nvrhi::ComputeState state; state.pipeline = computePipeline; @@ -2463,7 +2465,7 @@ void FGDetailManager::BuildDetailModelGPUData() auto& d = cachedModelGPUData[i]; d.minScale = m->m_fMinScale; d.maxScale = m->m_fMaxScale; - d.flags = *reinterpret_cast(&m->m_Flags.flags); + std::memcpy(&d.flags, &m->m_Flags.flags, sizeof(d.flags)); if (m->number_vertices > 0) { @@ -2587,7 +2589,8 @@ void FGDetailManager::RegenerateAllInstances(nvrhi::ICommandList* cmdList, nvrhi cmdList->clearBufferUInt(instanceCounterBuffer, 0); cmdList->clearBufferUInt(perSlotLocalCountersBuffer, 0); - auto dispatchInstanceGen = [&](u32 mode, const char* passName) { + auto dispatchInstanceGen = [this, cmdList, device, gpuProfiler, renderDevice, numBlocks, numGroupsX, numGroupsY]( + u32 mode, const char* passName) { if (gpuProfiler) gpuProfiler->BeginPass(cmdList, passName); InstanceGenParams genParams; @@ -2613,7 +2616,8 @@ void FGDetailManager::RegenerateAllInstances(nvrhi::ICommandList* cmdList, nvrhi if (gpuProfiler) gpuProfiler->EndPass(cmdList, passName); }; - auto dispatchPrefixSum = [&](nvrhi::ComputePipelineHandle pipeline, u32 groups, const char* passName) { + auto dispatchPrefixSum = [this, cmdList, device, gpuProfiler, renderDevice]( + nvrhi::ComputePipelineHandle pipeline, u32 groups, const char* passName) { if (gpuProfiler) gpuProfiler->BeginPass(cmdList, passName); auto* prefixRefl = GEnv.Render->GetShaderLoader()->GetCachedReflection("detail_prefix_sum", ".cs:main_scan_blocks"); diff --git a/src/Layers/xrRender/FGRenderBase.cpp b/src/Layers/xrRender/FGRenderBase.cpp index 45c75f8aca3..24c8ae93a02 100644 --- a/src/Layers/xrRender/FGRenderBase.cpp +++ b/src/Layers/xrRender/FGRenderBase.cpp @@ -13,6 +13,7 @@ #include "xrEngine/IRenderBackend.h" #include "xrEngine/GameFont.h" #include "xrEngine/PerformanceAlert.hpp" +#include "Upscaling/StreamlineDLSS.h" #include @@ -184,8 +185,23 @@ void FGRenderBase::End() ZoneScopedN("FGRenderBase::BackendEnd"); if (GEnv.Backend) { + if (!GEnv.Backend->IsInFrame()) + return; GEnv.Backend->EndFrame(); - GEnv.Backend->Present(psDeviceFlags.test(rsVSync)); + const bool vsync = psDeviceFlags.test(rsVSync); + nvrhi::ITexture* fgInterp = nullptr; + nvrhi::ITexture* fgReal = nullptr; + if (Streamline_TakeFgPresent(fgInterp, fgReal) && + GEnv.Backend->PresentFrameGeneration(fgInterp, fgReal)) + { + Streamline_NotifyFgPresented(true); + const float renderFps = Device.GetStats().fFPS; + Device.SetPresentedFps(renderFps > 1.f ? renderFps * 2.f : 0.f); + return; + } + Streamline_NotifyFgPresented(false); + Device.SetPresentedFps(0.f); + GEnv.Backend->Present(vsync); } } diff --git a/src/Layers/xrRender/FrameGraph/BindingSetBuilder.cpp b/src/Layers/xrRender/FrameGraph/BindingSetBuilder.cpp index e106c81c476..86da49b8b88 100644 --- a/src/Layers/xrRender/FrameGraph/BindingSetBuilder.cpp +++ b/src/Layers/xrRender/FrameGraph/BindingSetBuilder.cpp @@ -16,7 +16,11 @@ static void DeduplicateBySlotAndClass(xr_vectorsrvs.size() + m_lists->uavs.size() + m_lists->cbs.size() + m_lists->samplerItems.size()); @@ -111,18 +116,51 @@ BindingSetBuilder::BindingSetBuilder( nvrhi::IDevice* device, const char*) : m_lists(&GetOrBuildReflectedLists(&vsReflection, &psReflection, device)) + , m_device(device) { m_desc.bindings.reserve(m_lists->srvs.size() + m_lists->uavs.size() + m_lists->cbs.size() + m_lists->samplerItems.size()); } +static void WarnMissingOnce(const char* kind, const char* name) +{ + if (!name || !name[0]) + return; + static xr_set s_warned; + xr_string key; + key = kind; + key += ":"; + key += name; + if (!s_warned.insert(key).second) + return; + Msg("! [BindingSetBuilder] %s '%s' not in reflection (optional bind skipped)", kind, name); +} + +bool BindingSetBuilder::HasSRV(const char* name) const +{ + if (!m_lists || !name) + return false; + for (const auto& r : m_lists->srvs) + if (NameMatches(r.name, name)) + return true; + return false; +} + +bool BindingSetBuilder::HasUAV(const char* name) const +{ + if (!m_lists || !name) + return false; + for (const auto& r : m_lists->uavs) + if (NameMatches(r.name, name)) + return true; + return false; +} + int BindingSetBuilder::FindSRVSlot(const char* name) const { for (const auto& r : m_lists->srvs) if (NameMatches(r.name, name)) return static_cast(r.slot); - Msg("! [BindingSetBuilder] SRV '%s' not found in reflection (have %u SRVs)", name, m_lists->srvs.size()); - for (const auto& r : m_lists->srvs) - Msg(" SRV: '%s' @ t%u", r.name ? r.name : "(null)", r.slot); + WarnMissingOnce("SRV", name); return -1; } @@ -130,9 +168,7 @@ int BindingSetBuilder::FindUAVSlot(const char* name) const { for (const auto& r : m_lists->uavs) if (NameMatches(r.name, name)) return static_cast(r.slot); - Msg("! [BindingSetBuilder] UAV '%s' not found in reflection (have %u UAVs)", name, m_lists->uavs.size()); - for (const auto& r : m_lists->uavs) - Msg(" UAV: '%s' @ u%u", r.name ? r.name : "(null)", r.slot); + WarnMissingOnce("UAV", name); return -1; } @@ -140,15 +176,15 @@ int BindingSetBuilder::FindCBSlot(const char* name) const { for (const auto& r : m_lists->cbs) if (NameMatches(r.name, name)) return static_cast(r.slot); - Msg("! [BindingSetBuilder] CB '%s' not found in reflection (have %u CBs)", name, m_lists->cbs.size()); - for (const auto& r : m_lists->cbs) - Msg(" CB: '%s' @ b%u", r.name ? r.name : "(null)", r.slot); + WarnMissingOnce("CB", name); return -1; } BindingSetBuilder& BindingSetBuilder::Texture(const char* name, nvrhi::ITexture* texture, nvrhi::Format format, nvrhi::TextureSubresourceSet subresources) { + if (!texture) + return *this; int slot = FindSRVSlot(name); if (slot >= 0) m_desc.bindings.push_back(nvrhi::BindingSetItem::Texture_SRV(slot, texture, format, subresources)); @@ -158,6 +194,8 @@ BindingSetBuilder& BindingSetBuilder::Texture(const char* name, nvrhi::ITexture* BindingSetBuilder& BindingSetBuilder::TextureUAV(const char* name, nvrhi::ITexture* texture, nvrhi::Format format, nvrhi::TextureSubresourceSet subresources) { + if (!texture) + return *this; int slot = FindUAVSlot(name); if (slot >= 0) m_desc.bindings.push_back(nvrhi::BindingSetItem::Texture_UAV(slot, texture, format, subresources)); @@ -166,6 +204,8 @@ BindingSetBuilder& BindingSetBuilder::TextureUAV(const char* name, nvrhi::ITextu BindingSetBuilder& BindingSetBuilder::BufferSRV(const char* name, nvrhi::IBuffer* buffer) { + if (!buffer) + return *this; int slot = FindSRVSlot(name); if (slot >= 0) { for (const auto& r : m_lists->srvs) { @@ -183,6 +223,8 @@ BindingSetBuilder& BindingSetBuilder::BufferSRV(const char* name, nvrhi::IBuffer BindingSetBuilder& BindingSetBuilder::BufferUAV(const char* name, nvrhi::IBuffer* buffer) { + if (!buffer) + return *this; int slot = FindUAVSlot(name); if (slot >= 0) { for (const auto& r : m_lists->uavs) { @@ -280,6 +322,32 @@ nvrhi::BindingSetDesc BindingSetBuilder::Build() { AddSamplers(); + auto& cache = GetPassResourceCache(); + for (const auto& r : m_lists->srvs) { + bool found = false; + for (const auto& b : m_desc.bindings) { + if (GetBindingSetRegisterClass(b.type) == 0 && b.slot == r.slot) { + found = true; + break; + } + } + if (found) + continue; + WarnMissingOnce("unbound SRV", r.name); + if (!m_device) + continue; + if (r.layoutType == nvrhi::ResourceType::Texture_SRV) { + if (auto* tex = cache.GetDummyContactHistory(m_device)) + m_desc.bindings.push_back(nvrhi::BindingSetItem::Texture_SRV(r.slot, tex)); + } else if (r.layoutType == nvrhi::ResourceType::RawBuffer_SRV) { + if (auto* buf = cache.GetDummySRVBuffer(m_device)) + m_desc.bindings.push_back(nvrhi::BindingSetItem::RawBuffer_SRV(r.slot, buf)); + } else if (r.layoutType != nvrhi::ResourceType::RayTracingAccelStruct) { + if (auto* buf = cache.GetDummySRVBuffer(m_device)) + m_desc.bindings.push_back(nvrhi::BindingSetItem::StructuredBuffer_SRV(r.slot, buf)); + } + } + std::sort(m_desc.bindings.begin(), m_desc.bindings.end(), [](const nvrhi::BindingSetItem& a, const nvrhi::BindingSetItem& b) { int classA = GetBindingSetRegisterClass(a.type); diff --git a/src/Layers/xrRender/FrameGraph/BindingSetBuilder.h b/src/Layers/xrRender/FrameGraph/BindingSetBuilder.h index 327266defc4..d3e21df04de 100644 --- a/src/Layers/xrRender/FrameGraph/BindingSetBuilder.h +++ b/src/Layers/xrRender/FrameGraph/BindingSetBuilder.h @@ -31,6 +31,9 @@ class BindingSetBuilder { static void InvalidateReflectionCache(); + bool HasSRV(const char* name) const; + bool HasUAV(const char* name) const; + BindingSetBuilder& Texture(const char* name, nvrhi::ITexture* texture, nvrhi::Format format = nvrhi::Format::UNKNOWN, nvrhi::TextureSubresourceSet subresources = nvrhi::AllSubresources); @@ -58,6 +61,7 @@ class BindingSetBuilder { private: const ReflectedLists* m_lists; + nvrhi::IDevice* m_device = nullptr; nvrhi::BindingSetDesc m_desc; diff --git a/src/Layers/xrRender/FrameGraph/FGResourcePool.cpp b/src/Layers/xrRender/FrameGraph/FGResourcePool.cpp index eb24678fa5d..77352f7a52a 100644 --- a/src/Layers/xrRender/FrameGraph/FGResourcePool.cpp +++ b/src/Layers/xrRender/FrameGraph/FGResourcePool.cpp @@ -152,7 +152,10 @@ bool FGResourcePool::AreTexturesCompatible( a.height == b.height && a.format == b.format && a.mipLevels == b.mipLevels && - a.arraySize == b.arraySize; + a.arraySize == b.arraySize && + a.isRenderTarget == b.isRenderTarget && + a.isDepthStencil == b.isDepthStencil && + a.isUAV == b.isUAV; } // ═══════════════════════════════════════════════════ diff --git a/src/Layers/xrRender/FrameGraph/FrameGraph.cpp b/src/Layers/xrRender/FrameGraph/FrameGraph.cpp index cdfc5449460..6a209a2334e 100644 --- a/src/Layers/xrRender/FrameGraph/FrameGraph.cpp +++ b/src/Layers/xrRender/FrameGraph/FrameGraph.cpp @@ -1171,13 +1171,22 @@ void FrameGraph::OptimizeMemoryAliasing() { compatible = false; } - // Must have same format for textures - if (current->desc.type != ResourceDesc::Type::Buffer && - current->desc.format != candidate->desc.format) { - compatible = false; + if (current->desc.type != ResourceDesc::Type::Buffer) { + if (current->desc.format != candidate->desc.format || + current->desc.width != candidate->desc.width || + current->desc.height != candidate->desc.height || + current->desc.depth != candidate->desc.depth || + current->desc.arraySize != candidate->desc.arraySize || + current->desc.mipLevels != candidate->desc.mipLevels || + current->desc.sampleCount != candidate->desc.sampleCount || + current->desc.isRenderTarget != candidate->desc.isRenderTarget || + current->desc.isDepthStencil != candidate->desc.isDepthStencil || + current->desc.isUAV != candidate->desc.isUAV || + current->desc.allowUAV != candidate->desc.allowUAV) { + compatible = false; + } } - // Candidate must be large enough if (candidate->memorySize < current->memorySize) { compatible = false; } diff --git a/src/Layers/xrRender/FrameGraph/IPass.h b/src/Layers/xrRender/FrameGraph/IPass.h index 5e675f9e7f0..33c5782c798 100644 --- a/src/Layers/xrRender/FrameGraph/IPass.h +++ b/src/Layers/xrRender/FrameGraph/IPass.h @@ -24,6 +24,7 @@ struct DefaultOutputLayout { VirtualResourceHandle albedo; // RT0: Lit HDR color (RGBA16_FLOAT) VirtualResourceHandle normal; // RT1: World normal.xyz + Roughness.a (RGBA16_FLOAT) VirtualResourceHandle baseColor; // RT2: Unlit diffuse albedo.rgb + Metallic.a (RGBA8_UNORM) + VirtualResourceHandle worldPos; // RT3: World position.xyz + surface mark.w (RGBA32_FLOAT) VirtualResourceHandle depth; // Depth/Stencil (D32) VirtualResourceHandle distortion; // Distortion buffer (RG = UV offset, A = intensity) }; diff --git a/src/Layers/xrRender/FrameGraph/PassResourceCache.cpp b/src/Layers/xrRender/FrameGraph/PassResourceCache.cpp index f3b311a3997..3163a065316 100644 --- a/src/Layers/xrRender/FrameGraph/PassResourceCache.cpp +++ b/src/Layers/xrRender/FrameGraph/PassResourceCache.cpp @@ -157,9 +157,120 @@ nvrhi::ITexture* PassResourceCache::GetDummyShadowMap2D(nvrhi::IDevice* device) return m_dummyShadowMap2D; } +nvrhi::ITexture* PassResourceCache::GetDummyContactDepth(nvrhi::IDevice* device) { + if (!m_dummyContactDepth && device) { + nvrhi::TextureDesc desc; + desc.width = 1; + desc.height = 1; + desc.format = nvrhi::Format::R32_FLOAT; + desc.debugName = "DummyContactDepth"; + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + desc.dimension = nvrhi::TextureDimension::Texture2D; + desc.isShaderResource = true; + m_dummyContactDepth = device->createTexture(desc); + if (m_dummyContactDepth) { + nvrhi::CommandListHandle cmd = device->createCommandList(); + cmd->open(); + float farD = 1.0f; + cmd->writeTexture(m_dummyContactDepth, 0, 0, &farD, sizeof(farD)); + cmd->close(); + device->executeCommandList(cmd); + } + } + return m_dummyContactDepth; +} + +nvrhi::ITexture* PassResourceCache::GetDummyContactHistory(nvrhi::IDevice* device) { + if (!m_dummyContactHistory && device) { + nvrhi::TextureDesc desc; + desc.width = 1; + desc.height = 1; + desc.format = nvrhi::Format::RGBA16_FLOAT; + desc.debugName = "DummyContactHistory"; + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + desc.dimension = nvrhi::TextureDimension::Texture2D; + desc.isShaderResource = true; + m_dummyContactHistory = device->createTexture(desc); + if (m_dummyContactHistory) { + nvrhi::CommandListHandle cmd = device->createCommandList(); + cmd->open(); + float white[4] = {1.f, 0.f, 0.f, 0.f}; + cmd->writeTexture(m_dummyContactHistory, 0, 0, white, sizeof(white)); + cmd->close(); + device->executeCommandList(cmd); + } + } + return m_dummyContactHistory; +} + +nvrhi::ITexture* PassResourceCache::GetDummyCubeMap(nvrhi::IDevice* device) { + if (!m_dummyCubeMap) { + nvrhi::TextureDesc desc; + desc.width = 1; + desc.height = 1; + desc.format = nvrhi::Format::RGBA8_UNORM; + desc.dimension = nvrhi::TextureDimension::TextureCube; + desc.arraySize = 6; + desc.debugName = "DummyCubeMap"; + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + desc.isShaderResource = true; + m_dummyCubeMap = device->createTexture(desc); + } + return m_dummyCubeMap; +} + +nvrhi::IBuffer* PassResourceCache::GetDummySRVBuffer(nvrhi::IDevice* device) { + if (!m_dummySRVBuffer && device) { + nvrhi::BufferDesc desc; + desc.byteSize = 256; + desc.structStride = 16; + desc.canHaveRawViews = true; + desc.debugName = "DummySRVBuffer"; + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + m_dummySRVBuffer = device->createBuffer(desc); + } + return m_dummySRVBuffer; +} + +nvrhi::ITexture* PassResourceCache::GetDummyUAVTexture(nvrhi::IDevice* device) { + if (!m_dummyUAVTexture && device) { + nvrhi::TextureDesc desc; + desc.width = 1; + desc.height = 1; + desc.format = nvrhi::Format::RGBA16_FLOAT; + desc.debugName = "DummyUAVTexture"; + desc.isUAV = true; + desc.initialState = nvrhi::ResourceStates::UnorderedAccess; + desc.keepInitialState = true; + desc.dimension = nvrhi::TextureDimension::Texture2D; + m_dummyUAVTexture = device->createTexture(desc); + } + return m_dummyUAVTexture; +} + +nvrhi::IBuffer* PassResourceCache::GetDummyUAVBuffer(nvrhi::IDevice* device) { + if (!m_dummyUAVBuffer && device) { + nvrhi::BufferDesc desc; + desc.byteSize = 256; + desc.structStride = 16; + desc.canHaveRawViews = true; + desc.canHaveUAVs = true; + desc.debugName = "DummyUAVBuffer"; + desc.initialState = nvrhi::ResourceStates::UnorderedAccess; + desc.keepInitialState = true; + m_dummyUAVBuffer = device->createBuffer(desc); + } + return m_dummyUAVBuffer; +} + nvrhi::ISampler* PassResourceCache::GetSamplerByName(const char* smpName, nvrhi::IDevice* device) { - if (strstr(smpName, "smp_nofilter") || strstr(smpName, "smp_smap") || strstr(smpName, "smp_jitter")) + if (strstr(smpName, "smp_nofilter") || strstr(smpName, "smp_smap") || + strstr(smpName, "smp_jitter") || strstr(smpName, "smp_point")) return GetPointClampSampler(device); if (strstr(smpName, "smp_rtlinear")) return GetLinearClampSampler(device); @@ -180,6 +291,12 @@ nvrhi::BindingLayoutHandle PassResourceCache::GetOrCreateBindingLayout( nvrhi::IDevice* device) { u64 key = HashString(passName); + key = HashCombine(key, u64(desc.visibility)); + for (const auto& b : desc.bindings) { + key = HashCombine(key, u64(b.slot)); + key = HashCombine(key, u64(b.type)); + key = HashCombine(key, u64(b.size)); + } auto it = m_bindingLayouts.find(key); if (it != m_bindingLayouts.end()) { @@ -187,7 +304,6 @@ nvrhi::BindingLayoutHandle PassResourceCache::GetOrCreateBindingLayout( return it->second; } - // Create new layout m_stats.layoutMisses++; nvrhi::BindingLayoutHandle layout = device->createBindingLayout(desc); if (layout) { @@ -308,8 +424,33 @@ nvrhi::FramebufferHandle PassResourceCache::GetOrCreateFramebuffer( const nvrhi::FramebufferDesc& desc, nvrhi::IDevice* device) { - // Key combines pass name with all render target pointers - // This ensures we reuse framebuffers when the same RTs are bound + u32 attW = 0, attH = 0; + auto checkDim = [&](nvrhi::ITexture* tex) -> bool { + if (!tex) + return true; + const auto& d = tex->getDesc(); + if (!attW) { + attW = d.width; + attH = d.height; + return true; + } + return d.width == attW && d.height == attH; + }; + if (desc.depthAttachment.texture && !checkDim(desc.depthAttachment.texture)) { + const auto& d = desc.depthAttachment.texture->getDesc(); + Msg("! [FG] %s framebuffer skipped: depth %ux%u vs color %ux%u", + passName, d.width, d.height, attW, attH); + return nullptr; + } + for (const auto& attachment : desc.colorAttachments) { + if (attachment.texture && !checkDim(attachment.texture)) { + const auto& d = attachment.texture->getDesc(); + Msg("! [FG] %s framebuffer skipped: color %ux%u vs %ux%u", + passName, d.width, d.height, attW, attH); + return nullptr; + } + } + u64 key = HashString(passName); for (const auto& attachment : desc.colorAttachments) { @@ -448,12 +589,70 @@ static u64 HashBindingSetDesc(const nvrhi::BindingSetDesc& desc, nvrhi::IBinding return hash; } +static int LayoutRegisterClass(nvrhi::ResourceType type) +{ + switch (type) { + case nvrhi::ResourceType::Texture_SRV: + case nvrhi::ResourceType::TypedBuffer_SRV: + case nvrhi::ResourceType::StructuredBuffer_SRV: + case nvrhi::ResourceType::RawBuffer_SRV: + case nvrhi::ResourceType::RayTracingAccelStruct: + return 0; + case nvrhi::ResourceType::Texture_UAV: + case nvrhi::ResourceType::TypedBuffer_UAV: + case nvrhi::ResourceType::StructuredBuffer_UAV: + case nvrhi::ResourceType::RawBuffer_UAV: + return 1; + default: + return -1; + } +} + nvrhi::BindingSetHandle PassResourceCache::GetOrCreateBindingSet( const nvrhi::BindingSetDesc& desc, nvrhi::IBindingLayout* layout, nvrhi::IDevice* device) { - u64 key = HashBindingSetDesc(desc, layout); + nvrhi::BindingSetDesc padded = desc; + if (layout && layout->getDesc()) { + for (const auto& item : layout->getDesc()->bindings) { + const int cls = LayoutRegisterClass(item.type); + bool found = false; + for (const auto& b : padded.bindings) { + if (b.slot == item.slot && LayoutRegisterClass(b.type) == cls) { + found = true; + break; + } + } + if (found) + continue; + if (cls == 0) { + if (item.type == nvrhi::ResourceType::Texture_SRV) { + if (auto* tex = GetDummyContactHistory(device)) + padded.bindings.push_back(nvrhi::BindingSetItem::Texture_SRV(item.slot, tex)); + } else if (item.type == nvrhi::ResourceType::RawBuffer_SRV) { + if (auto* buf = GetDummySRVBuffer(device)) + padded.bindings.push_back(nvrhi::BindingSetItem::RawBuffer_SRV(item.slot, buf)); + } else if (item.type != nvrhi::ResourceType::RayTracingAccelStruct) { + if (auto* buf = GetDummySRVBuffer(device)) + padded.bindings.push_back(nvrhi::BindingSetItem::StructuredBuffer_SRV(item.slot, buf)); + } + } else if (cls == 1) { + if (item.type == nvrhi::ResourceType::Texture_UAV) { + if (auto* tex = GetDummyUAVTexture(device)) + padded.bindings.push_back(nvrhi::BindingSetItem::Texture_UAV(item.slot, tex)); + } else if (item.type == nvrhi::ResourceType::RawBuffer_UAV) { + if (auto* buf = GetDummyUAVBuffer(device)) + padded.bindings.push_back(nvrhi::BindingSetItem::RawBuffer_UAV(item.slot, buf)); + } else { + if (auto* buf = GetDummyUAVBuffer(device)) + padded.bindings.push_back(nvrhi::BindingSetItem::StructuredBuffer_UAV(item.slot, buf)); + } + } + } + } + + u64 key = HashBindingSetDesc(padded, layout); auto it = m_bindingSets.find(key); if (it != m_bindingSets.end()) { m_stats.bindingSetHits++; @@ -461,7 +660,7 @@ nvrhi::BindingSetHandle PassResourceCache::GetOrCreateBindingSet( } m_stats.bindingSetMisses++; - nvrhi::BindingSetHandle bindingSet = device->createBindingSet(desc, layout); + nvrhi::BindingSetHandle bindingSet = device->createBindingSet(padded, layout); if (bindingSet) m_bindingSets[key] = bindingSet; return bindingSet; @@ -488,6 +687,12 @@ void PassResourceCache::Clear() { m_commonShadowCmp = nullptr; m_dummyShadowMap = nullptr; m_dummyShadowMap2D = nullptr; + m_dummyContactDepth = nullptr; + m_dummyContactHistory = nullptr; + m_dummyCubeMap = nullptr; + m_dummySRVBuffer = nullptr; + m_dummyUAVTexture = nullptr; + m_dummyUAVBuffer = nullptr; Msg("* [PassResourceCache] Cleared all caches"); } diff --git a/src/Layers/xrRender/FrameGraph/PassResourceCache.h b/src/Layers/xrRender/FrameGraph/PassResourceCache.h index cb2d72e4248..aba94699a1f 100644 --- a/src/Layers/xrRender/FrameGraph/PassResourceCache.h +++ b/src/Layers/xrRender/FrameGraph/PassResourceCache.h @@ -51,6 +51,12 @@ class PassResourceCache { nvrhi::ISampler* GetShadowCmpSampler(nvrhi::IDevice* device); nvrhi::ITexture* GetDummyShadowMap(nvrhi::IDevice* device); nvrhi::ITexture* GetDummyShadowMap2D(nvrhi::IDevice* device); + nvrhi::ITexture* GetDummyContactDepth(nvrhi::IDevice* device); + nvrhi::ITexture* GetDummyContactHistory(nvrhi::IDevice* device); + nvrhi::ITexture* GetDummyCubeMap(nvrhi::IDevice* device); + nvrhi::IBuffer* GetDummySRVBuffer(nvrhi::IDevice* device); + nvrhi::ITexture* GetDummyUAVTexture(nvrhi::IDevice* device); + nvrhi::IBuffer* GetDummyUAVBuffer(nvrhi::IDevice* device); nvrhi::ISampler* GetSamplerByName(const char* smpName, nvrhi::IDevice* device); @@ -197,6 +203,12 @@ class PassResourceCache { nvrhi::SamplerHandle m_commonShadowCmp; nvrhi::TextureHandle m_dummyShadowMap; nvrhi::TextureHandle m_dummyShadowMap2D; + nvrhi::TextureHandle m_dummyContactDepth; + nvrhi::TextureHandle m_dummyContactHistory; + nvrhi::TextureHandle m_dummyCubeMap; + nvrhi::BufferHandle m_dummySRVBuffer; + nvrhi::TextureHandle m_dummyUAVTexture; + nvrhi::BufferHandle m_dummyUAVBuffer; Stats m_stats; diff --git a/src/Layers/xrRender/FrameGraph/ShaderCache.cpp b/src/Layers/xrRender/FrameGraph/ShaderCache.cpp index f6a532d68ea..ac1b06a2123 100644 --- a/src/Layers/xrRender/FrameGraph/ShaderCache.cpp +++ b/src/Layers/xrRender/FrameGraph/ShaderCache.cpp @@ -23,13 +23,21 @@ void ShaderCache::GetCachePath( u32 sourceHash, string_path& outPath) { + string_path safeName; + xr_strcpy(safeName, shaderName); + for (char* p = safeName; *p; ++p) + { + if (*p == '/' || *p == '\\') + *p = '_'; + } + string_path shaderDir; if (m_backendSubdir.empty()) xr_sprintf(shaderDir, "shaders_cache_fg%s%s%s", - DELIMITER, shaderName, extension); + DELIMITER, safeName, extension); else xr_sprintf(shaderDir, "shaders_cache_fg%s%s%s%s%s", - DELIMITER, m_backendSubdir.c_str(), DELIMITER, shaderName, extension); + DELIMITER, m_backendSubdir.c_str(), DELIMITER, safeName, extension); xr_sprintf(outPath, "%s%s%08X", shaderDir, DELIMITER, sourceHash); diff --git a/src/Layers/xrRender/FrameGraph/ShaderCache.h b/src/Layers/xrRender/FrameGraph/ShaderCache.h index e3e3284a808..796b36bedf9 100644 --- a/src/Layers/xrRender/FrameGraph/ShaderCache.h +++ b/src/Layers/xrRender/FrameGraph/ShaderCache.h @@ -131,7 +131,7 @@ class ShaderCache ExtractedReflection& outReflection ); - static constexpr u32 CACHE_VERSION = 6; + static constexpr u32 CACHE_VERSION = 8; Stats m_stats; bool m_cacheEnabled; xr_string m_backendSubdir; diff --git a/src/Layers/xrRender/FrameGraph/ShaderLoader.cpp b/src/Layers/xrRender/FrameGraph/ShaderLoader.cpp index 69f06db0945..91d26d6e9c4 100644 --- a/src/Layers/xrRender/FrameGraph/ShaderLoader.cpp +++ b/src/Layers/xrRender/FrameGraph/ShaderLoader.cpp @@ -4,6 +4,9 @@ #include "xrCore/FileCRC32.h" #include "Layers/xrRender/r_FrameGraphRenderer.h" #include "Layers/xrRender/xrRender_console.h" +#include +#include +#include namespace xray::render::framegraph { using namespace fg; @@ -46,8 +49,9 @@ void ResolveShaderSourceRelativePath( { pcstr pchr = strchr(name, '('); ptrdiff_t size = pchr ? pchr - name : xr_strlen(name); - strncpy(shName, name, size); - shName[size] = 0; + const size_t n = std::min(static_cast(size), sizeof(shName) - 1); + std::memcpy(shName, name, n); + shName[n] = 0; } // Only remove skinning suffix (_0, _1, _2, _3, _4) for vertex shaders @@ -59,25 +63,119 @@ void ResolveShaderSourceRelativePath( size_t len = xr_strlen(shName); if (len > 2 && shName[len - 2] == '_' && shName[len - 1] >= '0' && shName[len - 1] <= '4') { - // Check if this looks like a skinning suffix by checking if the base name exists string_path testName; xr_strcpy(testName, shName); - testName[len - 2] = 0; // Remove the "_X" suffix + testName[len - 2] = 0; string_path testFilename; strconcat(sizeof(testFilename), testFilename, "r5" DELIMITER, testName, extension); - // Only strip if the base file exists if (FS.exist("$game_shaders$", testFilename)) { - xr_strcpy(shName, testName); // Use the base name + xr_strcpy(shName, testName); } } } + { + char norm[256]; + size_t n = 0; + for (const char* p = shName; *p && n + 1 < sizeof(norm); ++p) + { + char c = *p; + if (c == '\\') + c = '/'; + norm[n++] = (char)tolower((unsigned char)c); + } + norm[n] = 0; + if (0 == xr_strcmp(norm, "effects/water")) + xr_strcpy(shName, "water"); + else if (0 == xr_strcmp(norm, "effects/waterd")) + xr_strcpy(shName, "waterd"); + } + + for (char* p = shName; *p; ++p) + { + if (*p == '/' || *p == '\\') + *p = '\\'; + } + strconcat(outRelativePathSize, outRelativePath, "r5" DELIMITER, shName, extension); } +u32 MixSourceHash(u32 a, u32 b) +{ + return a ^ ((b << 16) | (b >> 16)); +} + +void HashIncludesRecursive(const char* source, size_t len, u32& hash, xr_vector& visited) +{ + const char* p = source; + const char* end = source + len; + while (p < end) + { + const char* hashMark = static_cast(memchr(p, '#', size_t(end - p))); + if (!hashMark) + break; + p = hashMark + 1; + while (p < end && (*p == ' ' || *p == '\t')) + ++p; + if (p + 7 > end || strncmp(p, "include", 7) != 0) + continue; + p += 7; + while (p < end && (*p == ' ' || *p == '\t')) + ++p; + if (p >= end || (*p != '"' && *p != '<')) + continue; + const char delim = (*p == '"') ? '"' : '>'; + ++p; + const char* start = p; + while (p < end && *p != delim && *p != '\n' && *p != '\r') + ++p; + if (p >= end || *p != delim) + continue; + xr_string incName(start, p - start); + if (incName.empty()) + continue; + for (char& c : incName) + { + if (c == '/') + c = '\\'; + } + bool seen = false; + for (const auto& v : visited) + { + if (v == incName) + { + seen = true; + break; + } + } + if (seen) + continue; + visited.push_back(incName); + + string_path rel; + strconcat(sizeof(rel), rel, "r5" DELIMITER, incName.c_str()); + IReader* inc = FS.r_open("$game_shaders$", rel); + if (!inc) + continue; + const char* incSrc = static_cast(inc->pointer()); + const size_t incLen = inc->length(); + hash = MixSourceHash(hash, crc32(incSrc, incLen)); + HashIncludesRecursive(incSrc, incLen, hash, visited); + inc->close(); + } +} + +u32 HashShaderSource(const char* source, size_t len) +{ + u32 hash = ShaderCache::ComputeHash(source, len); + xr_vector visited; + HashIncludesRecursive(source, len, hash, visited); + return hash; +} + } // namespace ShaderLoader::ShaderLoader(xray::render::SlangCompiler* slangCompiler) @@ -156,7 +254,7 @@ bool ShaderLoader::CompileShader( return false; // Compute hash of shader source - u32 sourceHash = ShaderCache::ComputeHash( + u32 sourceHash = HashShaderSource( (const char*)fs->pointer(), fs->length() ); @@ -225,11 +323,14 @@ ShaderLoader::ShaderResult ShaderLoader::LoadVertexShader( // Open shader source file IReader* fs = OpenShaderFile(name, ".vs"); if (!fs) - return result; // Empty result + { + Msg("! [ShaderLoader] Failed to open %s.vs", name); + return result; + } WatchShaderFile(cacheKey, name, ".vs", entryPoint, xray::render::SlangCompiler::Stage::Vertex); // Compute hash of shader source - u32 sourceHash = ShaderCache::ComputeHash( + u32 sourceHash = HashShaderSource( (const char*)fs->pointer(), fs->length() ); @@ -360,11 +461,14 @@ ShaderLoader::ShaderResult ShaderLoader::LoadPixelShader( // Open shader source file IReader* fs = OpenShaderFile(name, ".ps"); if (!fs) - return result; // Empty result + { + Msg("! [ShaderLoader] Failed to open %s.ps", name); + return result; + } WatchShaderFile(cacheKey, name, ".ps", entryPoint, xray::render::SlangCompiler::Stage::Pixel); // Compute hash of shader source - u32 sourceHash = ShaderCache::ComputeHash( + u32 sourceHash = HashShaderSource( (const char*)fs->pointer(), fs->length() ); @@ -506,7 +610,7 @@ ShaderLoader::ShaderResult ShaderLoader::LoadComputeShader( WatchShaderFile(cacheKey, name, ".cs", entryPoint, xray::render::SlangCompiler::Stage::Compute); // Compute hash of shader source - u32 sourceHash = ShaderCache::ComputeHash( + u32 sourceHash = HashShaderSource( (const char*)fs->pointer(), fs->length() ); @@ -649,7 +753,7 @@ ShaderLoader::ShaderResult ShaderLoader::LoadAmplificationShader( WatchShaderFile(cacheKey, name, ".as", entryPoint, xray::render::SlangCompiler::Stage::Amplification); // Compute hash of shader source - u32 sourceHash = ShaderCache::ComputeHash( + u32 sourceHash = HashShaderSource( (const char*)fs->pointer(), fs->length() ); @@ -759,7 +863,7 @@ ShaderLoader::ShaderResult ShaderLoader::LoadMeshShader( WatchShaderFile(cacheKey, name, ".ms", entryPoint, xray::render::SlangCompiler::Stage::Mesh); // Compute hash of shader source - u32 sourceHash = ShaderCache::ComputeHash( + u32 sourceHash = HashShaderSource( (const char*)fs->pointer(), fs->length() ); @@ -879,11 +983,9 @@ bool ShaderLoader::CompileShaderWithDefines( definesStr.append(";"); } - u32 cacheKey = ShaderCache::ComputeHash( - sourceCode.c_str(), - sourceCode.length(), - definesStr.c_str() - ); + u32 cacheKey = HashShaderSource(sourceCode.c_str(), sourceCode.length()); + if (!definesStr.empty()) + cacheKey = MixSourceHash(cacheKey, crc32(definesStr.c_str(), definesStr.length())); // Try to load from cache ExtractedReflection cachedReflection; diff --git a/src/Layers/xrRender/FrameGraph/ShaderReflection.cpp b/src/Layers/xrRender/FrameGraph/ShaderReflection.cpp index 04fc0a6627e..6f88480fc70 100644 --- a/src/Layers/xrRender/FrameGraph/ShaderReflection.cpp +++ b/src/Layers/xrRender/FrameGraph/ShaderReflection.cpp @@ -1082,14 +1082,28 @@ static void FilterReflectionByUsage(ExtractedReflection& result, slang::ICompone srUsed, srUnused, dtsUsed, dtsUnused); } + auto isLegacyCommonSrv = [](const shared_str& name) -> bool { + const char* n = name.c_str(); + return n && n[0] == 's' && n[1] == '_'; + }; + auto& textures = result.rtBindings.inputTextures; textures.erase(std::remove_if(textures.begin(), textures.end(), - [&](const auto& t) { return !isUsed(SLANG_PARAMETER_CATEGORY_SHADER_RESOURCE, t.slot); }), + [&](const auto& t) { + if (!isLegacyCommonSrv(t.name)) + return false; + return !isUsed(SLANG_PARAMETER_CATEGORY_SHADER_RESOURCE, t.slot); + }), textures.end()); auto& uavs = result.rtBindings.uavBindings; uavs.erase(std::remove_if(uavs.begin(), uavs.end(), - [&](const auto& u) { return !isUsed(SLANG_PARAMETER_CATEGORY_UNORDERED_ACCESS, u.slot); }), + [&](const auto& u) { + const char* n = u.name.c_str(); + if (n && ((n[0] == 'u' && n[1] == '_') || (n[0] == 'g' && n[1] == '_'))) + return false; + return !isUsed(SLANG_PARAMETER_CATEGORY_UNORDERED_ACCESS, u.slot); + }), uavs.end()); auto& samplers = result.rtBindings.samplers; @@ -1119,22 +1133,32 @@ static void FilterReflectionByUsage(ExtractedReflection& result, slang::ICompone [&](const auto& cb) { return !isUsed(SLANG_PARAMETER_CATEGORY_CONSTANT_BUFFER, cb.slot); }), cbs.end()); + auto preferSrv = [&](const auto& keep, const auto& drop) -> bool { + if (keep.shape != ResourceShape::Texture && drop.shape == ResourceShape::Texture) + return true; + if (drop.shape != ResourceShape::Texture && keep.shape == ResourceShape::Texture) + return false; + const bool keepLegacy = isLegacyCommonSrv(keep.name); + const bool dropLegacy = isLegacyCommonSrv(drop.name); + if (keepLegacy != dropLegacy) + return !keepLegacy; + return true; + }; + for (size_t i = 0; i < textures.size(); ++i) { for (size_t j = i + 1; j < textures.size(); ) { if (textures[i].slot == textures[j].slot) { - if (textures[j].shape != ResourceShape::Texture && textures[i].shape == ResourceShape::Texture) + if (preferSrv(textures[i], textures[j])) + textures.erase(textures.begin() + j); + else { textures.erase(textures.begin() + i); --i; break; } - else - { - textures.erase(textures.begin() + j); - } } else ++j; diff --git a/src/Layers/xrRender/FrameGraphPasses/ClusterLightPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/ClusterLightPassSetup.cpp index 418a3d9137b..d51d2b948f8 100644 --- a/src/Layers/xrRender/FrameGraphPasses/ClusterLightPassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/ClusterLightPassSetup.cpp @@ -10,6 +10,9 @@ #include "Layers/xrRender/RenderContext/RenderDevice.h" #include "Layers/xrRender/RenderContext/RenderContext.h" +extern ENGINE_API int ps_r_rt_gi; +extern ENGINE_API int ps_r_path_tracer; + namespace xray::render::fg::passes { using namespace framegraph; @@ -120,7 +123,8 @@ void setupClusterLightPass( const Fmatrix& prevViewProj, bool hasPrevViewProj) { - bool useHiZ = hasPrevViewProj && hizPyramid.is_valid() && hizWidth > 0 && hizHeight > 0; + bool useHiZ = hasPrevViewProj && hizPyramid.is_valid() && hizWidth > 0 && hizHeight > 0 + && ps_r_rt_gi == 0 && ps_r_path_tracer == 0; fg.addCallbackPass( "ClusterLightAssign", @@ -176,6 +180,15 @@ void setupClusterLightPass( { const u32 zero = 0; cmdList->writeBuffer(data.lightManager->GetVisibleLightCountBuffer(), &zero, sizeof(u32)); + { + static std::array s_zeroMask{}; + const u32 clearCount = data.lightManager->GetLightCount(); + if (clearCount > 0) + cmdList->writeBuffer( + data.lightManager->GetVisibleLightIndicesBuffer(), + s_zeroMask.data(), + clearCount * sizeof(u32)); + } LightHiZCullCB cullCB; cullCB.prevViewProj = data.prevViewProj; @@ -203,18 +216,21 @@ void setupClusterLightPass( auto cullBindingSet = cache.GetOrCreateBindingSet( bsb.Build(), data.passState->cullLayout, nvDevice); - nvrhi::ComputeState cullState; - cullState.pipeline = data.passState->cullPipeline; - cullState.bindings = { cullBindingSet }; - cmdList->setComputeState(cullState); - - u32 groups = (data.lightManager->GetLightCount() + 63) / 64; - cmdList->dispatch(groups, 1, 1); - - cmdList->commitBarriers(); - if (psDeviceFlags.test(rsStatistic)) - data.lightManager->ScheduleStatsReadback(cmdList); - didHiZCull = true; + if (cullBindingSet) + { + nvrhi::ComputeState cullState; + cullState.pipeline = data.passState->cullPipeline; + cullState.bindings = { cullBindingSet }; + cmdList->setComputeState(cullState); + + u32 groups = (data.lightManager->GetLightCount() + 63) / 64; + cmdList->dispatch(groups, 1, 1); + + cmdList->commitBarriers(); + if (psDeviceFlags.test(rsStatistic)) + data.lightManager->ScheduleStatsReadback(cmdList); + didHiZCull = true; + } } } } diff --git a/src/Layers/xrRender/FrameGraphPasses/DecalPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/DecalPassSetup.cpp index e7640120eb3..759a388d01a 100644 --- a/src/Layers/xrRender/FrameGraphPasses/DecalPassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/DecalPassSetup.cpp @@ -33,8 +33,12 @@ struct DecalPassData { static void InitializeDecalResources(fg::RenderDevice* device, const nvrhi::FramebufferInfoEx& fbInfo, DecalPassState& state) { - if (state.initialized) + constexpr u32 kDecalPipeVersion = 8; + const u32 colorFmt = (u32)fbInfo.colorFormats[0]; + if (state.initialized && state.pipeVersion == kDecalPipeVersion && state.colorFormat == colorFmt) return; + state.initialized = false; + state.pipeline = nullptr; auto& cache = GetPassResourceCache(); nvrhi::IDevice* nvDevice = device->GetNVRHIDevice(); @@ -56,7 +60,7 @@ static void InitializeDecalResources(fg::RenderDevice* device, const nvrhi::Fram state.inputLayout = nvDevice->createInputLayout(&posAttr, 1, state.vs); state.bindingLayout = cache.GetOrCreateBindingLayoutFromReflection( - "Decal", *vsResult.reflection, *psResult.reflection, nvDevice); + "Decal_v2", *vsResult.reflection, *psResult.reflection, nvDevice); nvrhi::GraphicsPipelineDesc pipeDesc; pipeDesc.setVertexShader(state.vs); @@ -81,8 +85,12 @@ static void InitializeDecalResources(fg::RenderDevice* device, const nvrhi::Fram blend.setDestBlendAlpha(nvrhi::BlendFactor::InvSrcAlpha); blend.setBlendOpAlpha(nvrhi::BlendOp::Add); - state.pipeline = cache.GetOrCreatePipeline("Decal", pipeDesc, fbInfo, nvDevice); + char pipeName[64]; + xr_sprintf(pipeName, "Decal_v8_SrcA_%u", colorFmt); + state.pipeline = cache.GetOrCreatePipeline(pipeName, pipeDesc, fbInfo, nvDevice); state.initialized = state.pipeline != nullptr; + state.pipeVersion = kDecalPipeVersion; + state.colorFormat = colorFmt; } DefaultOutputLayout setupDecalPass( @@ -122,6 +130,10 @@ DefaultOutputLayout setupDecalPass( if (!depthTex || !normalTex || !colorTex) return; + nvrhi::FramebufferInfoEx fmtInfo; + fmtInfo.colorFormats.push_back(colorTex->getDesc().format); + InitializeDecalResources(data.device, fmtInfo, *data.passState); + data.decalMgr->Upload(ctx); if (data.decalMgr->GetActiveCount() == 0) diff --git a/src/Layers/xrRender/FrameGraphPasses/DecalPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/DecalPassSetup.h index 9233c1ef315..36c11f57ba0 100644 --- a/src/Layers/xrRender/FrameGraphPasses/DecalPassSetup.h +++ b/src/Layers/xrRender/FrameGraphPasses/DecalPassSetup.h @@ -28,6 +28,8 @@ struct DecalPassState { nvrhi::ShaderHandle vs; nvrhi::ShaderHandle ps; bool initialized = false; + u32 pipeVersion = 0; + u32 colorFormat = 0; }; framegraph::DefaultOutputLayout setupDecalPass( diff --git a/src/Layers/xrRender/FrameGraphPasses/DetailCullPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/DetailCullPassSetup.cpp index ff119fbdf67..336747e6df2 100644 --- a/src/Layers/xrRender/FrameGraphPasses/DetailCullPassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/DetailCullPassSetup.cpp @@ -6,6 +6,7 @@ #include "Layers/xrRender/RenderContext/RenderDevice.h" #include "Layers/xrRender/RenderContext/RenderContext.h" #include "Layers/xrRender/Profiler/GPUProfiler.h" +#include "Layers/xrRender/FrameGraph/FGResource.h" extern ENGINE_API float ps_r3_grass_blade_width; @@ -38,6 +39,9 @@ void setupDetailCullPass( xray::profiler::GPUProfiler* gpuProfiler, DetailPassState* detailState) { + if (!detailManager || !hiZPyramid.is_valid() || hiZMipLevels == 0) + return; + Fmatrix capturedPrevViewProj; bool hasPrevViewProj = (prevViewProj != nullptr); if (hasPrevViewProj) @@ -45,9 +49,33 @@ void setupDetailCullPass( else capturedPrevViewProj.identity(); + nvrhi::IBuffer* cullFence = nullptr; + if (detailManager) { + if (detailManager->billboardDrawArgsBuffer) + cullFence = detailManager->billboardDrawArgsBuffer; + else if (detailManager->drawArgsBuffer[0]) + cullFence = detailManager->drawArgsBuffer[0]; + else if (detailManager->decalDrawArgsBuffer) + cullFence = detailManager->decalDrawArgsBuffer; + } + VirtualResourceHandle cullArgsHandle{}; + if (cullFence) { + ResourceDesc fenceDesc; + fenceDesc.type = ResourceDesc::Type::Buffer; + fenceDesc.debugName = "DetailCull_Args"; + fenceDesc.bufferSize = sizeof(u32) * 5; + fenceDesc.structStride = sizeof(u32); + fenceDesc.isUAV = true; + fenceDesc.isTransient = false; + fenceDesc.isImported = true; + cullArgsHandle = fg.ImportBuffer("detail_cull_args", cullFence, fenceDesc); + if (detailState) + detailState->cullArgs = cullArgsHandle; + } + fg.addCallbackPass( "DetailCull", - [&, hiZPyramid, hiZWidth, hiZHeight, hiZMipLevels, capturedPrevViewProj, hasPrevViewProj, gpuProfiler, detailState]( + [&, hiZPyramid, hiZWidth, hiZHeight, hiZMipLevels, capturedPrevViewProj, hasPrevViewProj, gpuProfiler, detailState, cullArgsHandle]( FrameGraph& builder, PassHandle passHandle, DetailCullPassData& data) { RenderPassBuilder passBuilder(builder, passHandle); passBuilder.asyncCompute(); @@ -64,6 +92,8 @@ void setupDetailCullPass( data.detailState = detailState; data.hiZPyramid = passBuilder.read(hiZPyramid, ResourceState::ShaderResource); + if (cullArgsHandle.is_valid()) + passBuilder.write(cullArgsHandle, ResourceState::UnorderedAccess); }, [](const DetailCullPassData& data, const FrameGraph& fg, fg::RenderContext* ctx) { @@ -84,6 +114,10 @@ void setupDetailCullPass( if (!cmdList) return; + nvrhi::ITexture* hiZTexture = fg.GetPhysicalTexture(data.hiZPyramid); + if (!hiZTexture) + return; + if (data.detailState && !data.detailState->detailDataUploaded) { data.detailManager->UploadBufferData(cmdList); @@ -96,7 +130,6 @@ void setupDetailCullPass( data.detailState->lastBladeWidth = ps_r3_grass_blade_width; } - nvrhi::ITexture* hiZTexture = fg.GetPhysicalTexture(data.hiZPyramid); const float fadeDistance = g_pGamePersistent->Environment().CurrentEnv.far_plane; Fmatrix effectivePrevViewProj = data.hasPrevViewProj ? data.prevViewProj : Device.mFullTransform; diff --git a/src/Layers/xrRender/FrameGraphPasses/DetailPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/DetailPassSetup.cpp index 48e775c44a9..21e3ce6b8c0 100644 --- a/src/Layers/xrRender/FrameGraphPasses/DetailPassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/DetailPassSetup.cpp @@ -53,11 +53,16 @@ DefaultOutputLayout setupDetailPass( const DefaultOutputLayout& forwardInputs, u32 width, u32 height, - xray::profiler::GPUProfiler* gpuProfiler + xray::profiler::GPUProfiler* gpuProfiler, + VirtualResourceHandle cullArgs ) { if (detailManager && !detailManager->graphicsPipeline) { + auto* shaderLoader = GEnv.Render ? GEnv.Render->GetShaderLoader() : nullptr; + if (shaderLoader && (!detailManager->vertexShader || !detailManager->pixelShader)) + detailManager->LoadGraphicsShaders(shaderLoader); + nvrhi::FramebufferInfo fbInfo; fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); @@ -85,10 +90,15 @@ DefaultOutputLayout setupDetailPass( data.outputNormal = passBuilder.readWrite(forwardInputs.normal, ResourceState::RenderTarget); if (forwardInputs.baseColor.is_valid()) data.baseColor = passBuilder.readWrite(forwardInputs.baseColor, ResourceState::RenderTarget); + if (forwardInputs.worldPos.is_valid()) + data.worldPos = passBuilder.readWrite(forwardInputs.worldPos, ResourceState::RenderTarget); + if (cullArgs.is_valid()) + passBuilder.read(cullArgs, ResourceState::IndirectArgument); data.outputs.albedo = data.outputColor; data.outputs.normal = data.outputNormal; data.outputs.baseColor = data.baseColor; + data.outputs.worldPos = data.worldPos.is_valid() ? data.worldPos : forwardInputs.worldPos; data.outputs.depth = data.depth; }, [](const DetailPassData& data, const FrameGraph& fg, fg::RenderContext* ctx) @@ -130,6 +140,7 @@ DefaultOutputLayout setupDetailPass( nvrhi::ITexture* normalTexture = fg.GetPhysicalTexture(data.outputNormal); auto* baseColorRT = data.baseColor.is_valid() ? fg.GetPhysicalTexture(data.baseColor) : nullptr; + auto* worldPosRT = data.worldPos.is_valid() ? fg.GetPhysicalTexture(data.worldPos) : nullptr; nvrhi::FramebufferDesc fbDesc; fbDesc.addColorAttachment(colorTexture); @@ -137,9 +148,12 @@ DefaultOutputLayout setupDetailPass( fbDesc.addColorAttachment(normalTexture); if (baseColorRT) fbDesc.addColorAttachment(baseColorRT); + if (worldPosRT) + fbDesc.addColorAttachment(worldPosRT); fbDesc.setDepthAttachment(depthTexture); - nvrhi::FramebufferHandle framebuffer = data.device->GetNVRHIDevice()->createFramebuffer(fbDesc); + nvrhi::FramebufferHandle framebuffer = framegraph::GetPassResourceCache().GetOrCreateFramebuffer( + worldPosRT ? "DetailPassWPos" : "DetailPass", fbDesc, data.device->GetNVRHIDevice()); if (!framebuffer) return; @@ -154,6 +168,11 @@ DefaultOutputLayout setupDetailPass( auto detailGlobalsCB = cache.GetOrCreateVolatileCB("Detail", "DetailGlobals", sizeof(FGDetailManager::DetailFrameConstants), renderDevice); auto dynLightCB = cache.GetOrCreateVolatileCB("Detail", "DynLight", 48, renderDevice); + { + StaticGlobals sg = BuildStaticGlobals(); + cmdList->writeBuffer(staticGlobalsCB, &sg, sizeof(sg)); + } + // b3: DetailGlobals float windAngleDeg = 0.0f; float windSpeed = dm->windSpeed; @@ -227,6 +246,7 @@ DefaultOutputLayout setupDetailPass( bsb.BufferSRV("g_LightData", ClusteredLightManager::Instance().GetLightDataBuffer()); bsb.BufferSRV("g_ClusterGrid", ClusteredLightManager::Instance().GetClusterGridBuffer()); bsb.BufferSRV("g_LightIndexList", ClusteredLightManager::Instance().GetLightIndexListBuffer()); + BindEnvIblCubes(bsb, data.device); auto bindDesc = bsb.Build(); bindDesc.bindings.push_back(nvrhi::BindingSetItem::TypedBuffer_SRV(32, dm->cachedDummySlotIndirection)); return cache.GetOrCreateBindingSet(bindDesc, dm->graphicsBindingLayout, nvDev); @@ -248,6 +268,7 @@ DefaultOutputLayout setupDetailPass( bsb.BufferSRV("g_LightData", ClusteredLightManager::Instance().GetLightDataBuffer()); bsb.BufferSRV("g_ClusterGrid", ClusteredLightManager::Instance().GetClusterGridBuffer()); bsb.BufferSRV("g_LightIndexList", ClusteredLightManager::Instance().GetLightIndexListBuffer()); + BindEnvIblCubes(bsb, data.device); return cache.GetOrCreateBindingSet(bsb.Build(), layout, nvDev); }; @@ -308,6 +329,7 @@ DefaultOutputLayout setupDetailPass( decalBsb.BufferSRV("g_LightData", ClusteredLightManager::Instance().GetLightDataBuffer()); decalBsb.BufferSRV("g_ClusterGrid", ClusteredLightManager::Instance().GetClusterGridBuffer()); decalBsb.BufferSRV("g_LightIndexList", ClusteredLightManager::Instance().GetLightIndexListBuffer()); + BindEnvIblCubes(decalBsb, data.device); nvrhi::BindingSetHandle decalBindingSet = cache.GetOrCreateBindingSet(decalBsb.Build(), dm->decalBindingLayout, nvDev); nvrhi::GraphicsState state; @@ -333,6 +355,7 @@ DefaultOutputLayout setupDetailPass( outputs.albedo = passData.outputColor; outputs.normal = passData.outputNormal; outputs.baseColor = passData.baseColor; + outputs.worldPos = forwardInputs.worldPos; outputs.depth = passData.depth; return outputs; } diff --git a/src/Layers/xrRender/FrameGraphPasses/DetailPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/DetailPassSetup.h index 08241ed449c..236f252be62 100644 --- a/src/Layers/xrRender/FrameGraphPasses/DetailPassSetup.h +++ b/src/Layers/xrRender/FrameGraphPasses/DetailPassSetup.h @@ -32,6 +32,7 @@ namespace xray::render::fg::passes { struct DetailPassState { bool detailDataUploaded = false; float lastBladeWidth = 0.0f; + framegraph::VirtualResourceHandle cullArgs; }; struct DetailPassData { @@ -40,6 +41,7 @@ struct DetailPassData { framegraph::VirtualResourceHandle outputColor; framegraph::VirtualResourceHandle outputNormal; framegraph::VirtualResourceHandle baseColor; + framegraph::VirtualResourceHandle worldPos; fg::RenderDevice* device; fg::FGDetailManager* detailManager; framegraph::DefaultOutputLayout outputs; @@ -61,7 +63,8 @@ framegraph::DefaultOutputLayout setupDetailPass( const framegraph::DefaultOutputLayout& forwardInputs, u32 width, u32 height, - xray::profiler::GPUProfiler* gpuProfiler = nullptr + xray::profiler::GPUProfiler* gpuProfiler = nullptr, + framegraph::VirtualResourceHandle cullArgs = {} ); } // namespace xray::render::fg::passes diff --git a/src/Layers/xrRender/FrameGraphPasses/DistortionApplyPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/DistortionApplyPassSetup.cpp index c5a94072717..04fdf0de1e7 100644 --- a/src/Layers/xrRender/FrameGraphPasses/DistortionApplyPassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/DistortionApplyPassSetup.cpp @@ -20,6 +20,8 @@ using namespace framegraph; struct DistortionApplyData { VirtualResourceHandle sceneInput; VirtualResourceHandle distortionInput; + VirtualResourceHandle worldPosInput; + VirtualResourceHandle baseColorInput; VirtualResourceHandle depthInput; VirtualResourceHandle output; u32 width; @@ -28,11 +30,21 @@ struct DistortionApplyData { }; void InitializeDistortionApplyPass(nvrhi::IDevice* device, DistortionApplyPassState& state) { - if (state.initialized || !device) return; + constexpr u32 kVersion = 16; + if (state.initialized && state.version == kVersion) + return; + state.initialized = false; + state.version = kVersion; + state.pipeline = nullptr; + state.bindingLayout = nullptr; + + if (!device) + return; if (!GEnv.Render->GetShaderLoader()) return; + framegraph::BindingSetBuilder::InvalidateReflectionCache(); auto vsResult = GEnv.Render->GetShaderLoader()->LoadVertexShader("fullscreen"); auto psResult = GEnv.Render->GetShaderLoader()->LoadPixelShader("distortion_apply"); if (!vsResult.handle || !psResult.handle) { @@ -43,7 +55,7 @@ void InitializeDistortionApplyPass(nvrhi::IDevice* device, DistortionApplyPassSt auto& cache = GetPassResourceCache(); state.bindingLayout = cache.GetOrCreateBindingLayoutFromReflection( - "DistortionApply", *vsResult.reflection, *psResult.reflection, device); + "DistortionApply_v21_KeepReflect", *vsResult.reflection, *psResult.reflection, device); if (state.bindingLayout) { nvrhi::GraphicsPipelineDesc pipeDesc; @@ -59,7 +71,7 @@ void InitializeDistortionApplyPass(nvrhi::IDevice* device, DistortionApplyPassSt nvrhi::FramebufferInfoEx fbInfo; fbInfo.addColorFormat(nvrhi::Format::RGBA16_FLOAT); - state.pipeline = cache.GetOrCreatePipeline("DistortionApply", pipeDesc, fbInfo, device); + state.pipeline = cache.GetOrCreatePipeline("DistortionApply_v21_KeepReflect", pipeDesc, fbInfo, device); } state.initialized = true; } @@ -69,6 +81,8 @@ VirtualResourceHandle setupDistortionApplyPass( fg::RenderDevice* device, VirtualResourceHandle sceneColor, VirtualResourceHandle distortionRT, + VirtualResourceHandle worldPos, + VirtualResourceHandle baseColor, VirtualResourceHandle depth, u32 width, u32 height, @@ -77,6 +91,9 @@ VirtualResourceHandle setupDistortionApplyPass( if (device && device->GetNVRHIDevice()) InitializeDistortionApplyPass(device->GetNVRHIDevice(), passState); + if (!distortionRT.is_valid() || !sceneColor.is_valid()) + return sceneColor; + ResourceDesc outputDesc; outputDesc.type = ResourceDesc::Type::Texture2D; outputDesc.width = width; @@ -91,14 +108,22 @@ VirtualResourceHandle setupDistortionApplyPass( auto& passData = fg.addCallbackPass( "DistortionApply", - [sceneColor, distortionRT, depth, outputHandle, width, height, &passState](FrameGraph& builder, PassHandle passHandle, DistortionApplyData& data) { + [sceneColor, distortionRT, worldPos, baseColor, depth, outputHandle, width, height, &passState](FrameGraph& builder, PassHandle passHandle, DistortionApplyData& data) { RenderPassBuilder passBuilder(builder, passHandle); data.width = width; data.height = height; data.passState = &passState; data.sceneInput = passBuilder.read(sceneColor, ResourceState::ShaderResource); data.distortionInput = passBuilder.read(distortionRT, ResourceState::ShaderResource); - data.depthInput = passBuilder.read(depth, ResourceState::ShaderResource); + data.worldPosInput = worldPos.is_valid() + ? passBuilder.read(worldPos, ResourceState::ShaderResource) + : VirtualResourceHandle{}; + data.baseColorInput = baseColor.is_valid() + ? passBuilder.read(baseColor, ResourceState::ShaderResource) + : VirtualResourceHandle{}; + data.depthInput = depth.is_valid() + ? passBuilder.read(depth, ResourceState::ShaderResource) + : VirtualResourceHandle{}; data.output = passBuilder.write(outputHandle, ResourceState::RenderTarget); }, @@ -106,9 +131,10 @@ VirtualResourceHandle setupDistortionApplyPass( nvrhi::ICommandList* cmdList = ctx->GetCommandList(); auto* sceneTex = fg.GetPhysicalTexture(data.sceneInput); auto* distortTex = fg.GetPhysicalTexture(data.distortionInput); - auto* depthTex = fg.GetPhysicalTexture(data.depthInput); + auto* worldPosTex = data.worldPosInput.is_valid() ? fg.GetPhysicalTexture(data.worldPosInput) : nullptr; + auto* depthTex = data.depthInput.is_valid() ? fg.GetPhysicalTexture(data.depthInput) : nullptr; auto* outputTex = fg.GetPhysicalTexture(data.output); - if (!sceneTex || !distortTex || !depthTex || !outputTex) + if (!sceneTex || !distortTex || !outputTex) return; auto* ps = data.passState; @@ -126,10 +152,16 @@ VirtualResourceHandle setupDistortionApplyPass( return; BindingSetBuilder bsb(*vsRefl, *psRefl, device, "DistortionApply"); + nvrhi::ITexture* underColor = ps->waterUnderColor + ? ps->waterUnderColor + : sceneTex; bsb.ConstantBuffer("static_globals", staticGlobalsCB) .Texture("g_Snapshot", sceneTex) .Texture("g_Distortion", distortTex) - .Texture("g_Depth", depthTex); + .Texture("g_WorldPos", worldPosTex ? worldPosTex : cache.GetDummyContactHistory(device)) + .Texture("g_UnderColor", underColor) + .Texture("g_Depth", depthTex ? depthTex : cache.GetDummyContactDepth(device), + nvrhi::Format::R32_FLOAT); auto bindingSet = cache.GetOrCreateBindingSet(bsb.Build(), ps->bindingLayout, device); nvrhi::FramebufferDesc fbDesc; diff --git a/src/Layers/xrRender/FrameGraphPasses/DistortionApplyPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/DistortionApplyPassSetup.h index ab4a5b9884e..ea57476070d 100644 --- a/src/Layers/xrRender/FrameGraphPasses/DistortionApplyPassSetup.h +++ b/src/Layers/xrRender/FrameGraphPasses/DistortionApplyPassSetup.h @@ -17,7 +17,9 @@ namespace xray::render::fg::passes { struct DistortionApplyPassState { nvrhi::GraphicsPipelineHandle pipeline; nvrhi::BindingLayoutHandle bindingLayout; + nvrhi::ITexture* waterUnderColor = nullptr; bool initialized = false; + u32 version = 0; }; void InitializeDistortionApplyPass(nvrhi::IDevice* device, DistortionApplyPassState& state); @@ -27,6 +29,8 @@ framegraph::VirtualResourceHandle setupDistortionApplyPass( fg::RenderDevice* device, framegraph::VirtualResourceHandle sceneColor, framegraph::VirtualResourceHandle distortionRT, + framegraph::VirtualResourceHandle worldPos, + framegraph::VirtualResourceHandle baseColor, framegraph::VirtualResourceHandle depth, u32 width, u32 height, diff --git a/src/Layers/xrRender/FrameGraphPasses/ExposurePassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/ExposurePassSetup.cpp index 04754714a4d..c9d1dcce0b7 100644 --- a/src/Layers/xrRender/FrameGraphPasses/ExposurePassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/ExposurePassSetup.cpp @@ -1,4 +1,3 @@ -// xrRender/FrameGraphPasses/ExposurePassSetup.cpp #include "stdafx.h" #include "ExposurePassSetup.h" #include "PassVertexFormats.h" @@ -10,6 +9,8 @@ #include "Layers/xrRender/RenderContext/RenderContext.h" #include "Layers/xrRender/RenderContext/RenderDevice.h" #include "Layers/xrRender/FrameGraph/ShaderLoader.h" +#include "Layers/xrRender/xrRender_console.h" +#include namespace fg { @@ -20,23 +21,18 @@ namespace xray::render::fg::passes { using namespace framegraph; -// ═══════════════════════════════════════════════════════ -// EXPOSURE CONFIG -// ═══════════════════════════════════════════════════════ +namespace { +constexpr u32 kExposurePipeVersion = 5; +} ExposureConfig GetDefaultExposureConfig() { ExposureConfig config; - config.minLogLuminance = -10.0f; - config.maxLogLuminance = 4.0f; - config.lowPercentile = 0.5f; - config.highPercentile = 0.98f; - config.adaptSpeedUp = 3.0f; - config.adaptSpeedDown = 1.0f; - config.minExposure = 0.001f; - config.maxExposure = 64.0f; - config.exposureCompensation = 0.0f; - config.calibrationConstant = 12.5f; + const bool tonemapOn = ps_r2_ls_flags.test(R2FLAG_TONEMAP); + config.middleGray = ps_r2_tonemap_middlegray; + config.amount = tonemapOn ? ps_r2_tonemap_amount : 0.0f; + config.lowLum = ps_r2_tonemap_low_lum; + config.adaptation = ps_r2_tonemap_adaptation; return config; } @@ -45,15 +41,60 @@ nvrhi::ITexture* GetExposureTexture(const ExposurePassState& state) return state.exposureTexture.Get(); } -// ═══════════════════════════════════════════════════════ -// INITIALIZATION -// ═══════════════════════════════════════════════════════ +void PollExposureHistogram(ExposurePassState& state, nvrhi::IDevice* device) +{ + if (!device) + return; + const u32 readSlot = (state.histWriteSlot + 1u) % 3u; + if (!state.histReadback[readSlot]) + return; + void* mapped = device->mapBuffer(state.histReadback[readSlot], nvrhi::CpuAccessMode::Read); + if (!mapped) + return; + memcpy(state.histBins, mapped, sizeof(state.histBins)); + device->unmapBuffer(state.histReadback[readSlot]); +} + +static void UpdateMiddleGray(const ExposureConfig& config, float deltaTime, ExposurePassState& state, AdaptCB& out) +{ + state.f_luminance_adapt = + 0.9f * state.f_luminance_adapt + 0.1f * deltaTime * config.adaptation; + + Fvector3 none, full, result; + none.set(1.f, 0.f, 1.f); + full.set(config.middleGray, 1.f, config.lowLum); + result.lerp(none, full, config.amount); + + out.middleGrayX = result.x; + out.middleGrayY = result.y; + out.middleGrayZ = result.z; + out.middleGrayW = state.f_luminance_adapt; +} + +static float ComputeFallbackExposure(const ExposureConfig& config, float deltaTime, ExposurePassState& state) +{ + AdaptCB cb{}; + UpdateMiddleGray(config, deltaTime, state, cb); + const float Lw = 1.0f; + float scale = cb.middleGrayX / std::max(Lw * cb.middleGrayY + cb.middleGrayZ, 1e-6f); + state.currentExposure = std::lerp(state.currentExposure, scale, std::clamp(cb.middleGrayW, 0.f, 1.f)); + state.currentExposure = std::clamp(state.currentExposure, 1.f / 128.f, 20.f); + return state.currentExposure; +} void InitializeExposureResources(fg::RenderDevice* device, ExposurePassState& state) { - if (state.initialized) + if (state.initialized && state.pipeVersion == kExposurePipeVersion && state.adaptPipeline) return; + state.initialized = false; + state.pipeVersion = 0; + state.histogramPipeline = nullptr; + state.adaptPipeline = nullptr; + state.histogramLayout = nullptr; + state.adaptLayout = nullptr; + state.computeEnabled = false; + nvrhi::IDevice* nvDevice = device->GetNVRHIDevice(); if (!nvDevice) { Msg("! [ExposurePass] NVRHI device not available"); @@ -61,6 +102,7 @@ void InitializeExposureResources(fg::RenderDevice* device, ExposurePassState& st return; } + framegraph::BindingSetBuilder::InvalidateReflectionCache(); auto histogramResult = GEnv.Render->GetShaderLoader()->LoadComputeShader("luminance_histogram"); auto adaptResult = GEnv.Render->GetShaderLoader()->LoadComputeShader("exposure_adapt"); @@ -79,6 +121,7 @@ void InitializeExposureResources(fg::RenderDevice* device, ExposurePassState& st state.computeEnabled = histogramOK && adaptOK; + if (!state.histogramBuffer) { nvrhi::BufferDesc bufDesc; bufDesc.debugName = "ExposureHistogram"; @@ -93,6 +136,17 @@ void InitializeExposureResources(fg::RenderDevice* device, ExposurePassState& st Msg("! [ExposurePass] Failed to create histogram buffer"); } + for (u32 i = 0; i < 3; i++) { + if (state.histReadback[i]) + continue; + nvrhi::BufferDesc rd; + rd.debugName = "ExposureHistogramReadback"; + rd.byteSize = 64 * sizeof(u32); + rd.cpuAccess = nvrhi::CpuAccessMode::Read; + state.histReadback[i] = nvDevice->createBuffer(rd); + } + + if (!state.exposureTexture) { nvrhi::TextureDesc texDesc; texDesc.debugName = "ExposureValue"; @@ -106,6 +160,19 @@ void InitializeExposureResources(fg::RenderDevice* device, ExposurePassState& st state.exposureTexture = nvDevice->createTexture(texDesc); if (!state.exposureTexture) Msg("! [ExposurePass] Failed to create exposure texture"); + else + { + nvrhi::CommandListHandle cmd = nvDevice->createCommandList(); + if (cmd) + { + cmd->open(); + float seed = 1.0f; + cmd->writeTexture(state.exposureTexture, 0, 0, &seed, sizeof(float)); + cmd->close(); + nvDevice->executeCommandList(cmd); + } + state.currentExposure = 1.0f; + } } if (state.computeEnabled) { @@ -123,13 +190,13 @@ void InitializeExposureResources(fg::RenderDevice* device, ExposurePassState& st } { - state.adaptLayout = cache.GetOrCreateBindingLayoutFromReflection("ExposurePass_Adapt", *adaptResult.reflection, nvDevice); + state.adaptLayout = cache.GetOrCreateBindingLayoutFromReflection("ExposurePass_Adapt_v3", *adaptResult.reflection, nvDevice); if (state.adaptLayout) { nvrhi::ComputePipelineDesc pipeDesc; pipeDesc.CS = adaptResult.handle; pipeDesc.bindingLayouts = { state.adaptLayout }; - state.adaptPipeline = cache.GetOrCreateComputePipeline("ExposurePass_Adapt", pipeDesc, nvDevice); + state.adaptPipeline = cache.GetOrCreateComputePipeline("ExposurePass_Adapt_v3", pipeDesc, nvDevice); } } @@ -142,35 +209,10 @@ void InitializeExposureResources(fg::RenderDevice* device, ExposurePassState& st } state.initialized = true; + state.pipeVersion = kExposurePipeVersion; Msg("* [ExposurePass] Initialized (compute=%s)", state.computeEnabled ? "enabled" : "fallback"); } -// ═══════════════════════════════════════════════════════ -// FALLBACK: Fixed exposure calculation -// ═══════════════════════════════════════════════════════ - -static float ComputeFallbackExposure(const ExposureConfig& config, float deltaTime, ExposurePassState& state) -{ - float targetExposure = 1.0f; - - targetExposure *= std::exp2(config.exposureCompensation); - - targetExposure = std::clamp(targetExposure, config.minExposure, config.maxExposure); - - float adaptSpeed = (targetExposure > state.currentExposure) - ? config.adaptSpeedUp - : config.adaptSpeedDown; - - float adaptFactor = 1.0f - std::exp(-deltaTime * adaptSpeed); - state.currentExposure = std::lerp(state.currentExposure, targetExposure, adaptFactor); - - return state.currentExposure; -} - -// ═══════════════════════════════════════════════════════ -// SETUP EXPOSURE PASS -// ═══════════════════════════════════════════════════════ - ExposureOutput setupExposurePass( FrameGraph& fg, fg::RenderDevice* device, @@ -183,19 +225,25 @@ ExposureOutput setupExposurePass( { InitializeExposureResources(device, state); - // Create exposure texture resource in framegraph + if (!state.exposureTexture) + { + ExposureOutput empty{}; + return empty; + } + ResourceDesc exposureDesc; exposureDesc.type = ResourceDesc::Type::Texture2D; exposureDesc.debugName = "Exposure"; exposureDesc.width = 1; exposureDesc.height = 1; exposureDesc.format = nvrhi::Format::R32_FLOAT; - exposureDesc.isRenderTarget = false; exposureDesc.isUAV = true; + exposureDesc.allowUAV = true; + exposureDesc.isImported = true; - VirtualResourceHandle exposureHandle = fg.CreateTexture("exposure_rt", exposureDesc); + VirtualResourceHandle exposureHandle = fg.ImportTexture( + "Exposure", state.exposureTexture.Get(), exposureDesc); - // Create histogram buffer resource ResourceDesc histogramDesc; histogramDesc.type = ResourceDesc::Type::Buffer; histogramDesc.debugName = "LuminanceHistogram"; @@ -218,13 +266,8 @@ ExposureOutput setupExposurePass( data.height = height; data.passState = &state; - // Read HDR scene for histogram data.sceneColor = passBuilder.read(hdrSceneColor); - - // Write exposure output data.exposureTexture = passBuilder.write(exposureHandle, ResourceState::UnorderedAccess); - - // Write histogram (intermediate) data.histogramBuffer = passBuilder.write(histogramHandle, ResourceState::UnorderedAccess); }, @@ -249,8 +292,8 @@ ExposureOutput setupExposurePass( { HistogramCB histCB; - histCB.minLogLum = data.config.minLogLuminance; - histCB.logLumRange = data.config.maxLogLuminance - data.config.minLogLuminance; + histCB.minLogLum = -10.0f; + histCB.logLumRange = 14.0f; histCB.width = data.width; histCB.height = data.height; @@ -270,23 +313,16 @@ ExposureOutput setupExposurePass( u32 groupsX = (data.width + 15) / 16; u32 groupsY = (data.height + 15) / 16; ctx->Dispatch(groupsX, groupsY, 1); + if (ps->histReadback[ps->histWriteSlot]) { + cmdList->copyBuffer(ps->histReadback[ps->histWriteSlot], 0, ps->histogramBuffer, 0, 64 * sizeof(u32)); + ps->histWriteSlot = (ps->histWriteSlot + 1u) % 3u; + } } } { - AdaptCB adaptCB; - adaptCB.minLogLum = data.config.minLogLuminance; - adaptCB.logLumRange = data.config.maxLogLuminance - data.config.minLogLuminance; - adaptCB.lowPercentile = data.config.lowPercentile; - adaptCB.highPercentile = data.config.highPercentile; - adaptCB.adaptSpeedUp = data.config.adaptSpeedUp; - adaptCB.adaptSpeedDown = data.config.adaptSpeedDown; - adaptCB.deltaTime = data.deltaTime; - adaptCB.exposureCompensation = data.config.exposureCompensation; - adaptCB.minExposure = data.config.minExposure; - adaptCB.maxExposure = data.config.maxExposure; - adaptCB.calibrationConstant = data.config.calibrationConstant; - adaptCB.padding = 0.0f; + AdaptCB adaptCB{}; + UpdateMiddleGray(data.config, data.deltaTime, *ps, adaptCB); cmdList->writeBuffer(adaptCBHandle, &adaptCB, sizeof(adaptCB)); diff --git a/src/Layers/xrRender/FrameGraphPasses/ExposurePassSetup.h b/src/Layers/xrRender/FrameGraphPasses/ExposurePassSetup.h index 874c52b48ca..f5049807097 100644 --- a/src/Layers/xrRender/FrameGraphPasses/ExposurePassSetup.h +++ b/src/Layers/xrRender/FrameGraphPasses/ExposurePassSetup.h @@ -1,11 +1,9 @@ -// xrRender/FrameGraphPasses/ExposurePassSetup.h #pragma once #include "Layers/xrRender/FrameGraph/FGTypes.h" #include "Layers/xrRender/FrameGraph/FGResource.h" #include -// Forward declarations namespace xray::render { namespace fg { class RenderDevice; @@ -18,30 +16,6 @@ namespace xray::render::framegraph { namespace xray::render::fg::passes { -// ═══════════════════════════════════════════════════════ -// EXPOSURE PASS (Auto-Exposure / Eye Adaptation) -// ═══════════════════════════════════════════════════════ -// -// Computes scene exposure for HDR rendering using histogram-based -// auto-exposure with temporal eye adaptation. -// -// PIPELINE: -// 1. Luminance Histogram - Compute shader generates 64-bin histogram -// from HDR scene in log2 luminance space -// 2. Exposure Adaptation - Compute shader analyzes histogram, -// skips extreme values, computes target exposure, applies -// temporal smoothing for eye adaptation effect -// -// OUTPUT: -// - 1x1 R32_FLOAT texture containing exposure value -// - Sky pass reads this via s_tonemap.Load(int3(0,0,0)).x -// - Tonemap pass uses same exposure for HDR->LDR conversion -// -// REFERENCES: -// - Krzysztof Narkowicz: "Automatic Exposure" (2016) -// - Epic Games: "Auto Exposure in UE 4.25" (2020) -// - Hillaire: "A Scalable and Production Ready Sky and Atmosphere" (2020) - struct ExposurePassState { nvrhi::BufferHandle histogramBuffer; nvrhi::TextureHandle exposureTexture; @@ -52,28 +26,18 @@ struct ExposurePassState { bool initialized = false; bool computeEnabled = false; float currentExposure = 1.0f; + float f_luminance_adapt = 0.5f; + u32 pipeVersion = 0; + nvrhi::BufferHandle histReadback[3]; + u32 histWriteSlot = 0; + u32 histBins[64] = {}; }; struct ExposureConfig { - // Histogram parameters - float minLogLuminance = -10.0f; // Minimum log2 luminance (EV) - float maxLogLuminance = 4.0f; // Maximum log2 luminance (EV) - - // Percentile clamping (skip extreme values) - float lowPercentile = 0.5f; // Skip darkest 50% of pixels - float highPercentile = 0.98f; // Skip brightest 2% of pixels - - // Eye adaptation speed (f-stops per second) - float adaptSpeedUp = 3.0f; // Speed when brightening - float adaptSpeedDown = 1.0f; // Speed when darkening (slower) - - // Exposure limits - float minExposure = 0.001f; // Minimum exposure value - float maxExposure = 64.0f; // Maximum exposure value - - // Calibration - float exposureCompensation = 0.0f; // Manual EV adjustment - float calibrationConstant = 12.5f; // Reflected-light meter constant K + float middleGray = 1.0f; + float amount = 0.7f; + float lowLum = 0.0001f; + float adaptation = 1.0f; }; struct ExposurePassData { @@ -88,16 +52,13 @@ struct ExposurePassData { ExposurePassState* passState; }; -// Output handles from exposure pass struct ExposureOutput { - framegraph::VirtualResourceHandle exposureTexture; // 1x1 R32_FLOAT - framegraph::VirtualResourceHandle histogramBuffer; // 64 u32 bins (for debug) + framegraph::VirtualResourceHandle exposureTexture; + framegraph::VirtualResourceHandle histogramBuffer; }; void InitializeExposureResources(fg::RenderDevice* device, ExposurePassState& state); -// Setup the exposure pass -// Returns handle to 1x1 exposure texture ExposureOutput setupExposurePass( framegraph::FrameGraph& fg, fg::RenderDevice* device, @@ -112,5 +73,6 @@ ExposureOutput setupExposurePass( ExposureConfig GetDefaultExposureConfig(); nvrhi::ITexture* GetExposureTexture(const ExposurePassState& state); +void PollExposureHistogram(ExposurePassState& state, nvrhi::IDevice* device); } // namespace xray::render::fg::passes diff --git a/src/Layers/xrRender/FrameGraphPasses/ForwardColorPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/ForwardColorPassSetup.cpp index 508f9202e71..2c018de22f5 100644 --- a/src/Layers/xrRender/FrameGraphPasses/ForwardColorPassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/ForwardColorPassSetup.cpp @@ -4,6 +4,7 @@ #include "ShaderConstants.h" // CB layout definitions and FillGlobalConstants/FillDynamicTransforms #include "Layers/xrRender/FrameGraph/FrameGraph.h" #include "Layers/xrRender/FrameGraph/IPass.h" +#include "Layers/xrRender/FrameGraph/FGResource.h" #include "Layers/xrRender/FrameGraph/RenderPassBuilder.h" #include "Layers/xrRender/FrameGraph/ShaderLoader.h" // For loading bindless shaders #include "Layers/xrRender/Geometry/GeometryBatch.h" @@ -34,8 +35,12 @@ namespace xray::render::fg::passes { void InitializeForwardResources(fg::RenderDevice* device, const nvrhi::FramebufferInfoEx& fbInfo, ForwardColorPassState& state) { - if (state.bindlessInitialized) + constexpr u32 kForwardPipeVersion = 6; + if (state.bindlessInitialized && state.pipeVersion == kForwardPipeVersion) return; + state.bindlessInitialized = false; + state.terrainInitialized = false; + state.pipeVersion = 0; nvrhi::IDevice* nvDevice = device->GetNVRHIDevice(); if (!nvDevice) @@ -58,7 +63,7 @@ void InitializeForwardResources(fg::RenderDevice* device, const nvrhi::Framebuff auto& cache = framegraph::GetPassResourceCache(); - state.bindlessLayout = cache.GetOrCreateBindingLayoutFromReflection("ForwardColor", *vsResult.reflection, *psResult.reflection, nvDevice); + state.bindlessLayout = cache.GetOrCreateBindingLayoutFromReflection("ForwardColor_v4", *vsResult.reflection, *psResult.reflection, nvDevice); u32 attrCount = 0; auto* attrs = GetUnifiedVertexAttributes(attrCount); @@ -90,8 +95,9 @@ void InitializeForwardResources(fg::RenderDevice* device, const nvrhi::Framebuff pipeDesc.renderState.depthStencilState.depthFunc = nvrhi::ComparisonFunc::GreaterOrEqual; pipeDesc.renderState.rasterState.frontCounterClockwise = false; pipeDesc.renderState.rasterState.cullMode = nvrhi::RasterCullMode::Back; + pipeDesc.renderState.blendState.alphaToCoverageEnable = true; - state.bindlessPipeline = cache.GetOrCreatePipeline("ForwardColor", pipeDesc, fbInfo, nvDevice); + state.bindlessPipeline = cache.GetOrCreatePipeline("ForwardColor_v4_WPos", pipeDesc, fbInfo, nvDevice); if (!state.bindlessPipeline) { Msg("! [BindlessForward] Failed to create pipeline"); return; @@ -100,10 +106,12 @@ void InitializeForwardResources(fg::RenderDevice* device, const nvrhi::Framebuff QueryBindingLayoutFromPipeline(state.bindlessPipeline, state.bindlessLayout); auto terrainPsResult = shaderLoader->LoadPixelShader("bindless_terrain", "main"); - if (terrainPsResult.handle) { + if (!terrainPsResult.handle) { + Msg("! [BindlessForward] Failed to load bindless_terrain.ps — terrain will not render"); + } else { state.terrainPS = terrainPsResult.handle; state.terrainLayout = cache.GetOrCreateBindingLayoutFromReflection( - "ForwardColor_Terrain", *vsResult.reflection, *terrainPsResult.reflection, nvDevice); + "ForwardColor_Terrain_v6", *vsResult.reflection, *terrainPsResult.reflection, nvDevice); if (state.terrainLayout) { nvrhi::GraphicsPipelineDesc terrainPipeDesc; @@ -120,13 +128,18 @@ void InitializeForwardResources(fg::RenderDevice* device, const nvrhi::Framebuff terrainPipeDesc.renderState.depthStencilState.depthFunc = nvrhi::ComparisonFunc::GreaterOrEqual; terrainPipeDesc.renderState.rasterState.frontCounterClockwise = false; terrainPipeDesc.renderState.rasterState.cullMode = nvrhi::RasterCullMode::Back; - state.terrainPipeline = cache.GetOrCreatePipeline("ForwardColor_Terrain", terrainPipeDesc, fbInfo, nvDevice); + state.terrainPipeline = cache.GetOrCreatePipeline("ForwardColor_Terrain_v6_WPos", terrainPipeDesc, fbInfo, nvDevice); if (state.terrainPipeline) state.terrainInitialized = true; + else + Msg("! [BindlessForward] Failed to create terrain pipeline"); + } else { + Msg("! [BindlessForward] Failed to create terrain binding layout"); } } state.bindlessInitialized = true; + state.pipeVersion = kForwardPipeVersion; Msg("* [BindlessForward] Pipeline initialized"); } @@ -137,6 +150,7 @@ static void renderBindlessForward( nvrhi::ITexture* colorRT, nvrhi::ITexture* normalRT, nvrhi::ITexture* baseColorRT, + nvrhi::ITexture* worldPosRT, nvrhi::ITexture* depthRT, const BindlessForwardConfig& config, MaterialCache* materialCache, @@ -171,16 +185,22 @@ static void renderBindlessForward( fbDesc.addColorAttachment(normalRT); if (baseColorRT) fbDesc.addColorAttachment(baseColorRT); + if (worldPosRT) + fbDesc.addColorAttachment(worldPosRT); fbDesc.setDepthAttachment(depthRT); auto& cache = framegraph::GetPassResourceCache(); - auto framebuffer = cache.GetOrCreateFramebuffer("ForwardColor", fbDesc, nvDevice); + auto framebuffer = cache.GetOrCreateFramebuffer(worldPosRT ? "ForwardColorWPos" : "ForwardColor", fbDesc, nvDevice); auto lightingCB = cache.GetOrCreateVolatileCB("ForwardColor", "LightingCB", sizeof(LightingConstants), device); - auto staticGlobalsCB = cache.GetOrCreateVolatileCB("Frame", "StaticGlobals", sizeof(StaticGlobals), device); + auto staticGlobalsCB = cache.GetOrCreateVolatileCB("Frame", "StaticGlobals", sizeof(StaticGlobals), device, 512); auto drawIndexBuffer = GetOrCreateDrawIndexBuffer("ForwardColor", nvDevice); auto lightingData = FillLightingConstants(); cmdList->writeBuffer(lightingCB, &lightingData, sizeof(lightingData)); + { + StaticGlobals sg = BuildStaticGlobals(); + cmdList->writeBuffer(staticGlobalsCB, &sg, sizeof(sg)); + } auto& variantTexBuffer = bindless::VariantTextureBuffer::Instance(); @@ -193,7 +213,8 @@ static void renderBindlessForward( auto buildBindingDescForSet = [&](const BindlessDrawSet& set) -> nvrhi::BindingSetDesc { framegraph::BindingSetBuilder bsb(*vsReflection, *psReflection, nvDevice, "ForwardColor"); bsb.ConstantBuffer("static_globals", staticGlobalsCB); - bsb.BufferSRV("g_Materials", matBuffer.GetBuffer()); + BindBindlessMaterialTables(bsb); + BindEnvIblCubes(bsb, device); bsb.BufferSRV("g_InstanceData", set.instanceBuffer); bsb.BufferSRV("g_CompactBatchIndices", set.compactBatchIndicesBuffer); bsb.BufferSRV("g_CompactMaterialIDs", set.compactMaterialIDBuffer); @@ -324,7 +345,8 @@ static void renderBindlessForward( auto* terrainPsRefl = shaderLoader->GetCachedReflection("bindless_terrain", ".ps"); framegraph::BindingSetBuilder terrainBsb(*terrainVsRefl, *terrainPsRefl, nvDevice, "ForwardColor.Terrain"); terrainBsb.ConstantBuffer("static_globals", staticGlobalsCB); - terrainBsb.BufferSRV("g_TerrainMaterials", terrainMatBuffer.GetBuffer()); + BindBindlessMaterialTables(terrainBsb); + BindEnvIblCubes(terrainBsb, device); terrainBsb.BufferSRV("g_InstanceData", config.terrainInstanceBuffer); terrainBsb.BufferSRV("g_CompactBatchIndices", config.terrainCompactBatchIndicesBuffer); terrainBsb.BufferSRV("g_CompactMaterialIDs", config.terrainCompactMaterialIDBuffer); @@ -333,8 +355,9 @@ static void renderBindlessForward( terrainBsb.BufferSRV("g_LightIndexList", clm.GetLightIndexListBuffer()); auto terrainBindingSet = framegraph::GetPassResourceCache().GetOrCreateBindingSet(terrainBsb.Build(), ps.terrainLayout, nvDevice); - R_ASSERT2(terrainBindingSet, "Terrain binding set creation failed"); - + if (!terrainBindingSet) { + Msg("! [ForwardColor] Terrain binding set creation failed"); + } else { // Set up terrain graphics state nvrhi::GraphicsState terrainState; terrainState.pipeline = ps.terrainPipeline; @@ -360,6 +383,7 @@ static void renderBindlessForward( cmdList->setGraphicsState(terrainState); DrawIndexedIndirectCountOrFallback(cmdList, 0, 0, config.terrainObjectCount); + } } } @@ -387,11 +411,11 @@ framegraph::DefaultOutputLayout setupForwardColorPass( if (state) { nvrhi::FramebufferInfoEx fbInfo; - fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); - fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); - fbInfo.colorFormats.push_back(nvrhi::Format::RGBA8_UNORM); - fbInfo.colorFormats.push_back(nvrhi::Format::RGBA32_FLOAT); - fbInfo.depthFormat = nvrhi::Format::D32; + fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); + fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); + fbInfo.colorFormats.push_back(nvrhi::Format::RGBA8_UNORM); + fbInfo.colorFormats.push_back(nvrhi::Format::RGBA32_FLOAT); + fbInfo.depthFormat = nvrhi::Format::D32; InitializeForwardResources(device, fbInfo, *state); } @@ -417,6 +441,17 @@ framegraph::DefaultOutputLayout setupForwardColorPass( data.normal = passBuilder.write(normalInput, ResourceState::RenderTarget); if (baseColorInput.is_valid()) data.baseColor = passBuilder.write(baseColorInput, ResourceState::RenderTarget); + { + ResourceDesc wpDesc; + wpDesc.type = ResourceDesc::Type::Texture2D; + wpDesc.width = width; + wpDesc.height = height; + wpDesc.format = nvrhi::Format::RGBA32_FLOAT; + wpDesc.isRenderTarget = true; + wpDesc.isTransient = true; + wpDesc.debugName = "rt_ForwardWorldPos"; + data.worldPos = passBuilder.createTexture("rt_ForwardWorldPos", wpDesc); + } if (drawArgsInput.is_valid()) { data.drawArgsBuffer = passBuilder.read(drawArgsInput, ResourceState::IndirectArgument); @@ -425,6 +460,7 @@ framegraph::DefaultOutputLayout setupForwardColorPass( data.outputs.albedo = data.color; data.outputs.normal = data.normal; data.outputs.baseColor = data.baseColor; + data.outputs.worldPos = data.worldPos; data.outputs.depth = data.depth; }, @@ -439,6 +475,7 @@ framegraph::DefaultOutputLayout setupForwardColorPass( auto* colorRT = fg.GetPhysicalTexture(data.color); auto* normalRT = fg.GetPhysicalTexture(data.normal); auto* baseColorRT = data.baseColor.is_valid() ? fg.GetPhysicalTexture(data.baseColor) : nullptr; + auto* worldPosRT = data.worldPos.is_valid() ? fg.GetPhysicalTexture(data.worldPos) : nullptr; if (!depthRT || !colorRT) return; @@ -450,6 +487,8 @@ framegraph::DefaultOutputLayout setupForwardColorPass( cmdList->clearTextureFloat(normalRT, nvrhi::AllSubresources, nvrhi::Color(0.0f)); if (baseColorRT) cmdList->clearTextureFloat(baseColorRT, nvrhi::AllSubresources, nvrhi::Color(0.0f)); + if (worldPosRT) + cmdList->clearTextureFloat(worldPosRT, nvrhi::AllSubresources, nvrhi::Color(0.0f)); } // Check if we have geometry to render @@ -475,6 +514,7 @@ framegraph::DefaultOutputLayout setupForwardColorPass( colorRT, normalRT, baseColorRT, + worldPosRT, depthRT, data.bindlessConfig, data.materialCache, @@ -487,6 +527,7 @@ framegraph::DefaultOutputLayout setupForwardColorPass( outputs.albedo = passData.color; outputs.normal = passData.normal; outputs.baseColor = passData.baseColor; + outputs.worldPos = passData.worldPos; outputs.depth = passData.depth; return outputs; } diff --git a/src/Layers/xrRender/FrameGraphPasses/ForwardColorPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/ForwardColorPassSetup.h index 68e9a778d0e..e92d00bdf3d 100644 --- a/src/Layers/xrRender/FrameGraphPasses/ForwardColorPassSetup.h +++ b/src/Layers/xrRender/FrameGraphPasses/ForwardColorPassSetup.h @@ -119,6 +119,7 @@ struct ForwardColorPassState { nvrhi::ShaderHandle bindlessVS; nvrhi::ShaderHandle bindlessPS; bool bindlessInitialized = false; + u32 pipeVersion = 0; nvrhi::GraphicsPipelineHandle terrainPipeline; nvrhi::BindingLayoutHandle terrainLayout; nvrhi::ShaderHandle terrainPS; @@ -130,6 +131,7 @@ struct ForwardColorPassData { framegraph::VirtualResourceHandle color; framegraph::VirtualResourceHandle normal; framegraph::VirtualResourceHandle baseColor; + framegraph::VirtualResourceHandle worldPos; framegraph::VirtualResourceHandle drawArgsBuffer; fg::RenderDevice* device; const GeometryCollector* geometry; diff --git a/src/Layers/xrRender/FrameGraphPasses/GlowPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/GlowPassSetup.cpp new file mode 100644 index 00000000000..6aedcf09b5f --- /dev/null +++ b/src/Layers/xrRender/FrameGraphPasses/GlowPassSetup.cpp @@ -0,0 +1,304 @@ +#include "stdafx.h" +#include "GlowPassSetup.h" +#include "PassVertexFormats.h" +#include "Layers/xrRender/FrameGraph/FrameGraph.h" +#include "Layers/xrRender/FrameGraph/RenderPassBuilder.h" +#include "Layers/xrRender/FrameGraph/PassResourceCache.h" +#include "Layers/xrRender/FrameGraph/BindingSetBuilder.h" +#include "Layers/xrRender/FrameGraph/ShaderLoader.h" +#include "Layers/xrRender/RenderContext/RenderContext.h" +#include "Layers/xrRender/RenderContext/RenderDevice.h" +#include "Layers/xrRender/FrameGraphPasses/ShaderConstants.h" +#include "Layers/xrRender/ResourceManager/FGResourceManager.h" +#include "Layers/xrRender/ResourceManager/TextureManager.h" +#include "Layers/xrRender/r_FrameGraphRenderer.h" + +namespace xray::render::fg::passes { +using namespace framegraph; + +namespace { +xr_vector g_glowRegistry; +GlowCollectFn g_glowCollect = nullptr; + +void EnsureGlowResources(nvrhi::IDevice* nv, GlowPassState& state) +{ + if (state.initialized) + return; + + auto* shaderLoader = RImplementation.GetShaderLoader(); + if (!shaderLoader) + return; + auto vsResult = shaderLoader->LoadVertexShader("glow_forward"); + auto psResult = shaderLoader->LoadPixelShader("glow_forward"); + if (!vsResult.handle || !psResult.handle) + return; + state.vs = vsResult.handle; + state.ps = psResult.handle; + + auto& cache = GetPassResourceCache(); + state.bindingLayout = cache.GetOrCreateBindingLayoutFromReflection( + "GlowBillboard_v7", *vsResult.reflection, *psResult.reflection, nv); + if (!state.bindingLayout) + return; + + nvrhi::VertexAttributeDesc attribs[] = { + nvrhi::VertexAttributeDesc() + .setName("POSITION") + .setFormat(nvrhi::Format::RGB32_FLOAT) + .setOffset(offsetof(SunVertex, position)) + .setElementStride(sizeof(SunVertex)), + nvrhi::VertexAttributeDesc() + .setName("COLOR") + .setFormat(nvrhi::Format::BGRA8_UNORM) + .setOffset(offsetof(SunVertex, color)) + .setElementStride(sizeof(SunVertex)), + nvrhi::VertexAttributeDesc() + .setName("TEXCOORD") + .setFormat(nvrhi::Format::RG32_FLOAT) + .setOffset(offsetof(SunVertex, u)) + .setElementStride(sizeof(SunVertex)), + }; + state.inputLayout = cache.GetOrCreateInputLayout("GlowBillboard_v7", attribs, std::size(attribs), state.vs, nv); + + nvrhi::RenderState rs; + rs.blendState.targets[0].enableBlend(); + rs.blendState.targets[0].setSrcBlend(nvrhi::BlendFactor::SrcAlpha); + rs.blendState.targets[0].setDestBlend(nvrhi::BlendFactor::One); + rs.blendState.targets[0].setBlendOp(nvrhi::BlendOp::Add); + rs.blendState.targets[0].setSrcBlendAlpha(nvrhi::BlendFactor::One); + rs.blendState.targets[0].setDestBlendAlpha(nvrhi::BlendFactor::One); + rs.depthStencilState.setDepthTestEnable(true); + rs.depthStencilState.setDepthWriteEnable(false); + rs.depthStencilState.setDepthFunc(nvrhi::ComparisonFunc::GreaterOrEqual); + rs.rasterState.setCullMode(nvrhi::RasterCullMode::None); + + nvrhi::GraphicsPipelineDesc pso; + pso.inputLayout = state.inputLayout; + pso.VS = state.vs; + pso.PS = state.ps; + pso.bindingLayouts = { state.bindingLayout }; + pso.renderState = rs; + pso.primType = nvrhi::PrimitiveType::TriangleList; + + nvrhi::FramebufferInfoEx fbInfo; + fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); + fbInfo.depthFormat = nvrhi::Format::D32; + state.pipeline = cache.GetOrCreatePipeline("GlowBillboard_v7", pso, fbInfo, nv); + + u16 indices[6] = { 0, 1, 2, 2, 1, 3 }; + nvrhi::BufferDesc ibDesc; + ibDesc.byteSize = sizeof(indices); + ibDesc.isIndexBuffer = true; + ibDesc.initialState = nvrhi::ResourceStates::IndexBuffer; + ibDesc.keepInitialState = true; + ibDesc.debugName = "GlowBillboardIB"; + state.ib = nv->createBuffer(ibDesc); + { + auto cmd = nv->createCommandList(); + cmd->open(); + cmd->writeBuffer(state.ib, indices, sizeof(indices)); + cmd->close(); + nv->executeCommandList(cmd); + } + + nvrhi::TextureDesc td; + td.width = 1; + td.height = 1; + td.format = nvrhi::Format::RGBA8_UNORM; + td.initialState = nvrhi::ResourceStates::ShaderResource; + td.keepInitialState = true; + td.debugName = "GlowPlaceholder"; + state.placeholderTex = nv->createTexture(td); + const u32 white = 0xFFFFFFFFu; + { + auto cmd = nv->createCommandList(); + cmd->open(); + cmd->writeTexture(state.placeholderTex, 0, 0, &white, 4); + cmd->close(); + nv->executeCommandList(cmd); + } + + state.initialized = state.pipeline && state.ib && state.placeholderTex; +} + +void EnsureVB(nvrhi::IDevice* nv, GlowPassState& state, u32 vertCount) +{ + if (state.vb && state.vbCapacityVerts >= vertCount) + return; + nvrhi::BufferDesc vd; + vd.byteSize = sizeof(SunVertex) * _max(vertCount, 64u); + vd.isVertexBuffer = true; + vd.initialState = nvrhi::ResourceStates::VertexBuffer; + vd.keepInitialState = true; + vd.debugName = "GlowBillboardVB"; + state.vb = nv->createBuffer(vd); + state.vbCapacityVerts = (u32)(vd.byteSize / sizeof(SunVertex)); +} +} + +void GlowRegistry_Register(void* glow) +{ + if (!glow) + return; + if (std::find(g_glowRegistry.begin(), g_glowRegistry.end(), glow) == g_glowRegistry.end()) + g_glowRegistry.push_back(glow); +} + +void GlowRegistry_Unregister(void* glow) +{ + g_glowRegistry.erase(std::remove(g_glowRegistry.begin(), g_glowRegistry.end(), glow), g_glowRegistry.end()); +} + +void GlowRegistry_SetCollect(GlowCollectFn fn) +{ + g_glowCollect = fn; +} + +void GlowRegistry_Collect(xr_vector& out) +{ + out.clear(); + if (!g_glowCollect) + return; + for (void* g : g_glowRegistry) + { + GlowBillboard b{}; + if (g_glowCollect(g, b) && b.radius > 0.f) + out.push_back(b); + } +} + +framegraph::VirtualResourceHandle setupGlowBillboardPass( + FrameGraph& fg, + fg::RenderDevice* device, + VirtualResourceHandle sceneColor, + VirtualResourceHandle depth, + u32 width, + u32 height, + GlowPassState& state) +{ + xr_vector glows; + GlowRegistry_Collect(glows); + if (glows.empty() || !device) + return sceneColor; + + nvrhi::IDevice* nv = device->GetNVRHIDevice(); + EnsureGlowResources(nv, state); + if (!state.initialized) + return sceneColor; + + EnsureVB(nv, state, (u32)glows.size() * 4u); + + struct PassData + { + VirtualResourceHandle color; + VirtualResourceHandle depth; + GlowPassState* state = nullptr; + u32 width = 0, height = 0; + xr_vector glows; + }; + + auto& pass = fg.addCallbackPass( + "Glow Billboards", + [&](FrameGraph& builder, PassHandle passHandle, PassData& data) { + RenderPassBuilder pb(builder, passHandle); + data.color = pb.readWrite(sceneColor, ResourceState::RenderTarget); + if (depth.is_valid()) + data.depth = pb.read(depth, ResourceState::DepthStencilRead); + pb.sideEffects(); + data.state = &state; + data.width = width; + data.height = height; + data.glows = glows; + }, + [](const PassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + if (!data.state || !ctx || data.glows.empty()) + return; + auto* colorTex = fgGraph.GetPhysicalTexture(data.color); + auto* depthTex = data.depth.is_valid() ? fgGraph.GetPhysicalTexture(data.depth) : nullptr; + if (!colorTex) + return; + + auto* cmd = ctx->GetCommandList(); + auto* nv = cmd->getDevice(); + auto* renderDevice = ctx->GetDevice(); + if (!nv || !renderDevice) + return; + + if (depthTex && + (depthTex->getDesc().width != colorTex->getDesc().width || + depthTex->getDesc().height != colorTex->getDesc().height)) + return; + nvrhi::FramebufferDesc fbDesc; + fbDesc.addColorAttachment(colorTex); + if (depthTex) + fbDesc.setDepthAttachment(depthTex); + auto fb = nv->createFramebuffer(fbDesc); + if (!fb) + return; + + auto& cache = GetPassResourceCache(); + auto* vsRefl = RImplementation.GetShaderLoader()->GetCachedReflection("glow_forward", ".vs"); + auto* psRefl = RImplementation.GetShaderLoader()->GetCachedReflection("glow_forward", ".ps"); + if (!vsRefl || !psRefl) + return; + + auto dynamicCBBuffer = cache.GetOrCreateVolatileCB( + "GlowBillboard", "DynamicTransforms", sizeof(DynamicTransforms), renderDevice); + DynamicTransforms dynCB{}; + FillDynamicTransforms(dynCB, Fidentity); + cmd->writeBuffer(dynamicCBBuffer, &dynCB, sizeof(dynCB)); + + Fvector right = Device.vCameraRight; + Fvector up = Device.vCameraTop; + + for (const auto& g : data.glows) + { + Fvector sx, sy; + sx.mul(right, g.radius); + sy.mul(up, g.radius); + const u32 c = g.color.get(); + SunVertex verts[4]; + verts[0].position = { g.pos.x + sx.x - sy.x, g.pos.y + sx.y - sy.y, g.pos.z + sx.z - sy.z }; + verts[0].color = c; verts[0].u = 0.f; verts[0].v = 0.f; + verts[1].position = { g.pos.x + sx.x + sy.x, g.pos.y + sx.y + sy.y, g.pos.z + sx.z + sy.z }; + verts[1].color = c; verts[1].u = 0.f; verts[1].v = 1.f; + verts[2].position = { g.pos.x - sx.x - sy.x, g.pos.y - sx.y - sy.y, g.pos.z - sx.z - sy.z }; + verts[2].color = c; verts[2].u = 1.f; verts[2].v = 0.f; + verts[3].position = { g.pos.x - sx.x + sy.x, g.pos.y - sx.y + sy.y, g.pos.z - sx.z + sy.z }; + verts[3].color = c; verts[3].u = 1.f; verts[3].v = 1.f; + cmd->writeBuffer(data.state->vb, verts, sizeof(verts)); + + nvrhi::ITexture* tex = data.state->placeholderTex.Get(); + if (g.texture.size()) + { + auto* texManager = renderDevice->GetFGResourceManager() + ? renderDevice->GetFGResourceManager()->GetTextureManager() : nullptr; + if (texManager) + { + if (auto* t = texManager->GetNVRHITexture(texManager->LoadTexture(g.texture.c_str()))) + tex = t; + } + } + + BindingSetBuilder bsb(*vsRefl, *psRefl, nv, "GlowBillboard"); + bsb.ConstantBuffer("dynamic_transforms", dynamicCBBuffer) + .Texture("s_sun", tex); + auto bindingSet = cache.GetOrCreateBindingSet(bsb.Build(), data.state->bindingLayout, nv); + + nvrhi::GraphicsState gs; + gs.pipeline = data.state->pipeline; + gs.framebuffer = fb; + gs.bindings.push_back(bindingSet); + gs.vertexBuffers = { { data.state->vb, 0, 0 } }; + gs.indexBuffer = { data.state->ib, nvrhi::Format::R16_UINT, 0 }; + gs.viewport = nvrhi::ViewportState().addViewportAndScissorRect( + nvrhi::Viewport((float)data.width, (float)data.height)); + cmd->setGraphicsState(gs); + cmd->drawIndexed(nvrhi::DrawArguments{ 6, 1, 0, 0, 0 }); + } + }); + + return pass.color; +} + +} diff --git a/src/Layers/xrRender/FrameGraphPasses/GlowPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/GlowPassSetup.h new file mode 100644 index 00000000000..13da378e417 --- /dev/null +++ b/src/Layers/xrRender/FrameGraphPasses/GlowPassSetup.h @@ -0,0 +1,50 @@ +#pragma once + +#include "Layers/xrRender/FrameGraph/FGTypes.h" +#include "Layers/xrRender/FrameGraph/FGResource.h" +#include "Layers/xrRender/FrameGraphPasses/PassCommon.h" +#include + +namespace xray::render::framegraph { class FrameGraph; } +namespace xray::render::fg { class RenderDevice; } + +namespace xray::render::fg::passes { + +struct GlowBillboard +{ + Fvector pos{}; + float radius = 1.f; + Fcolor color{ 1.f, 1.f, 1.f, 1.f }; + shared_str texture; +}; + +struct GlowPassState +{ + nvrhi::GraphicsPipelineHandle pipeline; + nvrhi::BindingLayoutHandle bindingLayout; + nvrhi::InputLayoutHandle inputLayout; + nvrhi::ShaderHandle vs; + nvrhi::ShaderHandle ps; + nvrhi::BufferHandle vb; + nvrhi::BufferHandle ib; + nvrhi::TextureHandle placeholderTex; + u32 vbCapacityVerts = 0; + bool initialized = false; +}; + +void GlowRegistry_Register(void* glow); +void GlowRegistry_Unregister(void* glow); +using GlowCollectFn = bool (*)(void* glow, GlowBillboard& out); +void GlowRegistry_SetCollect(GlowCollectFn fn); +void GlowRegistry_Collect(xr_vector& out); + +framegraph::VirtualResourceHandle setupGlowBillboardPass( + framegraph::FrameGraph& fg, + fg::RenderDevice* device, + framegraph::VirtualResourceHandle sceneColor, + framegraph::VirtualResourceHandle depth, + u32 width, + u32 height, + GlowPassState& state); + +} diff --git a/src/Layers/xrRender/FrameGraphPasses/GrassShadowPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/GrassShadowPassSetup.cpp new file mode 100644 index 00000000000..600fe7acd91 --- /dev/null +++ b/src/Layers/xrRender/FrameGraphPasses/GrassShadowPassSetup.cpp @@ -0,0 +1,383 @@ +#include "stdafx.h" +#include "ShadowPassSetup.h" +#include "PassCommon.h" +#include "Layers/xrRender/FrameGraph/FrameGraph.h" +#include "Layers/xrRender/FrameGraph/RenderPassBuilder.h" +#include "Layers/xrRender/FrameGraph/PassResourceCache.h" +#include "Layers/xrRender/FrameGraph/BindingSetBuilder.h" +#include "Layers/xrRender/FrameGraph/ShaderLoader.h" +#include "Layers/xrRender/RenderContext/RenderContext.h" +#include "Layers/xrRender/RenderContext/RenderDevice.h" +#include "Layers/xrRender/ResourceManager/FGResourceManager.h" +#include "Layers/xrRender/ResourceManager/TextureManager.h" +#include "Layers/xrRender/ResourceManager/NativeRTFactory.h" +#include "Layers/xrRender/FGDetailManager.h" +#include "Layers/xrRender/xrRender_console.h" +#include "xrEngine/IGame_Persistent.h" +#include "xrEngine/Environment.h" +#include "xrEngine/IRenderBackend.h" +#include + +extern ENGINE_API float ps_r3_grass_blade_height; +extern ENGINE_API float ps_r3_grass_wind_displacement; +extern ENGINE_API float ps_r_rt_detail_dist; + +namespace xray::render::fg { +extern int ps_r__detail_gpu; +} + +namespace xray::render::fg::passes { + +using namespace framegraph; + +namespace { + +void ComputeSunNearOrtho(const Fvector& sunDir, float radius, u32 smapRes, Fmatrix& outClipVP, Fmatrix& outSampleVP) +{ + Fvector C = Device.vCameraPosition; + const float e = std::max(radius, 8.f); + const float texel = (e * 2.f) / float(std::max(smapRes, 1u)); + C.x = std::floor(C.x / texel) * texel; + C.z = std::floor(C.z / texel) * texel; + Fvector dir = sunDir; + if (dir.square_magnitude() < 1e-6f) + dir.set(0.f, -1.f, 0.f); + dir.normalize(); + Fvector eye; + eye.mad(C, dir, -radius * 1.5f); + Fvector up(0.f, 1.f, 0.f); + if (_abs(dir.dotproduct(up)) > 0.95f) + up.set(0.f, 0.f, 1.f); + Fmatrix view; + view.build_camera_dir(eye, dir, up); + Fmatrix proj; + proj.build_projection_ortho(e * 2.f, e * 2.f, 1.f, e * 4.f); + outClipVP.mul(proj, view); + Fmatrix toUV; + toUV.identity(); + toUV._11 = 0.5f; + toUV._22 = -0.5f; + toUV._33 = 1.f; + toUV._41 = 0.5f; + toUV._42 = 0.5f; + outSampleVP.mul(toUV, outClipVP); +} + +} + +void InitializeGrassShadowPass(fg::RenderDevice* device, GrassShadowPassState& state) +{ + if (!device) + return; + const u32 res = std::max(512u, std::min(ps_r2_smapsize, 4096u)); + if (state.initialized && state.resolution == res) + return; + if (state.initialized) + ShutdownGrassShadowPass(device, state); + + auto* resMgr = device->GetFGResourceManager(); + if (!resMgr || !resMgr->GetRTFactory() || !resMgr->GetTextureManager()) { + state.initialized = true; + state.enabled = false; + return; + } + state.resolution = res; + state.shadowHandle = resMgr->GetRTFactory()->CreateShadowMap(res, true, "rt_GrassShadow"); + state.shadowMap = resMgr->GetTextureManager()->GetNVRHITexture(state.shadowHandle); + + nvrhi::IDevice* nv = device->GetNVRHIDevice(); + auto* loader = GEnv.Render ? GEnv.Render->GetShaderLoader() : nullptr; + if (!nv || !loader) { + state.initialized = true; + state.enabled = false; + return; + } + + nvrhi::BufferDesc cbDesc; + cbDesc.byteSize = sizeof(ShadowCascadeCB); + cbDesc.isConstantBuffer = true; + cbDesc.isVolatile = true; + cbDesc.maxVersions = 16; + cbDesc.debugName = "GrassShadowCascadeCB"; + state.cascadeCB = nv->createBuffer(cbDesc); + cbDesc.byteSize = sizeof(GrassShadowCB); + cbDesc.debugName = "GrassShadowCB"; + state.grassCB = nv->createBuffer(cbDesc); + + auto& cache = GetPassResourceCache(); + nvrhi::IBindingLayout* bindlessLayout = GEnv.Backend ? GEnv.Backend->GetBindlessLayout() : nullptr; + nvrhi::FramebufferInfo fbInfo; + fbInfo.depthFormat = nvrhi::Format::D32; + + auto grassVs = loader->LoadVertexShader("detail_gpu_shadow", "main"); + auto grassPs = loader->LoadPixelShader("detail_gpu_shadow", "main"); + if (grassVs.handle && grassVs.reflection && grassPs.handle && grassPs.reflection) { + state.grassVs = grassVs.handle; + state.grassPs = grassPs.handle; + state.grassLayout = cache.GetOrCreateBindingLayoutFromReflection( + "GrassShadowBlade", *grassVs.reflection, *grassPs.reflection, nv); + if (state.grassLayout) { + nvrhi::VertexAttributeDesc grassAttrs[] = { + nvrhi::VertexAttributeDesc().setName("POSITION").setFormat(nvrhi::Format::RGB32_FLOAT).setOffset(0).setElementStride(sizeof(FGDetailManager::BladeVertex)), + nvrhi::VertexAttributeDesc().setName("TEXCOORD").setFormat(nvrhi::Format::RG32_FLOAT).setOffset(12).setElementStride(sizeof(FGDetailManager::BladeVertex)), + nvrhi::VertexAttributeDesc().setName("COLOR").setFormat(nvrhi::Format::R32_FLOAT).setArraySize(2).setOffset(20).setElementStride(sizeof(FGDetailManager::BladeVertex)), + }; + state.grassInputLayout = nv->createInputLayout(grassAttrs, 3, state.grassVs); + nvrhi::GraphicsPipelineDesc grassDesc; + grassDesc.VS = state.grassVs; + grassDesc.PS = state.grassPs; + grassDesc.inputLayout = state.grassInputLayout; + grassDesc.primType = nvrhi::PrimitiveType::TriangleList; + grassDesc.bindingLayouts = { state.grassLayout }; + if (bindlessLayout) + grassDesc.bindingLayouts.push_back(bindlessLayout); + grassDesc.renderState.depthStencilState.setDepthTestEnable(true); + grassDesc.renderState.depthStencilState.setDepthWriteEnable(true); + grassDesc.renderState.depthStencilState.setDepthFunc(nvrhi::ComparisonFunc::LessOrEqual); + grassDesc.renderState.rasterState.setCullMode(nvrhi::RasterCullMode::None); + grassDesc.renderState.rasterState.depthBias = 4; + grassDesc.renderState.rasterState.slopeScaledDepthBias = 3.0f; + state.grassPipeline = cache.GetOrCreatePipeline("GrassShadowBlade_v2", grassDesc, fbInfo, nv); + } + grassVs.reflection = nullptr; + grassPs.reflection = nullptr; + } + + auto bbVs = loader->LoadVertexShader("detail_billboard_shadow", "main"); + auto bbPs = loader->LoadPixelShader("detail_billboard_shadow", "main"); + if (bbVs.handle && bbVs.reflection && bbPs.handle && bbPs.reflection) { + state.billboardGrassVs = bbVs.handle; + state.billboardGrassPs = bbPs.handle; + state.billboardGrassLayout = cache.GetOrCreateBindingLayoutFromReflection( + "GrassShadowBillboard", *bbVs.reflection, *bbPs.reflection, nv); + if (state.billboardGrassLayout) { + nvrhi::GraphicsPipelineDesc bbDesc; + bbDesc.VS = state.billboardGrassVs; + bbDesc.PS = state.billboardGrassPs; + bbDesc.primType = nvrhi::PrimitiveType::TriangleList; + bbDesc.bindingLayouts = { state.billboardGrassLayout }; + if (bindlessLayout) + bbDesc.bindingLayouts.push_back(bindlessLayout); + bbDesc.renderState.depthStencilState.setDepthTestEnable(true); + bbDesc.renderState.depthStencilState.setDepthWriteEnable(true); + bbDesc.renderState.depthStencilState.setDepthFunc(nvrhi::ComparisonFunc::LessOrEqual); + bbDesc.renderState.rasterState.setCullMode(nvrhi::RasterCullMode::None); + state.billboardGrassPipeline = cache.GetOrCreatePipeline("GrassShadowBillboard_v2", bbDesc, fbInfo, nv); + } + bbVs.reflection = nullptr; + bbPs.reflection = nullptr; + } + + state.enabled = state.shadowMap && state.cascadeCB && state.grassCB && (state.grassPipeline || state.billboardGrassPipeline); + state.initialized = true; +} + +void ShutdownGrassShadowPass(fg::RenderDevice* device, GrassShadowPassState& state) +{ + (void)device; + state.grassPipeline = nullptr; + state.grassLayout = nullptr; + state.billboardGrassPipeline = nullptr; + state.billboardGrassLayout = nullptr; + state.grassInputLayout = nullptr; + state.grassVs = nullptr; + state.grassPs = nullptr; + state.billboardGrassVs = nullptr; + state.billboardGrassPs = nullptr; + state.cascadeCB = nullptr; + state.grassCB = nullptr; + state.shadowHandle = {}; + state.shadowMap = nullptr; + state.resolution = 0; + state.initialized = false; + state.enabled = false; +} + +GrassShadowOutputs setupGrassShadowPass( + FrameGraph& fg, + fg::RenderDevice* device, + FGDetailManager* detailManager, + const Fvector& sunDirection, + GrassShadowPassState& state, + VirtualResourceHandle orderAfter, + VirtualResourceHandle cullArgs) +{ + GrassShadowOutputs out{}; + if (!device || !detailManager || !ps_r2_ls_flags.test(R2FLAG_SUN_DETAILS)) + return out; + + InitializeGrassShadowPass(device, state); + if (!state.enabled || !state.shadowMap) + return out; + + ComputeSunNearOrtho(sunDirection, std::max(ps_r2_sun_near, ps_r_rt_detail_dist), state.resolution, state.clipVP, state.sampleVP); + + ResourceDesc desc; + desc.type = ResourceDesc::Type::Texture2D; + desc.width = state.resolution; + desc.height = state.resolution; + desc.format = nvrhi::Format::D32; + desc.isDepthStencil = true; + desc.isImported = true; + desc.debugName = "rt_GrassShadow"; + auto shadowHandle = fg.ImportTexture("rt_GrassShadow", state.shadowMap, desc); + + struct PassData { + VirtualResourceHandle shadow; + VirtualResourceHandle orderDep; + GrassShadowPassState* st = nullptr; + FGDetailManager* dm = nullptr; + fg::RenderDevice* device = nullptr; + }; + + auto& pass = fg.addCallbackPass( + "GrassShadow", + [&, shadowHandle, orderAfter](FrameGraph& builder, PassHandle passHandle, PassData& data) { + RenderPassBuilder pb(builder, passHandle); + if (orderAfter.is_valid()) + data.orderDep = pb.read(orderAfter, ResourceState::ShaderResource); + if (cullArgs.is_valid()) + pb.read(cullArgs, ResourceState::IndirectArgument); + data.shadow = pb.write(shadowHandle, ResourceState::DepthStencilWrite); + data.st = &state; + data.dm = detailManager; + data.device = device; + }, + [](const PassData& data, const FrameGraph& graph, fg::RenderContext* ctx) { + if (!data.st || !data.dm) + return; + auto* shadowTex = graph.GetPhysicalTexture(data.shadow); + if (!shadowTex) + shadowTex = data.st->shadowMap; + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + nvrhi::IDevice* nv = cmd ? cmd->getDevice() : nullptr; + if (!cmd || !nv || !shadowTex) + return; + + GrassShadowPassState& st = *data.st; + cmd->clearDepthStencilTexture(shadowTex, nvrhi::TextureSubresourceSet(0, 1, 0, 1), true, 1.0f, false, 0); + + ShadowCascadeCB ccb{}; + ccb.lightVP = st.clipVP; + cmd->writeBuffer(st.cascadeCB, &ccb, sizeof(ccb)); + GrassShadowCB gcb{}; + gcb.grassBladeHeight = ps_r3_grass_blade_height; + gcb.buildDetailsIndex = data.dm->buildDetailsBindlessIndex; + if (g_pGamePersistent) + gcb.windAngleDeg = g_pGamePersistent->Environment().CurrentEnv.wind_direction; + gcb.windSpeed = data.dm->windSpeed; + gcb.time = Device.fTimeGlobal; + gcb.windDisplacement = ps_r3_grass_wind_displacement; + cmd->writeBuffer(st.grassCB, &gcb, sizeof(gcb)); + + auto& cache = GetPassResourceCache(); + nvrhi::FramebufferDesc fbDesc; + fbDesc.setDepthAttachment(shadowTex); + auto fb = cache.GetOrCreateFramebuffer(make_string("GrassShadow_%u", st.resolution).c_str(), fbDesc, nv); + if (!fb) + return; + auto* shaderLoader = GEnv.Render->GetShaderLoader(); + nvrhi::IBindingSet* bindlessTable = GEnv.Backend ? GEnv.Backend->GetBindlessDescriptorTable() : nullptr; + const u32 cres = st.resolution; + nvrhi::Viewport vp(0.f, float(cres), 0.f, float(cres), 0.f, 1.f); + const bool billboardMode = !fg::ps_r__detail_gpu; + + if (billboardMode && st.billboardGrassPipeline && st.billboardGrassLayout && + data.dm->visibleBillboardInstancesBuffer && data.dm->billboardDrawArgsBuffer && + data.dm->pulledIndexBuffer && data.dm->maxPulledIndexCount > 0) { + auto* vsRefl = shaderLoader->GetCachedReflection("detail_billboard_shadow", ".vs"); + auto* psRefl = shaderLoader->GetCachedReflection("detail_billboard_shadow", ".ps"); + if (vsRefl && psRefl) { + BindingSetBuilder gsb(*vsRefl, *psRefl, nv, "GrassShadow.BB"); + gsb.ConstantBuffer("ShadowCascadeCB", st.cascadeCB); + gsb.ConstantBuffer("GrassShadowCB", st.grassCB); + BindBindlessMaterialTables(gsb); + gsb.BufferSRV("visible_indices", data.dm->visibleBillboardInstancesBuffer); + gsb.BufferSRV("detail_models", data.dm->detailModelsBuffer); + gsb.BufferSRV("pulled_vertices", data.dm->pulledVertexBuffer); + gsb.BufferSRV("all_instances", data.dm->generatedInstancesBuffer); + gsb.Texture("g_Perlin4D", data.dm->perlin4dTexture); + auto set = cache.GetOrCreateBindingSet(gsb.Build(), st.billboardGrassLayout, nv); + if (set) { + nvrhi::GraphicsState gs; + gs.pipeline = st.billboardGrassPipeline; + gs.framebuffer = fb; + gs.bindings = { set }; + if (bindlessTable) + gs.addBindingSet(bindlessTable); + gs.indexBuffer = { data.dm->pulledIndexBuffer, nvrhi::Format::R16_UINT, 0 }; + gs.viewport.addViewport(vp); + gs.viewport.addScissorRect(nvrhi::Rect(cres, cres)); + gs.indirectParams = data.dm->billboardDrawArgsBuffer; + cmd->setGraphicsState(gs); + cmd->drawIndexedIndirect(0); + } + if (data.dm->visibleDecalInstancesBuffer && data.dm->decalDrawArgsBuffer) { + BindingSetBuilder dsb(*vsRefl, *psRefl, nv, "GrassShadow.Decal"); + dsb.ConstantBuffer("ShadowCascadeCB", st.cascadeCB); + dsb.ConstantBuffer("GrassShadowCB", st.grassCB); + BindBindlessMaterialTables(dsb); + dsb.BufferSRV("visible_indices", data.dm->visibleDecalInstancesBuffer); + dsb.BufferSRV("detail_models", data.dm->detailModelsBuffer); + dsb.BufferSRV("pulled_vertices", data.dm->pulledVertexBuffer); + dsb.BufferSRV("all_instances", data.dm->generatedInstancesBuffer); + dsb.Texture("g_Perlin4D", data.dm->perlin4dTexture); + auto dset = cache.GetOrCreateBindingSet(dsb.Build(), st.billboardGrassLayout, nv); + if (dset) { + nvrhi::GraphicsState gs; + gs.pipeline = st.billboardGrassPipeline; + gs.framebuffer = fb; + gs.bindings = { dset }; + if (bindlessTable) + gs.addBindingSet(bindlessTable); + gs.indexBuffer = { data.dm->pulledIndexBuffer, nvrhi::Format::R16_UINT, 0 }; + gs.viewport.addViewport(vp); + gs.viewport.addScissorRect(nvrhi::Rect(cres, cres)); + gs.indirectParams = data.dm->decalDrawArgsBuffer; + cmd->setGraphicsState(gs); + cmd->drawIndexedIndirect(0); + } + } + } + } else if (!billboardMode && st.grassPipeline && st.grassLayout) { + auto* vsRefl = shaderLoader->GetCachedReflection("detail_gpu_shadow", ".vs"); + auto* psRefl = shaderLoader->GetCachedReflection("detail_gpu_shadow", ".ps"); + if (vsRefl && psRefl) { + for (u32 lod = 0; lod < 2 && lod < FGDetailManager::LOD_COUNT; ++lod) { + if (!data.dm->visibleInstancesBuffer[lod] || !data.dm->drawArgsBuffer[lod] || + !data.dm->bladeVertexBuffer[lod] || !data.dm->bladeIndexBuffer[lod]) + continue; + BindingSetBuilder gsb(*vsRefl, *psRefl, nv, "GrassShadow.Blade"); + gsb.ConstantBuffer("ShadowCascadeCB", st.cascadeCB); + gsb.ConstantBuffer("GrassShadowCB", st.grassCB); + gsb.BufferSRV("visible_indices", data.dm->visibleInstancesBuffer[lod]); + gsb.BufferSRV("all_instances", data.dm->generatedInstancesBuffer); + auto set = cache.GetOrCreateBindingSet(gsb.Build(), st.grassLayout, nv); + if (!set) + continue; + nvrhi::GraphicsState gs; + gs.pipeline = st.grassPipeline; + gs.framebuffer = fb; + gs.bindings = { set }; + if (bindlessTable) + gs.addBindingSet(bindlessTable); + gs.vertexBuffers = { { data.dm->bladeVertexBuffer[lod], 0, 0 } }; + gs.indexBuffer = { data.dm->bladeIndexBuffer[lod], nvrhi::Format::R16_UINT, 0 }; + gs.viewport.addViewport(vp); + gs.viewport.addScissorRect(nvrhi::Rect(cres, cres)); + gs.indirectParams = data.dm->drawArgsBuffer[lod]; + cmd->setGraphicsState(gs); + cmd->drawIndexedIndirect(0); + } + } + } + }); + + out.shadowMap = pass.shadow; + out.shadowTex = state.shadowMap; + out.sampleVP = state.sampleVP; + out.valid = true; + return out; +} + +} diff --git a/src/Layers/xrRender/FrameGraphPasses/LensFlarePassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/LensFlarePassSetup.cpp index 08f7dd202c7..e0c0b916afa 100644 --- a/src/Layers/xrRender/FrameGraphPasses/LensFlarePassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/LensFlarePassSetup.cpp @@ -35,8 +35,6 @@ framegraph::VirtualResourceHandle setupLensFlarePass(framegraph::FrameGraph& fg, nvrhi::FramebufferDesc fbDesc; fbDesc.addColorAttachment(outputRT); - if (depth) - fbDesc.setDepthAttachment(depth); auto framebuffer = cmdList->getDevice()->createFramebuffer(fbDesc); data.renderer->DispatchVisibility(cmdList, depth); diff --git a/src/Layers/xrRender/FrameGraphPasses/MotionVectorPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/MotionVectorPassSetup.cpp index 730c0491b5a..44a83c72291 100644 --- a/src/Layers/xrRender/FrameGraphPasses/MotionVectorPassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/MotionVectorPassSetup.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "MotionVectorPassSetup.h" +#include "TAAPassSetup.h" #include "Layers/xrRender/FrameGraph/BindingSetBuilder.h" #include "Layers/xrRender/FrameGraph/FrameGraph.h" #include "Layers/xrRender/FrameGraph/PassResourceCache.h" @@ -16,40 +17,57 @@ namespace fg namespace xray::render::fg::passes { using namespace framegraph; +namespace +{ +constexpr u32 kMotionVectorPipeVersion = 9; +} + static void InitializeResources(fg::RenderDevice* device, MotionVectorPassState& state) { - if (state.initialized) return; + if (state.initialized && state.pipeVersion == kMotionVectorPipeVersion && state.pipeline) + return; + + state.initialized = false; + state.pipeVersion = 0; + state.pipeline = nullptr; + state.layout = nullptr; + state.cb = nullptr; auto& cache = GetPassResourceCache(); nvrhi::IDevice* nvDevice = device->GetNVRHIDevice(); + BindingSetBuilder::InvalidateReflectionCache(); auto csResult = GEnv.Render->GetShaderLoader()->LoadComputeShader("restir_motion_vectors"); - if (!csResult.handle) return; + if (!csResult.handle || !csResult.reflection) + return; - state.layout = cache.GetOrCreateBindingLayoutFromReflection("MotionVector", *csResult.reflection, nvDevice); + state.layout = cache.GetOrCreateBindingLayoutFromReflection( + "MotionVector_v3", *csResult.reflection, nvDevice); nvrhi::ComputePipelineDesc pipeDesc; pipeDesc.CS = csResult.handle; pipeDesc.bindingLayouts = { state.layout }; - state.pipeline = cache.GetOrCreateComputePipeline("MotionVector", pipeDesc, nvDevice); + state.pipeline = cache.GetOrCreateComputePipeline("MotionVector_v3", pipeDesc, nvDevice); - state.cb = cache.GetOrCreateVolatileCB("MotionVector", "MotionVectorCB", 160, device); + state.cb = cache.GetOrCreateVolatileCB("MotionVector", "MotionVectorCB_v5", 320, device); state.initialized = true; + state.pipeVersion = kMotionVectorPipeVersion; } MotionVectorOutput setupMotionVectorPass( FrameGraph& fg, fg::RenderDevice* device, VirtualResourceHandle depthInput, - const Fmatrix& invViewProj, + const Fmatrix& viewProj, const Fmatrix& prevViewProj, + const Fmatrix& invViewProjJittered, u32 width, u32 height, MotionVectorPassState& state) { InitializeResources(device, state); - if (!state.pipeline) + if (!state.pipeline || !depthInput.is_valid()) return {}; ResourceDesc mvDesc; @@ -57,19 +75,29 @@ MotionVectorOutput setupMotionVectorPass( mvDesc.debugName = "rt_MotionVectors"; mvDesc.width = width; mvDesc.height = height; - mvDesc.format = nvrhi::Format::RG16_FLOAT; + mvDesc.format = nvrhi::Format::RGBA16_FLOAT; mvDesc.isUAV = true; - mvDesc.isTransient = true; + mvDesc.isTransient = false; auto mvHandle = fg.CreateTexture("rt_MotionVectors", mvDesc); + const Fvector cameraPos = Device.vCameraPosition; + const bool hasPrevCamera = state.hasPrevCamera; + const Fvector prevCameraPos = state.prevCameraPos; + state.prevCameraPos = cameraPos; + state.hasPrevCamera = true; + struct PassData { VirtualResourceHandle depth; VirtualResourceHandle motionVectors; fg::RenderDevice* device; MotionVectorPassState* state; - Fmatrix invViewProj; + Fmatrix viewProj; Fmatrix prevViewProj; + Fmatrix invViewProj; + Fvector cameraPos; + Fvector prevCameraPos; u32 width, height; + u32 hasPrevCamera; }; auto& passData = fg.addCallbackPass( @@ -80,28 +108,48 @@ MotionVectorOutput setupMotionVectorPass( data.motionVectors = pb.write(mvHandle, ResourceState::UnorderedAccess); data.device = device; data.state = &state; - data.invViewProj = invViewProj; + data.viewProj = viewProj; data.prevViewProj = prevViewProj; + data.invViewProj = invViewProjJittered; + data.cameraPos = cameraPos; + data.prevCameraPos = prevCameraPos; data.width = width; data.height = height; + data.hasPrevCamera = hasPrevCamera ? 1u : 0u; }, - [](const PassData& data, const FrameGraph& fg, fg::RenderContext* ctx) { - auto* depthTex = fg.GetPhysicalTexture(data.depth); - auto* mvTex = fg.GetPhysicalTexture(data.motionVectors); + [](const PassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* mvTex = fgGraph.GetPhysicalTexture(data.motionVectors); if (!depthTex || !mvTex) return; - struct { - Fmatrix invViewProj; + struct alignas(16) { + Fmatrix viewProj; Fmatrix prevViewProj; + Fmatrix invViewProj; float screenW, screenH; float invScreenW, invScreenH; + Fvector4 cameraPos; + Fvector4 prevCameraPos; + u32 hasPrevCamera; + float currJitterX, currJitterY; + float prevJitterX, prevJitterY; + float pad0, pad1, pad2; } cb; - cb.invViewProj = data.invViewProj; + cb.viewProj = data.viewProj; cb.prevViewProj = data.prevViewProj; + cb.invViewProj = data.invViewProj; cb.screenW = (float)data.width; cb.screenH = (float)data.height; cb.invScreenW = 1.0f / data.width; cb.invScreenH = 1.0f / data.height; + cb.cameraPos.set(data.cameraPos.x, data.cameraPos.y, data.cameraPos.z, 0.f); + cb.prevCameraPos.set(data.prevCameraPos.x, data.prevCameraPos.y, data.prevCameraPos.z, 0.f); + cb.hasPrevCamera = data.hasPrevCamera; + cb.currJitterX = g_taa_jitter_px; + cb.currJitterY = g_taa_jitter_py; + cb.prevJitterX = g_taa_jitter_prev_px; + cb.prevJitterY = g_taa_jitter_prev_py; + cb.pad0 = cb.pad1 = cb.pad2 = 0; nvrhi::IDevice* nvDevice = data.device->GetNVRHIDevice(); nvrhi::ICommandList* cmdList = ctx->GetCommandList(); diff --git a/src/Layers/xrRender/FrameGraphPasses/MotionVectorPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/MotionVectorPassSetup.h index 250a8103b58..6b5b08c77b7 100644 --- a/src/Layers/xrRender/FrameGraphPasses/MotionVectorPassSetup.h +++ b/src/Layers/xrRender/FrameGraphPasses/MotionVectorPassSetup.h @@ -13,6 +13,9 @@ struct MotionVectorPassState { nvrhi::BindingLayoutHandle layout; nvrhi::IBuffer* cb = nullptr; bool initialized = false; + u32 pipeVersion = 0; + Fvector prevCameraPos{}; + bool hasPrevCamera = false; }; struct MotionVectorOutput { @@ -23,8 +26,9 @@ MotionVectorOutput setupMotionVectorPass( framegraph::FrameGraph& fg, fg::RenderDevice* device, framegraph::VirtualResourceHandle depthInput, - const Fmatrix& invViewProj, + const Fmatrix& viewProj, const Fmatrix& prevViewProj, + const Fmatrix& invViewProjJittered, u32 width, u32 height, MotionVectorPassState& state); diff --git a/src/Layers/xrRender/FrameGraphPasses/ParticlePassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/ParticlePassSetup.cpp index 5dd938ea625..9742bf8ab91 100644 --- a/src/Layers/xrRender/FrameGraphPasses/ParticlePassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/ParticlePassSetup.cpp @@ -355,6 +355,62 @@ static u32 GenerateParticleVertices( return totalParticles; } +bool ParticleBatchLooksEmissive(const ParticleBatch& batch) +{ + if (batch.isHUDMode || batch.shaderVariant == ParticleShaderVariant::Distort) + return false; + if (batch.shaderVariant == ParticleShaderVariant::Emissive) + return true; + if (!batch.visual || batch.visual->getType() != MT_PARTICLE_EFFECT) + return false; + auto* pEffect = static_cast(batch.visual); + auto* pDef = pEffect ? pEffect->GetDefinition() : nullptr; + if (!pDef) + return false; + const char* sh = pDef->m_ShaderName.c_str(); + const char* tex = pDef->m_TextureName.c_str(); + auto has = [](const char* s, const char* k) { return s && strstr(s, k) != nullptr; }; + return has(sh, "glow") || has(sh, "flare") || has(tex, "glow") || has(tex, "fire") + || has(tex, "flame") || has(tex, "explosion") || has(tex, "ani-fire") + || has(tex, "ani-explosion") || has(tex, "grenade") || has(tex, "blast") + || has(tex, "flash") || has(tex, "flare") || has(tex, "spark") + || has(tex, "anomaly") || has(tex, "heat") || has(tex, "zhar") + || has(sh, "anomaly") || has(sh, "heat"); +} + +u32 BuildEmissiveParticleRTGeometry( + const xr_vector& worldBatches, + xr_vector& vertices, + xr_vector& indices, + u32 maxQuads) +{ + xr_vector filtered; + filtered.reserve(worldBatches.size()); + for (const auto& b : worldBatches) { + if (ParticleBatchLooksEmissive(b)) + filtered.push_back(b); + } + if (filtered.empty()) + return 0; + u32 quads = GenerateParticleVertices(filtered, vertices); + if (quads > maxQuads) { + vertices.resize(maxQuads * 4); + quads = maxQuads; + } + indices.resize(quads * 6); + for (u32 i = 0; i < quads; i++) { + const u32 v = i * 4; + const u32 o = i * 6; + indices[o + 0] = v + 0; + indices[o + 1] = v + 1; + indices[o + 2] = v + 2; + indices[o + 3] = v + 1; + indices[o + 4] = v + 3; + indices[o + 5] = v + 2; + } + return quads; +} + struct ParticleBlendDesc { nvrhi::BlendFactor srcBlend; nvrhi::BlendFactor destBlend; @@ -366,18 +422,21 @@ struct ParticleBlendDesc { }; static const ParticleBlendDesc s_blendDescs[PARTICLE_BLEND_COUNT] = { - { nvrhi::BlendFactor::One, nvrhi::BlendFactor::Zero, nvrhi::BlendFactor::One, nvrhi::BlendFactor::Zero, false, true, "ParticlePass_set" }, - { nvrhi::BlendFactor::SrcAlpha, nvrhi::BlendFactor::InvSrcAlpha, nvrhi::BlendFactor::One, nvrhi::BlendFactor::InvSrcAlpha, true, false, "ParticlePass_blend" }, - { nvrhi::BlendFactor::One, nvrhi::BlendFactor::One, nvrhi::BlendFactor::One, nvrhi::BlendFactor::One, true, false, "ParticlePass_add" }, - { nvrhi::BlendFactor::DstColor, nvrhi::BlendFactor::Zero, nvrhi::BlendFactor::One, nvrhi::BlendFactor::Zero, true, false, "ParticlePass_mul" }, - { nvrhi::BlendFactor::DstColor, nvrhi::BlendFactor::SrcColor, nvrhi::BlendFactor::One, nvrhi::BlendFactor::SrcAlpha, true, false, "ParticlePass_mul2x" }, - { nvrhi::BlendFactor::SrcAlpha, nvrhi::BlendFactor::One, nvrhi::BlendFactor::One, nvrhi::BlendFactor::One, true, false, "ParticlePass_alphaAdd" }, + { nvrhi::BlendFactor::One, nvrhi::BlendFactor::Zero, nvrhi::BlendFactor::One, nvrhi::BlendFactor::Zero, false, true, "ParticlePass_set_v2" }, + { nvrhi::BlendFactor::SrcAlpha, nvrhi::BlendFactor::InvSrcAlpha, nvrhi::BlendFactor::One, nvrhi::BlendFactor::InvSrcAlpha, true, false, "ParticlePass_blend_v2" }, + { nvrhi::BlendFactor::One, nvrhi::BlendFactor::One, nvrhi::BlendFactor::One, nvrhi::BlendFactor::One, true, false, "ParticlePass_add_v2" }, + { nvrhi::BlendFactor::DstColor, nvrhi::BlendFactor::Zero, nvrhi::BlendFactor::One, nvrhi::BlendFactor::Zero, true, false, "ParticlePass_mul_v2" }, + { nvrhi::BlendFactor::DstColor, nvrhi::BlendFactor::SrcColor, nvrhi::BlendFactor::One, nvrhi::BlendFactor::SrcAlpha, true, false, "ParticlePass_mul2x_v2" }, + { nvrhi::BlendFactor::SrcAlpha, nvrhi::BlendFactor::One, nvrhi::BlendFactor::One, nvrhi::BlendFactor::One, true, false, "ParticlePass_alphaAdd_v2" }, }; void InitializeParticleResources(fg::RenderDevice* device, const nvrhi::FramebufferInfoEx& fbInfo, ParticlePassState& state) { - if (state.initialized) + constexpr u32 kParticlePipeVersion = 2; + if (state.initialized && state.pipeVersion == kParticlePipeVersion) return; + state.initialized = false; + state.pipeVersion = 0; nvrhi::IDevice* nvDevice = device->GetNVRHIDevice(); if (!nvDevice) @@ -452,6 +511,7 @@ void InitializeParticleResources(fg::RenderDevice* device, const nvrhi::Framebuf } state.initialized = true; + state.pipeVersion = kParticlePipeVersion; Msg("* [ParticlePass] Pipeline initialization complete (6 blend modes + distortion)"); if (!state.cullPipeline) { @@ -504,8 +564,12 @@ void InitializeParticleResources(fg::RenderDevice* device, const nvrhi::Framebuf static void InitializeDistortionPipeline(fg::RenderDevice* device, const nvrhi::FramebufferInfoEx& fbInfo, ParticlePassState& state) { - if (state.distortInitialized) + constexpr u32 kDistortVersion = 2; + if (state.distortInitialized && state.distortVersion == kDistortVersion) return; + state.distortInitialized = false; + state.distortVersion = kDistortVersion; + state.distortPipeline = nullptr; nvrhi::IDevice* nvDevice = device->GetNVRHIDevice(); auto* shaderLoader = GEnv.Render->GetShaderLoader(); @@ -536,13 +600,16 @@ static void InitializeDistortionPipeline(fg::RenderDevice* device, const nvrhi:: pipeDesc.renderState.depthStencilState.depthWriteEnable = false; pipeDesc.renderState.depthStencilState.depthFunc = nvrhi::ComparisonFunc::GreaterOrEqual; pipeDesc.renderState.rasterState.cullMode = nvrhi::RasterCullMode::None; - pipeDesc.renderState.blendState.targets[0].enableBlend(); - pipeDesc.renderState.blendState.targets[0].srcBlend = nvrhi::BlendFactor::One; - pipeDesc.renderState.blendState.targets[0].destBlend = nvrhi::BlendFactor::One; - pipeDesc.renderState.blendState.targets[0].srcBlendAlpha = nvrhi::BlendFactor::One; - pipeDesc.renderState.blendState.targets[0].destBlendAlpha = nvrhi::BlendFactor::One; + auto& brt = pipeDesc.renderState.blendState.targets[0]; + brt.blendEnable = true; + brt.srcBlend = nvrhi::BlendFactor::SrcAlpha; + brt.destBlend = nvrhi::BlendFactor::InvSrcAlpha; + brt.blendOp = nvrhi::BlendOp::Add; + brt.srcBlendAlpha = nvrhi::BlendFactor::One; + brt.destBlendAlpha = nvrhi::BlendFactor::InvSrcAlpha; + brt.blendOpAlpha = nvrhi::BlendOp::Add; - state.distortPipeline = cache.GetOrCreatePipeline("ParticlePass_distort", pipeDesc, fbInfo, nvDevice); + state.distortPipeline = cache.GetOrCreatePipeline("ParticlePass_distort_v2_Alpha", pipeDesc, fbInfo, nvDevice); if (state.distortPipeline) { const auto& actualDesc = state.distortPipeline->getDesc(); @@ -568,13 +635,12 @@ ParticlePassOutput setupParticlePass( u32 hiZMipLevels, const Fmatrix* prevViewProj, VirtualResourceHandle prevDepth, - ParticlePassState* state) + ParticlePassState* state, + VirtualResourceHandle seedDistortion) { if (state) { nvrhi::FramebufferInfoEx fbInfo; fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); - fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); - fbInfo.colorFormats.push_back(nvrhi::Format::RGBA8_UNORM); fbInfo.depthFormat = nvrhi::Format::D32; InitializeParticleResources(device, fbInfo, *state); @@ -586,7 +652,7 @@ ParticlePassOutput setupParticlePass( auto& passData = fg.addCallbackPass( "Particles", - [&, width, height, hiZPyramid, hiZWidth, hiZHeight, hiZMipLevels, state](FrameGraph& builder, PassHandle passHandle, ParticlePassData& data) { + [&, width, height, hiZPyramid, hiZWidth, hiZHeight, hiZMipLevels, state, seedDistortion](FrameGraph& builder, PassHandle passHandle, ParticlePassData& data) { RenderPassBuilder passBuilder(builder, passHandle); data.width = width; @@ -612,19 +678,28 @@ ParticlePassOutput setupParticlePass( if (b.shaderVariant == ParticleShaderVariant::Distort) return true; return false; }; - data.hasDistortion = hasDistortBatch(worldParticleBatches) || hasDistortBatch(hudParticleBatches); + const bool hasParticleDistort = + hasDistortBatch(worldParticleBatches) || hasDistortBatch(hudParticleBatches); + data.hasDistortion = hasParticleDistort || seedDistortion.is_valid(); + data.importedDistortion = false; + data.seedDistortion = {}; if (data.hasDistortion) { - framegraph::ResourceDesc distDesc; - distDesc.type = framegraph::ResourceDesc::Type::Texture2D; - distDesc.width = width; - distDesc.height = height; - distDesc.format = nvrhi::Format::RGBA16_FLOAT; - distDesc.isRenderTarget = true; - distDesc.isTransient = true; - distDesc.isUAV = true; - distDesc.debugName = "rt_Distortion"; - data.distortionRT = passBuilder.createTexture("rt_Distortion", distDesc); + if (seedDistortion.is_valid()) { + data.distortionRT = passBuilder.readWrite(seedDistortion, ResourceState::RenderTarget); + data.importedDistortion = true; + } else { + framegraph::ResourceDesc distDesc; + distDesc.type = framegraph::ResourceDesc::Type::Texture2D; + distDesc.width = width; + distDesc.height = height; + distDesc.format = nvrhi::Format::RGBA16_FLOAT; + distDesc.isRenderTarget = true; + distDesc.isTransient = true; + distDesc.isUAV = true; + distDesc.debugName = "rt_Distortion"; + data.distortionRT = passBuilder.createTexture("rt_Distortion", distDesc); + } } if (hiZPyramid.is_valid()) @@ -632,10 +707,9 @@ ParticlePassOutput setupParticlePass( data.inputColor = passBuilder.read(forwardInputs.albedo); data.outputColor = passBuilder.write(forwardInputs.albedo, ResourceState::RenderTarget); - data.outputNormal = passBuilder.readWrite(forwardInputs.normal, ResourceState::RenderTarget); + data.outputNormal = forwardInputs.normal; data.depth = passBuilder.readWrite(forwardInputs.depth, ResourceState::DepthStencilWrite); - if (forwardInputs.baseColor.is_valid()) - data.baseColor = passBuilder.readWrite(forwardInputs.baseColor, ResourceState::RenderTarget); + data.baseColor = forwardInputs.baseColor; if (prevDepth.is_valid()) data.prevDepth = passBuilder.read(prevDepth, ResourceState::ShaderResource); @@ -655,7 +729,6 @@ ParticlePassOutput setupParticlePass( return; auto* colorRT = fg.GetPhysicalTexture(data.outputColor); - auto* normalRT = fg.GetPhysicalTexture(data.outputNormal); auto* depthRT = fg.GetPhysicalTexture(data.depth); if (!colorRT || !depthRT) return; @@ -670,18 +743,13 @@ ParticlePassOutput setupParticlePass( auto& matBuffer = MaterialBuffer::Instance(); matBuffer.Upload(ctx); - auto* baseColorRT = data.baseColor.is_valid() ? fg.GetPhysicalTexture(data.baseColor) : nullptr; auto* prevDepthTex = data.prevDepth.is_valid() ? fg.GetPhysicalTexture(data.prevDepth) : depthRT; nvrhi::FramebufferDesc fbDesc; fbDesc.addColorAttachment(colorRT); - if (normalRT) - fbDesc.addColorAttachment(normalRT); - if (baseColorRT) - fbDesc.addColorAttachment(baseColorRT); fbDesc.setDepthAttachment(depthRT); auto& cache = framegraph::GetPassResourceCache(); - auto framebuffer = cache.GetOrCreateFramebuffer("ParticlePass", fbDesc, nvDevice); + auto framebuffer = cache.GetOrCreateFramebuffer("ParticlePass_v2", fbDesc, nvDevice); if (!framebuffer) return; @@ -989,6 +1057,22 @@ ParticlePassOutput setupParticlePass( if (!distortRT) return; + bool hasParticleDistort = false; + auto checkDistort = [&](const xr_vector* batches) { + if (!batches) return; + for (const auto& b : *batches) + if (b.shaderVariant == ParticleShaderVariant::Distort) + hasParticleDistort = true; + }; + checkDistort(data.worldParticleBatches); + checkDistort(data.hudParticleBatches); + + if (!data.importedDistortion) + cmdList->clearTextureFloat(distortRT, nvrhi::AllSubresources, nvrhi::Color(0.5f, 0.5f, 0.f, 0.f)); + + if (!hasParticleDistort) + return; + nvrhi::FramebufferDesc distortFbDesc; distortFbDesc.addColorAttachment(distortRT); distortFbDesc.setDepthAttachment(depthRT); @@ -999,8 +1083,6 @@ ParticlePassOutput setupParticlePass( if (!data.passState->distortPipeline) return; - cmdList->clearTextureFloat(distortRT, nvrhi::AllSubresources, nvrhi::Color(0.f, 0.f, 0.f, 0.f)); - auto* distortVsReflection = shaderLoader->GetCachedReflection("bindless_particle", ".vs"); auto* distortPsReflection = shaderLoader->GetCachedReflection("bindless_particle_distort", ".ps"); BindingSetBuilder distortBsb(*distortVsReflection, *distortPsReflection, nvDevice, "Particle.Distort"); diff --git a/src/Layers/xrRender/FrameGraphPasses/ParticlePassSetup.h b/src/Layers/xrRender/FrameGraphPasses/ParticlePassSetup.h index db48cbf9269..0ec614a7e36 100644 --- a/src/Layers/xrRender/FrameGraphPasses/ParticlePassSetup.h +++ b/src/Layers/xrRender/FrameGraphPasses/ParticlePassSetup.h @@ -78,7 +78,9 @@ struct ParticlePassState { nvrhi::ShaderHandle distortPS; nvrhi::SamplerHandle sampler; bool initialized = false; + u32 pipeVersion = 0; bool distortInitialized = false; + u32 distortVersion = 0; nvrhi::BufferHandle particleVB; u32 particleVBSize = 0; nvrhi::BufferHandle quadIB; @@ -104,6 +106,7 @@ struct ParticlePassData { framegraph::VirtualResourceHandle baseColor; framegraph::VirtualResourceHandle hiZPyramid; framegraph::VirtualResourceHandle distortionRT; + framegraph::VirtualResourceHandle seedDistortion; framegraph::VirtualResourceHandle prevDepth; fg::RenderDevice* device; const xr_vector* worldParticleBatches; @@ -119,6 +122,7 @@ struct ParticlePassData { bool hasPrevViewProj; ParticlePassState* passState; bool hasDistortion; + bool importedDistortion; }; struct ParticlePassOutput { @@ -136,6 +140,14 @@ void InitializeParticleResources(fg::RenderDevice* device, const nvrhi::Framebuf // Renders AFTER forward color and skinning passes (particles on top of world+HUD) // Supports both world and HUD particles with proper FOV handling // When hiZPyramid is valid, uses GPU frustum + occlusion culling +bool ParticleBatchLooksEmissive(const ParticleBatch& batch); + +u32 BuildEmissiveParticleRTGeometry( + const xr_vector& worldBatches, + xr_vector& vertices, + xr_vector& indices, + u32 maxQuads); + ParticlePassOutput setupParticlePass( framegraph::FrameGraph& fg, fg::RenderDevice* device, @@ -151,7 +163,8 @@ ParticlePassOutput setupParticlePass( u32 hiZMipLevels = 0, const Fmatrix* prevViewProj = nullptr, framegraph::VirtualResourceHandle prevDepth = {}, - ParticlePassState* state = nullptr + ParticlePassState* state = nullptr, + framegraph::VirtualResourceHandle seedDistortion = {} ); } // namespace xray::render::fg::passes diff --git a/src/Layers/xrRender/FrameGraphPasses/PassCommon.cpp b/src/Layers/xrRender/FrameGraphPasses/PassCommon.cpp index b373e86a18e..baf66a8c6e0 100644 --- a/src/Layers/xrRender/FrameGraphPasses/PassCommon.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/PassCommon.cpp @@ -1,6 +1,14 @@ #include "stdafx.h" #include "PassCommon.h" #include "Layers/xrRender/FrameGraph/PassResourceCache.h" +#include "Layers/xrRender/FrameGraph/BindingSetBuilder.h" +#include "Layers/xrRender/Bindless/MaterialBuffer.h" +#include "Layers/xrRender/Bindless/TerrainMaterialBuffer.h" +#include "Layers/xrRender/Bindless/VariantTextureBuffer.h" +#include "Layers/xrRender/Bindless/BindlessTypes.h" +#include "Layers/xrRender/ResourceManager/FGResourceManager.h" +#include "Layers/xrRender/ResourceManager/TextureManager.h" +#include "Layers/xrRender/RenderContext/RenderDevice.h" #include "xrEngine/IGame_Persistent.h" #include "xrEngine/Environment.h" #include "xrEngine/device.h" @@ -71,4 +79,128 @@ u32 ExtractFrustumPlanes(Fvector4 outPlanes[6]) return count; } +void ResolveEnvSkyCubes(fg::RenderDevice* device, nvrhi::ITexture*& outSky0, nvrhi::ITexture*& outSky1) +{ + outSky0 = nullptr; + outSky1 = nullptr; + + nvrhi::IDevice* nvDevice = device ? device->GetNVRHIDevice() : nullptr; + auto* texManager = (device && device->GetFGResourceManager()) + ? device->GetFGResourceManager()->GetTextureManager() + : nullptr; + + static shared_str s_cachedName0; + static shared_str s_cachedName1; + static nvrhi::ITexture* s_cachedTex0 = nullptr; + static nvrhi::ITexture* s_cachedTex1 = nullptr; + + if (texManager && g_pGamePersistent) + { + auto& env = g_pGamePersistent->Environment(); + shared_str name0; + shared_str name1; + if (env.Current[0]) + { + name0 = env.Current[0]->sky_texture_env_name.size() + ? env.Current[0]->sky_texture_env_name + : env.Current[0]->sky_texture_name; + } + if (env.Current[1]) + { + name1 = env.Current[1]->sky_texture_env_name.size() + ? env.Current[1]->sky_texture_env_name + : env.Current[1]->sky_texture_name; + } + + if (name0.size() && name0 != s_cachedName0) + { + s_cachedName0 = name0; + s_cachedTex0 = texManager->GetNVRHITexture(texManager->LoadTexture(name0.c_str())); + } + if (name1.size() && name1 != s_cachedName1) + { + s_cachedName1 = name1; + s_cachedTex1 = texManager->GetNVRHITexture(texManager->LoadTexture(name1.c_str())); + } + + if (name0.size()) + outSky0 = s_cachedTex0; + if (name1.size()) + outSky1 = s_cachedTex1; + + static shared_str s_reportedName0; + static shared_str s_reportedName1; + if (name0 != s_reportedName0 || name1 != s_reportedName1) + { + Msg("* [EnvIBL] env_s0='%s' resolved=%d env_s1='%s' resolved=%d", + name0.c_str(), outSky0 ? 1 : 0, name1.c_str(), outSky1 ? 1 : 0); + s_reportedName0 = name0; + s_reportedName1 = name1; + } + } + + auto& cache = framegraph::GetPassResourceCache(); + if (!outSky0 && nvDevice) + outSky0 = cache.GetDummyCubeMap(nvDevice); + if (!outSky1 && nvDevice) + outSky1 = cache.GetDummyCubeMap(nvDevice); +} + +void BindBindlessMaterialTables(framegraph::BindingSetBuilder& bsb) +{ + static nvrhi::BufferHandle s_dummyMat; + static nvrhi::BufferHandle s_dummyTerrain; + static nvrhi::BufferHandle s_dummyVariant; + + auto ensureDummy = [](nvrhi::BufferHandle& slot, const char* name, u32 stride) -> nvrhi::IBuffer* { + if (!slot) + { + auto* backend = GEnv.Render ? GEnv.Render->GetRenderDevice() : nullptr; + nvrhi::IDevice* dev = backend ? backend->GetNVRHIDevice() : nullptr; + if (!dev) + return nullptr; + nvrhi::BufferDesc desc; + desc.byteSize = stride * 4; + desc.structStride = stride; + desc.debugName = name; + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + slot = dev->createBuffer(desc); + } + return slot.Get(); + }; + + nvrhi::IBuffer* mats = bindless::MaterialBuffer::Instance().GetBuffer(); + if (!mats) + mats = ensureDummy(s_dummyMat, "DummyMaterials", sizeof(bindless::MaterialData)); + if (mats && bsb.HasSRV("g_Materials")) + bsb.BufferSRV("g_Materials", mats); + + nvrhi::IBuffer* terrain = bindless::TerrainMaterialBuffer::Instance().GetBuffer(); + if (!terrain) + terrain = ensureDummy(s_dummyTerrain, "DummyTerrainMaterials", sizeof(bindless::TerrainMaterialData)); + if (terrain && bsb.HasSRV("g_TerrainMaterials")) + bsb.BufferSRV("g_TerrainMaterials", terrain); + + nvrhi::IBuffer* variants = bindless::VariantTextureBuffer::Instance().GetBuffer(); + if (!variants) + variants = ensureDummy(s_dummyVariant, "DummyVariantTextures", sizeof(bindless::VariantTextureData)); + if (variants && bsb.HasSRV("g_VariantTextures")) + bsb.BufferSRV("g_VariantTextures", variants); +} + +void BindEnvIblCubes(framegraph::BindingSetBuilder& bsb, fg::RenderDevice* device) +{ + if (!bsb.HasSRV("env_s0") && !bsb.HasSRV("env_s1")) + return; + + nvrhi::ITexture* sky0 = nullptr; + nvrhi::ITexture* sky1 = nullptr; + ResolveEnvSkyCubes(device, sky0, sky1); + if (bsb.HasSRV("env_s0") && sky0) + bsb.Texture("env_s0", sky0); + if (bsb.HasSRV("env_s1") && sky1) + bsb.Texture("env_s1", sky1); +} + } // namespace xray::render::fg::passes diff --git a/src/Layers/xrRender/FrameGraphPasses/PassCommon.h b/src/Layers/xrRender/FrameGraphPasses/PassCommon.h index 93d7be01b37..7598bd70dd3 100644 --- a/src/Layers/xrRender/FrameGraphPasses/PassCommon.h +++ b/src/Layers/xrRender/FrameGraphPasses/PassCommon.h @@ -3,6 +3,8 @@ #include #include "xrCore/xrCore.h" +namespace xray::render::framegraph { class BindingSetBuilder; } + namespace xray::render::fg::passes { inline void DrawIndexedIndirectCountOrFallback( @@ -68,4 +70,10 @@ LightingConstants FillLightingConstants(); u32 ExtractFrustumPlanes(Fvector4 outPlanes[6]); +void ResolveEnvSkyCubes(fg::RenderDevice* device, nvrhi::ITexture*& outSky0, nvrhi::ITexture*& outSky1); + +void BindBindlessMaterialTables(framegraph::BindingSetBuilder& bsb); + +void BindEnvIblCubes(framegraph::BindingSetBuilder& bsb, fg::RenderDevice* device); + } // namespace xray::render::fg::passes diff --git a/src/Layers/xrRender/FrameGraphPasses/PassVertexFormats.h b/src/Layers/xrRender/FrameGraphPasses/PassVertexFormats.h index c649065cec6..54cb955bc7a 100644 --- a/src/Layers/xrRender/FrameGraphPasses/PassVertexFormats.h +++ b/src/Layers/xrRender/FrameGraphPasses/PassVertexFormats.h @@ -17,6 +17,25 @@ struct SunVertex { u32 color; float u, v; }; + +struct CloudVertex { + Fvector3 p; + u32 dir; + u32 color; +}; + +struct PortalVertex { + Fvector3 p; + u32 color; +}; + +struct LodVertex { + Fvector3 p; + u32 color; + Fvector2 tc0; + Fvector2 tc1; + Fvector4 af; +}; #pragma pack(pop) struct TextVertex { @@ -68,18 +87,10 @@ struct HistogramCB { }; struct AdaptCB { - float minLogLum; - float logLumRange; - float lowPercentile; - float highPercentile; - float adaptSpeedUp; - float adaptSpeedDown; - float deltaTime; - float exposureCompensation; - float minExposure; - float maxExposure; - float calibrationConstant; - float padding; + float middleGrayX; + float middleGrayY; + float middleGrayZ; + float middleGrayW; }; } // namespace xray::render::fg::passes diff --git a/src/Layers/xrRender/FrameGraphPasses/PathTracerPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/PathTracerPassSetup.cpp index 3f26329313c..d3d12bd6e1c 100644 --- a/src/Layers/xrRender/FrameGraphPasses/PathTracerPassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/PathTracerPassSetup.cpp @@ -9,6 +9,7 @@ #include "Layers/xrRender/RenderContext/RenderContext.h" #include "Layers/xrRender/RenderContext/RenderDevice.h" #include "Layers/xrRender/RayTracing/RTAccelStructManager.h" +#include "Layers/xrRender/RayTracing/ReSTIRMemoryManager.h" #if defined(XR_PLATFORM_WINDOWS) #include "Layers/xrRender/Backend/D3D12Backend.h" #endif @@ -46,19 +47,19 @@ struct PathTracerCB { Fvector4 cameraPos_pad; Fvector4 sunDir_intensity; Fvector4 sunColor_skyWeight; + Fvector4 skyColor; float screenWidth; float screenHeight; u32 sampleIndex; u32 maxBounces; u32 identityStaticCount; u32 terrainBatchCount; - u32 transparentBatchCount; u32 skinnedBatchStart; u32 grassBatchStart; u32 detailAtlasIndex; - u32 pad[2]; + u32 pad[3]; }; -static_assert(sizeof(PathTracerCB) == 160, "PathTracerCB must be 160 bytes"); +static_assert(sizeof(PathTracerCB) == 176, "PathTracerCB must be 176 bytes"); static void CreatePlaceholderCubemap(nvrhi::IDevice* nvDevice) { @@ -107,7 +108,7 @@ static void InitializeResources(fg::RenderDevice* device) } s_pathtrace_shader = csResult.handle; - s_cb = cache.GetOrCreateVolatileCB("PathTracer", "PathTracerCB", sizeof(PathTracerCB), device); + s_cb = cache.GetOrCreateVolatileCB("PathTracer", "PathTracerCB_v2", sizeof(PathTracerCB), device); nvrhi::SamplerDesc samplerDesc; samplerDesc.setAllFilters(true); @@ -117,7 +118,7 @@ static void InitializeResources(fg::RenderDevice* device) CreatePlaceholderCubemap(nvDevice); CreatePlaceholderBuffer(nvDevice); - s_layout = cache.GetOrCreateBindingLayoutFromReflection("PathTracer", *csResult.reflection, nvDevice); + s_layout = cache.GetOrCreateBindingLayoutFromReflection("PathTracer_v3", *csResult.reflection, nvDevice); #if defined(XR_PLATFORM_WINDOWS) auto* backend = dynamic_cast(GEnv.Backend); @@ -252,6 +253,10 @@ PathTracerOutput setupPathTracerPass( cbData.cameraPos_pad = { cameraPos.x, cameraPos.y, cameraPos.z, 0.0f }; cbData.sunDir_intensity = { sunDir.x, sunDir.y, sunDir.z, sunIntensity }; cbData.sunColor_skyWeight = { sunColor.x, sunColor.y, sunColor.z, skyWeight }; + { + const Fvector3& skc = env.CurrentEnv.sky_color; + cbData.skyColor = { skc.x, skc.y, skc.z, 0.f }; + } cbData.screenWidth = static_cast(width); cbData.screenHeight = static_cast(height); cbData.sampleIndex = config.sampleIndex; @@ -260,24 +265,24 @@ PathTracerOutput setupPathTracerPass( const auto& batchCounts = accelMgr->GetBatchCounts(); cbData.identityStaticCount = batchCounts.identityStatic; cbData.terrainBatchCount = batchCounts.terrain; - cbData.transparentBatchCount = batchCounts.transparent; if (batchCounts.skinned > 0) cbData.skinnedBatchStart = batchCounts.identityStatic + batchCounts.terrain + batchCounts.transparent + batchCounts.instancedTotal; else - cbData.skinnedBatchStart = 0; + cbData.skinnedBatchStart = 0xFFFFFFFFu; if (batchCounts.grass > 0) cbData.grassBatchStart = batchCounts.identityStatic + batchCounts.terrain + batchCounts.transparent + batchCounts.instancedTotal + batchCounts.skinned; else - cbData.grassBatchStart = 0; + cbData.grassBatchStart = 0xFFFFFFFFu; cbData.detailAtlasIndex = accelMgr->GetDetailAtlasIndex(); cbData.pad[0] = 0; cbData.pad[1] = 0; + cbData.pad[2] = 0; auto& passData = fg.addCallbackPass( "Path Tracer", @@ -334,6 +339,13 @@ PathTracerOutput setupPathTracerPass( bsb.BufferSRV("g_SkinnedIB", skinnedIB); bsb.BufferSRV("g_GrassVB", grassVB); bsb.BufferSRV("g_GrassIB", grassIB); + { + auto& memMgr = ReSTIRMemoryManager::Instance(); + memMgr.Init(nvDevice); + nvrhi::ITexture* blueNoise = memMgr.GetBlueNoise() ? memMgr.GetBlueNoise() : memMgr.GetPlaceholderTex3D(); + if (bsb.HasSRV("t_BlueNoise")) + bsb.Texture("t_BlueNoise", blueNoise); + } bsb.TextureUAV("g_Accumulation", s_accumBuffer); bsb.TextureUAV("g_Output", outTex); auto& cache = GetPassResourceCache(); diff --git a/src/Layers/xrRender/FrameGraphPasses/PostProcessPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/PostProcessPassSetup.cpp new file mode 100644 index 00000000000..4e37e05be58 --- /dev/null +++ b/src/Layers/xrRender/FrameGraphPasses/PostProcessPassSetup.cpp @@ -0,0 +1,304 @@ +#include "stdafx.h" +#include "PostProcessPassSetup.h" +#include "Layers/xrRender/FrameGraph/FrameGraph.h" +#include "Layers/xrRender/FrameGraph/PassResourceCache.h" +#include "Layers/xrRender/FrameGraph/BindingSetBuilder.h" +#include "Layers/xrRender/FrameGraph/RenderPassBuilder.h" +#include "Layers/xrRender/FrameGraph/ShaderLoader.h" +#include "Layers/xrRender/RenderContext/RenderContext.h" +#include "Layers/xrRender/RenderContext/RenderDevice.h" +#include "Layers/xrRender/ResourceManager/FGResourceManager.h" +#include "Layers/xrRender/ResourceManager/TextureManager.h" +#include "Layers/xrRender/r4_rendertarget.h" +#include "Layers/xrRender/FrameGraphPasses/ShaderConstants.h" +#include "xrEngine/IGame_Persistent.h" +#include "xrEngine/ShadersExternalData.h" + +namespace xray::render::fg::passes +{ +using namespace framegraph; + +namespace +{ +struct alignas(16) PostProcessCB +{ + Fvector4 dual_r0; + Fvector4 dual_r1; + Fvector4 dual_l0; + Fvector4 dual_l1; + Fvector4 noise0; + Fvector4 noise1; + Fvector4 color; + Fvector4 gray; + Fvector4 brightness; + Fvector4 colormap; + Fvector4 mode; +}; + +constexpr u32 kPostProcessPipeVersion = 4; + +void EnsurePPPipeline(nvrhi::IDevice* nv, PostProcessPassState& st, nvrhi::Format outFmt) +{ + if (!nv) + return; + if (st.initialized && st.pipeline && st.pipelineFormat == outFmt && st.pipeVersion == kPostProcessPipeVersion) + return; + st.layout = nullptr; + st.pipeline = nullptr; + + auto* loader = GEnv.Render->GetShaderLoader(); + if (!loader) + { + st.initialized = true; + return; + } + auto vs = loader->LoadVertexShader("fullscreen"); + auto ps = loader->LoadPixelShader("postprocess"); + if (!vs.handle || !ps.handle) + { + st.initialized = true; + return; + } + auto& cache = GetPassResourceCache(); + if (!st.layout) + st.layout = cache.GetOrCreateBindingLayoutFromReflection( + "PostProcess_v3", *vs.reflection, *ps.reflection, nv); + if (st.layout) + { + nvrhi::GraphicsPipelineDesc desc; + desc.setVertexShader(vs.handle); + desc.setPixelShader(ps.handle); + desc.addBindingLayout(st.layout); + desc.setPrimType(nvrhi::PrimitiveType::TriangleList); + desc.renderState.blendState.targets[0].setBlendEnable(false); + desc.renderState.depthStencilState.setDepthTestEnable(false); + desc.renderState.depthStencilState.setDepthWriteEnable(false); + desc.renderState.rasterState.setCullMode(nvrhi::RasterCullMode::None); + nvrhi::FramebufferInfoEx fb; + fb.addColorFormat(outFmt); + char pipeName[64]; + xr_sprintf(pipeName, "PostProcess_v3_fmt%u", (u32)outFmt); + st.pipeline = cache.GetOrCreatePipeline(pipeName, desc, fb, nv); + st.pipelineFormat = outFmt; + } + + if (!st.noisePlaceholder) + { + nvrhi::TextureDesc td; + td.width = 1; + td.height = 1; + td.format = nvrhi::Format::RGBA8_UNORM; + td.debugName = "PP_NoisePlaceholder"; + td.initialState = nvrhi::ResourceStates::ShaderResource; + td.keepInitialState = true; + st.noisePlaceholder = nv->createTexture(td); + nvrhi::CommandListHandle cmd = nv->createCommandList(); + cmd->open(); + u32 gray = 0xFF808080; + cmd->writeTexture(st.noisePlaceholder, 0, 0, &gray, sizeof(gray)); + cmd->close(); + nv->executeCommandList(cmd); + } + + st.initialized = true; + st.pipeVersion = kPostProcessPipeVersion; +} +} + +VirtualResourceHandle setupPostProcessPass( + FrameGraph& fg, + fg::RenderDevice* device, + VirtualResourceHandle ldrInput, + VirtualResourceHandle outputTarget, + u32 width, + u32 height, + CRenderTarget* target, + PostProcessPassState& state) +{ + if (!device || !device->GetNVRHIDevice() || !ldrInput.is_valid() || !target) + return ldrInput; + + bool needNV = false; + if (g_pGamePersistent && g_pGamePersistent->m_pGShaderConstants) + needNV = g_pGamePersistent->m_pGShaderConstants->m_blender_mode.x > 0.5f; + if (!target->u_need_PP() && !needNV) + return ldrInput; + + nvrhi::Format outFmt = nvrhi::Format::RGBA8_UNORM; + if (GEnv.Backend && GEnv.Backend->GetBackBuffer()) + outFmt = GEnv.Backend->GetBackBuffer()->getDesc().format; + + EnsurePPPipeline(device->GetNVRHIDevice(), state, outFmt); + if (!state.pipeline || !state.layout) + return ldrInput; + + const bool hasOutputTarget = outputTarget.is_valid(); + + struct PassData + { + VirtualResourceHandle input, output; + u32 width, height; + PostProcessPassState* st = nullptr; + fg::RenderDevice* device = nullptr; + CRenderTarget* target = nullptr; + }; + + auto& pd = fg.addCallbackPass( + "PostProcess", + [&](FrameGraph& b, PassHandle ph, PassData& data) { + RenderPassBuilder pb(b, ph); + data.st = &state; + data.device = device; + data.target = target; + data.width = width; + data.height = height; + data.input = pb.read(ldrInput, ResourceState::ShaderResource); + + if (hasOutputTarget) + { + data.output = pb.write(outputTarget, ResourceState::RenderTarget); + } + else + { + ResourceDesc td; + td.type = ResourceDesc::Type::Texture2D; + td.width = width; + td.height = height; + td.format = outFmt; + td.isRenderTarget = true; + td.isTransient = true; + td.debugName = "rt_PostProcess"; + data.output = pb.createTexture("rt_PostProcess", td); + } + }, + [](const PassData& data, const FrameGraph& graph, fg::RenderContext* ctx) { + auto* cmd = ctx->GetCommandList(); + auto* nv = cmd ? cmd->getDevice() : nullptr; + auto* in = graph.GetPhysicalTexture(data.input); + auto* out = graph.GetPhysicalTexture(data.output); + if (!cmd || !nv || !in || !out || !data.st || !data.target) + return; + + nvrhi::Format fmt = out->getDesc().format; + EnsurePPPipeline(nv, *data.st, fmt); + if (!data.st->pipeline || !data.st->layout) + return; + + auto& cache = GetPassResourceCache(); + auto* loader = GEnv.Render->GetShaderLoader(); + auto* vsR = loader->GetCachedReflection("fullscreen", ".vs"); + auto* psR = loader->GetCachedReflection("postprocess", ".ps"); + if (!vsR || !psR) + return; + + Fvector2 n0, n1, r0, r1, l0, l1; + data.target->u_calc_tc_duality_ss(r0, r1, l0, l1); + data.target->u_calc_tc_noise(n0, n1); + + int gblend = clampr(iFloor((1.f - data.target->get_gray()) * 255.f), 0, 255); + int nblend = clampr(iFloor((1.f - data.target->get_noise()) * 255.f), 0, 255); + u32 p_color = subst_alpha(data.target->get_color_base(), nblend); + u32 p_gray = subst_alpha(data.target->get_color_gray(), gblend); + const Fvector& bright = data.target->get_color_add(); + + PostProcessCB cb{}; + cb.dual_r0.set(r0.x, r0.y, 0.f, 0.f); + cb.dual_r1.set(r1.x, r1.y, 0.f, 0.f); + cb.dual_l0.set(l0.x, l0.y, 0.f, 0.f); + cb.dual_l1.set(l1.x, l1.y, 0.f, 0.f); + cb.noise0.set(n0.x, n0.y, 0.f, 0.f); + cb.noise1.set(n1.x, n1.y, 0.f, 0.f); + cb.color.set( + float(color_get_R(p_color)) / 255.f, + float(color_get_G(p_color)) / 255.f, + float(color_get_B(p_color)) / 255.f, + float(color_get_A(p_color)) / 255.f); + cb.gray.set( + float(color_get_R(p_gray)) / 255.f, + float(color_get_G(p_gray)) / 255.f, + float(color_get_B(p_gray)) / 255.f, + float(color_get_A(p_gray)) / 255.f); + cb.brightness.set(bright.x, bright.y, bright.z, 0.f); + const float cmWanted = data.target->get_cm_influence(); + nvrhi::ITexture* grad0 = data.target->get_cm_texture(0); + nvrhi::ITexture* grad1 = data.target->get_cm_texture(1); + const float cmInf = (grad0 && cmWanted > 0.001f) ? cmWanted : 0.f; + cb.colormap.set( + cmInf, + data.target->get_cm_interpolate(), + cmWanted > 0.001f ? 1.f : 0.f, + 0.f); + + float blenderMode = 0.f; + float nvIntensity = 1.f; + if (g_pGamePersistent && g_pGamePersistent->m_pGShaderConstants) + { + blenderMode = g_pGamePersistent->m_pGShaderConstants->m_blender_mode.x; + nvIntensity = g_pGamePersistent->m_pGShaderConstants->hud_params.z; + } + cb.mode.set(blenderMode, nvIntensity, 0.f, 0.f); + + auto* pcb = cache.GetOrCreateVolatileCB("PostProcess", "PostProcessParams", sizeof(PostProcessCB), data.device); + if (pcb) + cmd->writeBuffer(pcb, &cb, sizeof(cb)); + + auto staticGlobalsCB = cache.GetOrCreateVolatileCB( + "Frame", "StaticGlobals", sizeof(StaticGlobals), data.device); + { + StaticGlobals sg = BuildStaticGlobals(); + cmd->writeBuffer(staticGlobalsCB, &sg, sizeof(sg)); + } + + nvrhi::ITexture* noiseTex = data.st->noisePlaceholder.Get(); + if (!data.st->noiseTexture) + { + auto* resMgr = data.device->GetFGResourceManager(); + auto* texMgr = resMgr ? resMgr->GetTextureManager() : nullptr; + if (texMgr) + { + auto handle = texMgr->LoadTexture("fx\\fx_noise2"); + data.st->noiseTexture = texMgr->GetNVRHITexture(handle); + } + } + if (data.st->noiseTexture) + noiseTex = data.st->noiseTexture; + + if (!grad0) + grad0 = data.st->noisePlaceholder.Get(); + if (!grad1) + grad1 = grad0 ? grad0 : data.st->noisePlaceholder.Get(); + + BindingSetBuilder bsb(*vsR, *psR, nv, "PostProcess"); + if (pcb) + bsb.ConstantBuffer("PostProcessParams", pcb); + bsb.ConstantBuffer("static_globals", staticGlobalsCB); + bsb.Texture("s_base0", in); + bsb.Texture("s_base1", in); + bsb.Texture("s_noise", noiseTex); + if (grad0) + bsb.Texture("s_grad0", grad0); + if (grad1) + bsb.Texture("s_grad1", grad1); + auto set = cache.GetOrCreateBindingSet(bsb.Build(), data.st->layout, nv); + if (!set) + return; + + nvrhi::FramebufferDesc fbDesc; + fbDesc.addColorAttachment(out); + auto fb = cache.GetOrCreateFramebuffer("PostProcess", fbDesc, nv); + if (!fb) + return; + + nvrhi::GraphicsState gs; + gs.pipeline = data.st->pipeline; + gs.framebuffer = fb; + gs.bindings = {set}; + gs.viewport.addViewportAndScissorRect(nvrhi::Viewport(float(data.width), float(data.height))); + cmd->setGraphicsState(gs); + cmd->draw(nvrhi::DrawArguments().setVertexCount(3)); + }); + + return pd.output; +} + +} diff --git a/src/Layers/xrRender/FrameGraphPasses/PostProcessPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/PostProcessPassSetup.h new file mode 100644 index 00000000000..f0354d81225 --- /dev/null +++ b/src/Layers/xrRender/FrameGraphPasses/PostProcessPassSetup.h @@ -0,0 +1,34 @@ +#pragma once + +#include "Layers/xrRender/FrameGraph/FGTypes.h" +#include "Layers/xrRender/FrameGraph/FGResource.h" +#include + +namespace xray::render::framegraph { class FrameGraph; } +namespace xray::render::fg { class RenderDevice; class CRenderTarget; } + +namespace xray::render::fg::passes +{ + +struct PostProcessPassState +{ + nvrhi::GraphicsPipelineHandle pipeline; + nvrhi::BindingLayoutHandle layout; + nvrhi::Format pipelineFormat = nvrhi::Format::UNKNOWN; + nvrhi::TextureHandle noisePlaceholder; + nvrhi::ITexture* noiseTexture = nullptr; + bool initialized = false; + u32 pipeVersion = 0; +}; + +framegraph::VirtualResourceHandle setupPostProcessPass( + framegraph::FrameGraph& fg, + fg::RenderDevice* device, + framegraph::VirtualResourceHandle ldrInput, + framegraph::VirtualResourceHandle outputTarget, + u32 width, + u32 height, + CRenderTarget* target, + PostProcessPassState& state); + +} diff --git a/src/Layers/xrRender/FrameGraphPasses/RainPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/RainPassSetup.cpp index 1587096c884..46741b1b55c 100644 --- a/src/Layers/xrRender/FrameGraphPasses/RainPassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/RainPassSetup.cpp @@ -30,6 +30,10 @@ framegraph::VirtualResourceHandle setupRainPass( auto* depth = fg.GetPhysicalTexture(data.depth); if (!cmdList || !outputRT) return; + if (depth && + (depth->getDesc().width != outputRT->getDesc().width || + depth->getDesc().height != outputRT->getDesc().height)) + return; nvrhi::FramebufferDesc fbDesc; fbDesc.addColorAttachment(outputRT); if (depth) fbDesc.setDepthAttachment(depth); diff --git a/src/Layers/xrRender/FrameGraphPasses/RainShadowPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/RainShadowPassSetup.cpp new file mode 100644 index 00000000000..0da225ea1d2 --- /dev/null +++ b/src/Layers/xrRender/FrameGraphPasses/RainShadowPassSetup.cpp @@ -0,0 +1,427 @@ +#include "stdafx.h" +#include "RainShadowPassSetup.h" +#include "PassCommon.h" +#include "Layers/xrRender/FrameGraph/FrameGraph.h" +#include "Layers/xrRender/FrameGraph/RenderPassBuilder.h" +#include "Layers/xrRender/FrameGraph/PassResourceCache.h" +#include "Layers/xrRender/FrameGraph/BindingSetBuilder.h" +#include "Layers/xrRender/FrameGraph/ShaderLoader.h" +#include "Layers/xrRender/RenderContext/RenderContext.h" +#include "Layers/xrRender/RenderContext/RenderDevice.h" +#include "Layers/xrRender/Geometry/MaterialCache.h" +#include "Layers/xrRender/Bindless/MaterialBuffer.h" +#include "Layers/xrRender/Bindless/TerrainMaterialBuffer.h" + +extern ENGINE_API int ps_r_rt_gi; +extern ENGINE_API int ps_r_path_tracer; +#include "Layers/xrRender/ResourceManager/FGResourceManager.h" +#include "Layers/xrRender/ResourceManager/TextureManager.h" +#include "Layers/xrRender/ResourceManager/NativeRTFactory.h" +#include "Layers/xrRender/xrRender_console.h" +#include "Layers/xrRender/GPUCullingManager.h" +#include "Layers/xrRender/r_FrameGraphRenderer.h" +#include "xrEngine/IRenderBackend.h" +#include "xrEngine/IGame_Persistent.h" +#include "xrEngine/Environment.h" +#include "xrEngine/device.h" + +namespace xray::render::fg::passes +{ + +namespace +{ + +void ComputeTopDownRainMatrices(float extent, float height, u32 smapRes, Fmatrix& outClipVP, Fmatrix& outSampleVP) +{ + const Fvector& C = Device.vCameraPosition; + const float e = std::max(extent, 8.f); + const float texel = (e * 2.f) / float(std::max(smapRes, 1u)); + + Fvector eye; + eye.set( + floorf(C.x / texel + 0.5f) * texel, + C.y + height, + floorf(C.z / texel + 0.5f) * texel); + Fvector dir(0.f, -1.f, 0.f); + Fvector up(0.f, 0.f, 1.f); + + Fmatrix view; + view.build_camera_dir(eye, dir, up); + + Fmatrix proj; + proj.build_projection_ortho(e * 2.f, e * 2.f, 1.f, height + 120.f); + + outClipVP.mul(proj, view); + + Fmatrix toUV; + toUV.identity(); + toUV._11 = 0.5f; + toUV._22 = -0.5f; + toUV._33 = 1.f; + toUV._41 = 0.5f; + toUV._42 = 0.5f; + outSampleVP.mul(toUV, outClipVP); +} + +} // namespace + +void InitializeRainShadowPass(fg::RenderDevice* device, RainShadowPassState& state) +{ + if (!device) + return; + + const u32 res = std::clamp((u32)ps_r3_dyn_wet_surf_sm_res, 64u, 2048u); + if (state.initialized && state.resolution == res) + return; + + if (state.initialized) + ShutdownRainShadowPass(device, state); + + auto* resMgr = device->GetFGResourceManager(); + if (!resMgr || !resMgr->GetRTFactory() || !resMgr->GetTextureManager()) + { + state.initialized = true; + state.enabled = false; + return; + } + + state.resolution = res; + state.rainSMHandle = resMgr->GetRTFactory()->CreateShadowMap(res, true, "rt_RainShadow"); + state.rainSM = resMgr->GetTextureManager()->GetNVRHITexture(state.rainSMHandle); + + auto* loader = GEnv.Render ? GEnv.Render->GetShaderLoader() : nullptr; + nvrhi::IDevice* nv = device->GetNVRHIDevice(); + if (nv && !state.rainCB) + { + nvrhi::BufferDesc cbDesc; + cbDesc.byteSize = sizeof(ShadowCascadeCB); + cbDesc.isConstantBuffer = true; + cbDesc.isVolatile = true; + cbDesc.maxVersions = 64; + cbDesc.debugName = "RainShadowCB"; + state.rainCB = nv->createBuffer(cbDesc); + } + if (loader && nv) + { + auto vs = loader->LoadVertexShader("shadow\\shadow_cascade", "main"); + auto ps = loader->LoadPixelShader("shadow\\shadow_cascade_rain", "main"); + if (vs.handle && ps.handle && vs.reflection && ps.reflection) + { + auto& cache = GetPassResourceCache(); + state.rainLayout = cache.GetOrCreateBindingLayoutFromReflection( + "RainShadowSolid", *vs.reflection, *ps.reflection, nv); + if (state.rainLayout) + { + u32 attrCount = 0; + auto* attrs = GetUnifiedVertexAttributes(attrCount); + auto inputLayout = nv->createInputLayout(attrs, attrCount, vs.handle); + + nvrhi::GraphicsPipelineDesc pipeDesc; + pipeDesc.VS = vs.handle; + pipeDesc.PS = ps.handle; + pipeDesc.inputLayout = inputLayout; + pipeDesc.primType = nvrhi::PrimitiveType::TriangleList; + pipeDesc.renderState.depthStencilState.setDepthTestEnable(true); + pipeDesc.renderState.depthStencilState.setDepthWriteEnable(true); + pipeDesc.renderState.depthStencilState.setDepthFunc(nvrhi::ComparisonFunc::LessOrEqual); + pipeDesc.renderState.rasterState.setCullMode(nvrhi::RasterCullMode::None); + pipeDesc.renderState.rasterState.depthBias = 2; + pipeDesc.renderState.rasterState.slopeScaledDepthBias = 1.5f; + + auto* backend = device->GetBackend(); + nvrhi::IBindingLayout* bindlessLayout = backend ? backend->GetBindlessLayout() : nullptr; + if (bindlessLayout) + pipeDesc.bindingLayouts = {state.rainLayout, bindlessLayout}; + else + pipeDesc.bindingLayouts = {state.rainLayout}; + + nvrhi::FramebufferInfoEx fbInfo; + fbInfo.depthFormat = nvrhi::Format::D32; + state.rainPipeline = cache.GetOrCreatePipeline("RainShadowSolid", pipeDesc, fbInfo, nv); + + auto terrainPs = loader->LoadPixelShader("shadow\\shadow_cascade_terrain", "main"); + if (terrainPs.handle && terrainPs.reflection) + { + state.terrainLayout = cache.GetOrCreateBindingLayoutFromReflection( + "RainShadowTerrain", *vs.reflection, *terrainPs.reflection, nv); + if (state.terrainLayout) + { + nvrhi::GraphicsPipelineDesc terrainDesc = pipeDesc; + terrainDesc.PS = terrainPs.handle; + if (bindlessLayout) + terrainDesc.bindingLayouts = {state.terrainLayout, bindlessLayout}; + else + terrainDesc.bindingLayouts = {state.terrainLayout}; + state.terrainPipeline = cache.GetOrCreatePipeline( + "RainShadowTerrain", terrainDesc, fbInfo, nv); + } + terrainPs.reflection = nullptr; + } + } + } + } + + state.initialized = true; + state.enabled = (state.rainSM != nullptr && state.rainPipeline != nullptr && state.rainCB != nullptr); + if (state.enabled) + Msg("* [RainShadow] Init: OK (%ux%u)", res, res); + else + Msg("! [RainShadow] Failed to create rain SM / solid pipeline"); +} + +void ShutdownRainShadowPass(fg::RenderDevice* device, RainShadowPassState& state) +{ + if (device && device->GetFGResourceManager() && device->GetFGResourceManager()->GetRTFactory()) + { + if (state.rainSMHandle.IsValid()) + device->GetFGResourceManager()->GetRTFactory()->ReleaseRenderTarget(state.rainSMHandle); + } + state.rainSMHandle = {}; + state.rainSM = nullptr; + state.rainPipeline = nullptr; + state.rainLayout = nullptr; + state.terrainPipeline = nullptr; + state.terrainLayout = nullptr; + state.rainCB = nullptr; + state.resolution = 0; + state.initialized = false; + state.enabled = false; +} + +RainShadowOutputs setupRainShadowPass( + framegraph::FrameGraph& fg, + fg::RenderDevice* device, + const BindlessForwardConfig& bindlessConfig, + RainShadowPassState& state) +{ + using namespace framegraph; + RainShadowOutputs outputs; + outputs.valid = false; + + if (!device) + return outputs; + + const float rainDensity = g_pGamePersistent + ? g_pGamePersistent->Environment().CurrentEnv.rain_density + : 0.f; + const bool wantRain = rainDensity >= 0.001f; + const bool wantWet = ps_r2_ls_flags.test(R3FLAG_DYN_WET_SURF); + if (!wantRain && !wantWet) + return outputs; + + InitializeRainShadowPass(device, state); + if (!state.enabled || !state.rainSM || !state.rainPipeline || !state.rainLayout || !state.rainCB) + return outputs; + + const bool rtWet = (ps_r_rt_gi != 0) || (ps_r_path_tracer != 0); + float requested; + float maxExtent; + if (rtWet) + { + const float farPlane = g_pGamePersistent + ? g_pGamePersistent->Environment().CurrentEnv.far_plane + : 400.f; + requested = std::max(farPlane * 0.65f, 250.f); + maxExtent = float(state.resolution) * 0.35f; + } + else + { + requested = std::max({ps_r3_dyn_wet_surf_far, ps_r3_dyn_wet_surf_near, 40.f}); + maxExtent = float(state.resolution) * 0.05f; + } + const float extent = std::min(requested, std::max(maxExtent, 16.f)); + ComputeTopDownRainMatrices(extent, 200.f, state.resolution, state.clipVP, state.sampleVP); + + ResourceDesc desc; + desc.type = ResourceDesc::Type::Texture2D; + desc.width = state.resolution; + desc.height = state.resolution; + desc.depth = 1; + desc.arraySize = 1; + desc.format = nvrhi::Format::D32; + desc.isDepthStencil = true; + desc.isImported = true; + desc.debugName = "rt_RainShadow"; + + auto rainHandle = fg.ImportTexture("rt_RainShadow", state.rainSM, desc); + + struct PassData + { + VirtualResourceHandle rainSM; + RainShadowPassState* rainState = nullptr; + BindlessForwardConfig bindlessConfig; + fg::RenderDevice* device = nullptr; + }; + + auto& passData = fg.addCallbackPass( + "RainShadow", + [&, rainHandle](FrameGraph& builder, PassHandle passHandle, PassData& data) { + RenderPassBuilder pb(builder, passHandle); + data.rainSM = pb.write(rainHandle, ResourceState::DepthStencilWrite); + data.rainState = &state; + data.bindlessConfig = bindlessConfig; + data.device = device; + }, + [](const PassData& data, const FrameGraph& graph, fg::RenderContext* ctx) { + if (!data.rainState) + return; + auto* rainTex = graph.GetPhysicalTexture(data.rainSM); + if (!rainTex) + rainTex = data.rainState->rainSM; + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + nvrhi::IDevice* nv = cmd ? cmd->getDevice() : nullptr; + if (!cmd || !nv || !rainTex) + return; + + RainShadowPassState& rs = *data.rainState; + + cmd->clearDepthStencilTexture( + rainTex, nvrhi::TextureSubresourceSet(0, 1, 0, 1), true, 1.0f, false, 0); + + if (!data.bindlessConfig.UseGPUCulling() || !data.bindlessConfig.UseMegaBuffers()) + { + static bool s_warned = false; + if (!s_warned && g_pGameLevel) + { + Msg("! [RainShadow] GPU bindless culling unavailable — rain SM empty (wet cover broken)"); + s_warned = true; + } + return; + } + + auto* shaderLoader = GEnv.Render->GetShaderLoader(); + auto* vsRefl = shaderLoader->GetCachedReflection("shadow\\shadow_cascade", ".vs"); + auto* psRefl = shaderLoader->GetCachedReflection("shadow\\shadow_cascade_rain", ".ps"); + auto drawIndexBuffer = GetOrCreateDrawIndexBuffer("RainShadow", nv); + if (!vsRefl || !psRefl || !drawIndexBuffer || !rs.rainPipeline || !rs.rainLayout || !rs.rainCB) + return; + + ShadowCascadeCB cbData{}; + cbData.lightVP = rs.clipVP; + cmd->writeBuffer(rs.rainCB, &cbData, sizeof(cbData)); + + auto& cache = GetPassResourceCache(); + nvrhi::FramebufferDesc fbDesc; + fbDesc.setDepthAttachment(rainTex); + auto fb = cache.GetOrCreateFramebuffer( + make_string("RainShadow_%u", rs.resolution).c_str(), fbDesc, nv); + if (!fb) + return; + + auto& matBuffer = bindless::MaterialBuffer::Instance(); + matBuffer.Upload(ctx); + + auto* backend = GEnv.Backend; + nvrhi::IBindingSet* bindlessTable = backend ? backend->GetBindlessDescriptorTable() : nullptr; + + const u32 cres = rs.resolution; + nvrhi::Viewport vp(0.f, float(cres), 0.f, float(cres), 0.f, 1.f); + + auto drawSet = [&](const BindlessDrawSet& set) { + if (!set.IsValid()) + return; + + nvrhi::IBuffer* batchIndices = set.compactBatchIndicesBuffer; + nvrhi::IBuffer* materialIDs = set.compactMaterialIDBuffer; + nvrhi::IBuffer* drawArgs = set.compactDrawArgsBuffer; + nvrhi::IBuffer* countBuffer = set.compactCountBuffer; + + BindingSetBuilder bsb(*vsRefl, *psRefl, nv, "RainShadow"); + bsb.ConstantBuffer("ShadowCascadeCB", rs.rainCB); + BindBindlessMaterialTables(bsb); + bsb.BufferSRV("g_InstanceData", set.instanceBuffer); + bsb.BufferSRV("g_CompactBatchIndices", batchIndices); + bsb.BufferSRV("g_CompactMaterialIDs", materialIDs); + auto bindingSet = cache.GetOrCreateBindingSet(bsb.Build(), rs.rainLayout, nv); + if (!bindingSet) + return; + + nvrhi::GraphicsState gs; + gs.pipeline = rs.rainPipeline; + gs.framebuffer = fb; + gs.bindings = {bindingSet}; + if (bindlessTable) + gs.addBindingSet(bindlessTable); + gs.vertexBuffers = { + {data.bindlessConfig.megaVertexBuffer, 0, 0}, + {drawIndexBuffer, 1, 0}}; + gs.indexBuffer = {data.bindlessConfig.megaIndexBuffer, nvrhi::Format::R32_UINT, 0}; + gs.viewport.addViewport(vp); + gs.viewport.addScissorRect(nvrhi::Rect(cres, cres)); + gs.indirectParams = drawArgs; + gs.indirectCountBuffer = countBuffer; + cmd->setGraphicsState(gs); + DrawIndexedIndirectCountOrFallback(cmd, 0, 0, set.totalObjectCount); + }; + + drawSet(data.bindlessConfig.staticSet); + drawSet(data.bindlessConfig.dynamicSet); + + if (rs.terrainPipeline && rs.terrainLayout && data.bindlessConfig.HasTerrain()) + { + if (auto* matCache = GEnv.Render ? GEnv.Render->GetMaterialCache() : nullptr) + matCache->FinalizePendingTerrainMaterials(ctx); + auto& terrainMatBuffer = bindless::TerrainMaterialBuffer::Instance(); + terrainMatBuffer.Upload(ctx); + + auto* terrainPsRefl = shaderLoader->GetCachedReflection( + "shadow\\shadow_cascade_terrain", ".ps"); + + const bool useCompact = data.bindlessConfig.UseTerrainCompaction(); + nvrhi::IBuffer* tInstance = data.bindlessConfig.terrainInstanceBuffer; + nvrhi::IBuffer* tBatch = useCompact + ? data.bindlessConfig.terrainCompactBatchIndicesBuffer + : data.bindlessConfig.terrainBatchIndicesBuffer; + nvrhi::IBuffer* tMat = useCompact + ? data.bindlessConfig.terrainCompactMaterialIDBuffer + : data.bindlessConfig.terrainMaterialIDBuffer; + nvrhi::IBuffer* tArgs = useCompact + ? data.bindlessConfig.terrainCompactDrawArgsBuffer + : data.bindlessConfig.terrainDrawArgsBuffer; + nvrhi::IBuffer* tCount = useCompact + ? data.bindlessConfig.terrainCompactCountBuffer + : nullptr; + + if (terrainPsRefl && terrainMatBuffer.GetBuffer() && tInstance && tBatch && tMat && tArgs) + { + BindingSetBuilder tbsb(*vsRefl, *terrainPsRefl, nv, "RainShadow.Terrain"); + tbsb.ConstantBuffer("ShadowCascadeCB", rs.rainCB); + BindBindlessMaterialTables(tbsb); + tbsb.BufferSRV("g_InstanceData", tInstance); + tbsb.BufferSRV("g_CompactBatchIndices", tBatch); + tbsb.BufferSRV("g_CompactMaterialIDs", tMat); + auto terrainSet = cache.GetOrCreateBindingSet(tbsb.Build(), rs.terrainLayout, nv); + if (terrainSet) + { + nvrhi::GraphicsState gs; + gs.pipeline = rs.terrainPipeline; + gs.framebuffer = fb; + gs.bindings = {terrainSet}; + if (bindlessTable) + gs.addBindingSet(bindlessTable); + gs.vertexBuffers = { + {data.bindlessConfig.megaVertexBuffer, 0, 0}, + {drawIndexBuffer, 1, 0}}; + gs.indexBuffer = { + data.bindlessConfig.megaIndexBuffer, nvrhi::Format::R32_UINT, 0}; + gs.viewport.addViewport(vp); + gs.viewport.addScissorRect(nvrhi::Rect(cres, cres)); + gs.indirectParams = tArgs; + if (tCount) + gs.indirectCountBuffer = tCount; + cmd->setGraphicsState(gs); + DrawIndexedIndirectCountOrFallback( + cmd, 0, 0, data.bindlessConfig.terrainObjectCount); + } + } + } + }); + + outputs.rainSM = passData.rainSM; + outputs.rainSMTex = state.rainSM; + outputs.sampleVP = state.sampleVP; + outputs.valid = true; + return outputs; +} + +} // namespace xray::render::fg::passes diff --git a/src/Layers/xrRender/FrameGraphPasses/RainShadowPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/RainShadowPassSetup.h new file mode 100644 index 00000000000..6d033800a23 --- /dev/null +++ b/src/Layers/xrRender/FrameGraphPasses/RainShadowPassSetup.h @@ -0,0 +1,56 @@ +#pragma once + +#include "Layers/xrRender/FrameGraph/FGTypes.h" +#include "Layers/xrRender/FrameGraph/FGResource.h" +#include "Layers/xrRender/FrameGraphPasses/ForwardColorPassSetup.h" +#include "Layers/xrRender/FrameGraphPasses/ShadowPassSetup.h" +#include "Layers/xrRender/RenderContext/ResourceHandle.h" +#include + +namespace xray::render::framegraph +{ +class FrameGraph; +} + +namespace xray::render::fg +{ +class RenderDevice; +} + +namespace xray::render::fg::passes +{ + +struct RainShadowPassState +{ + xray::render::fg::TextureHandle rainSMHandle; + nvrhi::ITexture* rainSM = nullptr; + nvrhi::GraphicsPipelineHandle rainPipeline; + nvrhi::BindingLayoutHandle rainLayout; + nvrhi::GraphicsPipelineHandle terrainPipeline; + nvrhi::BindingLayoutHandle terrainLayout; + nvrhi::BufferHandle rainCB; + u32 resolution = 0; + Fmatrix clipVP; + Fmatrix sampleVP; + bool initialized = false; + bool enabled = false; +}; + +struct RainShadowOutputs +{ + framegraph::VirtualResourceHandle rainSM; + nvrhi::ITexture* rainSMTex = nullptr; + Fmatrix sampleVP; + bool valid = false; +}; + +void InitializeRainShadowPass(fg::RenderDevice* device, RainShadowPassState& state); +void ShutdownRainShadowPass(fg::RenderDevice* device, RainShadowPassState& state); + +RainShadowOutputs setupRainShadowPass( + framegraph::FrameGraph& fg, + fg::RenderDevice* device, + const BindlessForwardConfig& bindlessConfig, + RainShadowPassState& state); + +} // namespace xray::render::fg::passes diff --git a/src/Layers/xrRender/FrameGraphPasses/ReSTIRGIPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/ReSTIRGIPassSetup.cpp index c9fcefafd44..d2aae8f2be4 100644 --- a/src/Layers/xrRender/FrameGraphPasses/ReSTIRGIPassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/ReSTIRGIPassSetup.cpp @@ -9,15 +9,45 @@ #include "Layers/xrRender/RenderContext/RenderContext.h" #include "Layers/xrRender/RenderContext/RenderDevice.h" #include "Layers/xrRender/RayTracing/RTAccelStructManager.h" -#if defined(XR_PLATFORM_WINDOWS) -#include "Layers/xrRender/Backend/D3D12Backend.h" -#endif +#include "Layers/xrRender/RayTracing/ReSTIRMemoryManager.h" +#include "Layers/xrRender/ClusteredLightManager.h" +#include "Layers/xrRender/Bindless/VariantTextureBuffer.h" #include "Layers/xrRender/ResourceManager/FGResourceManager.h" #include "Layers/xrRender/ResourceManager/TextureManager.h" #include "xrEngine/Environment.h" #include "xrEngine/IGame_Persistent.h" +#include "Layers/xrRender/xrRender_console.h" +#include "Layers/xrRender/FrameGraphPasses/ShaderConstants.h" +#include "Layers/xrRender/FrameGraphPasses/PassCommon.h" +#include "Layers/xrRender/FrameGraphPasses/TAAPassSetup.h" #include +extern ENGINE_API float SunshaftsIntensity; +extern ECORE_API u32 ps_r_sun_shafts; +extern ENGINE_API int ps_r_rt_gi; +extern ENGINE_API int ps_r_denoise; +extern ENGINE_API int ps_r_rt_gi_spatial_samples; +extern ENGINE_API float ps_r_rt_gi_spatial_radius; +extern ENGINE_API int ps_r_rt_gi_m_max; +extern ENGINE_API int ps_r_rt_di_candidates; +extern ENGINE_API int ps_r_rt_di_spatial_samples; +extern ENGINE_API float ps_r_rt_di_spatial_radius; +extern ENGINE_API int ps_r_rt_di_m_max; +extern ENGINE_API int ps_r_rt_gi_atrous_steps; +extern ENGINE_API float ps_r_rt_gi_temporal_alpha; +extern ENGINE_API int ps_r_rt_gi_bounces; +extern ENGINE_API int ps_r_rt_gi_cache_size; +extern ENGINE_API float ps_r_rt_gi_cache_cell; +extern ENGINE_API float ps_r_rt_gi_ambient_scale; +extern ENGINE_API float ps_r_rt_detail_dist; +extern ENGINE_API float ps_r_rt_gi_lod_dist; +extern ENGINE_API float ps_r3_grass_wind_multiplier; +extern ENGINE_API float ps_r_rt_sun_angular; +extern ENGINE_API int ps_r_rt_refl; +extern ENGINE_API int ps_r_rt_pt_bounces; +extern ENGINE_API int ps_r_rt_pt_ccap; +extern ENGINE_API int ps_r_rt_pt_decorrelate; + namespace fg { extern xray::render::FrameGraphRenderer RImplementation; @@ -27,15 +57,13 @@ namespace xray::render::fg::passes { using namespace framegraph; -static nvrhi::BufferHandle s_rtgiPlaceholderBuffer; -static nvrhi::TextureHandle s_rtgiPlaceholderCube; - struct ReSTIRGICB { Fmatrix invViewProj; Fmatrix prevViewProj; Fvector4 cameraPos; Fvector4 sunDir_intensity; Fvector4 sunColor_skyWeight; + Fvector4 skyColor; float screenWidth; float screenHeight; float giIntensity; @@ -45,9 +73,212 @@ struct ReSTIRGICB { u32 skinnedBatchStart; u32 grassBatchStart; u32 detailAtlasIndex; - u32 pad[3]; + u32 numLights; + u32 wetEnabled; + float wetStrength; + Fvector4 clusterParams; + Fvector4 clusterDepth; + Fvector4 diSampleParams; + u32 bounces; + u32 cacheSize; + float cacheCellSize; + u32 cacheMaxAge; + u32 grassShadowEnabled; + u32 padA[3]; + Fmatrix grassShadowVP; + Fmatrix worldToView; + Fvector4 hemiColor; + float lodDist; + float ambientScale; + float sunAngular; + u32 hudSkinnedStart; + u32 particleBatchStart; + float fullWidth; + float fullHeight; + u32 padEnd; + Fmatrix prevInvViewProj; + u32 hasPrevSunVis; + float currJitterX; + float currJitterY; + float prevJitterX; + float prevJitterY; + float windSpeed; + u32 padSun[2]; +}; +static_assert(sizeof(ReSTIRGICB) == 592, "ReSTIRGICB must be 592 bytes"); + +struct DITemporalCB { + Fmatrix invViewProj; + Fmatrix prevInvViewProj; + Fvector4 cameraPos; + float screenWidth; + float screenHeight; + float invScreenWidth; + float invScreenHeight; + u32 frameIndex; + u32 mMax; + float currJitterX; + float currJitterY; + float prevJitterX; + float prevJitterY; + float fullWidth; + float fullHeight; + Fvector4 clusterParams; + Fvector4 clusterScales; +}; +static_assert(sizeof(DITemporalCB) == 224, "DITemporalCB must be 224 bytes"); + +struct DISpatialCB { + Fmatrix invViewProj; + Fmatrix worldToView; + Fvector4 cameraPos; + float screenWidth; + float screenHeight; + float invScreenWidth; + float invScreenHeight; + u32 frameIndex; + u32 spatialSamples; + float spatialRadius; + u32 mMax; + Fvector4 clusterParams; + Fvector4 clusterScales; + float fullWidth; + float fullHeight; +}; +static_assert(sizeof(DISpatialCB) == 216, "DISpatialCB must be 216 bytes"); + +struct DIShadeCB { + Fmatrix invViewProj; + Fmatrix worldToView; + Fvector4 cameraPos; + float screenWidth; + float screenHeight; + u32 grassBatchStart; + u32 detailAtlasIndex; + Fvector4 clusterParams; + Fvector4 clusterDepth; + u32 identityStaticCount; + u32 terrainBatchCount; + u32 skinnedBatchStart; + u32 particleBatchStart; + u32 hudSkinnedStart; + float fullWidth; + float fullHeight; + u32 pad2; +}; +static_assert(sizeof(DIShadeCB) == 224, "DIShadeCB must be 224 bytes"); + +struct BlurCB { + float screenWidth; + float screenHeight; + float invScreenWidth; + float invScreenHeight; + float phiNormal; + float phiDepth; + u32 step; + u32 mode; +}; +static_assert(sizeof(BlurCB) == 32, "BlurCB must be 32 bytes"); + +struct TemporalFilterCB { + Fmatrix invViewProj; + Fmatrix prevInvViewProj; + Fvector4 cameraPos; + float screenWidth; + float screenHeight; + float invScreenWidth; + float invScreenHeight; + float alpha; + float envAdapt; + float currJitterX; + float currJitterY; + float prevJitterX; + float prevJitterY; + u32 enabled; + u32 pad1; }; -static_assert(sizeof(ReSTIRGICB) == 224, "ReSTIRGICB must be 224 bytes"); +static_assert(sizeof(TemporalFilterCB) == 192, "TemporalFilterCB must be 192 bytes"); + +struct SpecCB { + Fmatrix invViewProj; + Fmatrix prevInvViewProj; + Fmatrix prevViewProj; + Fvector4 cameraPos; + float screenWidth; + float screenHeight; + float invScreenWidth; + float invScreenHeight; + u32 frameIndex; + u32 spatialSamples; + float spatialRadius; + u32 hasPrev; +}; +static_assert(sizeof(SpecCB) == 240, "SpecCB must be 240 bytes"); + +struct PTInitialCB { + Fmatrix invViewProj; + Fvector4 cameraPos; + Fvector4 sunDirIntensity; + Fvector4 sunColorSky; + float screenWidth; + float screenHeight; + float invScreenWidth; + float invScreenHeight; + u32 frameIndex; + u32 maxBounces; + u32 identityStaticCount; + u32 terrainBatchCount; + u32 skinnedBatchStart; + u32 grassBatchStart; + u32 detailAtlasIndex; + u32 numLights; + u32 particleBatchStart; + u32 hudSkinnedStart; + u32 pad0; + u32 pad1; +}; +static_assert(sizeof(PTInitialCB) == 176, "PTInitialCB must be 176 bytes"); + +struct PTTemporalCB { + Fmatrix invViewProj; + Fmatrix prevInvViewProj; + Fvector4 cameraPos; + float screenWidth; + float screenHeight; + float invScreenWidth; + float invScreenHeight; + u32 frameIndex; + float cCap; + u32 hasPrev; + u32 pad; +}; +static_assert(sizeof(PTTemporalCB) == 176, "PTTemporalCB must be 176 bytes"); + +struct PTSpatialCB { + Fmatrix invViewProj; + Fvector4 cameraPos; + float screenWidth; + float screenHeight; + float invScreenWidth; + float invScreenHeight; + u32 frameIndex; + u32 pairIndex; + u32 flipX; + u32 flipY; + int offX; + int offY; + u32 pass; + u32 pad; +}; +static_assert(sizeof(PTSpatialCB) == 128, "PTSpatialCB must be 128 bytes"); + +struct PTDupCB { + float screenWidth; + float screenHeight; + u32 pad0; + u32 pad1; +}; +static_assert(sizeof(PTDupCB) == 16, "PTDupCB must be 16 bytes"); struct TemporalCB { Fmatrix invViewProj; @@ -58,9 +289,35 @@ struct TemporalCB { float invScreenWidth; float invScreenHeight; u32 frameIndex; - u32 pad[3]; + float envAdapt; + float currJitterX; + float currJitterY; + float prevJitterX; + float prevJitterY; +}; +static_assert(sizeof(TemporalCB) == 184, "TemporalCB must be 184 bytes"); + +struct SpatialCB { + Fmatrix invViewProj; + Fvector4 cameraPos; + float screenWidth; + float screenHeight; + float invScreenWidth; + float invScreenHeight; + u32 frameIndex; + u32 spatialSamples; + float spatialRadius; + u32 mMax; + u32 identityStaticCount; + u32 terrainBatchCount; + u32 skinnedBatchStart; + u32 grassBatchStart; + u32 detailAtlasIndex; + u32 particleBatchStart; + float lodDist; + u32 hudSkinnedStart; }; -static_assert(sizeof(TemporalCB) == 176, "TemporalCB must be 176 bytes"); +static_assert(sizeof(SpatialCB) == 144, "SpatialCB must be 144 bytes"); struct CompositeCB { Fmatrix invViewProj; @@ -68,155 +325,284 @@ struct CompositeCB { float screenWidth; float screenHeight; float giIntensity; - u32 pad; + u32 denoiseApply; + Fvector4 fogParams; + Fvector4 fogColor; + Fvector4 sunDir; + Fvector4 sunColor; + float ambientScale; + u32 cacheSize; + float cacheCellSize; + u32 useDdgi; + u32 addDirect; + float giWidth; + float giHeight; + u32 shaftWidth; + u32 shaftHeight; + u32 pad0; + u32 pad1; + u32 pad2; +}; +static_assert(sizeof(CompositeCB) == 208, "CompositeCB must be 208 bytes"); + +struct WaterCB { + Fmatrix invViewProj; + Fmatrix viewProj; + Fvector4 cameraPos; + Fvector4 sunDir_intensity; + Fvector4 sunColor_skyWeight; + Fvector4 skyColor; + float screenWidth; + float screenHeight; + float giIntensity; + u32 identityStaticCount; + u32 terrainBatchCount; + u32 skinnedBatchStart; + u32 grassBatchStart; + u32 detailAtlasIndex; + u32 hudSkinnedStart; + float lodDist; + u32 pad1; + u32 pad2; + Fvector4 hemiColor; }; -static_assert(sizeof(CompositeCB) == 96, "CompositeCB must be 96 bytes"); +static_assert(sizeof(WaterCB) == 256, "WaterCB must be 256 bytes"); -static void CreatePlaceholders(nvrhi::IDevice* nvDevice) +struct WetCB { + Fmatrix invViewProj; + Fvector4 cameraPos; + float screenWidth; + float screenHeight; + float deltaTime; + float rainFactor; + float dryRate; + float maxWet; + u32 pad[2]; +}; +static_assert(sizeof(WetCB) == 112, "WetCB must be 112 bytes"); + +struct SunshaftCB { + Fmatrix invViewProj; + Fmatrix prevViewProj; + Fvector4 cameraPos; + Fvector4 sunDir_intensity; + Fvector4 sunColor; + float screenWidth; + float screenHeight; + float shaftIntensity; + float shaftLength; + u32 identityStaticCount; + u32 terrainBatchCount; + u32 skinnedBatchStart; + u32 grassBatchStart; + u32 detailAtlasIndex; + u32 hudSkinnedStart; + u32 shaftSteps; + u32 alphaEveryN; + float fullWidth; + float fullHeight; + u32 particleBatchStart; + u32 frameIndex; + u32 hasPrev; + Fvector prevSunDir; +}; +static_assert(sizeof(SunshaftCB) == 256, "SunshaftCB must be 256 bytes"); + +struct DDGICB { + Fmatrix invViewProj; + Fvector4 cameraPos; + Fvector4 gridOrigin_spacing; + Fvector4 gridDims_intensity; + float screenWidth; + float screenHeight; + u32 frameIndex; + float pad; +}; +static_assert(sizeof(DDGICB) == 128, "DDGICB must be 128 bytes"); + +static const u32 kReSTIRPipeVersion = 141; + +struct RTBatchStarts { + u32 identityStatic; + u32 terrain; + u32 skinnedStart; + u32 grassStart; + u32 hudStart; + u32 particleStart; + u32 detailAtlas; +}; + +static RTBatchStarts ComputeBatchStarts(const RTAccelStructManager* accelMgr) { - if (!s_rtgiPlaceholderBuffer) { - nvrhi::BufferDesc desc; - desc.debugName = "RTGI_PlaceholderBuf"; - desc.byteSize = 4; - desc.canHaveRawViews = true; - desc.initialState = nvrhi::ResourceStates::ShaderResource; - desc.keepInitialState = true; - s_rtgiPlaceholderBuffer = nvDevice->createBuffer(desc); - } - if (!s_rtgiPlaceholderCube) { - nvrhi::TextureDesc desc; - desc.debugName = "RTGI_PlaceholderCube"; - desc.width = 1; - desc.height = 1; - desc.dimension = nvrhi::TextureDimension::TextureCube; - desc.arraySize = 6; - desc.format = nvrhi::Format::RGBA8_UNORM; - desc.initialState = nvrhi::ResourceStates::ShaderResource; - desc.keepInitialState = true; - s_rtgiPlaceholderCube = nvDevice->createTexture(desc); - } + const auto& bc = accelMgr->GetBatchCounts(); + const u32 base = bc.identityStatic + bc.terrain + bc.transparent + bc.instancedTotal; + RTBatchStarts s; + s.identityStatic = bc.identityStatic; + s.terrain = bc.terrain; + s.skinnedStart = bc.skinned > 0 ? base : 0xFFFFFFFFu; + s.grassStart = bc.grass > 0 ? base + bc.skinned : 0xFFFFFFFFu; + s.hudStart = (bc.skinnedHud > 0 && s.skinnedStart != 0xFFFFFFFFu) + ? s.skinnedStart + bc.skinnedWorld + : 0xFFFFFFFFu; + s.particleStart = bc.particles > 0 + ? base + bc.skinned + (bc.grass > 0 ? 1u : 0u) + : 0xFFFFFFFFu; + s.detailAtlas = accelMgr->GetDetailAtlasIndex(); + return s; } -static void InitializeResources(fg::RenderDevice* device, ReSTIRGIPassState& state) -{ - if (state.initialized) return; +} // namespace xray::render::fg::passes - auto& cache = GetPassResourceCache(); - nvrhi::IDevice* nvDevice = device->GetNVRHIDevice(); - CreatePlaceholders(nvDevice); +bool g_restirPipelinesReady = false; +bool g_restirReplaceForward = false; - nvrhi::SamplerDesc samplerDesc; - samplerDesc.setAllFilters(true); - samplerDesc.setAllAddressModes(nvrhi::SamplerAddressMode::Repeat); - state.sampler = cache.GetOrCreateSampler("RTGI", samplerDesc, nvDevice); +namespace xray::render::fg::passes { - state.cb = cache.GetOrCreateVolatileCB("RTGI", "RTGI_CB", - (u32)std::max({ sizeof(ReSTIRGICB), sizeof(TemporalCB), sizeof(CompositeCB) }), device); +static void ComputeEnvAdapt(const CEnvironment& env, float& envScale, float& envAdapt) +{ + const auto& desc = env.CurrentEnv; + const auto lum = [](float x, float y, float z) { + return 0.2126f * x + 0.7152f * y + 0.0722f * z; + }; + const float envL = std::max( + lum(desc.sky_color.x, desc.sky_color.y, desc.sky_color.z) + + lum(desc.sun_color.x, desc.sun_color.y, desc.sun_color.z) + + lum(desc.hemi_color.x, desc.hemi_color.y, desc.hemi_color.z), + 1e-4f); + static float s_refEnvL = -1.f; + static float s_smoothEnvL = -1.f; + if (s_refEnvL < 0.f) + s_refEnvL = envL; + if (s_smoothEnvL < 0.f) + s_smoothEnvL = envL; + const float prevSmooth = s_smoothEnvL; + s_smoothEnvL = std::clamp(envL, prevSmooth * 0.94f, prevSmooth * 1.06f); + envAdapt = s_smoothEnvL / prevSmooth; + envScale = s_smoothEnvL / s_refEnvL; +} - // --- Initial pass layout (RT + bindless) --- - { - auto csResult = GEnv.Render->GetShaderLoader()->LoadComputeShader("restir_gi_initial"); - if (csResult.handle) { - state.initialLayout = cache.GetOrCreateBindingLayoutFromReflection("RTGI_Initial", *csResult.reflection, nvDevice); -#if defined(XR_PLATFORM_WINDOWS) - auto* backend = dynamic_cast(GEnv.Backend); - nvrhi::IBindingLayout* bindlessLayout = backend ? backend->GetBindlessLayout() : nullptr; -#else - nvrhi::IBindingLayout* bindlessLayout = nullptr; -#endif - - nvrhi::ComputePipelineDesc pipeDesc; - pipeDesc.CS = csResult.handle; - if (bindlessLayout) - pipeDesc.bindingLayouts = { state.initialLayout, bindlessLayout }; - else - pipeDesc.bindingLayouts = { state.initialLayout }; - state.initialPipeline = nvDevice->createComputePipeline(pipeDesc); - } - } +void ShutdownReSTIRGI(ReSTIRGIPassState& state); - // --- Temporal pass layout --- - { - auto csResult = GEnv.Render->GetShaderLoader()->LoadComputeShader("restir_gi_temporal"); - if (csResult.handle) { - state.temporalLayout = cache.GetOrCreateBindingLayoutFromReflection("RTGI_Temporal", *csResult.reflection, nvDevice); - nvrhi::ComputePipelineDesc pipeDesc; - pipeDesc.CS = csResult.handle; - pipeDesc.bindingLayouts = { state.temporalLayout }; - state.temporalPipeline = nvDevice->createComputePipeline(pipeDesc); - } - } +static void InitializeResources(fg::RenderDevice* device, ReSTIRGIPassState& state) +{ + if (state.initialized && state.pipeVersion == kReSTIRPipeVersion) + return; - // --- Composite pass layout --- - { - auto csResult = GEnv.Render->GetShaderLoader()->LoadComputeShader("restir_gi_composite"); - if (csResult.handle) { - state.compositeLayout = cache.GetOrCreateBindingLayoutFromReflection("RTGI_Composite", *csResult.reflection, nvDevice); - nvrhi::ComputePipelineDesc pipeDesc; - pipeDesc.CS = csResult.handle; - pipeDesc.bindingLayouts = { state.compositeLayout }; - state.compositePipeline = nvDevice->createComputePipeline(pipeDesc); - } + if (state.initialized) { + state.initialPipeline = nullptr; + state.initialLayout = nullptr; + state.temporalPipeline = nullptr; + state.temporalLayout = nullptr; + state.spatialPipeline = nullptr; + state.spatialLayout = nullptr; + state.compositePipeline = nullptr; + state.compositeLayout = nullptr; + state.wetPipeline = nullptr; + state.wetLayout = nullptr; + state.sunshaftsPipeline = nullptr; + state.sunshaftsLayout = nullptr; + state.ddgiPipeline = nullptr; + state.ddgiLayout = nullptr; + state.waterPipeline = nullptr; + state.waterLayout = nullptr; + state.diTemporalPipeline = nullptr; + state.diTemporalLayout = nullptr; + state.diSpatialPipeline = nullptr; + state.diSpatialLayout = nullptr; + state.diShadePipeline = nullptr; + state.diShadeLayout = nullptr; + state.blurPipeline = nullptr; + state.blurLayout = nullptr; + state.temporalFilterPipeline = nullptr; + state.temporalFilterLayout = nullptr; + state.specTemporalPipeline = nullptr; + state.specTemporalLayout = nullptr; + state.ptInitialPipeline = nullptr; + state.ptInitialLayout = nullptr; + state.ptTemporalPipeline = nullptr; + state.ptTemporalLayout = nullptr; + state.ptSpatialPipeline = nullptr; + state.ptSpatialLayout = nullptr; + state.ptDupPipeline = nullptr; + state.ptDupLayout = nullptr; + state.initialized = false; + state.enabled = false; + g_restirPipelinesReady = false; + g_restirReplaceForward = false; } - state.enabled = state.initialPipeline && state.temporalPipeline && state.compositePipeline; + auto& cache = GetPassResourceCache(); + nvrhi::IDevice* nvDevice = device->GetNVRHIDevice(); + ReSTIRMemoryManager::Instance().Init(nvDevice); + + const u32 cbSize = (u32)std::max({ + sizeof(ReSTIRGICB), sizeof(TemporalCB), sizeof(SpatialCB), sizeof(CompositeCB), + sizeof(WetCB), sizeof(SunshaftCB), sizeof(DDGICB), sizeof(WaterCB), + sizeof(DITemporalCB), sizeof(DISpatialCB), sizeof(DIShadeCB), + sizeof(BlurCB), sizeof(TemporalFilterCB), sizeof(SpecCB), + sizeof(PTInitialCB), sizeof(PTTemporalCB), sizeof(PTSpatialCB), sizeof(PTDupCB) }); + state.cb = cache.GetOrCreateVolatileCB("RTGI", "RTGI_CB_v103", cbSize, device, 256); + + auto loadPipe = [&](const char* name, nvrhi::ComputePipelineHandle& pipe, nvrhi::BindingLayoutHandle& layout, bool bindless) { + auto csResult = GEnv.Render->GetShaderLoader()->LoadComputeShader(name); + if (!csResult.handle) { + Msg("! [ReSTIR] Failed to load shader '%s'", name); + return; + } + layout = cache.GetOrCreateBindingLayoutFromReflection(name, *csResult.reflection, nvDevice); + nvrhi::IBindingLayout* bindlessLayout = nullptr; + if (bindless && GEnv.Backend) + bindlessLayout = GEnv.Backend->GetBindlessLayout(); + nvrhi::ComputePipelineDesc pipeDesc; + pipeDesc.CS = csResult.handle; + if (bindlessLayout) + pipeDesc.bindingLayouts = { layout, bindlessLayout }; + else + pipeDesc.bindingLayouts = { layout }; + pipe = nvDevice->createComputePipeline(pipeDesc); + if (!pipe) + Msg("! [ReSTIR] Failed to create pipeline '%s'", name); + }; + + loadPipe("restir_gi_initial", state.initialPipeline, state.initialLayout, true); + loadPipe("restir_gi_temporal", state.temporalPipeline, state.temporalLayout, false); + loadPipe("restir_gi_spatial", state.spatialPipeline, state.spatialLayout, true); + loadPipe("restir_gi_composite", state.compositePipeline, state.compositeLayout, false); + loadPipe("restir_wet", state.wetPipeline, state.wetLayout, true); + loadPipe("restir_sunshafts", state.sunshaftsPipeline, state.sunshaftsLayout, true); + loadPipe("restir_ddgi", state.ddgiPipeline, state.ddgiLayout, false); + loadPipe("restir_water_rt", state.waterPipeline, state.waterLayout, true); + loadPipe("restir_di_temporal", state.diTemporalPipeline, state.diTemporalLayout, true); + loadPipe("restir_di_spatial", state.diSpatialPipeline, state.diSpatialLayout, true); + loadPipe("restir_di_shade", state.diShadePipeline, state.diShadeLayout, true); + loadPipe("restir_gi_blur", state.blurPipeline, state.blurLayout, false); + loadPipe("restir_gi_temporal_filter", state.temporalFilterPipeline, state.temporalFilterLayout, false); + loadPipe("restir_spec_temporal", state.specTemporalPipeline, state.specTemporalLayout, false); + loadPipe("restir_pt_initial", state.ptInitialPipeline, state.ptInitialLayout, true); + loadPipe("restir_pt_temporal", state.ptTemporalPipeline, state.ptTemporalLayout, false); + loadPipe("restir_pt_spatial", state.ptSpatialPipeline, state.ptSpatialLayout, false); + loadPipe("restir_pt_dupmap", state.ptDupPipeline, state.ptDupLayout, false); + + state.enabled = state.initialPipeline && state.temporalPipeline && state.spatialPipeline && state.compositePipeline; state.initialized = true; + state.pipeVersion = kReSTIRPipeVersion; + g_restirPipelinesReady = state.enabled; + if (!state.enabled) + g_restirReplaceForward = false; if (state.enabled) - Msg("* [ReSTIR GI] All pipelines created successfully"); + Msg("* [ReSTIR] Pipelines ready v%u (water=%s bindless=%s)", + kReSTIRPipeVersion, + state.waterPipeline ? "ok" : "off", + GEnv.Backend && GEnv.Backend->GetBindlessLayout() ? "yes" : "no"); else - Msg("! [ReSTIR GI] Pipeline creation failed (initial=%s temporal=%s composite=%s)", + Msg("! [ReSTIR] Pipeline creation failed (initial=%s temporal=%s spatial=%s composite=%s)", state.initialPipeline ? "ok" : "FAIL", state.temporalPipeline ? "ok" : "FAIL", + state.spatialPipeline ? "ok" : "FAIL", state.compositePipeline ? "ok" : "FAIL"); } -static void EnsurePersistentTextures(nvrhi::IDevice* nvDevice, ReSTIRGIPassState& state, u32 width, u32 height) -{ - if (state.reservoirA[0] && state.texWidth == width && state.texHeight == height) - return; - - for (int i = 0; i < 2; i++) { - { - nvrhi::TextureDesc desc; - desc.debugName = i == 0 ? "RTGI_ReservoirA_0" : "RTGI_ReservoirA_1"; - desc.width = width; - desc.height = height; - desc.format = nvrhi::Format::RGBA32_FLOAT; - desc.isUAV = true; - desc.initialState = nvrhi::ResourceStates::UnorderedAccess; - desc.keepInitialState = true; - state.reservoirA[i] = nvDevice->createTexture(desc); - } - { - nvrhi::TextureDesc desc; - desc.debugName = i == 0 ? "RTGI_ReservoirB_0" : "RTGI_ReservoirB_1"; - desc.width = width; - desc.height = height; - desc.format = nvrhi::Format::RGBA32_FLOAT; - desc.isUAV = true; - desc.initialState = nvrhi::ResourceStates::UnorderedAccess; - desc.keepInitialState = true; - state.reservoirB[i] = nvDevice->createTexture(desc); - } - } - - { - nvrhi::TextureDesc desc; - desc.debugName = "RTGI_DirectLighting"; - desc.width = width; - desc.height = height; - desc.format = nvrhi::Format::RGBA16_FLOAT; - desc.isUAV = true; - desc.initialState = nvrhi::ResourceStates::UnorderedAccess; - desc.keepInitialState = true; - state.directLighting = nvDevice->createTexture(desc); - } - - state.texWidth = width; - state.texHeight = height; -} - struct InitialPassData { fg::RenderDevice* device; RTAccelStructManager* accelMgr; @@ -224,11 +610,19 @@ struct InitialPassData { VirtualResourceHandle depth; VirtualResourceHandle normal; VirtualResourceHandle baseColor; + VirtualResourceHandle worldPos; + VirtualResourceHandle sceneColorIn; ReSTIRGICB cbData; u32 width, height; nvrhi::ITexture* sky0; nvrhi::ITexture* sky1; - u32 writeIdx; + VirtualResourceHandle grassShadow; + nvrhi::ITexture* grassShadowTex = nullptr; + bool hasGrassShadow = false; + VirtualResourceHandle prevDepth; + VirtualResourceHandle prevNormals; + VirtualResourceHandle motionVectors; + bool hasPrevSunVis = false; }; struct TemporalPassData { @@ -238,12 +632,11 @@ struct TemporalPassData { VirtualResourceHandle normal; VirtualResourceHandle prevNormals; VirtualResourceHandle baseColor; + VirtualResourceHandle worldPos; VirtualResourceHandle prevDepth; VirtualResourceHandle motionVectors; TemporalCB cbData; u32 width, height; - u32 readIdx; - u32 writeIdx; }; struct CompositePassData { @@ -252,11 +645,45 @@ struct CompositePassData { VirtualResourceHandle depth; VirtualResourceHandle normal; VirtualResourceHandle baseColor; + VirtualResourceHandle worldPos; VirtualResourceHandle sceneColorIn; VirtualResourceHandle sceneColor; CompositeCB cbData; + nvrhi::ITexture* sky0; + nvrhi::ITexture* sky1; + u32 width, height; +}; + +struct WetPassData { + fg::RenderDevice* device; + RTAccelStructManager* accelMgr; + ReSTIRGIPassState* state; + VirtualResourceHandle depth; + VirtualResourceHandle normal; + VirtualResourceHandle worldPos; + WetCB cbData; + u32 width, height; +}; + +struct SunshaftPassData { + fg::RenderDevice* device; + RTAccelStructManager* accelMgr; + ReSTIRGIPassState* state; + VirtualResourceHandle depth; + VirtualResourceHandle prevDepth; + VirtualResourceHandle normal; + SunshaftCB cbData; + u32 width, height; +}; + +struct DDGIPassData { + fg::RenderDevice* device; + ReSTIRGIPassState* state; + VirtualResourceHandle depth; + VirtualResourceHandle normal; + VirtualResourceHandle baseColor; + DDGICB cbData; u32 width, height; - u32 reservoirIdx; }; ReSTIRGIOutput setupReSTIRGIPass( @@ -266,20 +693,24 @@ ReSTIRGIOutput setupReSTIRGIPass( VirtualResourceHandle depth, VirtualResourceHandle normal, VirtualResourceHandle baseColor, + VirtualResourceHandle worldPos, VirtualResourceHandle prevNormals, VirtualResourceHandle prevDepth, VirtualResourceHandle motionVectors, VirtualResourceHandle sceneColorIn, const Fmatrix& invViewProj, const Fmatrix& prevViewProj, + const Fmatrix& prevInvViewProj, const Fvector& cameraPos, float giIntensity, u32 width, u32 height, ReSTIRGIPassState& state, - bool hasPrevFrameData) + bool hasPrevFrameData, + const GrassShadowOutputs& grassShadow) { InitializeResources(device, state); + ReSTIRGIOutput output{}; ResourceDesc outDesc; outDesc.type = ResourceDesc::Type::Texture2D; outDesc.debugName = "rtgi_SceneColor"; @@ -288,47 +719,59 @@ ReSTIRGIOutput setupReSTIRGIPass( outDesc.format = nvrhi::Format::RGBA16_FLOAT; outDesc.isUAV = true; outDesc.isRenderTarget = true; - outDesc.isTransient = true; + outDesc.isTransient = false; VirtualResourceHandle outHandle = fg.CreateTexture("rtgi_SceneColor", outDesc); if (!state.enabled || !accelMgr || !accelMgr->IsReady()) { - auto& passData = fg.addCallbackPass( - "ReSTIR GI (disabled)", - [&](FrameGraph& builder, PassHandle passHandle, CompositePassData& data) { - RenderPassBuilder pb(builder, passHandle); - data.sceneColor = pb.write(outHandle, ResourceState::UnorderedAccess); - }, - [](const CompositePassData&, const FrameGraph&, fg::RenderContext*) {} - ); - return { passData.sceneColor }; + static bool s_logged = false; + if (!s_logged && ps_r_rt_gi) { + s_logged = true; + Msg("! [ReSTIR] Pass inactive (enabled=%d accelReady=%d) — passthrough", + state.enabled ? 1 : 0, (accelMgr && accelMgr->IsReady()) ? 1 : 0); + } + g_restirReplaceForward = false; + output.sceneColor = sceneColorIn; + return output; } - nvrhi::IDevice* nvDevice = device->GetNVRHIDevice(); - EnsurePersistentTextures(nvDevice, state, width, height); + g_restirReplaceForward = true; + + auto& mem = ReSTIRMemoryManager::Instance(); + mem.Ensure(width, height); + if (!mem.IsReady()) { + Msg("! [ReSTIR] MemoryManager not ready after Ensure(%ux%u)", width, height); + g_restirReplaceForward = false; + output.sceneColor = sceneColorIn; + return output; + } - u32 writeIdx = state.currTemporalIdx; - u32 readIdx = 1 - writeIdx; + const u32 giW = mem.GetGiWidth(); + const u32 giH = mem.GetGiHeight(); + const u32 shaftW = mem.GetShaftWidth(); + const u32 shaftH = mem.GetShaftHeight(); - CEnvironment& env = g_pGamePersistent->Environment(); - auto* resourceManager = device->GetFGResourceManager(); - auto* texManager = resourceManager ? resourceManager->GetTextureManager() : nullptr; + output.noisyDiffuse = mem.GetNoisyDiffuse(); + output.noisySpecular = mem.GetNoisySpecular(); + output.hitDistance = mem.GetHitDistance(); - nvrhi::ITexture* sky0Tex = s_rtgiPlaceholderCube.Get(); - nvrhi::ITexture* sky1Tex = s_rtgiPlaceholderCube.Get(); + const u32 workIdx = ReSTIRMemoryManager::WorkIndex(); + const u32 histIdx = ReSTIRMemoryManager::HistoryIndex(); + + CEnvironment& env = g_pGamePersistent->Environment(); + float envScale = 1.f; + float envAdapt = 1.f; + ComputeEnvAdapt(env, envScale, envAdapt); + nvrhi::ITexture* sky0Tex = mem.GetPlaceholderCube(); + nvrhi::ITexture* sky1Tex = mem.GetPlaceholderCube(); float skyWeight = env.CurrentEnv.weight; - if (texManager && env.Current[0] && env.Current[1]) { - if (env.Current[0]->sky_texture_name.size()) { - auto h0 = texManager->LoadTexture(env.Current[0]->sky_texture_name.c_str()); - nvrhi::ITexture* t = texManager->GetNVRHITexture(h0); - if (t) sky0Tex = t; - } - if (env.Current[1]->sky_texture_name.size()) { - auto h1 = texManager->LoadTexture(env.Current[1]->sky_texture_name.c_str()); - nvrhi::ITexture* t = texManager->GetNVRHITexture(h1); - if (t) sky1Tex = t; - } - } + nvrhi::ITexture* envSky0 = nullptr; + nvrhi::ITexture* envSky1 = nullptr; + ResolveEnvSkyCubes(device, envSky0, envSky1); + if (envSky0) + sky0Tex = envSky0; + if (envSky1) + sky1Tex = envSky1; Fvector sunDir = env.CurrentEnv.sun_dir; Fvector3 sc = { env.CurrentEnv.sun_color.x, env.CurrentEnv.sun_color.y, env.CurrentEnv.sun_color.z }; @@ -339,132 +782,359 @@ ReSTIRGIOutput setupReSTIRGIPass( else sunColor.set(0, 0, 0); + auto& clm = ClusteredLightManager::Instance(); const auto& batchCounts = accelMgr->GetBatchCounts(); + const bool wetEnabled = ps_r2_ls_flags.test(R3FLAG_DYN_WET_SURF); + float rainFactor = 0.f; + if (g_pGamePersistent && wetEnabled) + rainFactor = g_pGamePersistent->Environment().CurrentEnv.rain_density; + float shaftIntensityEarly = 0.f; + if (ps_r_sun_shafts > 0 && g_pGamePersistent && !Device.dwPrecacheFrame) + { + shaftIntensityEarly = g_pGamePersistent->Environment().CurrentEnv.m_fSunShaftsIntensity; + if (SunshaftsIntensity > 0.f) + shaftIntensityEarly = SunshaftsIntensity; + } + const bool wantSunshafts = state.sunshaftsPipeline && ps_r_sun_shafts > 0 && shaftIntensityEarly >= 0.0001f; + ReSTIRGICB initialCB; initialCB.invViewProj = invViewProj; initialCB.prevViewProj = prevViewProj; initialCB.cameraPos = { cameraPos.x, cameraPos.y, cameraPos.z, 0 }; initialCB.sunDir_intensity = { sunDir.x, sunDir.y, sunDir.z, sunIntensity }; initialCB.sunColor_skyWeight = { sunColor.x, sunColor.y, sunColor.z, skyWeight }; - initialCB.screenWidth = (float)width; - initialCB.screenHeight = (float)height; + { + const Fvector3& skc = env.CurrentEnv.sky_color; + initialCB.skyColor = { skc.x, skc.y, skc.z, envScale }; + } + initialCB.screenWidth = (float)giW; + initialCB.screenHeight = (float)giH; initialCB.giIntensity = giIntensity; initialCB.frameIndex = Device.dwFrame; initialCB.identityStaticCount = batchCounts.identityStatic; initialCB.terrainBatchCount = batchCounts.terrain; initialCB.skinnedBatchStart = batchCounts.skinned > 0 ? batchCounts.identityStatic + batchCounts.terrain + batchCounts.transparent + batchCounts.instancedTotal - : 0; + : 0xFFFFFFFFu; initialCB.grassBatchStart = batchCounts.grass > 0 ? batchCounts.identityStatic + batchCounts.terrain + batchCounts.transparent + batchCounts.instancedTotal + batchCounts.skinned - : 0; + : 0xFFFFFFFFu; initialCB.detailAtlasIndex = accelMgr->GetDetailAtlasIndex(); - initialCB.pad[0] = initialCB.pad[1] = initialCB.pad[2] = 0; + initialCB.hudSkinnedStart = (batchCounts.skinnedHud > 0 && initialCB.skinnedBatchStart != 0xFFFFFFFFu) + ? initialCB.skinnedBatchStart + batchCounts.skinnedWorld + : 0xFFFFFFFFu; + initialCB.particleBatchStart = batchCounts.particles > 0 + ? (initialCB.grassBatchStart != 0xFFFFFFFFu + ? initialCB.grassBatchStart + 1u + : (initialCB.skinnedBatchStart != 0xFFFFFFFFu + ? initialCB.skinnedBatchStart + batchCounts.skinned + : batchCounts.identityStatic + batchCounts.terrain + batchCounts.transparent + batchCounts.instancedTotal)) + : 0xFFFFFFFFu; + initialCB.fullWidth = (float)width; + initialCB.fullHeight = (float)height; + initialCB.padEnd = 0; + initialCB.prevInvViewProj = prevInvViewProj; + initialCB.hasPrevSunVis = (hasPrevFrameData && motionVectors.is_valid()) ? 1u : 0u; + initialCB.currJitterX = g_taa_jitter_px; + initialCB.currJitterY = g_taa_jitter_py; + initialCB.prevJitterX = g_taa_jitter_prev_px; + initialCB.prevJitterY = g_taa_jitter_prev_py; + initialCB.windSpeed = 0.f; + if (g_pGamePersistent) + initialCB.windSpeed = g_pGamePersistent->Environment().CurrentEnv.wind_velocity * ps_r3_grass_wind_multiplier; + initialCB.padSun[0] = 0; + initialCB.padSun[1] = 0; + initialCB.numLights = clm.GetLightCount(); + if (initialCB.numLights > RESTIR_MAX_LIGHTS) + initialCB.numLights = RESTIR_MAX_LIGHTS; + initialCB.wetEnabled = wetEnabled ? 1u : 0u; + initialCB.wetStrength = 1.0f; + + ClusterCB ccb = clm.BuildClusterCB(width, height, 0.2f, 500.f); + initialCB.clusterParams = { ccb.gridDims.x, ccb.gridDims.y, ccb.gridDims.z, (float)clm.GetLightCount() }; + initialCB.clusterDepth = ccb.depthParams; + const u32 diCand = (u32)std::clamp(ps_r_rt_di_candidates, 1, 16); + initialCB.diSampleParams = { + (float)clm.GetDILightCount(), + clm.GetDIPowerSum(), + (float)diCand, + 0.f + }; + initialCB.bounces = (u32)std::clamp(ps_r_rt_gi_bounces, 1, 2); + initialCB.cacheSize = (u32)std::max(0, ps_r_rt_gi_cache_size); + initialCB.cacheCellSize = std::max(0.05f, ps_r_rt_gi_cache_cell); + initialCB.cacheMaxAge = 64; + initialCB.grassShadowEnabled = grassShadow.valid ? 1u : 0u; + if (grassShadow.valid) + initialCB.grassShadowVP = grassShadow.sampleVP; + else + initialCB.grassShadowVP.identity(); + initialCB.worldToView = Device.mView; + { + const Fvector4& hc = env.CurrentEnv.hemi_color; + initialCB.hemiColor = { hc.x, hc.y, hc.z, hc.w }; + } + initialCB.lodDist = std::max(10.f, ps_r_rt_gi_lod_dist); + initialCB.ambientScale = std::clamp(ps_r_rt_gi_ambient_scale, 0.f, 1.f); + initialCB.sunAngular = std::clamp(ps_r_rt_sun_angular, 0.001f, 0.05f); + mem.EnsureIrradianceCache(initialCB.cacheSize); + const bool usePT = (ps_r_rt_gi >= 2) && state.ptInitialPipeline && mem.GetPTReservoirA(0); + if (usePT) + initialCB.bounces = (u32)std::clamp(ps_r_rt_pt_bounces, 1, 8); ResourceDesc persistDesc; persistDesc.type = ResourceDesc::Type::Texture2D; - persistDesc.width = width; - persistDesc.height = height; + persistDesc.width = giW; + persistDesc.height = giH; persistDesc.isImported = true; persistDesc.isTransient = false; persistDesc.isUAV = true; auto dlDesc = persistDesc; dlDesc.format = nvrhi::Format::RGBA16_FLOAT; - VirtualResourceHandle fgDirectLighting = fg.ImportTexture("rtgi_DirectLighting", state.directLighting.Get(), dlDesc); - - auto resDesc = persistDesc; - resDesc.format = nvrhi::Format::RGBA32_FLOAT; - VirtualResourceHandle fgResA = fg.ImportTexture("rtgi_ResA_W", state.reservoirA[writeIdx].Get(), resDesc); - VirtualResourceHandle fgResB = fg.ImportTexture("rtgi_ResB_W", state.reservoirB[writeIdx].Get(), resDesc); + VirtualResourceHandle fgDirectLighting = fg.ImportTexture("rtgi_DirectLighting", mem.GetDirectLighting(), dlDesc); + VirtualResourceHandle fgNoisyDiff = fg.ImportTexture("rtgi_NoisyDiffuse", mem.GetNoisyDiffuse(), dlDesc); + VirtualResourceHandle fgNoisySpec = fg.ImportTexture("rtgi_NoisySpecular", mem.GetNoisySpecular(), dlDesc); + VirtualResourceHandle fgDdgiAmb = fg.ImportTexture("rtgi_DDGIAmbient", mem.GetDdgiAmbient(), dlDesc); + + auto shaftDesc = persistDesc; + shaftDesc.width = shaftW; + shaftDesc.height = shaftH; + shaftDesc.format = nvrhi::Format::RGBA16_FLOAT; + VirtualResourceHandle fgSunshafts = fg.ImportTexture("rtgi_Sunshafts", mem.GetSunshafts(), shaftDesc); + + auto hitDesc = persistDesc; + hitDesc.format = nvrhi::Format::R16_FLOAT; + VirtualResourceHandle fgHitDist = fg.ImportTexture("rtgi_HitDistance", mem.GetHitDistance(), hitDesc); + + auto wetDesc = persistDesc; + wetDesc.width = width; + wetDesc.height = height; + wetDesc.format = nvrhi::Format::R16_FLOAT; + VirtualResourceHandle fgWet = fg.ImportTexture("rtgi_WetAccum", mem.GetWetAccum(), wetDesc); fg.GetRTRegistry().RegisterRT("rt_DirectLighting", fgDirectLighting); - fg.GetRTRegistry().RegisterRT("rt_GI_ReservoirA", fgResA); - fg.GetRTRegistry().RegisterRT("rt_GI_ReservoirB", fgResB); + fg.GetRTRegistry().RegisterRT("rt_GI_NoisyDiffuse", fgNoisyDiff); + fg.GetRTRegistry().RegisterRT("rt_GI_NoisySpecular", fgNoisySpec); + fg.GetRTRegistry().RegisterRT("rt_GI_HitDistance", fgHitDist); + + const bool runWetPass = state.wetPipeline != nullptr; + if (runWetPass) { + WetCB wetCB{}; + wetCB.invViewProj = invViewProj; + wetCB.cameraPos = { cameraPos.x, cameraPos.y, cameraPos.z, 0 }; + wetCB.screenWidth = (float)width; + wetCB.screenHeight = (float)height; + wetCB.deltaTime = Device.fTimeDelta; + wetCB.rainFactor = (wetEnabled ? rainFactor : 0.f) * 0.35f; + wetCB.dryRate = 0.08f; + wetCB.maxWet = 1.0f; + + fg.addCallbackPass( + "ReSTIR Wet", + [&, wetCB, fgWet, accelMgr](FrameGraph& builder, PassHandle passHandle, WetPassData& data) { + RenderPassBuilder pb(builder, passHandle); + data.depth = pb.read(depth, ResourceState::ShaderResource); + data.normal = pb.read(normal, ResourceState::ShaderResource); + if (worldPos.is_valid()) + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); + pb.readWrite(fgWet, ResourceState::UnorderedAccess); + pb.sideEffects(); + data.device = device; + data.accelMgr = accelMgr; + data.state = &state; + data.cbData = wetCB; + data.width = width; + data.height = height; + }, + [](const WetPassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* worldPosTex = data.worldPos.is_valid() ? fgGraph.GetPhysicalTexture(data.worldPos) : nullptr; + auto* wetTex = ReSTIRMemoryManager::Instance().GetWetAccum(); + auto* skyOpenTex = ReSTIRMemoryManager::Instance().GetSkyOpen(); + if (!depthTex || !normalTex || !wetTex || !skyOpenTex || !data.state->wetPipeline || !data.accelMgr) + return; + auto* tlas = data.accelMgr->GetTLAS(); + auto* batchInfo = data.accelMgr->GetBatchInfoBuffer(); + auto* megaVB = data.accelMgr->GetMegaVB(); + auto* megaIB = data.accelMgr->GetMegaIB(); + auto* matBuf = data.accelMgr->GetMaterialBuffer(); + if (!tlas || !batchInfo || !megaVB || !megaIB || !matBuf) + return; + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + cmd->writeBuffer(data.state->cb, &data.cbData, sizeof(WetCB)); + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_wet", ".cs"); + if (!refl) return; + BindingSetBuilder bsb(*refl, nv, "ReSTIR.Wet"); + bsb.ConstantBuffer("WetParams", data.state->cb); + bsb.Texture("t_Depth", depthTex); + bsb.Texture("t_Normal", normalTex); + bsb.Texture("t_WorldPos", worldPosTex ? worldPosTex : ReSTIRMemoryManager::Instance().GetPlaceholderColorTex()); + bsb.AccelStruct("g_SceneTLAS", tlas); + bsb.BufferSRV("g_BatchInfo", batchInfo); + bsb.BufferSRV("g_MegaVB", megaVB); + bsb.BufferSRV("g_MegaIB", megaIB); + bsb.BufferSRV("g_Materials", matBuf); + bsb.TextureUAV("u_WetAccum", wetTex); + bsb.TextureUAV("u_SkyOpen", skyOpenTex); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->wetLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->wetPipeline; + cs.bindings = { bs }; + auto* backend = data.device->GetBackend(); + if (backend && backend->GetBindlessDescriptorTable()) + cs.bindings.push_back(backend->GetBindlessDescriptorTable()); + cmd->setTextureState(skyOpenTex, nvrhi::AllSubresources, nvrhi::ResourceStates::UnorderedAccess); + cmd->setTextureState(wetTex, nvrhi::AllSubresources, nvrhi::ResourceStates::UnorderedAccess); + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + cmd->setTextureState(skyOpenTex, nvrhi::AllSubresources, nvrhi::ResourceStates::ShaderResource); + cmd->setTextureState(wetTex, nvrhi::AllSubresources, nvrhi::ResourceStates::ShaderResource); + } + ); + + } else if (mem.GetSkyOpen()) { + struct SkyOpenClearData {}; + fg.addCallbackPass( + "ReSTIR SkyOpen Clear", + [&](FrameGraph& builder, PassHandle passHandle, SkyOpenClearData&) { + RenderPassBuilder pb(builder, passHandle); + pb.sideEffects(); + }, + [](const SkyOpenClearData&, const FrameGraph&, fg::RenderContext* ctx) { + auto* sky = ReSTIRMemoryManager::Instance().GetSkyOpen(); + if (!sky || !ctx) return; + ctx->GetCommandList()->clearTextureFloat(sky, nvrhi::AllSubresources, nvrhi::Color(1.f)); + }); + } - // ============================================ - // PASS 1: Initial Sample (RT shadow + bounce) - // ============================================ + if (!usePT) fg.addCallbackPass( - "ReSTIR GI Initial", - [&, sky0Tex, sky1Tex, initialCB, writeIdx, fgDirectLighting, fgResA, fgResB](FrameGraph& builder, PassHandle passHandle, InitialPassData& data) { + "ReSTIR Initial", + [&, sky0Tex, sky1Tex, initialCB, fgDirectLighting, fgNoisyDiff, fgNoisySpec, fgHitDist, fgWet, grassShadow, sceneColorIn, hasPrevFrameData]( + FrameGraph& builder, PassHandle passHandle, InitialPassData& data) { RenderPassBuilder pb(builder, passHandle); data.depth = pb.read(depth, ResourceState::ShaderResource); data.normal = pb.read(normal, ResourceState::ShaderResource); data.baseColor = pb.read(baseColor, ResourceState::ShaderResource); + if (worldPos.is_valid()) + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); + data.sceneColorIn = pb.read(sceneColorIn, ResourceState::ShaderResource); pb.write(fgDirectLighting, ResourceState::UnorderedAccess); - pb.write(fgResA, ResourceState::UnorderedAccess); - pb.write(fgResB, ResourceState::UnorderedAccess); + pb.write(fgNoisyDiff, ResourceState::UnorderedAccess); + pb.write(fgNoisySpec, ResourceState::UnorderedAccess); + pb.write(fgHitDist, ResourceState::UnorderedAccess); + pb.read(fgWet, ResourceState::ShaderResource); + data.hasGrassShadow = grassShadow.valid && grassShadow.shadowMap.is_valid(); + if (data.hasGrassShadow) + data.grassShadow = pb.read(grassShadow.shadowMap, ResourceState::ShaderResource); + data.grassShadowTex = grassShadow.shadowTex; + data.hasPrevSunVis = hasPrevFrameData && motionVectors.is_valid(); + if (data.hasPrevSunVis) { + if (prevDepth.is_valid()) + data.prevDepth = pb.read(prevDepth, ResourceState::ShaderResource); + if (prevNormals.is_valid()) + data.prevNormals = pb.read(prevNormals, ResourceState::ShaderResource); + data.motionVectors = pb.read(motionVectors, ResourceState::ShaderResource); + } pb.sideEffects(); data.device = device; data.accelMgr = accelMgr; data.state = &state; data.cbData = initialCB; - data.width = width; - data.height = height; + data.width = giW; + data.height = giH; data.sky0 = sky0Tex; data.sky1 = sky1Tex; - data.writeIdx = writeIdx; }, - [](const InitialPassData& data, const FrameGraph& fg, fg::RenderContext* ctx) { - auto* depthTex = fg.GetPhysicalTexture(data.depth); - auto* normalTex = fg.GetPhysicalTexture(data.normal); - auto* baseColorTex = fg.GetPhysicalTexture(data.baseColor); - if (!depthTex || !normalTex || !baseColorTex) { - Msg("! [RTGI Initial] Null FG texture: depth=%d normal=%d baseColor=%d", !!depthTex, !!normalTex, !!baseColorTex); + [workIdx](const InitialPassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* baseColorTex = fgGraph.GetPhysicalTexture(data.baseColor); + auto* worldPosTex = data.worldPos.is_valid() ? fgGraph.GetPhysicalTexture(data.worldPos) : nullptr; + auto* sceneInTex = fgGraph.GetPhysicalTexture(data.sceneColorIn); + if (!depthTex || !normalTex || !baseColorTex) return; - } nvrhi::ITexture* sky0 = data.sky0; nvrhi::ITexture* sky1 = data.sky1; - nvrhi::ITexture* directLit = data.state->directLighting.Get(); - nvrhi::ITexture* resA = data.state->reservoirA[data.writeIdx].Get(); - nvrhi::ITexture* resB = data.state->reservoirB[data.writeIdx].Get(); auto* tlas = data.accelMgr->GetTLAS(); auto* batchInfo = data.accelMgr->GetBatchInfoBuffer(); auto* megaVB = data.accelMgr->GetMegaVB(); auto* megaIB = data.accelMgr->GetMegaIB(); auto* matBuf = data.accelMgr->GetMaterialBuffer(); auto* terrainBuf = data.accelMgr->GetTerrainMaterialBuffer(); - - if (!sky0 || !sky1 || !directLit || !resA || !resB || - !tlas || !batchInfo || !megaVB || !megaIB || !matBuf || !terrainBuf) { - Msg("! [RTGI Initial] Null binding: sky0=%d sky1=%d directLit=%d resA=%d resB=%d tlas=%d batch=%d megaVB=%d megaIB=%d mat=%d terrain=%d", - !!sky0, !!sky1, !!directLit, !!resA, !!resB, !!tlas, !!batchInfo, !!megaVB, !!megaIB, !!matBuf, !!terrainBuf); + nvrhi::IBuffer* resBuf = memMgr.GetReservoirBuffer(workIdx); + nvrhi::ITexture* directLit = memMgr.GetDirectLighting(); + nvrhi::ITexture* noisyDiff = memMgr.GetNoisyDiffuse(); + nvrhi::ITexture* noisySpec = memMgr.GetNoisySpecular(); + nvrhi::ITexture* hitDist = memMgr.GetHitDistance(); + nvrhi::ITexture* wetTex = memMgr.GetWetAccum(); + nvrhi::ITexture* skyOpenTex = memMgr.GetSkyOpen(); + + if (!sky0 || !sky1 || !directLit || !resBuf || !tlas || !batchInfo || !megaVB || !megaIB || !matBuf || !terrainBuf) return; - } - nvrhi::IDevice* nvDevice = data.device->GetNVRHIDevice(); - nvrhi::ICommandList* cmdList = ctx->GetCommandList(); + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + memMgr.EnsureBlueNoiseUploaded(cmd); + if (memMgr.ConsumeHistoryReset()) + memMgr.ClearHistoryTargets(cmd); ReSTIRGICB cb = data.cbData; - const auto& bc = data.accelMgr->GetBatchCounts(); - cb.identityStaticCount = bc.identityStatic; - cb.terrainBatchCount = bc.terrain; - cb.skinnedBatchStart = bc.skinned > 0 - ? bc.identityStatic + bc.terrain + bc.transparent + bc.instancedTotal - : 0; - cb.grassBatchStart = bc.grass > 0 - ? bc.identityStatic + bc.terrain + bc.transparent + bc.instancedTotal + bc.skinned - : 0; - cb.detailAtlasIndex = data.accelMgr->GetDetailAtlasIndex(); - cmdList->writeBuffer(data.state->cb, &cb, sizeof(ReSTIRGICB)); + const RTBatchStarts starts = ComputeBatchStarts(data.accelMgr); + cb.identityStaticCount = starts.identityStatic; + cb.terrainBatchCount = starts.terrain; + cb.skinnedBatchStart = starts.skinnedStart; + cb.grassBatchStart = starts.grassStart; + cb.hudSkinnedStart = starts.hudStart; + cb.particleBatchStart = starts.particleStart; + cb.detailAtlasIndex = starts.detailAtlas; + auto& clmLocal = ClusteredLightManager::Instance(); + cb.numLights = clmLocal.GetLightCount(); + if (cb.numLights > RESTIR_MAX_LIGHTS) + cb.numLights = RESTIR_MAX_LIGHTS; + ClusterCB ccbLive = clmLocal.BuildClusterCB((u32)data.cbData.fullWidth, (u32)data.cbData.fullHeight, 0.2f, 500.f); + cb.clusterParams = { ccbLive.gridDims.x, ccbLive.gridDims.y, ccbLive.gridDims.z, (float)clmLocal.GetLightCount() }; + cb.clusterDepth = ccbLive.depthParams; + cb.diSampleParams = { + (float)clmLocal.GetDILightCount(), + clmLocal.GetDIPowerSum(), + (float)std::clamp(ps_r_rt_di_candidates, 1, 16), + 0.f + }; + cmd->writeBuffer(data.state->cb, &cb, sizeof(ReSTIRGICB)); nvrhi::IBuffer* skinnedVB = data.accelMgr->GetSkinnedOutputVB(); nvrhi::IBuffer* skinnedIB = data.accelMgr->GetSkinnedIB(); nvrhi::IBuffer* grassVB = data.accelMgr->GetGrassOutputVB(); nvrhi::IBuffer* grassIB = data.accelMgr->GetGrassIB(); - if (!skinnedVB) skinnedVB = s_rtgiPlaceholderBuffer.Get(); - if (!skinnedIB) skinnedIB = s_rtgiPlaceholderBuffer.Get(); - if (!grassVB) grassVB = s_rtgiPlaceholderBuffer.Get(); - if (!grassIB) grassIB = s_rtgiPlaceholderBuffer.Get(); - - auto* shaderLoader = GEnv.Render->GetShaderLoader(); - auto* csReflection = shaderLoader->GetCachedReflection("restir_gi_initial", ".cs"); - if (!csReflection) return; - - framegraph::BindingSetBuilder bsb(*csReflection, nvDevice, "ReSTIRGI.Initial"); + if (!skinnedVB) skinnedVB = memMgr.GetPlaceholderBuffer(); + if (!skinnedIB) skinnedIB = memMgr.GetPlaceholderBuffer(); + if (!grassVB) grassVB = memMgr.GetPlaceholderBuffer(); + if (!grassIB) grassIB = memMgr.GetPlaceholderBuffer(); + + nvrhi::IBuffer* lights = clmLocal.GetLightDataBuffer(); + if (!lights) + lights = memMgr.GetLightDataBuffer(); + nvrhi::IBuffer* clusterGrid = clmLocal.GetClusterGridBuffer(); + nvrhi::IBuffer* lightIndexList = clmLocal.GetLightIndexListBuffer(); + nvrhi::IBuffer* diIndices = clmLocal.GetDILightIndicesBuffer(); + nvrhi::IBuffer* diCdf = clmLocal.GetDILightCDFBuffer(); + if (!clusterGrid) clusterGrid = memMgr.GetPlaceholderBuffer(); + if (!lightIndexList) lightIndexList = memMgr.GetPlaceholderBuffer(); + if (!diIndices) diIndices = memMgr.GetPlaceholderBuffer(); + if (!diCdf) diCdf = memMgr.GetPlaceholderBuffer(); + + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_gi_initial", ".cs"); + if (!refl) return; + + BindingSetBuilder bsb(*refl, nv, "ReSTIR.Initial"); bsb.ConstantBuffer("ReSTIRGIParams", data.state->cb); bsb.AccelStruct("g_SceneTLAS", tlas); bsb.BufferSRV("g_BatchInfo", batchInfo); @@ -475,201 +1145,1725 @@ ReSTIRGIOutput setupReSTIRGIPass( bsb.BufferSRV("g_SkinnedVB", skinnedVB); bsb.BufferSRV("g_Materials", matBuf); bsb.BufferSRV("g_TerrainMaterials", terrainBuf); + bsb.BufferSRV("g_VariantTextures", bindless::VariantTextureBuffer::Instance().GetBuffer()); bsb.BufferSRV("g_SkinnedIB", skinnedIB); bsb.BufferSRV("g_GrassVB", grassVB); bsb.BufferSRV("g_GrassIB", grassIB); bsb.Texture("t_Depth", depthTex); bsb.Texture("t_Normal", normalTex); bsb.Texture("t_BaseColor", baseColorTex); + bsb.Texture("t_WorldPos", worldPosTex ? worldPosTex : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_SceneColorIn", sceneInTex ? sceneInTex : memMgr.GetPlaceholderColorTex()); + bsb.BufferSRV("g_Lights", lights); + bsb.Texture("t_WetAccum", wetTex ? wetTex : memMgr.GetPlaceholderTex()); + if (bsb.HasSRV("t_SkyOpen")) + bsb.Texture("t_SkyOpen", skyOpenTex ? skyOpenTex : memMgr.GetPlaceholderTex()); + nvrhi::ITexture* grassShadowTex = data.grassShadowTex; + if (!grassShadowTex && data.hasGrassShadow && data.grassShadow.is_valid()) + grassShadowTex = fgGraph.GetPhysicalTexture(data.grassShadow); + bsb.Texture("t_GrassShadow", grassShadowTex ? grassShadowTex : memMgr.GetPlaceholderTex()); + nvrhi::ITexture* blueNoise = memMgr.GetBlueNoise(); + if (bsb.HasSRV("t_BlueNoise")) + bsb.Texture("t_BlueNoise", blueNoise ? blueNoise : memMgr.GetPlaceholderTex3D()); + const u32 sunWrite = data.state->currTemporalIdx & 1u; + nvrhi::ITexture* prevSunVis = data.hasPrevSunVis ? memMgr.GetSunVis(1u - sunWrite) : nullptr; + bsb.Texture("t_PrevSunVis", prevSunVis ? prevSunVis : memMgr.GetPlaceholderTex()); + auto* mvTex = data.motionVectors.is_valid() ? fgGraph.GetPhysicalTexture(data.motionVectors) : nullptr; + auto* prevDepthTex = data.prevDepth.is_valid() ? fgGraph.GetPhysicalTexture(data.prevDepth) : depthTex; + auto* prevNormalTex = data.prevNormals.is_valid() ? fgGraph.GetPhysicalTexture(data.prevNormals) : normalTex; + bsb.Texture("t_MotionVectors", mvTex ? mvTex : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_PrevDepth", prevDepthTex ? prevDepthTex : memMgr.GetPlaceholderTex()); + bsb.Texture("t_PrevNormal", prevNormalTex ? prevNormalTex : memMgr.GetPlaceholderColorTex()); + nvrhi::IBuffer* particleVB = data.accelMgr->GetParticleOutputVB(); + nvrhi::IBuffer* particleIB = data.accelMgr->GetParticleIB(); + if (!particleVB) particleVB = memMgr.GetPlaceholderBuffer(); + if (!particleIB) particleIB = memMgr.GetPlaceholderBuffer(); + bsb.BufferSRV("g_ParticleVB", particleVB); + bsb.BufferSRV("g_ParticleIB", particleIB); + bsb.BufferSRV("g_ClusterGrid", clusterGrid); + bsb.BufferSRV("g_LightIndexList", lightIndexList); + bsb.BufferSRV("g_DILightIndices", diIndices); + bsb.BufferSRV("g_DILightCDF", diCdf); bsb.TextureUAV("u_DirectLighting", directLit); - bsb.TextureUAV("u_ReservoirA", resA); - bsb.TextureUAV("u_ReservoirB", resB); - auto& cache = GetPassResourceCache(); - auto bindingSet = cache.GetOrCreateBindingSet(bsb.Build(), data.state->initialLayout, nvDevice); + bsb.BufferUAV("u_Reservoir", resBuf); + bsb.TextureUAV("u_NoisyDiffuse", noisyDiff); + bsb.TextureUAV("u_NoisySpecular", noisySpec); + bsb.TextureUAV("u_HitDistance", hitDist); + nvrhi::ITexture* diRes = memMgr.GetDIReservoir(data.state->currTemporalIdx); + bsb.TextureUAV("u_DIReservoir", diRes ? diRes : memMgr.GetPlaceholderColorTex()); + nvrhi::ITexture* specA = memMgr.GetSpecReservoirA(0); + nvrhi::ITexture* specB = memMgr.GetSpecReservoirB(0); + bsb.TextureUAV("u_SpecReservoirA", specA ? specA : memMgr.GetPlaceholderColorTex()); + bsb.TextureUAV("u_SpecReservoirB", specB ? specB : memMgr.GetPlaceholderColorTex()); + nvrhi::IBuffer* cache = memMgr.GetIrradianceCache(); + bsb.BufferUAV("u_IrradianceCache", cache ? cache : memMgr.GetPlaceholderBuffer()); + nvrhi::ITexture* sunVis = memMgr.GetSunVis(sunWrite); + bsb.TextureUAV("u_SunVis", sunVis ? sunVis : memMgr.GetPlaceholderTex()); + auto bindingSet = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->initialLayout, nv); if (!bindingSet) return; nvrhi::ComputeState cs; cs.pipeline = data.state->initialPipeline; cs.bindings = { bindingSet }; - -#if defined(XR_PLATFORM_WINDOWS) - auto* backend = dynamic_cast(GEnv.Backend); - if (backend) { - auto* bindlessTable = backend->GetBindlessDescriptorTable(); - if (bindlessTable) + if (GEnv.Backend) { + if (auto* bindlessTable = GEnv.Backend->GetBindlessDescriptorTable()) cs.addBindingSet(bindlessTable); } -#endif - - cmdList->setComputeState(cs); - cmdList->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); } ); - // ============================================ - // PASS 2: Temporal Resampling - // ============================================ - if (hasPrevFrameData && motionVectors.is_valid()) { + if (!usePT && hasPrevFrameData && motionVectors.is_valid() && state.temporalPipeline) { TemporalCB temporalCB; temporalCB.invViewProj = invViewProj; - temporalCB.prevInvViewProj.invert_44(prevViewProj); + temporalCB.prevInvViewProj = prevInvViewProj; temporalCB.cameraPos = { cameraPos.x, cameraPos.y, cameraPos.z, 0 }; - temporalCB.screenWidth = (float)width; - temporalCB.screenHeight = (float)height; - temporalCB.invScreenWidth = 1.0f / width; - temporalCB.invScreenHeight = 1.0f / height; + temporalCB.screenWidth = (float)giW; + temporalCB.screenHeight = (float)giH; + temporalCB.invScreenWidth = 1.0f / (float)giW; + temporalCB.invScreenHeight = 1.0f / (float)giH; temporalCB.frameIndex = Device.dwFrame; - temporalCB.pad[0] = temporalCB.pad[1] = temporalCB.pad[2] = 0; + temporalCB.envAdapt = envAdapt; + temporalCB.currJitterX = g_taa_jitter_px; + temporalCB.currJitterY = g_taa_jitter_py; + temporalCB.prevJitterX = g_taa_jitter_prev_px; + temporalCB.prevJitterY = g_taa_jitter_prev_py; fg.addCallbackPass( - "ReSTIR GI Temporal", - [&, temporalCB, readIdx, writeIdx, fgResA, fgResB](FrameGraph& builder, PassHandle passHandle, TemporalPassData& data) { + "ReSTIR Temporal", + [&, temporalCB](FrameGraph& builder, PassHandle passHandle, TemporalPassData& data) { RenderPassBuilder pb(builder, passHandle); data.depth = pb.read(depth, ResourceState::ShaderResource); data.normal = pb.read(normal, ResourceState::ShaderResource); if (prevNormals.is_valid()) data.prevNormals = pb.read(prevNormals, ResourceState::ShaderResource); data.baseColor = pb.read(baseColor, ResourceState::ShaderResource); + if (worldPos.is_valid()) + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); if (prevDepth.is_valid()) data.prevDepth = pb.read(prevDepth, ResourceState::ShaderResource); data.motionVectors = pb.read(motionVectors, ResourceState::ShaderResource); - pb.readWrite(fgResA, ResourceState::UnorderedAccess); - pb.readWrite(fgResB, ResourceState::UnorderedAccess); pb.sideEffects(); data.device = device; data.state = &state; data.cbData = temporalCB; - data.width = width; - data.height = height; - data.readIdx = readIdx; - data.writeIdx = writeIdx; + data.width = giW; + data.height = giH; }, - [](const TemporalPassData& data, const FrameGraph& fg, fg::RenderContext* ctx) { - auto* depthTex = fg.GetPhysicalTexture(data.depth); - auto* normalTex = fg.GetPhysicalTexture(data.normal); - auto* prevNormalsTex = data.prevNormals.is_valid() ? fg.GetPhysicalTexture(data.prevNormals) : normalTex; - auto* baseColorTex = fg.GetPhysicalTexture(data.baseColor); - auto* prevDepthTex = data.prevDepth.is_valid() ? fg.GetPhysicalTexture(data.prevDepth) : depthTex; - auto* mvTex = fg.GetPhysicalTexture(data.motionVectors); + [workIdx, histIdx](const TemporalPassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* prevNormalsTex = data.prevNormals.is_valid() ? fgGraph.GetPhysicalTexture(data.prevNormals) : normalTex; + auto* baseColorTex = fgGraph.GetPhysicalTexture(data.baseColor); + auto* worldPosTex = data.worldPos.is_valid() ? fgGraph.GetPhysicalTexture(data.worldPos) : nullptr; + auto* prevDepthTex = data.prevDepth.is_valid() ? fgGraph.GetPhysicalTexture(data.prevDepth) : depthTex; + auto* mvTex = fgGraph.GetPhysicalTexture(data.motionVectors); if (!depthTex || !normalTex || !baseColorTex || !mvTex) return; - nvrhi::IDevice* nvDevice = data.device->GetNVRHIDevice(); - nvrhi::ICommandList* cmdList = ctx->GetCommandList(); - - cmdList->writeBuffer(data.state->cb, &data.cbData, sizeof(TemporalCB)); + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + cmd->writeBuffer(data.state->cb, &data.cbData, sizeof(TemporalCB)); - auto* shaderLoader = GEnv.Render->GetShaderLoader(); - auto* csReflection = shaderLoader->GetCachedReflection("restir_gi_temporal", ".cs"); - if (!csReflection) return; + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_gi_temporal", ".cs"); + if (!refl) return; - framegraph::BindingSetBuilder bsb(*csReflection, nvDevice, "ReSTIRGI.Temporal"); + BindingSetBuilder bsb(*refl, nv, "ReSTIR.Temporal"); bsb.ConstantBuffer("ReSTIRTemporalParams", data.state->cb); - bsb.Texture("t_PrevReservoirA", data.state->reservoirA[data.readIdx]); - bsb.Texture("t_PrevReservoirB", data.state->reservoirB[data.readIdx]); + bsb.BufferSRV("t_PrevReservoir", memMgr.GetReservoirBuffer(histIdx)); bsb.Texture("t_MotionVectors", mvTex); bsb.Texture("t_Depth", depthTex); bsb.Texture("t_Normal", normalTex); bsb.Texture("t_PrevNormal", prevNormalsTex); bsb.Texture("t_BaseColor", baseColorTex); + bsb.Texture("t_WorldPos", worldPosTex ? worldPosTex : memMgr.GetPlaceholderColorTex()); bsb.Texture("t_PrevDepth", prevDepthTex); - bsb.TextureUAV("u_ReservoirA", data.state->reservoirA[data.writeIdx]); - bsb.TextureUAV("u_ReservoirB", data.state->reservoirB[data.writeIdx]); - auto& cache = GetPassResourceCache(); - auto bindingSet = cache.GetOrCreateBindingSet(bsb.Build(), data.state->temporalLayout, nvDevice); + if (bsb.HasSRV("t_SkyOpen")) + bsb.Texture("t_SkyOpen", memMgr.GetSkyOpen() ? memMgr.GetSkyOpen() : memMgr.GetPlaceholderTex()); + bsb.BufferUAV("u_Reservoir", memMgr.GetReservoirBuffer(workIdx)); + auto bindingSet = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->temporalLayout, nv); if (!bindingSet) return; nvrhi::ComputeState cs; cs.pipeline = data.state->temporalPipeline; cs.bindings = { bindingSet }; + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + } + ); + } - cmdList->setComputeState(cs); - cmdList->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + if (!usePT && state.spatialPipeline && ps_r_rt_gi_spatial_samples > 0) { + SpatialCB spatialCB; + spatialCB.invViewProj = invViewProj; + spatialCB.cameraPos = { cameraPos.x, cameraPos.y, cameraPos.z, 0 }; + spatialCB.screenWidth = (float)giW; + spatialCB.screenHeight = (float)giH; + spatialCB.invScreenWidth = 1.0f / (float)giW; + spatialCB.invScreenHeight = 1.0f / (float)giH; + spatialCB.frameIndex = Device.dwFrame; + spatialCB.spatialSamples = (u32)ps_r_rt_gi_spatial_samples; + spatialCB.spatialRadius = ps_r_rt_gi_spatial_radius; + spatialCB.mMax = (u32)std::max(1, ps_r_rt_gi_m_max); + spatialCB.identityStaticCount = initialCB.identityStaticCount; + spatialCB.terrainBatchCount = initialCB.terrainBatchCount; + spatialCB.skinnedBatchStart = initialCB.skinnedBatchStart; + spatialCB.grassBatchStart = initialCB.grassBatchStart; + spatialCB.detailAtlasIndex = initialCB.detailAtlasIndex; + spatialCB.particleBatchStart = initialCB.particleBatchStart; + spatialCB.lodDist = initialCB.lodDist; + spatialCB.hudSkinnedStart = initialCB.hudSkinnedStart; + + struct SpatialPassDataRT { + fg::RenderDevice* device; + RTAccelStructManager* accelMgr; + ReSTIRGIPassState* state; + VirtualResourceHandle depth; + VirtualResourceHandle normal; + VirtualResourceHandle baseColor; + VirtualResourceHandle worldPos; + SpatialCB cbData; + u32 width, height; + }; + + fg.addCallbackPass( + "ReSTIR Spatial", + [&, spatialCB](FrameGraph& builder, PassHandle passHandle, SpatialPassDataRT& data) { + RenderPassBuilder pb(builder, passHandle); + data.depth = pb.read(depth, ResourceState::ShaderResource); + data.normal = pb.read(normal, ResourceState::ShaderResource); + data.baseColor = pb.read(baseColor, ResourceState::ShaderResource); + if (worldPos.is_valid()) + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); + pb.sideEffects(); + data.device = device; + data.accelMgr = accelMgr; + data.state = &state; + data.cbData = spatialCB; + data.width = giW; + data.height = giH; + }, + [workIdx, histIdx](const SpatialPassDataRT& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* baseColorTex = fgGraph.GetPhysicalTexture(data.baseColor); + auto* worldPosTex = data.worldPos.is_valid() ? fgGraph.GetPhysicalTexture(data.worldPos) : nullptr; + if (!depthTex || !normalTex || !baseColorTex) return; + + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + SpatialCB cb = data.cbData; + const RTBatchStarts starts = ComputeBatchStarts(data.accelMgr); + cb.identityStaticCount = starts.identityStatic; + cb.terrainBatchCount = starts.terrain; + cb.skinnedBatchStart = starts.skinnedStart; + cb.grassBatchStart = starts.grassStart; + cb.hudSkinnedStart = starts.hudStart; + cb.particleBatchStart = starts.particleStart; + cb.detailAtlasIndex = starts.detailAtlas; + cmd->writeBuffer(data.state->cb, &cb, sizeof(SpatialCB)); + + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_gi_spatial", ".cs"); + if (!refl) return; + + nvrhi::IBuffer* grassVB = data.accelMgr->GetGrassOutputVB(); + nvrhi::IBuffer* grassIB = data.accelMgr->GetGrassIB(); + if (!grassVB) grassVB = memMgr.GetPlaceholderBuffer(); + if (!grassIB) grassIB = memMgr.GetPlaceholderBuffer(); + nvrhi::IBuffer* particleVB = data.accelMgr->GetParticleOutputVB(); + nvrhi::IBuffer* particleIB = data.accelMgr->GetParticleIB(); + if (!particleVB) particleVB = memMgr.GetPlaceholderBuffer(); + if (!particleIB) particleIB = memMgr.GetPlaceholderBuffer(); + + BindingSetBuilder bsb(*refl, nv, "ReSTIR.Spatial"); + bsb.ConstantBuffer("ReSTIRSpatialParams", data.state->cb); + bsb.BufferSRV("t_InReservoir", memMgr.GetReservoirBuffer(workIdx)); + bsb.AccelStruct("g_SceneTLAS", data.accelMgr->GetTLAS()); + bsb.BufferSRV("g_BatchInfo", data.accelMgr->GetBatchInfoBuffer()); + bsb.BufferSRV("g_MegaVB", data.accelMgr->GetMegaVB()); + bsb.BufferSRV("g_MegaIB", data.accelMgr->GetMegaIB()); + bsb.BufferSRV("g_GrassVB", grassVB); + bsb.BufferSRV("g_GrassIB", grassIB); + bsb.BufferSRV("g_ParticleVB", particleVB); + bsb.BufferSRV("g_ParticleIB", particleIB); + bsb.Texture("t_Depth", depthTex); + bsb.Texture("t_Normal", normalTex); + bsb.Texture("t_BaseColor", baseColorTex); + bsb.Texture("t_WorldPos", worldPosTex ? worldPosTex : memMgr.GetPlaceholderColorTex()); + if (bsb.HasSRV("t_BlueNoise")) + bsb.Texture("t_BlueNoise", memMgr.GetBlueNoise() ? memMgr.GetBlueNoise() : memMgr.GetPlaceholderTex3D()); + if (bsb.HasSRV("t_SkyOpen")) + bsb.Texture("t_SkyOpen", memMgr.GetSkyOpen() ? memMgr.GetSkyOpen() : memMgr.GetPlaceholderTex()); + BindBindlessMaterialTables(bsb); + bsb.BufferUAV("u_OutReservoir", memMgr.GetReservoirBuffer(histIdx)); + bsb.TextureUAV("u_NoisyDiffuse", memMgr.GetNoisyDiffuse()); + auto bindingSet = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->spatialLayout, nv); + if (!bindingSet) return; + + nvrhi::ComputeState cs; + cs.pipeline = data.state->spatialPipeline; + cs.bindings = { bindingSet }; + if (GEnv.Backend) + if (auto* t = GEnv.Backend->GetBindlessDescriptorTable()) + cs.addBindingSet(t); + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); } ); } - // ============================================ - // PASS 3: Composite (direct + indirect → scene) - // ============================================ + if (!usePT && ps_r_rt_refl > 0 && state.specTemporalPipeline) { + SpecCB specCB{}; + specCB.invViewProj = invViewProj; + specCB.prevInvViewProj = prevInvViewProj; + specCB.prevViewProj = prevViewProj; + specCB.cameraPos = { cameraPos.x, cameraPos.y, cameraPos.z, 0 }; + specCB.screenWidth = (float)giW; + specCB.screenHeight = (float)giH; + specCB.invScreenWidth = 1.0f / (float)giW; + specCB.invScreenHeight = 1.0f / (float)giH; + specCB.frameIndex = Device.dwFrame; + specCB.spatialSamples = (ps_r_rt_refl >= 2) ? 4u : 0u; + specCB.spatialRadius = 8.0f; + specCB.hasPrev = hasPrevFrameData ? 1u : 0u; + struct SpecPassData { + fg::RenderDevice* device; + ReSTIRGIPassState* state; + VirtualResourceHandle depth, normal, prevNormals, baseColor, worldPos, prevDepth, motionVectors; + SpecCB cbData; + u32 width, height; + }; + fg.addCallbackPass( + "ReSTIR Spec Temporal", + [&, specCB](FrameGraph& builder, PassHandle passHandle, SpecPassData& data) { + RenderPassBuilder pb(builder, passHandle); + data.depth = pb.read(depth, ResourceState::ShaderResource); + data.normal = pb.read(normal, ResourceState::ShaderResource); + if (prevNormals.is_valid()) + data.prevNormals = pb.read(prevNormals, ResourceState::ShaderResource); + data.baseColor = pb.read(baseColor, ResourceState::ShaderResource); + if (worldPos.is_valid()) + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); + if (prevDepth.is_valid()) + data.prevDepth = pb.read(prevDepth, ResourceState::ShaderResource); + if (motionVectors.is_valid()) + data.motionVectors = pb.read(motionVectors, ResourceState::ShaderResource); + pb.sideEffects(); + data.device = device; + data.state = &state; + data.cbData = specCB; + data.width = giW; + data.height = giH; + }, + [](const SpecPassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* prevN = data.prevNormals.is_valid() ? fgGraph.GetPhysicalTexture(data.prevNormals) : normalTex; + auto* baseColorTex = fgGraph.GetPhysicalTexture(data.baseColor); + auto* worldPosTex = data.worldPos.is_valid() ? fgGraph.GetPhysicalTexture(data.worldPos) : nullptr; + auto* prevDepthTex = data.prevDepth.is_valid() ? fgGraph.GetPhysicalTexture(data.prevDepth) : depthTex; + auto* mvTex = data.motionVectors.is_valid() ? fgGraph.GetPhysicalTexture(data.motionVectors) : nullptr; + if (!depthTex || !normalTex || !baseColorTex) return; + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + cmd->writeBuffer(data.state->cb, &data.cbData, sizeof(SpecCB)); + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_spec_temporal", ".cs"); + if (!refl) return; + BindingSetBuilder bsb(*refl, nv, "ReSTIR.SpecTemporal"); + bsb.ConstantBuffer("ReSTIRSpecParams", data.state->cb); + bsb.Texture("t_CurrA", memMgr.GetSpecReservoirA(0)); + bsb.Texture("t_CurrB", memMgr.GetSpecReservoirB(0)); + bsb.Texture("t_PrevA", memMgr.GetSpecReservoirA(1)); + bsb.Texture("t_PrevB", memMgr.GetSpecReservoirB(1)); + bsb.Texture("t_MotionVectors", mvTex ? mvTex : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_Depth", depthTex); + bsb.Texture("t_Normal", normalTex); + bsb.Texture("t_PrevNormal", prevN); + bsb.Texture("t_BaseColor", baseColorTex); + bsb.Texture("t_WorldPos", worldPosTex ? worldPosTex : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_PrevDepth", prevDepthTex); + bsb.TextureUAV("u_OutA", memMgr.GetSpecReservoirA(1)); + bsb.TextureUAV("u_OutB", memMgr.GetSpecReservoirB(1)); + bsb.TextureUAV("u_NoisySpecular", memMgr.GetNoisySpecular()); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->specTemporalLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->specTemporalPipeline; + cs.bindings = { bs }; + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + }); + } + + const u32 diWrite = state.currTemporalIdx & 1u; + const u32 diRead = 1u - diWrite; + Fmatrix worldToView = Device.mView; + ClusterCB clusterForDI = clm.BuildClusterCB(width, height, 0.2f, 500.f); + + if (!usePT && hasPrevFrameData && motionVectors.is_valid() && state.diTemporalPipeline && mem.GetDIReservoir(diWrite)) { + DITemporalCB diTempCB{}; + diTempCB.invViewProj = invViewProj; + { + diTempCB.prevInvViewProj = prevInvViewProj; + } + diTempCB.cameraPos = { cameraPos.x, cameraPos.y, cameraPos.z, 0 }; + diTempCB.screenWidth = (float)giW; + diTempCB.screenHeight = (float)giH; + diTempCB.fullWidth = (float)width; + diTempCB.fullHeight = (float)height; + diTempCB.invScreenWidth = 1.0f / (float)giW; + diTempCB.invScreenHeight = 1.0f / (float)giH; + diTempCB.frameIndex = Device.dwFrame; + diTempCB.mMax = (u32)std::max(1, ps_r_rt_di_m_max); + diTempCB.currJitterX = g_taa_jitter_px; + diTempCB.currJitterY = g_taa_jitter_py; + diTempCB.prevJitterX = g_taa_jitter_prev_px; + diTempCB.prevJitterY = g_taa_jitter_prev_py; + diTempCB.clusterParams = { clusterForDI.gridDims.x, clusterForDI.gridDims.y, clusterForDI.gridDims.z, (float)clm.GetLightCount() }; + diTempCB.clusterScales = clusterForDI.depthParams; + + struct DITemporalPassData { + fg::RenderDevice* device; + ReSTIRGIPassState* state; + VirtualResourceHandle depth, normal, baseColor, worldPos, prevDepth, prevNormals, motionVectors; + DITemporalCB cbData; + u32 width, height, writeIdx, readIdx; + }; + + fg.addCallbackPass( + "ReSTIR DI Temporal", + [&, diTempCB, diWrite, diRead](FrameGraph& builder, PassHandle passHandle, DITemporalPassData& data) { + RenderPassBuilder pb(builder, passHandle); + data.depth = pb.read(depth, ResourceState::ShaderResource); + data.normal = pb.read(normal, ResourceState::ShaderResource); + data.baseColor = pb.read(baseColor, ResourceState::ShaderResource); + if (worldPos.is_valid()) + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); + if (prevDepth.is_valid()) + data.prevDepth = pb.read(prevDepth, ResourceState::ShaderResource); + if (prevNormals.is_valid()) + data.prevNormals = pb.read(prevNormals, ResourceState::ShaderResource); + data.motionVectors = pb.read(motionVectors, ResourceState::ShaderResource); + pb.sideEffects(); + data.device = device; + data.state = &state; + data.cbData = diTempCB; + data.width = giW; + data.height = giH; + data.writeIdx = diWrite; + data.readIdx = diRead; + }, + [](const DITemporalPassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* prevNormalsTex = data.prevNormals.is_valid() ? fgGraph.GetPhysicalTexture(data.prevNormals) : normalTex; + auto* baseColorTex = fgGraph.GetPhysicalTexture(data.baseColor); + auto* worldPosTex = data.worldPos.is_valid() ? fgGraph.GetPhysicalTexture(data.worldPos) : nullptr; + auto* prevDepthTex = data.prevDepth.is_valid() ? fgGraph.GetPhysicalTexture(data.prevDepth) : depthTex; + auto* motionTex = fgGraph.GetPhysicalTexture(data.motionVectors); + auto* lights = ClusteredLightManager::Instance().GetLightDataBuffer(); + if (!depthTex || !normalTex || !baseColorTex || !motionTex || !lights) return; + nvrhi::ITexture* dst = memMgr.GetDIReservoir(data.writeIdx); + nvrhi::ITexture* prev = memMgr.GetDIReservoir(data.readIdx); + if (!dst || !prev) return; + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + cmd->writeBuffer(data.state->cb, &data.cbData, sizeof(DITemporalCB)); + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_di_temporal", ".cs"); + if (!refl) return; + BindingSetBuilder bsb(*refl, nv, "ReSTIR.DITemporal"); + bsb.ConstantBuffer("ReSTIRDITemporalParams", data.state->cb); + bsb.BufferSRV("g_LightData", lights); + bsb.Texture("t_PrevDI", prev); + bsb.Texture("t_MotionVectors", motionTex); + bsb.Texture("t_Depth", depthTex); + bsb.Texture("t_PrevNormal", prevNormalsTex); + bsb.Texture("t_BaseColor", baseColorTex); + bsb.Texture("t_WorldPos", worldPosTex ? worldPosTex : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_PrevDepth", prevDepthTex); + bsb.Texture("t_Normal", normalTex); + if (bsb.HasSRV("t_SkyOpen")) + bsb.Texture("t_SkyOpen", memMgr.GetSkyOpen() ? memMgr.GetSkyOpen() : memMgr.GetPlaceholderTex()); + bsb.TextureUAV("u_DIReservoir", dst); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->diTemporalLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->diTemporalPipeline; + cs.bindings = { bs }; + if (GEnv.Backend) + if (auto* t = GEnv.Backend->GetBindlessDescriptorTable()) + cs.addBindingSet(t); + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + } + ); + } + + u32 diSrc = diWrite; + u32 diDst = diRead; + if (!usePT && state.diSpatialPipeline && ps_r_rt_di_spatial_samples > 0 && mem.GetDIReservoir(0)) { + DISpatialCB diSpatCB{}; + diSpatCB.invViewProj = invViewProj; + diSpatCB.worldToView = worldToView; + diSpatCB.cameraPos = { cameraPos.x, cameraPos.y, cameraPos.z, 0 }; + diSpatCB.screenWidth = (float)giW; + diSpatCB.screenHeight = (float)giH; + diSpatCB.fullWidth = (float)width; + diSpatCB.fullHeight = (float)height; + diSpatCB.invScreenWidth = 1.0f / (float)giW; + diSpatCB.invScreenHeight = 1.0f / (float)giH; + diSpatCB.frameIndex = Device.dwFrame; + diSpatCB.spatialSamples = (u32)ps_r_rt_di_spatial_samples; + diSpatCB.spatialRadius = ps_r_rt_di_spatial_radius; + diSpatCB.mMax = (u32)std::max(1, ps_r_rt_di_m_max); + diSpatCB.clusterParams = { clusterForDI.gridDims.x, clusterForDI.gridDims.y, clusterForDI.gridDims.z, (float)clm.GetLightCount() }; + diSpatCB.clusterScales = clusterForDI.depthParams; + + struct DISpatialPassData { + fg::RenderDevice* device; + ReSTIRGIPassState* state; + VirtualResourceHandle depth, normal, baseColor, worldPos; + DISpatialCB cbData; + u32 width, height, srcIdx, dstIdx; + }; + + fg.addCallbackPass( + "ReSTIR DI Spatial", + [&, diSpatCB, diSrc, diDst](FrameGraph& builder, PassHandle passHandle, DISpatialPassData& data) { + RenderPassBuilder pb(builder, passHandle); + data.depth = pb.read(depth, ResourceState::ShaderResource); + data.normal = pb.read(normal, ResourceState::ShaderResource); + data.baseColor = pb.read(baseColor, ResourceState::ShaderResource); + if (worldPos.is_valid()) + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); + pb.sideEffects(); + data.device = device; + data.state = &state; + data.cbData = diSpatCB; + data.width = giW; + data.height = giH; + data.srcIdx = diSrc; + data.dstIdx = diDst; + }, + [](const DISpatialPassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* baseColorTex = fgGraph.GetPhysicalTexture(data.baseColor); + auto* worldPosTex = data.worldPos.is_valid() ? fgGraph.GetPhysicalTexture(data.worldPos) : nullptr; + auto* lights = ClusteredLightManager::Instance().GetLightDataBuffer(); + nvrhi::ITexture* src = memMgr.GetDIReservoir(data.srcIdx); + nvrhi::ITexture* dst = memMgr.GetDIReservoir(data.dstIdx); + if (!depthTex || !normalTex || !baseColorTex || !src || !dst || !lights) return; + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + cmd->writeBuffer(data.state->cb, &data.cbData, sizeof(DISpatialCB)); + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_di_spatial", ".cs"); + if (!refl) return; + BindingSetBuilder bsb(*refl, nv, "ReSTIR.DISpatial"); + bsb.ConstantBuffer("ReSTIRDISpatialParams", data.state->cb); + bsb.BufferSRV("g_LightData", lights); + bsb.Texture("t_SrcDI", src); + bsb.Texture("t_Depth", depthTex); + bsb.Texture("t_BaseColor", baseColorTex); + bsb.Texture("t_WorldPos", worldPosTex ? worldPosTex : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_Normal", normalTex); + bsb.TextureUAV("u_DIReservoir", dst); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->diSpatialLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->diSpatialPipeline; + cs.bindings = { bs }; + if (GEnv.Backend) + if (auto* t = GEnv.Backend->GetBindlessDescriptorTable()) + cs.addBindingSet(t); + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + } + ); + std::swap(diSrc, diDst); + } + + if (!usePT && state.diShadePipeline && mem.GetDIReservoir(diSrc)) { + DIShadeCB diShadeCB{}; + diShadeCB.invViewProj = invViewProj; + diShadeCB.worldToView = worldToView; + diShadeCB.cameraPos = { cameraPos.x, cameraPos.y, cameraPos.z, 0 }; + diShadeCB.screenWidth = (float)giW; + diShadeCB.screenHeight = (float)giH; + diShadeCB.fullWidth = (float)width; + diShadeCB.fullHeight = (float)height; + diShadeCB.grassBatchStart = initialCB.grassBatchStart; + diShadeCB.detailAtlasIndex = initialCB.detailAtlasIndex; + diShadeCB.clusterParams = { clusterForDI.gridDims.x, clusterForDI.gridDims.y, clusterForDI.gridDims.z, (float)clm.GetLightCount() }; + diShadeCB.clusterDepth = clusterForDI.depthParams; + diShadeCB.identityStaticCount = initialCB.identityStaticCount; + diShadeCB.terrainBatchCount = initialCB.terrainBatchCount; + diShadeCB.skinnedBatchStart = initialCB.skinnedBatchStart; + diShadeCB.particleBatchStart = initialCB.particleBatchStart; + diShadeCB.hudSkinnedStart = initialCB.hudSkinnedStart; + diShadeCB.pad2 = Device.dwFrame; + + struct DIShadePassData { + fg::RenderDevice* device; + RTAccelStructManager* accelMgr; + ReSTIRGIPassState* state; + VirtualResourceHandle depth, normal, baseColor, worldPos; + DIShadeCB cbData; + u32 width, height, srcIdx; + }; + + fg.addCallbackPass( + "ReSTIR DI Shade", + [&, diShadeCB, diSrc, fgDirectLighting](FrameGraph& builder, PassHandle passHandle, DIShadePassData& data) { + RenderPassBuilder pb(builder, passHandle); + data.depth = pb.read(depth, ResourceState::ShaderResource); + data.normal = pb.read(normal, ResourceState::ShaderResource); + data.baseColor = pb.read(baseColor, ResourceState::ShaderResource); + if (worldPos.is_valid()) + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); + pb.readWrite(fgDirectLighting, ResourceState::UnorderedAccess); + pb.sideEffects(); + data.device = device; + data.accelMgr = accelMgr; + data.state = &state; + data.cbData = diShadeCB; + data.width = giW; + data.height = giH; + data.srcIdx = diSrc; + }, + [](const DIShadePassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* baseColorTex = fgGraph.GetPhysicalTexture(data.baseColor); + auto* worldPosTex = data.worldPos.is_valid() ? fgGraph.GetPhysicalTexture(data.worldPos) : nullptr; + auto* lights = ClusteredLightManager::Instance().GetLightDataBuffer(); + auto* clusterGrid = ClusteredLightManager::Instance().GetClusterGridBuffer(); + auto* lightIndexList = ClusteredLightManager::Instance().GetLightIndexListBuffer(); + auto* tlas = data.accelMgr->GetTLAS(); + auto* batchInfo = data.accelMgr->GetBatchInfoBuffer(); + auto* megaVB = data.accelMgr->GetMegaVB(); + auto* megaIB = data.accelMgr->GetMegaIB(); + auto* diTex = memMgr.GetDIReservoir(data.srcIdx); + auto* direct = memMgr.GetDirectLighting(); + if (!depthTex || !diTex || !direct || !tlas || !lights || !clusterGrid || !lightIndexList) return; + nvrhi::IBuffer* grassVB = data.accelMgr->GetGrassOutputVB(); + nvrhi::IBuffer* grassIB = data.accelMgr->GetGrassIB(); + if (!grassVB) grassVB = memMgr.GetPlaceholderBuffer(); + if (!grassIB) grassIB = memMgr.GetPlaceholderBuffer(); + nvrhi::IBuffer* particleVB = data.accelMgr->GetParticleOutputVB(); + nvrhi::IBuffer* particleIB = data.accelMgr->GetParticleIB(); + if (!particleVB) particleVB = memMgr.GetPlaceholderBuffer(); + if (!particleIB) particleIB = memMgr.GetPlaceholderBuffer(); + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + DIShadeCB cb = data.cbData; + const RTBatchStarts starts = ComputeBatchStarts(data.accelMgr); + cb.identityStaticCount = starts.identityStatic; + cb.terrainBatchCount = starts.terrain; + cb.skinnedBatchStart = starts.skinnedStart; + cb.grassBatchStart = starts.grassStart; + cb.hudSkinnedStart = starts.hudStart; + cb.particleBatchStart = starts.particleStart; + cb.detailAtlasIndex = starts.detailAtlas; + cmd->writeBuffer(data.state->cb, &cb, sizeof(DIShadeCB)); + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_di_shade", ".cs"); + if (!refl) return; + BindingSetBuilder bsb(*refl, nv, "ReSTIR.DIShade"); + bsb.ConstantBuffer("ReSTIRDIShadeParams", data.state->cb); + bsb.AccelStruct("g_SceneTLAS", tlas); + bsb.BufferSRV("g_LightData", lights); + bsb.BufferSRV("g_ClusterGrid", clusterGrid); + bsb.BufferSRV("g_LightIndexList", lightIndexList); + bsb.BufferSRV("g_BatchInfo", batchInfo); + bsb.BufferSRV("g_MegaVB", megaVB); + bsb.BufferSRV("g_MegaIB", megaIB); + bsb.BufferSRV("g_GrassVB", grassVB); + bsb.BufferSRV("g_GrassIB", grassIB); + bsb.BufferSRV("g_ParticleVB", particleVB); + bsb.BufferSRV("g_ParticleIB", particleIB); + bsb.Texture("t_DIReservoir", diTex); + bsb.Texture("t_Depth", depthTex); + bsb.Texture("t_BaseColor", baseColorTex); + bsb.Texture("t_WorldPos", worldPosTex ? worldPosTex : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_Normal", normalTex); + if (bsb.HasSRV("t_BlueNoise")) + bsb.Texture("t_BlueNoise", memMgr.GetBlueNoise() ? memMgr.GetBlueNoise() : memMgr.GetPlaceholderTex3D()); + if (bsb.HasSRV("t_SkyOpen")) + bsb.Texture("t_SkyOpen", memMgr.GetSkyOpen() ? memMgr.GetSkyOpen() : memMgr.GetPlaceholderTex()); + BindBindlessMaterialTables(bsb); + bsb.TextureUAV("u_DirectLighting", direct); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->diShadeLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->diShadePipeline; + cs.bindings = { bs }; + if (GEnv.Backend) + if (auto* t = GEnv.Backend->GetBindlessDescriptorTable()) + cs.addBindingSet(t); + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + } + ); + } + + if (usePT) { + struct PTInitData { + fg::RenderDevice* device; + RTAccelStructManager* accelMgr; + ReSTIRGIPassState* state; + VirtualResourceHandle depth, normal, baseColor, worldPos, sceneColorIn; + VirtualResourceHandle grassShadow, prevDepth, prevNormals, motionVectors; + nvrhi::ITexture* grassShadowTex = nullptr; + bool hasGrassShadow = false; + bool hasPrevSunVis = false; + ReSTIRGICB cb; + nvrhi::ITexture* sky0; + nvrhi::ITexture* sky1; + u32 width, height; + }; + fg.addCallbackPass( + "ReSTIR PT Initial", + [&, initialCB, sky0Tex, sky1Tex, grassShadow, sceneColorIn, hasPrevFrameData]( + FrameGraph& builder, PassHandle passHandle, PTInitData& data) { + RenderPassBuilder pb(builder, passHandle); + data.depth = pb.read(depth, ResourceState::ShaderResource); + data.normal = pb.read(normal, ResourceState::ShaderResource); + data.baseColor = pb.read(baseColor, ResourceState::ShaderResource); + if (worldPos.is_valid()) + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); + data.sceneColorIn = pb.read(sceneColorIn, ResourceState::ShaderResource); + data.hasGrassShadow = grassShadow.valid && grassShadow.shadowMap.is_valid(); + if (data.hasGrassShadow) + data.grassShadow = pb.read(grassShadow.shadowMap, ResourceState::ShaderResource); + data.grassShadowTex = grassShadow.shadowTex; + data.hasPrevSunVis = hasPrevFrameData && motionVectors.is_valid(); + if (data.hasPrevSunVis) { + if (prevDepth.is_valid()) + data.prevDepth = pb.read(prevDepth, ResourceState::ShaderResource); + if (prevNormals.is_valid()) + data.prevNormals = pb.read(prevNormals, ResourceState::ShaderResource); + data.motionVectors = pb.read(motionVectors, ResourceState::ShaderResource); + } + pb.sideEffects(); + data.device = device; + data.accelMgr = accelMgr; + data.state = &state; + data.cb = initialCB; + data.sky0 = sky0Tex; + data.sky1 = sky1Tex; + data.width = giW; + data.height = giH; + }, + [](const PTInitData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + auto& clm = ClusteredLightManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* baseColorTex = fgGraph.GetPhysicalTexture(data.baseColor); + auto* worldPosTex = data.worldPos.is_valid() ? fgGraph.GetPhysicalTexture(data.worldPos) : nullptr; + auto* sceneInTex = data.sceneColorIn.is_valid() ? fgGraph.GetPhysicalTexture(data.sceneColorIn) : nullptr; + auto* tlas = data.accelMgr->GetTLAS(); + if (!depthTex || !tlas) return; + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + ReSTIRGICB cb = data.cb; + const RTBatchStarts starts = ComputeBatchStarts(data.accelMgr); + cb.identityStaticCount = starts.identityStatic; + cb.terrainBatchCount = starts.terrain; + cb.skinnedBatchStart = starts.skinnedStart; + cb.grassBatchStart = starts.grassStart; + cb.hudSkinnedStart = starts.hudStart; + cb.detailAtlasIndex = starts.detailAtlas; + cb.numLights = clm.GetLightCount(); + ClusterCB ccbLive = clm.BuildClusterCB((u32)cb.fullWidth, (u32)cb.fullHeight, 0.2f, 500.f); + cb.clusterParams = { ccbLive.gridDims.x, ccbLive.gridDims.y, ccbLive.gridDims.z, (float)clm.GetLightCount() }; + cb.clusterDepth = ccbLive.depthParams; + cb.diSampleParams = { + (float)clm.GetDILightCount(), + clm.GetDIPowerSum(), + (float)std::clamp(ps_r_rt_di_candidates, 1, 16), + 0.f + }; + cb.hasPrevSunVis = data.hasPrevSunVis ? 1u : 0u; + cmd->writeBuffer(data.state->cb, &cb, sizeof(ReSTIRGICB)); + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_pt_initial", ".cs"); + if (!refl) return; + nvrhi::IBuffer* lights = clm.GetLightDataBuffer(); + if (!lights) lights = memMgr.GetPlaceholderBuffer(); + nvrhi::IBuffer* clusterGrid = clm.GetClusterGridBuffer(); + nvrhi::IBuffer* lightIndexList = clm.GetLightIndexListBuffer(); + nvrhi::IBuffer* diIndices = clm.GetDILightIndicesBuffer(); + nvrhi::IBuffer* diCdf = clm.GetDILightCDFBuffer(); + if (!clusterGrid) clusterGrid = memMgr.GetPlaceholderBuffer(); + if (!lightIndexList) lightIndexList = memMgr.GetPlaceholderBuffer(); + if (!diIndices) diIndices = memMgr.GetPlaceholderBuffer(); + if (!diCdf) diCdf = memMgr.GetPlaceholderBuffer(); + nvrhi::IBuffer* skinnedVB = data.accelMgr->GetSkinnedOutputVB(); + nvrhi::IBuffer* skinnedIB = data.accelMgr->GetSkinnedIB(); + nvrhi::IBuffer* grassVB = data.accelMgr->GetGrassOutputVB(); + nvrhi::IBuffer* grassIB = data.accelMgr->GetGrassIB(); + if (!skinnedVB) skinnedVB = memMgr.GetPlaceholderBuffer(); + if (!skinnedIB) skinnedIB = memMgr.GetPlaceholderBuffer(); + if (!grassVB) grassVB = memMgr.GetPlaceholderBuffer(); + if (!grassIB) grassIB = memMgr.GetPlaceholderBuffer(); + nvrhi::IBuffer* particleVB = data.accelMgr->GetParticleOutputVB(); + nvrhi::IBuffer* particleIB = data.accelMgr->GetParticleIB(); + if (!particleVB) particleVB = memMgr.GetPlaceholderBuffer(); + if (!particleIB) particleIB = memMgr.GetPlaceholderBuffer(); + BindingSetBuilder bsb(*refl, nv, "ReSTIR.PTInitial"); + bsb.ConstantBuffer("ReSTIRGIParams", data.state->cb); + bsb.AccelStruct("g_SceneTLAS", tlas); + bsb.BufferSRV("g_BatchInfo", data.accelMgr->GetBatchInfoBuffer()); + bsb.BufferSRV("g_MegaVB", data.accelMgr->GetMegaVB()); + bsb.BufferSRV("g_MegaIB", data.accelMgr->GetMegaIB()); + bsb.Texture("g_Sky0", data.sky0 ? data.sky0 : memMgr.GetPlaceholderCube()); + bsb.Texture("g_Sky1", data.sky1 ? data.sky1 : memMgr.GetPlaceholderCube()); + bsb.BufferSRV("g_SkinnedVB", skinnedVB); + bsb.BufferSRV("g_SkinnedIB", skinnedIB); + bsb.BufferSRV("g_GrassVB", grassVB); + bsb.BufferSRV("g_GrassIB", grassIB); + bsb.Texture("t_Depth", depthTex); + bsb.Texture("t_Normal", normalTex); + bsb.Texture("t_BaseColor", baseColorTex); + bsb.BufferSRV("g_Lights", lights); + bsb.Texture("t_WetAccum", memMgr.GetWetAccum() ? memMgr.GetWetAccum() : memMgr.GetPlaceholderTex()); + bsb.BufferSRV("g_ClusterGrid", clusterGrid); + bsb.BufferSRV("g_LightIndexList", lightIndexList); + bsb.BufferSRV("g_DILightIndices", diIndices); + bsb.BufferSRV("g_DILightCDF", diCdf); + bsb.Texture("t_WorldPos", worldPosTex ? worldPosTex : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_SceneColorIn", sceneInTex ? sceneInTex : memMgr.GetPlaceholderColorTex()); + if (bsb.HasSRV("t_SkyOpen")) + bsb.Texture("t_SkyOpen", memMgr.GetSkyOpen() ? memMgr.GetSkyOpen() : memMgr.GetPlaceholderTex()); + nvrhi::ITexture* grassShadowTex = data.grassShadowTex; + if (!grassShadowTex && data.hasGrassShadow && data.grassShadow.is_valid()) + grassShadowTex = fgGraph.GetPhysicalTexture(data.grassShadow); + bsb.Texture("t_GrassShadow", grassShadowTex ? grassShadowTex : memMgr.GetPlaceholderTex()); + bsb.BufferSRV("g_ParticleVB", particleVB); + bsb.BufferSRV("g_ParticleIB", particleIB); + nvrhi::ITexture* blueNoise = memMgr.GetBlueNoise(); + if (bsb.HasSRV("t_BlueNoise")) + bsb.Texture("t_BlueNoise", blueNoise ? blueNoise : memMgr.GetPlaceholderTex3D()); + const u32 sunWrite = data.state->currTemporalIdx & 1u; + nvrhi::ITexture* prevSunVis = data.hasPrevSunVis ? memMgr.GetSunVis(1u - sunWrite) : nullptr; + bsb.Texture("t_PrevSunVis", prevSunVis ? prevSunVis : memMgr.GetPlaceholderTex()); + auto* mvTex = data.motionVectors.is_valid() ? fgGraph.GetPhysicalTexture(data.motionVectors) : nullptr; + auto* prevDepthTex = data.prevDepth.is_valid() ? fgGraph.GetPhysicalTexture(data.prevDepth) : depthTex; + auto* prevNormalTex = data.prevNormals.is_valid() ? fgGraph.GetPhysicalTexture(data.prevNormals) : normalTex; + bsb.Texture("t_MotionVectors", mvTex ? mvTex : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_PrevDepth", prevDepthTex ? prevDepthTex : memMgr.GetPlaceholderTex()); + bsb.Texture("t_PrevNormal", prevNormalTex ? prevNormalTex : memMgr.GetPlaceholderColorTex()); + BindBindlessMaterialTables(bsb); + bsb.TextureUAV("u_PTA", memMgr.GetPTReservoirA(0)); + bsb.TextureUAV("u_PTB", memMgr.GetPTReservoirB(0)); + bsb.TextureUAV("u_NoisyDiffuse", memMgr.GetNoisyDiffuse()); + bsb.TextureUAV("u_NoisySpecular", memMgr.GetNoisySpecular()); + bsb.TextureUAV("u_HitDistance", memMgr.GetHitDistance()); + bsb.TextureUAV("u_DirectLighting", memMgr.GetDirectLighting()); + nvrhi::ITexture* sunVis = memMgr.GetSunVis(sunWrite); + bsb.TextureUAV("u_SunVis", sunVis ? sunVis : memMgr.GetPlaceholderTex()); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->ptInitialLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->ptInitialPipeline; + cs.bindings = { bs }; + if (GEnv.Backend) + if (auto* t = GEnv.Backend->GetBindlessDescriptorTable()) + cs.addBindingSet(t); + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + }); + + if (state.ptDupPipeline) { + PTDupCB dupCB{ (float)giW, (float)giH, 0, 0 }; + struct PTDupData { + fg::RenderDevice* device; + ReSTIRGIPassState* state; + PTDupCB cb; + u32 width, height; + }; + fg.addCallbackPass( + "ReSTIR PT DupMap", + [&, dupCB](FrameGraph& builder, PassHandle passHandle, PTDupData& data) { + RenderPassBuilder pb(builder, passHandle); + pb.sideEffects(); + data.device = device; + data.state = &state; + data.cb = dupCB; + data.width = giW; + data.height = giH; + }, + [](const PTDupData& data, const FrameGraph&, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + cmd->writeBuffer(data.state->cb, &data.cb, sizeof(PTDupCB)); + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_pt_dupmap", ".cs"); + if (!refl) return; + BindingSetBuilder bsb(*refl, nv, "ReSTIR.PTDup"); + bsb.ConstantBuffer("ReSTIRPTDup", data.state->cb); + bsb.Texture("t_PTB", memMgr.GetPTReservoirB(0)); + bsb.TextureUAV("u_Dup", memMgr.GetPTDupMap()); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->ptDupLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->ptDupPipeline; + cs.bindings = { bs }; + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + }); + } + + if (hasPrevFrameData && motionVectors.is_valid() && state.ptTemporalPipeline) { + PTTemporalCB tcb{}; + tcb.invViewProj = invViewProj; + tcb.prevInvViewProj = prevInvViewProj; + tcb.cameraPos = { cameraPos.x, cameraPos.y, cameraPos.z, 0 }; + tcb.screenWidth = (float)giW; + tcb.screenHeight = (float)giH; + tcb.invScreenWidth = 1.f / (float)giW; + tcb.invScreenHeight = 1.f / (float)giH; + tcb.frameIndex = Device.dwFrame; + tcb.cCap = (float)std::clamp(ps_r_rt_pt_ccap, 1, 32); + tcb.hasPrev = 1; + struct PTTempData { + fg::RenderDevice* device; + ReSTIRGIPassState* state; + VirtualResourceHandle depth, normal, prevNormals, worldPos, prevDepth, motionVectors, baseColor; + PTTemporalCB cb; + u32 width, height; + }; + fg.addCallbackPass( + "ReSTIR PT Temporal", + [&, tcb](FrameGraph& builder, PassHandle passHandle, PTTempData& data) { + RenderPassBuilder pb(builder, passHandle); + data.depth = pb.read(depth, ResourceState::ShaderResource); + data.normal = pb.read(normal, ResourceState::ShaderResource); + data.baseColor = pb.read(baseColor, ResourceState::ShaderResource); + if (prevNormals.is_valid()) + data.prevNormals = pb.read(prevNormals, ResourceState::ShaderResource); + if (worldPos.is_valid()) + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); + if (prevDepth.is_valid()) + data.prevDepth = pb.read(prevDepth, ResourceState::ShaderResource); + data.motionVectors = pb.read(motionVectors, ResourceState::ShaderResource); + pb.sideEffects(); + data.device = device; + data.state = &state; + data.cb = tcb; + data.width = giW; + data.height = giH; + }, + [](const PTTempData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* prevN = data.prevNormals.is_valid() ? fgGraph.GetPhysicalTexture(data.prevNormals) : normalTex; + auto* worldPosTex = data.worldPos.is_valid() ? fgGraph.GetPhysicalTexture(data.worldPos) : nullptr; + auto* prevDepthTex = data.prevDepth.is_valid() ? fgGraph.GetPhysicalTexture(data.prevDepth) : depthTex; + auto* mvTex = fgGraph.GetPhysicalTexture(data.motionVectors); + if (!depthTex || !mvTex) return; + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + cmd->writeBuffer(data.state->cb, &data.cb, sizeof(PTTemporalCB)); + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_pt_temporal", ".cs"); + if (!refl) return; + BindingSetBuilder bsb(*refl, nv, "ReSTIR.PTTemporal"); + bsb.ConstantBuffer("ReSTIRPTTemporal", data.state->cb); + bsb.Texture("t_CurrA", memMgr.GetPTReservoirA(0)); + bsb.Texture("t_CurrB", memMgr.GetPTReservoirB(0)); + bsb.Texture("t_PrevA", memMgr.GetPTReservoirA(1)); + bsb.Texture("t_PrevB", memMgr.GetPTReservoirB(1)); + bsb.Texture("t_MotionVectors", mvTex); + bsb.Texture("t_Depth", depthTex); + bsb.Texture("t_Normal", normalTex); + bsb.Texture("t_PrevNormal", prevN); + bsb.Texture("t_WorldPos", worldPosTex ? worldPosTex : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_PrevDepth", prevDepthTex); + bsb.Texture("t_DupMap", memMgr.GetPTDupMap() ? memMgr.GetPTDupMap() : memMgr.GetPlaceholderTex()); + { + auto* bcTex = data.baseColor.is_valid() ? fgGraph.GetPhysicalTexture(data.baseColor) : nullptr; + bsb.Texture("t_BaseColor", bcTex ? bcTex : memMgr.GetPlaceholderColorTex()); + } + if (bsb.HasSRV("t_SkyOpen")) + bsb.Texture("t_SkyOpen", memMgr.GetSkyOpen() ? memMgr.GetSkyOpen() : memMgr.GetPlaceholderTex()); + bsb.TextureUAV("u_OutA", memMgr.GetPTReservoirA(1)); + bsb.TextureUAV("u_OutB", memMgr.GetPTReservoirB(1)); + bsb.TextureUAV("u_NoisyDiffuse", memMgr.GetNoisyDiffuse()); + bsb.TextureUAV("u_NoisySpecular", memMgr.GetNoisySpecular()); + bsb.TextureUAV("u_HitDistance", memMgr.GetHitDistance()); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->ptTemporalLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->ptTemporalPipeline; + cs.bindings = { bs }; + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + }); + } + + if (state.ptSpatialPipeline && mem.GetPairingTex(0)) { + for (u32 pair = 0; pair < 3; ++pair) { + PTSpatialCB scb{}; + scb.invViewProj = invViewProj; + scb.cameraPos = { cameraPos.x, cameraPos.y, cameraPos.z, 0 }; + scb.screenWidth = (float)giW; + scb.screenHeight = (float)giH; + scb.invScreenWidth = 1.f / (float)giW; + scb.invScreenHeight = 1.f / (float)giH; + scb.frameIndex = Device.dwFrame; + scb.pairIndex = pair; + scb.flipX = ps_r_rt_pt_decorrelate ? (Device.dwFrame & 1u) : 0; + scb.flipY = ps_r_rt_pt_decorrelate ? ((Device.dwFrame >> 1) & 1u) : 0; + scb.offX = ps_r_rt_pt_decorrelate ? int((Device.dwFrame * 13u + pair * 7u) % 17u) - 8 : 0; + scb.offY = ps_r_rt_pt_decorrelate ? int((Device.dwFrame * 29u + pair * 11u) % 17u) - 8 : 0; + scb.pass = pair; + struct PTSpData { + fg::RenderDevice* device; + ReSTIRGIPassState* state; + VirtualResourceHandle depth, normal, worldPos, baseColor; + PTSpatialCB cb; + u32 width, height, pair; + }; + fg.addCallbackPass( + "ReSTIR PT Spatial", + [&, scb, pair](FrameGraph& builder, PassHandle passHandle, PTSpData& data) { + RenderPassBuilder pb(builder, passHandle); + data.depth = pb.read(depth, ResourceState::ShaderResource); + data.normal = pb.read(normal, ResourceState::ShaderResource); + data.baseColor = pb.read(baseColor, ResourceState::ShaderResource); + if (worldPos.is_valid()) + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); + pb.sideEffects(); + data.device = device; + data.state = &state; + data.cb = scb; + data.width = giW; + data.height = giH; + data.pair = pair; + }, + [](const PTSpData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* worldPosTex = data.worldPos.is_valid() ? fgGraph.GetPhysicalTexture(data.worldPos) : nullptr; + if (!depthTex) return; + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + cmd->writeBuffer(data.state->cb, &data.cb, sizeof(PTSpatialCB)); + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_pt_spatial", ".cs"); + if (!refl) return; + nvrhi::ITexture* srcA = (data.pair == 0) ? memMgr.GetPTReservoirA(1) : memMgr.GetPTReservoirA(0); + nvrhi::ITexture* srcB = (data.pair == 0) ? memMgr.GetPTReservoirB(1) : memMgr.GetPTReservoirB(0); + nvrhi::ITexture* dstA = (data.pair == 0) ? memMgr.GetPTReservoirA(0) : memMgr.GetPTReservoirA(1); + nvrhi::ITexture* dstB = (data.pair == 0) ? memMgr.GetPTReservoirB(0) : memMgr.GetPTReservoirB(1); + if (!srcA) srcA = memMgr.GetPTReservoirA(0); + if (!srcB) srcB = memMgr.GetPTReservoirB(0); + BindingSetBuilder bsb(*refl, nv, "ReSTIR.PTSpatial"); + bsb.ConstantBuffer("ReSTIRPTSpatial", data.state->cb); + bsb.Texture("t_CurrA", srcA); + bsb.Texture("t_CurrB", srcB); + bsb.Texture("t_Depth", depthTex); + bsb.Texture("t_Normal", normalTex); + bsb.Texture("t_WorldPos", worldPosTex ? worldPosTex : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_Pair", memMgr.GetPairingTex(data.pair) ? memMgr.GetPairingTex(data.pair) : memMgr.GetPlaceholderColorTex()); + { + auto* bcTex = data.baseColor.is_valid() ? fgGraph.GetPhysicalTexture(data.baseColor) : nullptr; + bsb.Texture("t_BaseColor", bcTex ? bcTex : memMgr.GetPlaceholderColorTex()); + } + bsb.TextureUAV("u_OutA", dstA); + bsb.TextureUAV("u_OutB", dstB); + bsb.TextureUAV("u_NoisyDiffuse", memMgr.GetNoisyDiffuse()); + bsb.TextureUAV("u_NoisySpecular", memMgr.GetNoisySpecular()); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->ptSpatialLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->ptSpatialPipeline; + cs.bindings = { bs }; + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + }); + } + } + } + + const float shaftIntensity = shaftIntensityEarly; + const bool renderSunshafts = wantSunshafts; + + if (renderSunshafts) { + SunshaftCB shaftCB{}; + shaftCB.invViewProj = invViewProj; + shaftCB.prevViewProj = prevViewProj; + shaftCB.cameraPos = { cameraPos.x, cameraPos.y, cameraPos.z, 0 }; + shaftCB.sunDir_intensity = { sunDir.x, sunDir.y, sunDir.z, sunIntensity }; + shaftCB.sunColor = { sc.x, sc.y, sc.z, 0 }; + shaftCB.screenWidth = (float)shaftW; + shaftCB.screenHeight = (float)shaftH; + shaftCB.shaftIntensity = shaftIntensity; + float shaftFar = 250.f; + if (g_pGamePersistent) + shaftFar = std::max(250.f, std::min(g_pGamePersistent->Environment().CurrentEnv.far_plane, 400.f)); + shaftCB.shaftLength = shaftFar; + shaftCB.identityStaticCount = batchCounts.identityStatic; + shaftCB.terrainBatchCount = batchCounts.terrain; + shaftCB.skinnedBatchStart = initialCB.skinnedBatchStart; + shaftCB.grassBatchStart = initialCB.grassBatchStart; + shaftCB.detailAtlasIndex = initialCB.detailAtlasIndex; + shaftCB.hudSkinnedStart = initialCB.hudSkinnedStart; + const u32 shaftSteps = (ps_r_sun_shafts <= 1) ? 20u : (ps_r_sun_shafts == 2 ? 20u : 40u); + shaftCB.shaftSteps = shaftSteps; + shaftCB.alphaEveryN = (ps_r_sun_shafts <= 1) ? 4u : 2u; + shaftCB.fullWidth = (float)width; + shaftCB.fullHeight = (float)height; + shaftCB.particleBatchStart = initialCB.particleBatchStart; + shaftCB.frameIndex = Device.dwFrame; + shaftCB.hasPrev = hasPrevFrameData ? 1u : 0u; + static Fvector s_prevSunDir = { 0, -1, 0 }; + shaftCB.prevSunDir = s_prevSunDir; + s_prevSunDir = sunDir; + + fg.addCallbackPass( + "ReSTIR Sunshafts", + [&, shaftCB, fgSunshafts](FrameGraph& builder, PassHandle passHandle, SunshaftPassData& data) { + RenderPassBuilder pb(builder, passHandle); + data.depth = pb.read(depth, ResourceState::ShaderResource); + if (hasPrevFrameData && prevDepth.is_valid()) + data.prevDepth = pb.read(prevDepth, ResourceState::ShaderResource); + else + data.prevDepth = {}; + pb.write(fgSunshafts, ResourceState::UnorderedAccess); + pb.sideEffects(); + data.device = device; + data.accelMgr = accelMgr; + data.state = &state; + data.cbData = shaftCB; + data.width = shaftW; + data.height = shaftH; + }, + [](const SunshaftPassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* outTex = memMgr.GetSunshafts(); + auto* tlas = data.accelMgr->GetTLAS(); + if (!depthTex || !outTex || !tlas) return; + + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + SunshaftCB cb = data.cbData; + const RTBatchStarts starts = ComputeBatchStarts(data.accelMgr); + cb.identityStaticCount = starts.identityStatic; + cb.terrainBatchCount = starts.terrain; + cb.skinnedBatchStart = starts.skinnedStart; + cb.grassBatchStart = starts.grassStart; + cb.hudSkinnedStart = starts.hudStart; + cb.detailAtlasIndex = starts.detailAtlas; + cb.particleBatchStart = starts.particleStart; + cmd->writeBuffer(data.state->cb, &cb, sizeof(SunshaftCB)); + + nvrhi::IBuffer* skinnedVB = data.accelMgr->GetSkinnedOutputVB(); + nvrhi::IBuffer* skinnedIB = data.accelMgr->GetSkinnedIB(); + nvrhi::IBuffer* grassVB = data.accelMgr->GetGrassOutputVB(); + nvrhi::IBuffer* grassIB = data.accelMgr->GetGrassIB(); + if (!skinnedVB) skinnedVB = memMgr.GetPlaceholderBuffer(); + if (!skinnedIB) skinnedIB = memMgr.GetPlaceholderBuffer(); + if (!grassVB) grassVB = memMgr.GetPlaceholderBuffer(); + if (!grassIB) grassIB = memMgr.GetPlaceholderBuffer(); + + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_sunshafts", ".cs"); + if (!refl) return; + BindingSetBuilder bsb(*refl, nv, "ReSTIR.Sunshafts"); + bsb.ConstantBuffer("SunshaftParams", data.state->cb); + bsb.AccelStruct("g_SceneTLAS", tlas); + bsb.BufferSRV("g_BatchInfo", data.accelMgr->GetBatchInfoBuffer()); + bsb.BufferSRV("g_MegaVB", data.accelMgr->GetMegaVB()); + bsb.BufferSRV("g_MegaIB", data.accelMgr->GetMegaIB()); + bsb.BufferSRV("g_SkinnedVB", skinnedVB); + bsb.BufferSRV("g_SkinnedIB", skinnedIB); + bsb.BufferSRV("g_GrassVB", grassVB); + bsb.BufferSRV("g_GrassIB", grassIB); + auto* prevDepthTex = data.prevDepth.is_valid() ? fgGraph.GetPhysicalTexture(data.prevDepth) : nullptr; + bsb.Texture("t_Depth", depthTex); + if (bsb.HasSRV("t_SkyOpen")) + bsb.Texture("t_SkyOpen", memMgr.GetSkyOpen() ? memMgr.GetSkyOpen() : memMgr.GetPlaceholderTex()); + if (bsb.HasSRV("t_BlueNoise")) + bsb.Texture("t_BlueNoise", memMgr.GetBlueNoise() ? memMgr.GetBlueNoise() : memMgr.GetPlaceholderTex3D()); + bsb.Texture("t_PrevSunshafts", memMgr.GetSunshaftsHist() ? memMgr.GetSunshaftsHist() : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_PrevDepth", prevDepthTex ? prevDepthTex : memMgr.GetPlaceholderTex()); + BindBindlessMaterialTables(bsb); + bsb.TextureUAV("u_Sunshafts", outTex); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->sunshaftsLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->sunshaftsPipeline; + cs.bindings = { bs }; + if (GEnv.Backend) { + if (auto* bindlessTable = GEnv.Backend->GetBindlessDescriptorTable()) + cs.addBindingSet(bindlessTable); + } + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + if (memMgr.GetSunshaftsHist() && outTex) + cmd->copyTexture(memMgr.GetSunshaftsHist(), nvrhi::TextureSlice(), outTex, nvrhi::TextureSlice()); + } + ); + } else if (mem.GetSunshafts()) { + struct SunshaftClearData {}; + fg.addCallbackPass( + "ReSTIR Sunshafts Clear", + [&, fgSunshafts](FrameGraph& builder, PassHandle passHandle, SunshaftClearData&) { + RenderPassBuilder pb(builder, passHandle); + pb.write(fgSunshafts, ResourceState::UnorderedAccess); + pb.sideEffects(); + }, + [](const SunshaftClearData&, const FrameGraph&, fg::RenderContext* ctx) { + auto* outTex = ReSTIRMemoryManager::Instance().GetSunshafts(); + if (!outTex || !ctx) return; + ctx->GetCommandList()->clearTextureFloat( + outTex, nvrhi::AllSubresources, nvrhi::Color(0.f, 0.f, 0.f, 0.f)); + } + ); + } + + if (state.ddgiPipeline && initialCB.cacheSize == 0) { + DDGICB ddgiCB{}; + ddgiCB.invViewProj = invViewProj; + ddgiCB.cameraPos = { cameraPos.x, cameraPos.y, cameraPos.z, 0 }; + ddgiCB.gridOrigin_spacing = { cameraPos.x - 32.f, cameraPos.y - 8.f, cameraPos.z - 32.f, 4.0f }; + ddgiCB.gridDims_intensity = { 16.f, 8.f, 16.f, 0.35f * giIntensity }; + ddgiCB.screenWidth = (float)giW; + ddgiCB.screenHeight = (float)giH; + ddgiCB.frameIndex = Device.dwFrame; + ddgiCB.pad = envAdapt; + + fg.addCallbackPass( + "ReSTIR DDGI", + [&, ddgiCB, fgDirectLighting, fgDdgiAmb](FrameGraph& builder, PassHandle passHandle, DDGIPassData& data) { + RenderPassBuilder pb(builder, passHandle); + data.depth = pb.read(depth, ResourceState::ShaderResource); + data.normal = pb.read(normal, ResourceState::ShaderResource); + data.baseColor = pb.read(baseColor, ResourceState::ShaderResource); + pb.read(fgDirectLighting, ResourceState::ShaderResource); + pb.write(fgDdgiAmb, ResourceState::UnorderedAccess); + pb.sideEffects(); + data.device = device; + data.state = &state; + data.cbData = ddgiCB; + data.width = giW; + data.height = giH; + }, + [](const DDGIPassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* baseColorTex = fgGraph.GetPhysicalTexture(data.baseColor); + auto* direct = memMgr.GetDirectLighting(); + auto* probes = memMgr.GetDdgiProbes(); + auto* ambient = memMgr.GetDdgiAmbient(); + if (!depthTex || !normalTex || !baseColorTex || !direct || !probes || !ambient) return; + + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + cmd->writeBuffer(data.state->cb, &data.cbData, sizeof(DDGICB)); + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_ddgi", ".cs"); + if (!refl) return; + BindingSetBuilder bsb(*refl, nv, "ReSTIR.DDGI"); + bsb.ConstantBuffer("DDGIParams", data.state->cb); + bsb.Texture("t_Depth", depthTex); + bsb.Texture("t_Normal", normalTex); + bsb.Texture("t_BaseColor", baseColorTex); + bsb.Texture("t_DirectLighting", direct); + bsb.TextureUAV("u_ProbeIrradiance", probes); + bsb.TextureUAV("u_AmbientOut", ambient); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->ddgiLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->ddgiPipeline; + cs.bindings = { bs }; + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + } + ); + } + + const bool inTreeDenoise = state.blurPipeline != nullptr; + + if (inTreeDenoise && hasPrevFrameData && motionVectors.is_valid() && state.temporalFilterPipeline) { + TemporalFilterCB tfCB{}; + tfCB.invViewProj = invViewProj; + tfCB.prevInvViewProj = prevInvViewProj; + tfCB.cameraPos = { cameraPos.x, cameraPos.y, cameraPos.z, 0 }; + tfCB.screenWidth = (float)giW; + tfCB.screenHeight = (float)giH; + tfCB.invScreenWidth = 1.0f / (float)giW; + tfCB.invScreenHeight = 1.0f / (float)giH; + tfCB.alpha = std::clamp(ps_r_rt_gi_temporal_alpha, 0.f, 0.98f); + tfCB.envAdapt = envAdapt; + tfCB.currJitterX = g_taa_jitter_px; + tfCB.currJitterY = g_taa_jitter_py; + tfCB.prevJitterX = g_taa_jitter_prev_px; + tfCB.prevJitterY = g_taa_jitter_prev_py; + tfCB.enabled = 1; + tfCB.pad1 = 0; + struct TFPassData { + fg::RenderDevice* device; + ReSTIRGIPassState* state; + VirtualResourceHandle depth, normal, worldPos, prevDepth, prevNormals, motionVectors; + TemporalFilterCB cbData; + u32 width, height; + }; + fg.addCallbackPass( + "ReSTIR Temporal Filter", + [&, tfCB](FrameGraph& builder, PassHandle passHandle, TFPassData& data) { + RenderPassBuilder pb(builder, passHandle); + data.depth = pb.read(depth, ResourceState::ShaderResource); + data.normal = pb.read(normal, ResourceState::ShaderResource); + if (worldPos.is_valid()) + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); + if (prevDepth.is_valid()) + data.prevDepth = pb.read(prevDepth, ResourceState::ShaderResource); + if (prevNormals.is_valid()) + data.prevNormals = pb.read(prevNormals, ResourceState::ShaderResource); + data.motionVectors = pb.read(motionVectors, ResourceState::ShaderResource); + pb.sideEffects(); + data.device = device; + data.state = &state; + data.cbData = tfCB; + data.width = giW; + data.height = giH; + }, + [](const TFPassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* worldPosTex = data.worldPos.is_valid() ? fgGraph.GetPhysicalTexture(data.worldPos) : nullptr; + auto* prevDepthTex = data.prevDepth.is_valid() ? fgGraph.GetPhysicalTexture(data.prevDepth) : depthTex; + auto* prevNormalsTex = data.prevNormals.is_valid() ? fgGraph.GetPhysicalTexture(data.prevNormals) : normalTex; + auto* motionTex = fgGraph.GetPhysicalTexture(data.motionVectors); + if (!depthTex || !memMgr.GetNoisyDiffuse() || !memMgr.GetHistDiffuse()) return; + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + cmd->writeBuffer(data.state->cb, &data.cbData, sizeof(TemporalFilterCB)); + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_gi_temporal_filter", ".cs"); + if (!refl) return; + BindingSetBuilder bsb(*refl, nv, "ReSTIR.TemporalFilter"); + bsb.ConstantBuffer("TemporalFilterParams", data.state->cb); + bsb.Texture("t_CurrDiffuse", memMgr.GetNoisyDiffuse()); + bsb.Texture("t_CurrSpecular", memMgr.GetNoisySpecular()); + bsb.Texture("t_HistDiffuse", memMgr.GetHistDiffuse()); + bsb.Texture("t_HistSpecular", memMgr.GetHistSpecular()); + bsb.Texture("t_MotionVectors", motionTex); + bsb.Texture("t_Depth", depthTex); + bsb.Texture("t_Normal", normalTex); + bsb.Texture("t_WorldPos", worldPosTex ? worldPosTex : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_PrevDepth", prevDepthTex ? prevDepthTex : depthTex); + bsb.Texture("t_PrevNormal", prevNormalsTex ? prevNormalsTex : normalTex); + bsb.TextureUAV("u_OutDiffuse", memMgr.GetBlurTemp()); + bsb.TextureUAV("u_OutSpecular", memMgr.GetBlurTempSpec()); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->temporalFilterLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->temporalFilterPipeline; + cs.bindings = { bs }; + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + cmd->copyTexture(memMgr.GetNoisyDiffuse(), nvrhi::TextureSlice(), memMgr.GetBlurTemp(), nvrhi::TextureSlice()); + cmd->copyTexture(memMgr.GetNoisySpecular(), nvrhi::TextureSlice(), memMgr.GetBlurTempSpec(), nvrhi::TextureSlice()); + } + ); + } + + if (inTreeDenoise) { + const int atrousSteps = std::clamp(ps_r_rt_gi_atrous_steps, 1, 6); + for (int i = 0; i < atrousSteps; ++i) { + const bool last = (i == atrousSteps - 1); + BlurCB blurCB{}; + blurCB.screenWidth = (float)giW; + blurCB.screenHeight = (float)giH; + blurCB.invScreenWidth = 1.0f / (float)giW; + blurCB.invScreenHeight = 1.0f / (float)giH; + blurCB.phiNormal = 4.f; + blurCB.phiDepth = 6.f; + blurCB.step = 1u << (u32)(atrousSteps - 1 - i); + blurCB.mode = 0u; + struct BlurPassData { + fg::RenderDevice* device; + ReSTIRGIPassState* state; + VirtualResourceHandle depth, normal, worldPos, sceneColorIn, sceneColor; + BlurCB cbData; + u32 width, height; + bool last; + }; + fg.addCallbackPass( + "ReSTIR Blur", + [&, blurCB, last](FrameGraph& builder, PassHandle passHandle, BlurPassData& data) { + RenderPassBuilder pb(builder, passHandle); + data.depth = pb.read(depth, ResourceState::ShaderResource); + data.normal = pb.read(normal, ResourceState::ShaderResource); + if (worldPos.is_valid()) + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); + data.sceneColorIn = pb.read(sceneColorIn, ResourceState::ShaderResource); + pb.sideEffects(); + data.device = device; + data.state = &state; + data.cbData = blurCB; + data.width = giW; + data.height = giH; + data.last = last; + }, + [](const BlurPassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* worldPosTex = data.worldPos.is_valid() ? fgGraph.GetPhysicalTexture(data.worldPos) : nullptr; + auto* sceneIn = fgGraph.GetPhysicalTexture(data.sceneColorIn); + if (!depthTex || !memMgr.GetNoisyDiffuse()) return; + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + cmd->writeBuffer(data.state->cb, &data.cbData, sizeof(BlurCB)); + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_gi_blur", ".cs"); + if (!refl) return; + BindingSetBuilder bsb(*refl, nv, "ReSTIR.Blur"); + bsb.ConstantBuffer("BlurParams", data.state->cb); + bsb.Texture("t_DirectLighting", memMgr.GetDirectLighting()); + bsb.Texture("t_NoisyDiffuse", memMgr.GetNoisyDiffuse()); + bsb.Texture("t_Depth", depthTex); + bsb.Texture("t_Normal", normalTex); + bsb.Texture("t_SceneColorIn", sceneIn ? sceneIn : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_NoisySpecular", memMgr.GetNoisySpecular()); + bsb.Texture("t_ClassifyWorldPos", worldPosTex ? worldPosTex : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_WorldPos", worldPosTex ? worldPosTex : memMgr.GetPlaceholderColorTex()); + bsb.TextureUAV("u_SceneColor", memMgr.GetPlaceholderColorTex()); + bsb.TextureUAV("u_FilteredDiffuse", memMgr.GetBlurTemp()); + bsb.TextureUAV("u_FilteredSpecular", memMgr.GetBlurTempSpec()); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->blurLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->blurPipeline; + cs.bindings = { bs }; + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + cmd->copyTexture(memMgr.GetNoisyDiffuse(), nvrhi::TextureSlice(), memMgr.GetBlurTemp(), nvrhi::TextureSlice()); + cmd->copyTexture(memMgr.GetNoisySpecular(), nvrhi::TextureSlice(), memMgr.GetBlurTempSpec(), nvrhi::TextureSlice()); + if (data.last) { + cmd->copyTexture(memMgr.GetHistDiffuse(), nvrhi::TextureSlice(), memMgr.GetNoisyDiffuse(), nvrhi::TextureSlice()); + cmd->copyTexture(memMgr.GetHistSpecular(), nvrhi::TextureSlice(), memMgr.GetNoisySpecular(), nvrhi::TextureSlice()); + } + } + ); + } + } + CompositeCB compositeCB; compositeCB.invViewProj = invViewProj; compositeCB.cameraPos = { cameraPos.x, cameraPos.y, cameraPos.z, 0 }; compositeCB.screenWidth = (float)width; compositeCB.screenHeight = (float)height; + compositeCB.giWidth = (float)giW; + compositeCB.giHeight = (float)giH; + compositeCB.shaftWidth = shaftW; + compositeCB.shaftHeight = shaftH; compositeCB.giIntensity = giIntensity; - compositeCB.pad = 0; + compositeCB.denoiseApply = 0u; + compositeCB.ambientScale = std::clamp(ps_r_rt_gi_ambient_scale, 0.f, 1.f); + compositeCB.cacheSize = initialCB.cacheSize; + compositeCB.cacheCellSize = initialCB.cacheCellSize; + compositeCB.useDdgi = (initialCB.cacheSize == 0) ? 1u : 0u; + compositeCB.addDirect = g_restirReplaceForward ? 1u : 0u; + compositeCB.pad0 = Device.dwFrame; + { + StaticGlobals fogFill{}; + FillGlobalConstants(fogFill); + compositeCB.fogParams = fogFill.fog_params; + { + const Fvector3& sky = env.CurrentEnv.sky_color; + compositeCB.fogColor = { sky.x, sky.y, sky.z, fogFill.fog_color.w }; + compositeCB.cameraPos.w = env.CurrentEnv.weight; + } + compositeCB.sunDir = { sunDir.x, sunDir.y, sunDir.z, 0 }; + compositeCB.sunColor = { sc.x, sc.y, sc.z, sunIntensity }; + } auto& compositeData = fg.addCallbackPass( - "ReSTIR GI Composite", - [&, compositeCB, writeIdx, outHandle, fgDirectLighting, fgResA, fgResB](FrameGraph& builder, PassHandle passHandle, CompositePassData& data) { + "ReSTIR Composite", + [&, compositeCB, outHandle, fgDirectLighting, fgSunshafts, fgDdgiAmb, fgNoisyDiff, fgNoisySpec, sky0Tex, sky1Tex]( + FrameGraph& builder, PassHandle passHandle, CompositePassData& data) { RenderPassBuilder pb(builder, passHandle); data.depth = pb.read(depth, ResourceState::ShaderResource); data.normal = pb.read(normal, ResourceState::ShaderResource); data.baseColor = pb.read(baseColor, ResourceState::ShaderResource); + if (worldPos.is_valid()) + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); data.sceneColorIn = pb.read(sceneColorIn, ResourceState::ShaderResource); pb.read(fgDirectLighting, ResourceState::ShaderResource); - pb.read(fgResA, ResourceState::ShaderResource); - pb.read(fgResB, ResourceState::ShaderResource); + pb.read(fgSunshafts, ResourceState::ShaderResource); + pb.read(fgDdgiAmb, ResourceState::ShaderResource); + pb.read(fgNoisyDiff, ResourceState::ShaderResource); + pb.read(fgNoisySpec, ResourceState::ShaderResource); data.sceneColor = pb.write(outHandle, ResourceState::UnorderedAccess); data.device = device; data.state = &state; data.cbData = compositeCB; + data.sky0 = sky0Tex; + data.sky1 = sky1Tex; data.width = width; data.height = height; - data.reservoirIdx = writeIdx; }, - [](const CompositePassData& data, const FrameGraph& fg, fg::RenderContext* ctx) { - auto* depthTex = fg.GetPhysicalTexture(data.depth); - auto* normalTex = fg.GetPhysicalTexture(data.normal); - auto* baseColorTex = fg.GetPhysicalTexture(data.baseColor); - auto* sceneColorInTex = fg.GetPhysicalTexture(data.sceneColorIn); - auto* outTex = fg.GetPhysicalTexture(data.sceneColor); + [histIdx](const CompositePassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* baseColorTex = fgGraph.GetPhysicalTexture(data.baseColor); + auto* worldPosTex = data.worldPos.is_valid() ? fgGraph.GetPhysicalTexture(data.worldPos) : nullptr; + auto* sceneColorInTex = fgGraph.GetPhysicalTexture(data.sceneColorIn); + auto* outTex = fgGraph.GetPhysicalTexture(data.sceneColor); if (!depthTex || !normalTex || !baseColorTex || !sceneColorInTex || !outTex) return; - nvrhi::ITexture* directLit = data.state->directLighting.Get(); - nvrhi::ITexture* resA = data.state->reservoirA[data.reservoirIdx].Get(); - nvrhi::ITexture* resB = data.state->reservoirB[data.reservoirIdx].Get(); - if (!directLit || !resA || !resB) { - Msg("! [RTGI Composite] Null persistent texture: directLit=%d resA=%d resB=%d idx=%d", - !!directLit, !!resA, !!resB, data.reservoirIdx); - return; - } - - nvrhi::IDevice* nvDevice = data.device->GetNVRHIDevice(); - nvrhi::ICommandList* cmdList = ctx->GetCommandList(); + nvrhi::ITexture* directLit = memMgr.GetDirectLighting(); + nvrhi::IBuffer* resBuf = memMgr.GetReservoirBuffer(histIdx); + nvrhi::ITexture* shafts = memMgr.GetSunshafts(); + nvrhi::ITexture* ddgi = memMgr.GetDdgiAmbient(); + auto blitIn = [&]() { + nvrhi::ICommandList* c = ctx->GetCommandList(); + c->copyTexture(outTex, nvrhi::TextureSlice(), sceneColorInTex, nvrhi::TextureSlice()); + }; + if (!directLit || !resBuf) { blitIn(); return; } - cmdList->writeBuffer(data.state->cb, &data.cbData, sizeof(CompositeCB)); + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + cmd->writeBuffer(data.state->cb, &data.cbData, sizeof(CompositeCB)); - auto* shaderLoader = GEnv.Render->GetShaderLoader(); - auto* csReflection = shaderLoader->GetCachedReflection("restir_gi_composite", ".cs"); - if (!csReflection) return; + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_gi_composite", ".cs"); + if (!refl) { blitIn(); return; } - framegraph::BindingSetBuilder bsb(*csReflection, nvDevice, "ReSTIRGI.Spatial"); + BindingSetBuilder bsb(*refl, nv, "ReSTIR.Composite"); bsb.ConstantBuffer("CompositeParams", data.state->cb); bsb.Texture("t_DirectLighting", directLit); - bsb.Texture("t_ReservoirA", resA); - bsb.Texture("t_ReservoirB", resB); + bsb.BufferSRV("t_Reservoir", resBuf); bsb.Texture("t_Depth", depthTex); - bsb.Texture("t_Normal", normalTex); bsb.Texture("t_BaseColor", baseColorTex); bsb.Texture("t_SceneColorIn", sceneColorInTex); + bsb.Texture("t_Normal", normalTex); + bsb.Texture("t_NoisyDiffuse", memMgr.GetNoisyDiffuse()); + bsb.Texture("t_NoisySpecular", memMgr.GetNoisySpecular()); + bsb.Texture("t_Sunshafts", shafts ? shafts : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_DDGIAmbient", ddgi ? ddgi : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_WorldPos", worldPosTex ? worldPosTex : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_SpecReservoirA", memMgr.GetSpecReservoirA(0) ? memMgr.GetSpecReservoirA(0) : memMgr.GetPlaceholderColorTex()); + bsb.Texture("t_SpecReservoirB", memMgr.GetSpecReservoirB(0) ? memMgr.GetSpecReservoirB(0) : memMgr.GetPlaceholderColorTex()); + if (auto* cache = memMgr.GetIrradianceCache()) + bsb.BufferSRV("g_IrradianceCache", cache); + else + bsb.BufferSRV("g_IrradianceCache", memMgr.GetPlaceholderBuffer()); + if (bsb.HasSRV("t_SkyOpen")) + bsb.Texture("t_SkyOpen", memMgr.GetSkyOpen() ? memMgr.GetSkyOpen() : memMgr.GetPlaceholderTex()); + if (bsb.HasSRV("g_Sky0")) + bsb.Texture("g_Sky0", data.sky0 ? data.sky0 : memMgr.GetPlaceholderCube()); + if (bsb.HasSRV("g_Sky1")) + bsb.Texture("g_Sky1", data.sky1 ? data.sky1 : memMgr.GetPlaceholderCube()); bsb.TextureUAV("u_SceneColor", outTex); - auto& cache = GetPassResourceCache(); - auto bindingSet = cache.GetOrCreateBindingSet(bsb.Build(), data.state->compositeLayout, nvDevice); - if (!bindingSet) return; + auto bindingSet = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->compositeLayout, nv); + if (!bindingSet) { blitIn(); return; } nvrhi::ComputeState cs; cs.pipeline = data.state->compositePipeline; cs.bindings = { bindingSet }; - - cmdList->setComputeState(cs); - cmdList->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); } ); - state.currTemporalIdx ^= 1; + VirtualResourceHandle finalColor = compositeData.sceneColor; + + if (state.waterPipeline && state.waterLayout && accelMgr && worldPos.is_valid()) { + WaterCB waterCB{}; + waterCB.invViewProj = invViewProj; + waterCB.viewProj = Device.mFullTransform; + waterCB.cameraPos = { cameraPos.x, cameraPos.y, cameraPos.z, 80.f }; + waterCB.sunDir_intensity = initialCB.sunDir_intensity; + waterCB.sunColor_skyWeight = initialCB.sunColor_skyWeight; + waterCB.skyColor = initialCB.skyColor; + waterCB.screenWidth = (float)width; + waterCB.screenHeight = (float)height; + waterCB.lodDist = std::max(10.f, ps_r_rt_gi_lod_dist); + waterCB.giIntensity = giIntensity; + waterCB.identityStaticCount = initialCB.identityStaticCount; + waterCB.terrainBatchCount = initialCB.terrainBatchCount; + waterCB.skinnedBatchStart = initialCB.skinnedBatchStart; + waterCB.grassBatchStart = initialCB.grassBatchStart; + waterCB.detailAtlasIndex = initialCB.detailAtlasIndex; + waterCB.hudSkinnedStart = initialCB.hudSkinnedStart; + waterCB.pad1 = waterCB.pad2 = 0; + { + Fvector4 hemi = { 0.3f, 0.4f, 0.5f, 1.f }; + if (g_pGamePersistent) + { + const auto& h = g_pGamePersistent->Environment().CurrentEnv.hemi_color; + hemi.set(h.x, h.y, h.z, h.w); + } + waterCB.hemiColor = hemi; + } + + ResourceDesc waterOutDesc = outDesc; + waterOutDesc.debugName = "rtgi_WaterSceneColor"; + VirtualResourceHandle waterOut = fg.CreateTexture("rtgi_WaterSceneColor", waterOutDesc); + + struct WaterPassData { + VirtualResourceHandle depth; + VirtualResourceHandle normal; + VirtualResourceHandle baseColor; + VirtualResourceHandle worldPos; + VirtualResourceHandle sceneIn; + VirtualResourceHandle sceneOut; + fg::RenderDevice* device = nullptr; + RTAccelStructManager* accelMgr = nullptr; + ReSTIRGIPassState* state = nullptr; + WaterCB cbData{}; + nvrhi::ITexture* sky0 = nullptr; + nvrhi::ITexture* sky1 = nullptr; + nvrhi::ITexture* underWorldPos = nullptr; + nvrhi::ITexture* underColor = nullptr; + u32 width = 0; + u32 height = 0; + }; + + auto& waterData = fg.addCallbackPass( + "ReSTIR Water RT", + [&, waterCB, finalColor, waterOut, sky0Tex, sky1Tex](FrameGraph& builder, PassHandle passHandle, WaterPassData& data) { + RenderPassBuilder pb(builder, passHandle); + data.depth = pb.read(depth, ResourceState::ShaderResource); + data.normal = pb.read(normal, ResourceState::ShaderResource); + data.baseColor = pb.read(baseColor, ResourceState::ShaderResource); + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); + data.sceneIn = pb.read(finalColor, ResourceState::ShaderResource); + data.sceneOut = pb.write(waterOut, ResourceState::UnorderedAccess); + pb.sideEffects(); + data.device = device; + data.accelMgr = accelMgr; + data.state = &state; + data.cbData = waterCB; + data.sky0 = sky0Tex; + data.sky1 = sky1Tex; + data.underWorldPos = state.waterUnderWorldPos; + data.underColor = state.waterUnderColor; + data.width = width; + data.height = height; + }, + [](const WaterPassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& memMgr = ReSTIRMemoryManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* baseColorTex = fgGraph.GetPhysicalTexture(data.baseColor); + auto* worldPosTex = fgGraph.GetPhysicalTexture(data.worldPos); + auto* sceneIn = fgGraph.GetPhysicalTexture(data.sceneIn); + auto* sceneOut = fgGraph.GetPhysicalTexture(data.sceneOut); + auto blitIn = [&]() { + if (sceneIn && sceneOut) + ctx->GetCommandList()->copyTexture( + sceneOut, nvrhi::TextureSlice(), sceneIn, nvrhi::TextureSlice()); + }; + if (!data.state || !data.state->waterPipeline || !data.accelMgr) { + blitIn(); + return; + } + auto* tlas = data.accelMgr->GetTLAS(); + auto* batchInfo = data.accelMgr->GetBatchInfoBuffer(); + auto* megaVB = data.accelMgr->GetMegaVB(); + auto* megaIB = data.accelMgr->GetMegaIB(); + if (!depthTex || !normalTex || !baseColorTex || !worldPosTex || !sceneIn || !sceneOut || + !tlas || !batchInfo || !megaVB || !megaIB) { + blitIn(); + return; + } + + nvrhi::IDevice* nvDevice = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmdList = ctx->GetCommandList(); - return { compositeData.sceneColor }; + WaterCB cb = data.cbData; + const RTBatchStarts starts = ComputeBatchStarts(data.accelMgr); + cb.identityStaticCount = starts.identityStatic; + cb.terrainBatchCount = starts.terrain; + cb.skinnedBatchStart = starts.skinnedStart; + cb.grassBatchStart = starts.grassStart; + cb.hudSkinnedStart = starts.hudStart; + cb.detailAtlasIndex = starts.detailAtlas; + cmdList->writeBuffer(data.state->cb, &cb, sizeof(WaterCB)); + + auto* csReflection = GEnv.Render->GetShaderLoader()->GetCachedReflection("restir_water_rt", ".cs"); + if (!csReflection) { + blitIn(); + return; + } + + nvrhi::IBuffer* skinnedVB = data.accelMgr->GetSkinnedOutputVB(); + nvrhi::IBuffer* skinnedIB = data.accelMgr->GetSkinnedIB(); + nvrhi::IBuffer* grassVB = data.accelMgr->GetGrassOutputVB(); + nvrhi::IBuffer* grassIB = data.accelMgr->GetGrassIB(); + if (!skinnedVB) skinnedVB = memMgr.GetPlaceholderBuffer(); + if (!skinnedIB) skinnedIB = memMgr.GetPlaceholderBuffer(); + if (!grassVB) grassVB = memMgr.GetPlaceholderBuffer(); + if (!grassIB) grassIB = memMgr.GetPlaceholderBuffer(); + + auto& cache = GetPassResourceCache(); + nvrhi::ITexture* sky0 = data.sky0 ? data.sky0 : memMgr.GetPlaceholderCube(); + nvrhi::ITexture* sky1 = data.sky1 ? data.sky1 : memMgr.GetPlaceholderCube(); + nvrhi::ITexture* underWP = data.underWorldPos + ? data.underWorldPos + : cache.GetDummyContactHistory(nvDevice); + nvrhi::ITexture* underColor = data.underColor + ? data.underColor + : memMgr.GetPlaceholderColorTex(); + + BindingSetBuilder bsb(*csReflection, nvDevice, "ReSTIR.Water"); + bsb.ConstantBuffer("ReSTIRWaterParams", data.state->cb); + bsb.AccelStruct("g_SceneTLAS", tlas); + bsb.BufferSRV("g_BatchInfo", batchInfo); + bsb.BufferSRV("g_MegaVB", megaVB); + bsb.BufferSRV("g_MegaIB", megaIB); + bsb.Texture("g_Sky0", sky0); + bsb.Texture("g_Sky1", sky1); + bsb.BufferSRV("g_SkinnedVB", skinnedVB); + BindBindlessMaterialTables(bsb); + bsb.BufferSRV("g_SkinnedIB", skinnedIB); + bsb.BufferSRV("g_GrassVB", grassVB); + bsb.BufferSRV("g_GrassIB", grassIB); + bsb.Texture("t_Depth", depthTex); + bsb.Texture("t_Normal", normalTex); + bsb.Texture("t_BaseColor", baseColorTex); + bsb.Texture("t_WorldPos", worldPosTex); + bsb.Texture("t_ClassifyWorldPos", worldPosTex); + bsb.Texture("t_SceneColorIn", sceneIn); + bsb.Texture("t_UnderWorldPos", underWP); + bsb.Texture("t_UnderColor", underColor); + bsb.TextureUAV("u_SceneColor", sceneOut); + auto bindingSet = cache.GetOrCreateBindingSet(bsb.Build(), data.state->waterLayout, nvDevice); + if (!bindingSet) { + blitIn(); + return; + } + + nvrhi::ComputeState cs; + cs.pipeline = data.state->waterPipeline; + cs.bindings = { bindingSet }; + if (auto* backend = data.device ? data.device->GetBackend() : nullptr) { + if (auto* bindlessTable = backend->GetBindlessDescriptorTable()) + cs.addBindingSet(bindlessTable); + } + cmdList->setComputeState(cs); + cmdList->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + } + ); + finalColor = waterData.sceneOut; + } + + state.currTemporalIdx = 1u - (state.currTemporalIdx & 1u); + output.sceneColor = finalColor; + return output; } void ShutdownReSTIRGI(ReSTIRGIPassState& state) @@ -678,19 +2872,47 @@ void ShutdownReSTIRGI(ReSTIRGIPassState& state) state.initialLayout = nullptr; state.temporalPipeline = nullptr; state.temporalLayout = nullptr; + state.spatialPipeline = nullptr; + state.spatialLayout = nullptr; state.compositePipeline = nullptr; state.compositeLayout = nullptr; + state.wetPipeline = nullptr; + state.wetLayout = nullptr; + state.sunshaftsPipeline = nullptr; + state.sunshaftsLayout = nullptr; + state.ddgiPipeline = nullptr; + state.ddgiLayout = nullptr; + state.waterPipeline = nullptr; + state.waterLayout = nullptr; + state.diTemporalPipeline = nullptr; + state.diTemporalLayout = nullptr; + state.diSpatialPipeline = nullptr; + state.diSpatialLayout = nullptr; + state.diShadePipeline = nullptr; + state.diShadeLayout = nullptr; + state.blurPipeline = nullptr; + state.blurLayout = nullptr; + state.temporalFilterPipeline = nullptr; + state.temporalFilterLayout = nullptr; + state.specTemporalPipeline = nullptr; + state.specTemporalLayout = nullptr; + state.ptInitialPipeline = nullptr; + state.ptInitialLayout = nullptr; + state.ptTemporalPipeline = nullptr; + state.ptTemporalLayout = nullptr; + state.ptSpatialPipeline = nullptr; + state.ptSpatialLayout = nullptr; + state.ptDupPipeline = nullptr; + state.ptDupLayout = nullptr; state.cb = nullptr; - state.sampler = nullptr; - for (int i = 0; i < 2; i++) { - state.reservoirA[i] = nullptr; - state.reservoirB[i] = nullptr; - } - state.directLighting = nullptr; - s_rtgiPlaceholderBuffer = nullptr; - s_rtgiPlaceholderCube = nullptr; + state.waterUnderWorldPos = nullptr; + state.waterUnderColor = nullptr; + ReSTIRMemoryManager::Instance().Shutdown(); state.initialized = false; state.enabled = false; + state.pipeVersion = 0; + g_restirPipelinesReady = false; + g_restirReplaceForward = false; } } diff --git a/src/Layers/xrRender/FrameGraphPasses/ReSTIRGIPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/ReSTIRGIPassSetup.h index e88ce649ef9..11bf8186ce5 100644 --- a/src/Layers/xrRender/FrameGraphPasses/ReSTIRGIPassSetup.h +++ b/src/Layers/xrRender/FrameGraphPasses/ReSTIRGIPassSetup.h @@ -2,6 +2,7 @@ #include "Layers/xrRender/FrameGraph/FGTypes.h" #include "Layers/xrRender/FrameGraph/FGResource.h" +#include "Layers/xrRender/FrameGraphPasses/ShadowPassSetup.h" #include namespace xray::render::fg { class RenderDevice; } @@ -15,25 +16,54 @@ struct ReSTIRGIPassState { nvrhi::BindingLayoutHandle initialLayout; nvrhi::ComputePipelineHandle temporalPipeline; nvrhi::BindingLayoutHandle temporalLayout; + nvrhi::ComputePipelineHandle spatialPipeline; + nvrhi::BindingLayoutHandle spatialLayout; nvrhi::ComputePipelineHandle compositePipeline; nvrhi::BindingLayoutHandle compositeLayout; + nvrhi::ComputePipelineHandle wetPipeline; + nvrhi::BindingLayoutHandle wetLayout; + nvrhi::ComputePipelineHandle sunshaftsPipeline; + nvrhi::BindingLayoutHandle sunshaftsLayout; + nvrhi::ComputePipelineHandle ddgiPipeline; + nvrhi::BindingLayoutHandle ddgiLayout; + nvrhi::ComputePipelineHandle waterPipeline; + nvrhi::BindingLayoutHandle waterLayout; + nvrhi::ComputePipelineHandle diTemporalPipeline; + nvrhi::BindingLayoutHandle diTemporalLayout; + nvrhi::ComputePipelineHandle diSpatialPipeline; + nvrhi::BindingLayoutHandle diSpatialLayout; + nvrhi::ComputePipelineHandle diShadePipeline; + nvrhi::BindingLayoutHandle diShadeLayout; + nvrhi::ComputePipelineHandle blurPipeline; + nvrhi::BindingLayoutHandle blurLayout; + nvrhi::ComputePipelineHandle temporalFilterPipeline; + nvrhi::BindingLayoutHandle temporalFilterLayout; + nvrhi::ComputePipelineHandle specTemporalPipeline; + nvrhi::BindingLayoutHandle specTemporalLayout; + nvrhi::ComputePipelineHandle ptInitialPipeline; + nvrhi::BindingLayoutHandle ptInitialLayout; + nvrhi::ComputePipelineHandle ptTemporalPipeline; + nvrhi::BindingLayoutHandle ptTemporalLayout; + nvrhi::ComputePipelineHandle ptSpatialPipeline; + nvrhi::BindingLayoutHandle ptSpatialLayout; + nvrhi::ComputePipelineHandle ptDupPipeline; + nvrhi::BindingLayoutHandle ptDupLayout; + u32 currTemporalIdx = 0; nvrhi::IBuffer* cb = nullptr; - nvrhi::SamplerHandle sampler; - - nvrhi::TextureHandle reservoirA[2]; - nvrhi::TextureHandle reservoirB[2]; - nvrhi::TextureHandle directLighting; - u32 currTemporalIdx = 0; - u32 texWidth = 0; - u32 texHeight = 0; + nvrhi::ITexture* waterUnderWorldPos = nullptr; + nvrhi::ITexture* waterUnderColor = nullptr; bool initialized = false; bool enabled = false; + u32 pipeVersion = 0; }; struct ReSTIRGIOutput { framegraph::VirtualResourceHandle sceneColor; + nvrhi::ITexture* noisyDiffuse = nullptr; + nvrhi::ITexture* noisySpecular = nullptr; + nvrhi::ITexture* hitDistance = nullptr; }; ReSTIRGIOutput setupReSTIRGIPass( @@ -43,17 +73,20 @@ ReSTIRGIOutput setupReSTIRGIPass( framegraph::VirtualResourceHandle depth, framegraph::VirtualResourceHandle normal, framegraph::VirtualResourceHandle baseColor, + framegraph::VirtualResourceHandle worldPos, framegraph::VirtualResourceHandle prevNormals, framegraph::VirtualResourceHandle prevDepth, framegraph::VirtualResourceHandle motionVectors, framegraph::VirtualResourceHandle sceneColorIn, const Fmatrix& invViewProj, const Fmatrix& prevViewProj, + const Fmatrix& prevInvViewProj, const Fvector& cameraPos, float giIntensity, u32 width, u32 height, ReSTIRGIPassState& state, - bool hasPrevFrameData + bool hasPrevFrameData, + const GrassShadowOutputs& grassShadow = {} ); void ShutdownReSTIRGI(ReSTIRGIPassState& state); diff --git a/src/Layers/xrRender/FrameGraphPasses/ShaderConstants.cpp b/src/Layers/xrRender/FrameGraphPasses/ShaderConstants.cpp index 5520fc11ec6..f001f6b58f3 100644 --- a/src/Layers/xrRender/FrameGraphPasses/ShaderConstants.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/ShaderConstants.cpp @@ -8,11 +8,15 @@ namespace xray::render::fg::passes { using namespace xray::render::fg; void GetSunLightData(SunLightData& outSun, float hdrIntensity) { + outSun.color.set(1.0f, 0.95f, 0.9f); + outSun.direction.set(0.577f, -0.577f, 0.577f); + outSun.intensity = hdrIntensity; + auto* sun = static_cast(Lights.sun._get()); if (sun) { outSun.color.set(sun->color.r, sun->color.g, sun->color.b); outSun.direction = sun->direction; - outSun.intensity = 1.f; + outSun.intensity = hdrIntensity; } } diff --git a/src/Layers/xrRender/FrameGraphPasses/ShaderConstants.h b/src/Layers/xrRender/FrameGraphPasses/ShaderConstants.h index 56040273ff1..5a1ab20f302 100644 --- a/src/Layers/xrRender/FrameGraphPasses/ShaderConstants.h +++ b/src/Layers/xrRender/FrameGraphPasses/ShaderConstants.h @@ -4,10 +4,29 @@ #include "xrCore/xrCore.h" #include "xrCore/_vector3d.h" #include "xrCore/_matrix.h" +#include "xrEngine/device.h" +#include "xrEngine/IGame_Persistent.h" +#include "xrEngine/Environment.h" +#include "Include/xrAPI/xrAPI.h" +#include // Forward declarations of X-Ray engine globals extern ECORE_API float ps_r2_sun_lumscale_hemi; +extern ECORE_API float ps_r2_sun_lumscale_amb; +extern ECORE_API Flags32 ps_r2_ls_flags; +extern ECORE_API float ps_r2_df_parallax_h; extern ENGINE_API int ps_fg_pbr_diffuse_mode; +extern ENGINE_API int ps_r_rt_gi; +extern ENGINE_API float ps_r_rt_gi_ambient_scale; +extern ENGINE_API int ps_r_path_tracer; +extern bool g_restirPipelinesReady; +extern bool g_restirReplaceForward; +extern ENGINE_API int ps_r_hdr10; +extern ENGINE_API float ps_r_hdr10_hud; +extern ENGINE_API float ps_r_hdr10_paper_white; +extern ENGINE_API int ps_r_vol_fog; +extern ENGINE_API int ps_r_atmosphere; +extern ENGINE_API float ps_r_atmosphere_strength; extern ENGINE_API Fvector4 ps_dev_param_1; extern ENGINE_API Fvector4 ps_dev_param_2; extern ENGINE_API Fvector4 ps_dev_param_3; @@ -21,6 +40,24 @@ namespace xray::render { namespace xray::render::fg::passes { +inline u32& RenderResW() { static u32 w = 0; return w; } +inline u32& RenderResH() { static u32 h = 0; return h; } +inline void SetRenderResolution(u32 w, u32 h) +{ + RenderResW() = std::max(1u, w); + RenderResH() = std::max(1u, h); +} +inline u32 GetRenderWidth() +{ + const u32 w = RenderResW(); + return w ? w : std::max(1u, (u32)Device.dwWidth); +} +inline u32 GetRenderHeight() +{ + const u32 h = RenderResH(); + return h ? h : std::max(1u, (u32)Device.dwHeight); +} + // ══════════════════════════════════════════════════════════ // PBR TEXTURE SLOT ASSIGNMENTS (Forward+ Rendering) // ══════════════════════════════════════════════════════════ @@ -82,7 +119,10 @@ struct alignas(16) SkinnedMaterialCB { u32 skeletonBoneOffset; u32 splatOffset; u32 splatCount; + u32 hudLit; + u32 pad[3]; }; +static_assert(sizeof(SkinnedMaterialCB) == 32, "SkinnedMaterialCB must be 32 bytes"); // Slot 2: Static Globals (EXTENDED for Forward+) // UPDATED ONCE PER FRAME! Contains view/projection matrices, lighting, fog, etc. @@ -135,57 +175,76 @@ using GlobalConstants = StaticGlobals; inline void FillGlobalConstants(GlobalConstants& cb) { cb.m_V = Device.mView; cb.m_P = Device.mProject; - cb.m_VP.mul(Device.mProject, Device.mView); - - // Timers - cb.timers.set( - Device.fTimeGlobal, // Game time - Device.fTimeDelta, // Frame delta - _sin(Device.fTimeGlobal), // sin(time) - _cos(Device.fTimeGlobal) // cos(time) - ); - - // Fog (use X-Ray's global fog state if available, otherwise defaults) - // TODO: Hook into X-Ray's CFogOfWar or environment system - cb.fog_plane.set(0.0f, 1.0f, 0.0f, 0.0f); // Plane equation - cb.fog_params.set(0.0f, 1000.0f, 0.001f, 0.0f); // near, far, density - cb.fog_color.set(0.5f, 0.5f, 0.6f, 1.0f); // Grayish-blue fog + cb.m_VP = Device.mFullTransform; + + const float t = Device.fTimeGlobal; + cb.timers.set(t, t * 10.f, t / 10.f, _sin(t)); + + if (g_pGamePersistent) + { + const auto& env = g_pGamePersistent->Environment().CurrentEnv; + const float n = env.fog_near; + const float f = std::max(env.fog_far, n + 1.0f); + const float r = 1.0f / (f - n); + if (ps_r_vol_fog) + cb.fog_params.set(0.0f, 0.0f, 0.0f, 0.0f); + else + cb.fog_params.set(-n * r, r, r, r); + cb.fog_color.set(env.fog_color.x, env.fog_color.y, env.fog_color.z, + (ps_r_atmosphere != 0) ? std::clamp(ps_r_atmosphere_strength, 0.f, 4.f) : 0.0f); + + Fvector4 plane; + const Fmatrix& M = Device.mFullTransform; + plane.x = -(M._14 + M._13); + plane.y = -(M._24 + M._23); + plane.z = -(M._34 + M._33); + plane.w = -(M._44 + M._43); + const float denom = -1.0f / _sqrt(_sqr(plane.x) + _sqr(plane.y) + _sqr(plane.z)); + plane.mul(denom); + const float B = r; + cb.fog_plane.set(-plane.x * B, -plane.y * B, -plane.z * B, 1.0f - (plane.w - n) * B); + } + else + { + cb.fog_plane.set(0.0f, 1.0f, 0.0f, 0.0f); + cb.fog_params.set(0.0f, 0.001f, 0.001f, 0.001f); + cb.fog_color.set(0.5f, 0.5f, 0.6f, 0.0f); + } - // Lighting - defaults, will be overridden by FillSunConstants if sun is available - cb.L_ambient.set(0.2f, 0.2f, 0.2f, 1.0f); // Ambient (placeholder) - cb.L_sun_color.set(1.0f, 0.95f, 0.9f); // Warm sunlight (placeholder) + cb.L_ambient.set(0.2f, 0.2f, 0.2f, 1.0f); + cb.L_sun_color.set(1.0f, 0.95f, 0.9f); cb.pbr_diffuse_mode = (float)ps_fg_pbr_diffuse_mode; - cb.L_sun_dir_w.set(0.577f, -0.577f, 0.577f); // Diagonal down (placeholder) + cb.L_sun_dir_w.set(0.577f, -0.577f, 0.577f); cb.L_hemi_color.set(0.3f, 0.4f, 0.5f, ps_r2_sun_lumscale_hemi); - // Camera position cb.eye_position = Device.vCameraPosition; + const u32 rw = GetRenderWidth(); + const u32 rh = GetRenderHeight(); const float VertTan = -1.0f * tanf(deg2rad(Device.fFOV / 2.0f)); const float HorzTan = -VertTan / Device.fASPECT; - // Vertex decompression (used for quantized positions) - cb.pos_decompression_params.set(HorzTan, VertTan, (2.0f * HorzTan) / (float)Device.dwWidth, (2.0f * VertTan) / (float)Device.dwHeight); - cb.pos_decompression_params2.set((float)Device.dwWidth, (float)Device.dwHeight, 1.0f / (float)Device.dwWidth, 1.0f / (float)Device.dwHeight); + cb.pos_decompression_params.set(HorzTan, VertTan, (2.0f * HorzTan) / (float)rw, (2.0f * VertTan) / (float)rh); + cb.pos_decompression_params2.set((float)rw, (float)rh, 1.0f / (float)rw, 1.0f / (float)rh); - // Parallax mapping - cb.parallax.set(0.02f, -0.01f, 0.0f, 0.0f); // height scale, min samples, max samples, unused + const float ph = ps_r2_df_parallax_h; + cb.parallax.set(ph, -0.5f * ph, 0.0f, 0.0f); + if (ps_r2_ls_flags.test(1u << 22)) + cb.parallax.z = 1.0f; + if (((ps_r_rt_gi != 0) && g_restirReplaceForward) || (ps_r_path_tracer != 0)) + cb.parallax.w = -1.0f; - // Screen resolution (for UI shaders and other effects) cb.screen_res.set( - (float)Device.dwWidth, // x = width - (float)Device.dwHeight, // y = height - 1.0f / (float)Device.dwWidth, // z = 1/width - 1.0f / (float)Device.dwHeight // w = 1/height + (float)Device.dwWidth, + (float)Device.dwHeight, + 1.0f / (float)Device.dwWidth, + 1.0f / (float)Device.dwHeight ); - // Clear padding to avoid uninitialized memory warnings cb.hud_fov = psHUD_FOV; - cb.padding3 = 0.0f; - - // ═══════════════════════════════════════════════════════ - // FORWARD+ EXTENSIONS (Phase 1.3) - // ═══════════════════════════════════════════════════════ + cb.padding3 = 1.0f; + if (GEnv.Backend && GEnv.Backend->IsHdr10()) + cb.padding3 = ps_r_hdr10_hud / std::max(ps_r_hdr10_paper_white, 1.f); cb.m_InvVP.invert(cb.m_VP); @@ -193,13 +252,12 @@ inline void FillGlobalConstants(GlobalConstants& cb) { cb.shadow_matrices[i].identity(); cb.cascade_splits.set(10.0f, 50.0f, 150.0f, 500.0f); - // Cluster grid parameters (PLACEHOLDER - Phase 5: will be populated from light culling pass) - cb.cluster_params.set(16.0f, 16.0f, 24.0f, 0.0f); // 16×16×24 grid, 0 lights for now - cb.cluster_scales.set(0.1f, 500.0f, 1.0f, 1.0f); // z_near, z_far, scale_x, scale_y + cb.cluster_params.set(16.0f, 16.0f, 24.0f, 0.0f); + cb.cluster_scales.set(0.1f, 500.0f, 1.0f, 1.0f); - // Camera direction vector (for lighting calculations) cb.camera_direction.set(Device.vCameraDirection.x, Device.vCameraDirection.y, - Device.vCameraDirection.z, 0.0f); + Device.vCameraDirection.z, + ps_r2_ls_flags.test(1u << 20) ? 1.0f : 0.0f); cb.dev_param_1 = ps_dev_param_1; cb.dev_param_2 = ps_dev_param_2; @@ -224,8 +282,23 @@ struct SunLightData { }; inline void FillSunConstants(StaticGlobals& cb, const SunLightData& sun) { + if (!g_pGamePersistent) + { + cb.L_sun_color.set(0.f, 0.f, 0.f); + cb.L_sun_dir_w.set(0.f, -1.f, 0.f); + cb.L_ambient.set(0.2f, 0.2f, 0.2f, 1.0f); + cb.L_hemi_color.set(0.3f, 0.4f, 0.5f, 1.0f); + return; + } + const auto& desc = g_pGamePersistent->Environment().CurrentEnv; + float ambScale = 1.0f; + const bool rtAmbientCut = + (((ps_r_rt_gi != 0) && g_restirReplaceForward) || (ps_r_path_tracer != 0)); + if (rtAmbientCut) + ambScale = std::clamp(ps_r_rt_gi_ambient_scale, 0.0f, 1.0f); + cb.L_sun_color.set( sun.color.x * sun.intensity, sun.color.y * sun.intensity, @@ -238,16 +311,20 @@ inline void FillSunConstants(StaticGlobals& cb, const SunLightData& sun) { sun.direction.z ); + const float minamb = 0.001f; cb.L_ambient.set( - desc.ambient.x, - desc.ambient.y, - desc.ambient.z + std::max(desc.ambient.x * 2.f, minamb) * ps_r2_sun_lumscale_amb * ambScale, + std::max(desc.ambient.y * 2.f, minamb) * ps_r2_sun_lumscale_amb * ambScale, + std::max(desc.ambient.z * 2.f, minamb) * ps_r2_sun_lumscale_amb * ambScale, + desc.weight ); + const float hemiScale = 2.f * ps_r2_sun_lumscale_hemi * ambScale; cb.L_hemi_color.set( - desc.hemi_color.x, - desc.hemi_color.y, - desc.hemi_color.z + desc.env_color.x * hemiScale, + desc.env_color.y * hemiScale, + desc.env_color.z * hemiScale, + 1.0f ); } diff --git a/src/Layers/xrRender/FrameGraphPasses/ShadowPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/ShadowPassSetup.h new file mode 100644 index 00000000000..382a2d66f11 --- /dev/null +++ b/src/Layers/xrRender/FrameGraphPasses/ShadowPassSetup.h @@ -0,0 +1,82 @@ +#pragma once + +#include "Layers/xrRender/FrameGraph/FGTypes.h" +#include "Layers/xrRender/FrameGraph/FGResource.h" +#include "Layers/xrRender/RenderContext/ResourceHandle.h" +#include + +namespace xray::render::framegraph +{ +class FrameGraph; +} + +namespace xray::render::fg +{ +class RenderDevice; +class FGDetailManager; +} + +namespace xray::render::fg::passes +{ + +struct alignas(16) ShadowCascadeCB +{ + Fmatrix lightVP; +}; +static_assert(sizeof(ShadowCascadeCB) % 16 == 0); + +struct alignas(16) GrassShadowCB +{ + float grassBladeHeight; + u32 buildDetailsIndex; + float windAngleDeg; + float windSpeed; + float time; + float windDisplacement; + float pad0, pad1; +}; +static_assert(sizeof(GrassShadowCB) % 16 == 0); + +struct GrassShadowPassState +{ + nvrhi::GraphicsPipelineHandle grassPipeline; + nvrhi::BindingLayoutHandle grassLayout; + nvrhi::GraphicsPipelineHandle billboardGrassPipeline; + nvrhi::BindingLayoutHandle billboardGrassLayout; + nvrhi::InputLayoutHandle grassInputLayout; + nvrhi::ShaderHandle grassVs; + nvrhi::ShaderHandle grassPs; + nvrhi::ShaderHandle billboardGrassVs; + nvrhi::ShaderHandle billboardGrassPs; + nvrhi::BufferHandle cascadeCB; + nvrhi::BufferHandle grassCB; + xray::render::fg::TextureHandle shadowHandle; + nvrhi::ITexture* shadowMap = nullptr; + Fmatrix clipVP; + Fmatrix sampleVP; + u32 resolution = 0; + bool initialized = false; + bool enabled = false; +}; + +struct GrassShadowOutputs +{ + framegraph::VirtualResourceHandle shadowMap; + nvrhi::ITexture* shadowTex = nullptr; + Fmatrix sampleVP; + bool valid = false; +}; + +void InitializeGrassShadowPass(fg::RenderDevice* device, GrassShadowPassState& state); +void ShutdownGrassShadowPass(fg::RenderDevice* device, GrassShadowPassState& state); + +GrassShadowOutputs setupGrassShadowPass( + framegraph::FrameGraph& fg, + fg::RenderDevice* device, + fg::FGDetailManager* detailManager, + const Fvector& sunDirection, + GrassShadowPassState& state, + framegraph::VirtualResourceHandle orderAfter = {}, + framegraph::VirtualResourceHandle cullArgs = {}); + +} diff --git a/src/Layers/xrRender/FrameGraphPasses/SkinningPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/SkinningPassSetup.cpp index 672da1687b6..960fd62419e 100644 --- a/src/Layers/xrRender/FrameGraphPasses/SkinningPassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/SkinningPassSetup.cpp @@ -27,7 +27,9 @@ #include "Layers/xrRender/Decals/OverlayManager.h" #include "PassCommon.h" #include "Layers/xrRender/ClusteredLightManager.h" +#include "Layers/xrRender/RayTracing/ReSTIRMemoryManager.h" #include "xrCore/FMesh.hpp" +#include "xrEngine/IGame_Persistent.h" extern ENGINE_API float psHUD_FOV; @@ -64,8 +66,11 @@ static Fmatrix ApplyHUDFOVAdjustment(const Fmatrix& worldMatrix) void InitializeSkinningResources(fg::RenderDevice* device, const nvrhi::FramebufferInfoEx& fbInfo, SkinningPassState& state) { - if (state.initialized) + constexpr u32 kSkinningPipeVersion = 7; + if (state.initialized && state.pipeVersion == kSkinningPipeVersion) return; + state.initialized = false; + state.pipeVersion = 0; nvrhi::IDevice* nvDevice = device->GetNVRHIDevice(); if (!nvDevice) @@ -79,6 +84,11 @@ void InitializeSkinningResources(fg::RenderDevice* device, const nvrhi::Framebuf nvrhi::IBindingLayout* bindlessLayout = backend ? backend->GetBindlessLayout() : nullptr; auto& cache = framegraph::GetPassResourceCache(); + nvrhi::FramebufferInfoEx worldFbInfo = fbInfo; + if (worldFbInfo.colorFormats.size() < 4) + worldFbInfo.colorFormats.push_back(nvrhi::Format::RGBA32_FLOAT); + nvrhi::FramebufferInfoEx hudFbInfo = worldFbInfo; + auto skinnedPsResult = shaderLoader->LoadPixelShader("bindless_skinned", "main"); if (!skinnedPsResult.handle) { Msg("! [SkinningPass] Failed to load pixel shader"); @@ -92,7 +102,7 @@ void InitializeSkinningResources(fg::RenderDevice* device, const nvrhi::Framebuf auto hudPsResult = shaderLoader->LoadPixelShader("bindless_skinned_hud", "main"); if (hudPsResult.handle) { state.hudPS = hudPsResult.handle; - state.hudLayout = cache.GetOrCreateBindingLayoutFromReflection("SkinningPass_HUD", *skinnedVsForReflection.reflection, *hudPsResult.reflection, nvDevice); + state.hudLayout = cache.GetOrCreateBindingLayoutFromReflection("SkinningPass_HUD6", *skinnedVsForReflection.reflection, *hudPsResult.reflection, nvDevice); } if (!state.hudLayout) state.hudLayout = state.layout; @@ -123,7 +133,7 @@ void InitializeSkinningResources(fg::RenderDevice* device, const nvrhi::Framebuf variant.vs = vsResult.handle; variant.inputLayout = nvDevice->createInputLayout(attribs, attrCount, variant.vs); auto pipeDesc = buildPipelineDesc(variant.vs, variant.inputLayout); - variant.pipeline = cache.GetOrCreatePipeline(cacheName, pipeDesc, fbInfo, nvDevice); + variant.pipeline = cache.GetOrCreatePipeline(cacheName, pipeDesc, worldFbInfo, nvDevice); if (variant.pipeline) QueryBindingLayoutFromPipeline(variant.pipeline, state.layout); }; @@ -135,7 +145,7 @@ void InitializeSkinningResources(fg::RenderDevice* device, const nvrhi::Framebuf hudVariant.inputLayout = worldVariant.inputLayout; auto pipeDesc = buildPipelineDesc(worldVariant.vs, worldVariant.inputLayout, state.hudPS); pipeDesc.bindingLayouts[0] = state.hudLayout; - hudVariant.pipeline = cache.GetOrCreatePipeline(cacheName, pipeDesc, fbInfo, nvDevice); + hudVariant.pipeline = cache.GetOrCreatePipeline(cacheName, pipeDesc, hudFbInfo, nvDevice); }; auto mdiPsResult = shaderLoader->LoadPixelShader("bindless_skinned_mdi", "main"); @@ -165,7 +175,7 @@ void InitializeSkinningResources(fg::RenderDevice* device, const nvrhi::Framebuf variant.inputLayout = nvDevice->createInputLayout(attribs, baseAttrCount + 1, variant.vs); auto pipeDesc = buildPipelineDesc(variant.vs, variant.inputLayout, state.mdiPS); pipeDesc.bindingLayouts[0] = state.mdiLayout; - variant.pipeline = cache.GetOrCreatePipeline(cacheName, pipeDesc, fbInfo, nvDevice); + variant.pipeline = cache.GetOrCreatePipeline(cacheName, pipeDesc, worldFbInfo, nvDevice); }; { @@ -177,8 +187,8 @@ void InitializeSkinningResources(fg::RenderDevice* device, const nvrhi::Framebuf nvrhi::VertexAttributeDesc().setName("BINORMAL").setFormat(nvrhi::Format::BGRA8_UNORM).setOffset(16).setElementStride(stride), nvrhi::VertexAttributeDesc().setName("TEXCOORD").setFormat(nvrhi::Format::RG16_SNORM).setOffset(20).setElementStride(stride), }; - initVariant(state.nonHQ, "bindless_skinned", "SkinningPass_nonHQ", attribs, 5); - initMDIVariant(state.mdiNonHQ, "bindless_skinned_mdi", "SkinningPass_mdi_nonHQ", attribs, 5); + initVariant(state.nonHQ, "bindless_skinned", "SkinningPass_nonHQ_v6", attribs, 5); + initMDIVariant(state.mdiNonHQ, "bindless_skinned_mdi", "SkinningPass_mdi_nonHQ_v6", attribs, 5); } { @@ -190,8 +200,8 @@ void InitializeSkinningResources(fg::RenderDevice* device, const nvrhi::Framebuf nvrhi::VertexAttributeDesc().setName("BINORMAL").setFormat(nvrhi::Format::BGRA8_UNORM).setOffset(24).setElementStride(stride), nvrhi::VertexAttributeDesc().setName("TEXCOORD").setFormat(nvrhi::Format::RG32_FLOAT).setOffset(28).setElementStride(stride), }; - initVariant(state.hq1w, "bindless_skinned_hq", "SkinningPass_hq1w", attribs, 5); - initMDIVariant(state.mdiHQ1w, "bindless_skinned_hq_mdi", "SkinningPass_mdi_hq1w", attribs, 5); + initVariant(state.hq1w, "bindless_skinned_hq", "SkinningPass_hq1w_v6", attribs, 5); + initMDIVariant(state.mdiHQ1w, "bindless_skinned_hq_mdi", "SkinningPass_mdi_hq1w_v6", attribs, 5); } { @@ -204,8 +214,8 @@ void InitializeSkinningResources(fg::RenderDevice* device, const nvrhi::Framebuf nvrhi::VertexAttributeDesc().setName("TEXCOORD").setFormat(nvrhi::Format::RG32_FLOAT).setOffset(28).setElementStride(stride), nvrhi::VertexAttributeDesc().setName("BLENDINDICES").setFormat(nvrhi::Format::BGRA8_UNORM).setOffset(36).setElementStride(stride), }; - initVariant(state.hq4w, "bindless_skinned_4w", "SkinningPass_hq4w", attribs, 6); - initMDIVariant(state.mdiHQ4w, "bindless_skinned_4w_mdi", "SkinningPass_mdi_hq4w", attribs, 6); + initVariant(state.hq4w, "bindless_skinned_4w", "SkinningPass_hq4w_v6", attribs, 6); + initMDIVariant(state.mdiHQ4w, "bindless_skinned_4w_mdi", "SkinningPass_mdi_hq4w_v6", attribs, 6); } { @@ -217,8 +227,8 @@ void InitializeSkinningResources(fg::RenderDevice* device, const nvrhi::Framebuf nvrhi::VertexAttributeDesc().setName("BINORMAL").setFormat(nvrhi::Format::BGRA8_UNORM).setOffset(24).setElementStride(stride), nvrhi::VertexAttributeDesc().setName("TEXCOORD").setFormat(nvrhi::Format::RGBA32_FLOAT).setOffset(28).setElementStride(stride), }; - initVariant(state.hq2w, "bindless_skinned_2w", "SkinningPass_hq2w", attribs, 5); - initMDIVariant(state.mdiHQ2w, "bindless_skinned_2w_mdi", "SkinningPass_mdi_hq2w", attribs, 5); + initVariant(state.hq2w, "bindless_skinned_2w", "SkinningPass_hq2w_v6", attribs, 5); + initMDIVariant(state.mdiHQ2w, "bindless_skinned_2w_mdi", "SkinningPass_mdi_hq2w_v6", attribs, 5); } { @@ -230,18 +240,37 @@ void InitializeSkinningResources(fg::RenderDevice* device, const nvrhi::Framebuf nvrhi::VertexAttributeDesc().setName("BINORMAL").setFormat(nvrhi::Format::BGRA8_UNORM).setOffset(24).setElementStride(stride), nvrhi::VertexAttributeDesc().setName("TEXCOORD").setFormat(nvrhi::Format::RGBA32_FLOAT).setOffset(28).setElementStride(stride), }; - initVariant(state.hq3w, "bindless_skinned_3w", "SkinningPass_hq3w", attribs, 5); - initMDIVariant(state.mdiHQ3w, "bindless_skinned_3w_mdi", "SkinningPass_mdi_hq3w", attribs, 5); + initVariant(state.hq3w, "bindless_skinned_3w", "SkinningPass_hq3w_v6", attribs, 5); + initMDIVariant(state.mdiHQ3w, "bindless_skinned_3w_mdi", "SkinningPass_mdi_hq3w_v6", attribs, 5); } - initHudVariant(state.hudNonHQ, state.nonHQ, "SkinningPass_hud_nonHQ"); - initHudVariant(state.hudHQ1w, state.hq1w, "SkinningPass_hud_hq1w"); - initHudVariant(state.hudHQ2w, state.hq2w, "SkinningPass_hud_hq2w"); - initHudVariant(state.hudHQ3w, state.hq3w, "SkinningPass_hud_hq3w"); - initHudVariant(state.hudHQ4w, state.hq4w, "SkinningPass_hud_hq4w"); + initHudVariant(state.hudNonHQ, state.nonHQ, "SkinningPass_hud6_nonHQ_v7"); + initHudVariant(state.hudHQ1w, state.hq1w, "SkinningPass_hud6_hq1w_v7"); + initHudVariant(state.hudHQ2w, state.hq2w, "SkinningPass_hud6_hq2w_v7"); + initHudVariant(state.hudHQ3w, state.hq3w, "SkinningPass_hud6_hq3w_v7"); + initHudVariant(state.hudHQ4w, state.hq4w, "SkinningPass_hud6_hq4w_v7"); + + if (!state.scopeDummy) + { + nvrhi::TextureDesc td; + td.width = 1; + td.height = 1; + td.format = nvrhi::Format::RGBA8_UNORM; + td.debugName = "Skinning_ScopeDummy"; + td.initialState = nvrhi::ResourceStates::ShaderResource; + td.keepInitialState = true; + state.scopeDummy = nvDevice->createTexture(td); + nvrhi::CommandListHandle uploadCmd = nvDevice->createCommandList(); + uploadCmd->open(); + u32 black = 0; + uploadCmd->writeTexture(state.scopeDummy, 0, 0, &black, sizeof(black)); + uploadCmd->close(); + nvDevice->executeCommandList(uploadCmd); + } state.initialized = true; - Msg("* [SkinningPass] Pipeline initialization complete"); + state.pipeVersion = kSkinningPipeVersion; + Msg("* [SkinningPass] Pipeline initialization complete v7"); } // ═══════════════════════════════════════════════════════════════════════════ @@ -410,7 +439,11 @@ static SkinnedPhaseContext BuildSkinnedPhaseContext( nvrhi::IBuffer* splatBuffer, const nvrhi::Viewport& viewport, const nvrhi::Rect& scissor, - bool isHUD) + bool isHUD, + fg::RenderDevice* device = nullptr, + nvrhi::ITexture* sceneDepth = nullptr, + nvrhi::IBuffer* hudScopeCB = nullptr, + nvrhi::ITexture* scopeColor = nullptr) { using namespace fg; using namespace fg::bindless; @@ -447,6 +480,15 @@ static SkinnedPhaseContext BuildSkinnedPhaseContext( bsb.BufferSRV("g_LightData", ClusteredLightManager::Instance().GetLightDataBuffer()); bsb.BufferSRV("g_ClusterGrid", ClusteredLightManager::Instance().GetClusterGridBuffer()); bsb.BufferSRV("g_LightIndexList", ClusteredLightManager::Instance().GetLightIndexListBuffer()); + BindEnvIblCubes(bsb, device); + if (isHUD) + { + if (hudScopeCB) + bsb.ConstantBuffer("HudScopeCB", hudScopeCB); + nvrhi::ITexture* vp2 = scopeColor ? scopeColor : state.scopeDummy.Get(); + if (vp2) + bsb.Texture("s_vp2", vp2); + } ctx.bindingSet = cache.GetOrCreateBindingSet(bsb.Build(), activeLayout, nvDevice); return ctx; @@ -479,6 +521,7 @@ static void DrawSkinnedBatch( matIdData.skeletonBoneOffset = skeletonBoneOffset; matIdData.splatOffset = splatRange.offset; matIdData.splatCount = splatRange.count; + matIdData.hudLit = ctx.isHUD ? 1u : 0u; cmdList->writeBuffer(ctx.materialIdCB, &matIdData, sizeof(matIdData)); u32 variantIdx = MaterialBuffer::Instance().GetShaderVariant(batch.bindlessMaterialID); @@ -492,15 +535,17 @@ static void DrawSkinnedBatch( for (u32 p = 0; p < passCount; p++) { nvrhi::IGraphicsPipeline* pipeline; - if (variant) { + if (ctx.isHUD) { + pipeline = SelectHUDSkinnedPipeline(state, batch.vertexStride, batch.skinningRenderMode); + } else if (variant) { u32 fmt = GetSkinnedVertexFormatID(batch.skinningRenderMode, batch.vertexStride); pipeline = VariantPSOCache::Instance().GetOrCreatePSO( nvDevice, ctx.framebuffer, variantIdx, *variant, p, fmt, - GetSkinnedInputLayout(state, fmt), state.layout, ctx.bindlessLayout); + GetSkinnedInputLayout(state, fmt), + state.layout, + ctx.bindlessLayout); } else { - pipeline = ctx.isHUD - ? SelectHUDSkinnedPipeline(state, batch.vertexStride, batch.skinningRenderMode) - : SelectSkinnedPipeline(state, batch.vertexStride, batch.skinningRenderMode); + pipeline = SelectSkinnedPipeline(state, batch.vertexStride, batch.skinningRenderMode); } if (!pipeline) continue; @@ -555,12 +600,15 @@ framegraph::DefaultOutputLayout setupSkinningPass( fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); fbInfo.colorFormats.push_back(nvrhi::Format::RGBA8_UNORM); + fbInfo.colorFormats.push_back(nvrhi::Format::RGBA32_FLOAT); fbInfo.depthFormat = nvrhi::Format::D32; InitializeSkinningResources(device, fbInfo, *state); } + const char* passName = (!geometry && hudBatches) ? "Skinning HUD Pass" : "Skinning Pass"; + auto& passData = fg.addCallbackPass( - "Skinning Pass", + passName, // ═══════════════════════════════════════════════════════ // SETUP LAMBDA @@ -585,11 +633,30 @@ framegraph::DefaultOutputLayout setupSkinningPass( data.normal = passBuilder.readWrite(inputs.normal, ResourceState::RenderTarget); if (inputs.baseColor.is_valid()) data.baseColor = passBuilder.readWrite(inputs.baseColor, ResourceState::RenderTarget); + if (inputs.worldPos.is_valid()) + { + data.worldPos = passBuilder.readWrite(inputs.worldPos, ResourceState::RenderTarget); + data.clearWorldPos = false; + } + else + { + ResourceDesc wpDesc; + wpDesc.type = ResourceDesc::Type::Texture2D; + wpDesc.width = width; + wpDesc.height = height; + wpDesc.format = nvrhi::Format::RGBA32_FLOAT; + wpDesc.isRenderTarget = true; + wpDesc.isTransient = true; + wpDesc.debugName = "rt_SkinnedWorldPos"; + data.worldPos = passBuilder.createTexture("rt_SkinnedWorldPos", wpDesc); + data.clearWorldPos = true; + } data.depth = passBuilder.readWrite(inputs.depth, ResourceState::DepthStencilWrite); data.outputs.albedo = data.color; data.outputs.normal = data.normal; data.outputs.baseColor = data.baseColor; + data.outputs.worldPos = data.worldPos; data.outputs.depth = data.depth; }, @@ -622,6 +689,7 @@ framegraph::DefaultOutputLayout setupSkinningPass( auto* colorRT = fg.GetPhysicalTexture(data.color); auto* normalRT = fg.GetPhysicalTexture(data.normal); auto* baseColorRT = data.baseColor.is_valid() ? fg.GetPhysicalTexture(data.baseColor) : nullptr; + auto* worldPosRT = data.worldPos.is_valid() ? fg.GetPhysicalTexture(data.worldPos) : nullptr; auto* depthRT = fg.GetPhysicalTexture(data.depth); if (!colorRT || !depthRT) return; @@ -631,14 +699,20 @@ framegraph::DefaultOutputLayout setupSkinningPass( if (!nvDevice || !cmdList) return; + if (worldPosRT && data.clearWorldPos) + cmdList->clearTextureFloat(worldPosRT, nvrhi::AllSubresources, nvrhi::Color(0.f, 0.f, 0.f, 0.f)); + nvrhi::FramebufferDesc fbDesc; fbDesc.addColorAttachment(colorRT); if (normalRT) fbDesc.addColorAttachment(normalRT); if (baseColorRT) fbDesc.addColorAttachment(baseColorRT); + if (worldPosRT) + fbDesc.addColorAttachment(worldPosRT); fbDesc.setDepthAttachment(depthRT); - auto framebuffer = framegraph::GetPassResourceCache().GetOrCreateFramebuffer("SkinningPass", fbDesc, nvDevice); + const char* fbKey = worldPosRT ? "SkinningPassWP_v6" : "SkinningPass"; + auto framebuffer = framegraph::GetPassResourceCache().GetOrCreateFramebuffer(fbKey, fbDesc, nvDevice); if (!framebuffer) return; @@ -661,7 +735,7 @@ framegraph::DefaultOutputLayout setupSkinningPass( auto dynTransformsCB = cache.GetOrCreateVolatileCB("SkinningPass", "DynTransforms", sizeof(DynamicTransforms), data.device, 1024 * 8); auto staticGlobalsCB = cache.GetOrCreateVolatileCB("Frame", "StaticGlobals", sizeof(StaticGlobals), data.device); auto shaderParamsCB = cache.GetOrCreateVolatileCB("SkinningPass", "ShaderParams", sizeof(ShaderParams), data.device, 512); - auto materialIdCB = cache.GetOrCreateVolatileCB("SkinningPass", "MaterialId", sizeof(SkinnedMaterialCB), data.device, 1024 * 8); + auto materialIdCB = cache.GetOrCreateVolatileCB("SkinningPass", "MaterialId_v2", sizeof(SkinnedMaterialCB), data.device, 1024 * 8); ShaderParams shaderParams = {}; shaderParams.m_AlphaRef = 0.5f; @@ -749,6 +823,7 @@ framegraph::DefaultOutputLayout setupSkinningPass( bsb.BufferSRV("g_LightData", ClusteredLightManager::Instance().GetLightDataBuffer()); bsb.BufferSRV("g_ClusterGrid", ClusteredLightManager::Instance().GetClusterGridBuffer()); bsb.BufferSRV("g_LightIndexList", ClusteredLightManager::Instance().GetLightIndexListBuffer()); + BindEnvIblCubes(bsb, data.device); auto& cache = framegraph::GetPassResourceCache(); nvrhi::BindingSetHandle mdiBindingSet = cache.GetOrCreateBindingSet(bsb.Build(), data.passState->mdiLayout, nvDevice); @@ -801,17 +876,109 @@ framegraph::DefaultOutputLayout setupSkinningPass( // PHASE 2: HUD SKINNED MESHES (depth [0.9, 1.0]) // ═══════════════════════════════════════════════════════ if (hasHUDSkinned) { + static u32 s_hudLitLogFrames = 0; + if (s_hudLitLogFrames < 3) { + ++s_hudLitLogFrames; + Msg("* [HUD] skinned batches=%u lights=%u", + (u32)data.hudBatches->size(), + ClusteredLightManager::Instance().GetLightCount()); + } nvrhi::Viewport hudViewport( 0.0f, static_cast(rtDesc.width), 0.0f, static_cast(rtDesc.height), - 0.9f, 1.0f + 0.0f, 1.0f ); + nvrhi::IFramebuffer* hudFramebuffer = framebuffer; + if (worldPosRT) { + nvrhi::FramebufferDesc hudFbDesc; + hudFbDesc.addColorAttachment(colorRT); + if (normalRT) + hudFbDesc.addColorAttachment(normalRT); + if (baseColorRT) + hudFbDesc.addColorAttachment(baseColorRT); + hudFbDesc.addColorAttachment(worldPosRT); + hudFbDesc.setDepthAttachment(depthRT); + hudFramebuffer = framegraph::GetPassResourceCache().GetOrCreateFramebuffer( + "SkinningPassHUD", hudFbDesc, nvDevice); + if (!hudFramebuffer) + hudFramebuffer = framebuffer; + } + + auto hudGlobalsCB = cache.GetOrCreateVolatileCB( + "SkinningPass", "StaticGlobals_HUD", sizeof(StaticGlobals), data.device); + { + StaticGlobals hudSg = BuildStaticGlobals(); + auto& clm = ClusteredLightManager::Instance(); + if (clm.IsReady()) + { + if (clm.GetLightCount() > 0) + clm.Upload(cmdList); + float zNear = VIEWPORT_NEAR; + float zFar = g_pGamePersistent + ? g_pGamePersistent->Environment().CurrentEnv.far_plane + : 500.f; + auto ccb = clm.BuildClusterCB(rtDesc.width, rtDesc.height, zNear, zFar); + hudSg.cluster_params.set( + ccb.gridDims.x, ccb.gridDims.y, ccb.gridDims.z, + (float)clm.GetLightCount()); + hudSg.cluster_scales.set( + ccb.depthParams.x, ccb.depthParams.y, ccb.depthParams.z, ccb.depthParams.w); + } + if (((ps_r_rt_gi != 0) && g_restirReplaceForward) || (ps_r_path_tracer != 0)) + hudSg.parallax.w = -1.0f; + else + hudSg.parallax.w = 0.0f; + hudSg.pos_decompression_params2.set( + (float)rtDesc.width, (float)rtDesc.height, + 1.0f / (float)rtDesc.width, 1.0f / (float)rtDesc.height); + cmdList->writeBuffer(hudGlobalsCB, &hudSg, sizeof(hudSg)); + } + + nvrhi::ITexture* scopeSrc = colorRT; + if (scopeSrc) + { + const auto& cd = scopeSrc->getDesc(); + if (!data.passState->scopeCopy || + data.passState->scopeCopy->getDesc().width != cd.width || + data.passState->scopeCopy->getDesc().height != cd.height) + { + nvrhi::TextureDesc td = cd; + td.debugName = "Skinning_ScopeCopy"; + td.isRenderTarget = false; + td.isShaderResource = true; + td.initialState = nvrhi::ResourceStates::ShaderResource; + td.keepInitialState = true; + data.passState->scopeCopy = nvDevice->createTexture(td); + } + if (data.passState->scopeCopy) + { + cmdList->setTextureState(scopeSrc, nvrhi::AllSubresources, nvrhi::ResourceStates::CopySource); + cmdList->setTextureState(data.passState->scopeCopy, nvrhi::AllSubresources, nvrhi::ResourceStates::CopyDest); + cmdList->copyTexture(data.passState->scopeCopy, nvrhi::TextureSlice(), scopeSrc, nvrhi::TextureSlice()); + cmdList->setTextureState(data.passState->scopeCopy, nvrhi::AllSubresources, nvrhi::ResourceStates::ShaderResource); + cmdList->setTextureState(scopeSrc, nvrhi::AllSubresources, nvrhi::ResourceStates::RenderTarget); + } + } + + auto hudScopeCB = cache.GetOrCreateVolatileCB( + "SkinningPass", "HudScopeCB", 32, data.device); + { + Fvector4 params, zoom; + params.set(0, 0, 0, 0); + zoom.set(0, 0, 0, 0); + if (g_pGamePersistent && g_pGamePersistent->m_pGShaderConstants) + params = g_pGamePersistent->m_pGShaderConstants->hud_params; + struct HudScopeCB { Fvector4 hud; Fvector4 zoom; } cb{params, zoom}; + cmdList->writeBuffer(hudScopeCB, &cb, sizeof(cb)); + } + SkinnedPhaseContext hudCtx = BuildSkinnedPhaseContext( - *data.passState, nvDevice, framebuffer, - dynTransformsCB, staticGlobalsCB, materialIdCB, + *data.passState, nvDevice, hudFramebuffer, + dynTransformsCB, hudGlobalsCB, materialIdCB, globalBoneBuffer, bindlessTable, bindlessLayout, splatBuffer, - hudViewport, scissor, true); + hudViewport, scissor, true, data.device, depthRT, + hudScopeCB, data.passState->scopeCopy.Get()); for (const auto& batch : *data.hudBatches) { Fmatrix adjustedWorldMatrix = ApplyHUDFOVAdjustment(batch.worldMatrix); @@ -829,6 +996,7 @@ framegraph::DefaultOutputLayout setupSkinningPass( outputs.albedo = passData.color; outputs.normal = passData.normal; outputs.baseColor = passData.baseColor; + outputs.worldPos = passData.worldPos; outputs.depth = passData.depth; return outputs; } diff --git a/src/Layers/xrRender/FrameGraphPasses/SkinningPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/SkinningPassSetup.h index cb015077795..3075598c854 100644 --- a/src/Layers/xrRender/FrameGraphPasses/SkinningPassSetup.h +++ b/src/Layers/xrRender/FrameGraphPasses/SkinningPassSetup.h @@ -66,7 +66,10 @@ struct SkinningPassState { SkinningPipelineVariant hudHQ3w; SkinningPipelineVariant hudHQ4w; nvrhi::SamplerHandle linearSampler; + nvrhi::TextureHandle scopeDummy; + nvrhi::TextureHandle scopeCopy; bool initialized = false; + u32 pipeVersion = 0; }; void InitializeSkinningResources(fg::RenderDevice* device, const nvrhi::FramebufferInfoEx& fbInfo, SkinningPassState& state); @@ -75,6 +78,7 @@ struct SkinningPassData { framegraph::VirtualResourceHandle color; framegraph::VirtualResourceHandle normal; framegraph::VirtualResourceHandle baseColor; + framegraph::VirtualResourceHandle worldPos; framegraph::VirtualResourceHandle depth; framegraph::VirtualResourceHandle skinnedDrawArgs; fg::RenderDevice* device; @@ -86,6 +90,7 @@ struct SkinningPassData { framegraph::DefaultOutputLayout outputs; SkinningPassState* passState; decals::OverlayManager* overlayMgr; + bool clearWorldPos = false; }; // Main skinning pass setup function diff --git a/src/Layers/xrRender/FrameGraphPasses/SkyPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/SkyPassSetup.cpp index 9767de3d8e6..0e03ae0235d 100644 --- a/src/Layers/xrRender/FrameGraphPasses/SkyPassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/SkyPassSetup.cpp @@ -13,19 +13,21 @@ framegraph::VirtualResourceHandle setupSkyPass( framegraph::VirtualResourceHandle depthInput, FGEnvironmentRender* renderer, u32 width, - u32 height) + u32 height, + bool composite) { using namespace framegraph; auto& passData = fg.addCallbackPass( - "Sky", - [colorInput, depthInput, renderer, width, height](FrameGraph& builder, PassHandle passHandle, SkyPassData& data) { + composite ? "SkyComposite" : "Sky", + [colorInput, depthInput, renderer, width, height, composite](FrameGraph& builder, PassHandle passHandle, SkyPassData& data) { RenderPassBuilder passBuilder(builder, passHandle); data.renderer = renderer; data.width = width; data.height = height; + data.composite = composite; data.colorOutput = passBuilder.write(colorInput, ResourceState::RenderTarget); - data.depthOutput = passBuilder.read(depthInput, ResourceState::DepthStencilRead); + data.depthOutput = passBuilder.read(depthInput, ResourceState::ShaderResource); }, [](const SkyPassData& data, const FrameGraph& fg, fg::RenderContext* ctx) { if (!data.renderer) return; @@ -41,7 +43,12 @@ framegraph::VirtualResourceHandle setupSkyPass( CEnvironment* environment = g_pGamePersistent ? &g_pGamePersistent->Environment() : nullptr; if (!environment) return; - data.renderer->DrawSky(cmdList, framebuffer, environment, data.width, data.height); + nvrhi::ITexture* depthRT = nullptr; + if (data.depthOutput.is_valid()) + depthRT = fg.GetPhysicalTexture(data.depthOutput); + + data.renderer->DrawSky(cmdList, framebuffer, environment, data.width, data.height, depthRT, data.composite); + data.renderer->DrawClouds(cmdList, framebuffer, environment, data.width, data.height); }); return passData.colorOutput; diff --git a/src/Layers/xrRender/FrameGraphPasses/SkyPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/SkyPassSetup.h index 514de5b6e31..1a74cd2bf0f 100644 --- a/src/Layers/xrRender/FrameGraphPasses/SkyPassSetup.h +++ b/src/Layers/xrRender/FrameGraphPasses/SkyPassSetup.h @@ -19,6 +19,7 @@ struct SkyPassData { FGEnvironmentRender* renderer; u32 width; u32 height; + bool composite = false; }; framegraph::VirtualResourceHandle setupSkyPass( @@ -27,7 +28,8 @@ framegraph::VirtualResourceHandle setupSkyPass( framegraph::VirtualResourceHandle depthInput, FGEnvironmentRender* renderer, u32 width, - u32 height + u32 height, + bool composite = false ); } // namespace xray::render::fg::passes diff --git a/src/Layers/xrRender/FrameGraphPasses/TAAPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/TAAPassSetup.cpp new file mode 100644 index 00000000000..ed9839b6d3c --- /dev/null +++ b/src/Layers/xrRender/FrameGraphPasses/TAAPassSetup.cpp @@ -0,0 +1,359 @@ +#include "stdafx.h" +#include "TAAPassSetup.h" +#include "ShaderConstants.h" +#include "Layers/xrRender/FrameGraph/FrameGraph.h" +#include "Layers/xrRender/FrameGraph/PassResourceCache.h" +#include "Layers/xrRender/FrameGraph/BindingSetBuilder.h" +#include "Layers/xrRender/FrameGraph/RenderPassBuilder.h" +#include "Layers/xrRender/FrameGraph/ShaderLoader.h" +#include "Layers/xrRender/RenderContext/RenderContext.h" +#include "Layers/xrRender/RenderContext/RenderDevice.h" + +extern ENGINE_API float ps_r_taa_sharpness; +extern ENGINE_API int ps_r_taa; +extern ENGINE_API int ps_r_taa_jitter; +extern ENGINE_API int ps_r_upscale; + +namespace xray::render::fg::passes +{ +using namespace framegraph; + +float g_taa_jitter_px = 0.f; +float g_taa_jitter_py = 0.f; +float g_taa_jitter_prev_px = 0.f; +float g_taa_jitter_prev_py = 0.f; +Fmatrix g_taa_unjittered_full_transform; +Fmatrix g_taa_unjittered_inv_full_transform; + +static float Halton(u32 index, u32 base) +{ + float f = 1.f; + float r = 0.f; + while (index > 0) + { + f /= float(base); + r += f * float(index % base); + index /= base; + } + return r; +} + +void ApplyTAAJitter() +{ + g_taa_unjittered_full_transform = Device.mFullTransform; + g_taa_unjittered_inv_full_transform.invert_44(g_taa_unjittered_full_transform); + + g_taa_jitter_prev_px = g_taa_jitter_px; + g_taa_jitter_prev_py = g_taa_jitter_py; + + const bool wantJitter = (ps_r_taa_jitter != 0) && (ps_r_taa != 0 || ps_r_upscale != 0); + if (!wantJitter) + { + g_taa_jitter_px = 0.f; + g_taa_jitter_py = 0.f; + return; + } + + const u32 phase = Device.dwFrame % 16u; + const float jx = Halton(phase + 1, 2) - 0.5f; + const float jy = Halton(phase + 1, 3) - 0.5f; + g_taa_jitter_px = jx; + g_taa_jitter_py = jy; + + const float w = float(std::max(1u, GetRenderWidth())); + const float h = float(std::max(1u, GetRenderHeight())); + Fmatrix jitterMat; + jitterMat.identity(); + jitterMat._31 = (jx * 2.f) / w; + jitterMat._32 = (jy * 2.f) / h; + Device.mFullTransform.mul(jitterMat, g_taa_unjittered_full_transform); + Device.mInvFullTransform.invert_44(Device.mFullTransform); +} + +namespace +{ +void EnsureHistory(nvrhi::IDevice* nv, TAAPassState& state, u32 w, u32 h) +{ + if (state.history[0] && state.historyW == w && state.historyH == h) + return; + for (int i = 0; i < 2; ++i) + { + nvrhi::TextureDesc td; + td.width = w; + td.height = h; + td.format = nvrhi::Format::RGBA16_FLOAT; + td.isRenderTarget = true; + td.isShaderResource = true; + td.initialState = nvrhi::ResourceStates::ShaderResource; + td.keepInitialState = true; + td.debugName = (i == 0) ? "rt_TAA_History0" : "rt_TAA_History1"; + state.history[i] = nv->createTexture(td); + } + state.historyW = w; + state.historyH = h; + state.hasHistory = false; + state.historyIndex = 0; + + // Clear so first temporal frame never blends garbage (nested-frame look) + if (state.history[0] && state.history[1]) + { + nvrhi::CommandListHandle cmd = nv->createCommandList(); + cmd->open(); + cmd->clearTextureFloat(state.history[0], nvrhi::AllSubresources, nvrhi::Color(0.f)); + cmd->clearTextureFloat(state.history[1], nvrhi::AllSubresources, nvrhi::Color(0.f)); + cmd->close(); + nv->executeCommandList(cmd); + } +} + +constexpr u32 kTaaPipeVersion = 7; + +void InitializeTAA(nvrhi::IDevice* device, TAAPassState& state) +{ + if (state.initialized && state.pipeVersion == kTaaPipeVersion && state.pipeline && state.layout) + return; + state.initialized = false; + state.pipeVersion = 0; + state.pipeline = nullptr; + state.layout = nullptr; + if (!device) + return; + auto* loader = GEnv.Render->GetShaderLoader(); + if (!loader) + { + state.initialized = true; + return; + } + auto vs = loader->LoadVertexShader("fullscreen"); + auto ps = loader->LoadPixelShader("taa"); + if (!vs.handle || !ps.handle) + { + state.initialized = true; + return; + } + auto& cache = GetPassResourceCache(); + state.layout = cache.GetOrCreateBindingLayoutFromReflection( + "TAA_v7", *vs.reflection, *ps.reflection, device); + if (state.layout) + { + nvrhi::GraphicsPipelineDesc desc; + desc.setVertexShader(vs.handle); + desc.setPixelShader(ps.handle); + desc.addBindingLayout(state.layout); + desc.setPrimType(nvrhi::PrimitiveType::TriangleList); + desc.renderState.blendState.targets[0].setBlendEnable(false); + desc.renderState.depthStencilState.setDepthTestEnable(false); + desc.renderState.depthStencilState.setDepthWriteEnable(false); + desc.renderState.rasterState.setCullMode(nvrhi::RasterCullMode::None); + nvrhi::FramebufferInfoEx fb; + fb.addColorFormat(nvrhi::Format::RGBA16_FLOAT); + state.pipeline = cache.GetOrCreatePipeline("TAA_v7", desc, fb, device); + } + state.pipeVersion = kTaaPipeVersion; + state.initialized = true; +} +} // namespace + +void ShutdownTAAPass(TAAPassState& state) +{ + state.pipeline = nullptr; + state.layout = nullptr; + state.history[0] = nullptr; + state.history[1] = nullptr; + state.hasHistory = false; + state.initialized = false; + state.pipeVersion = 0; +} + +framegraph::VirtualResourceHandle setupTAAPass( + FrameGraph& fg, + fg::RenderDevice* device, + VirtualResourceHandle sceneColor, + VirtualResourceHandle depth, + VirtualResourceHandle motionVectors, + u32 width, + u32 height, + bool hasPrevFrame, + TAAPassState& state, + VirtualResourceHandle worldPos) +{ + if (!device || !device->GetNVRHIDevice()) + return sceneColor; + + InitializeTAA(device->GetNVRHIDevice(), state); + if (!state.pipeline || !state.layout) + return sceneColor; + + EnsureHistory(device->GetNVRHIDevice(), state, width, height); + if (!state.history[0] || !state.history[1]) + return sceneColor; + + { + const Fvector camPos = Device.vCameraPosition; + const Fvector camDir = Device.vCameraDirection; + if (state.hasCameraHistory) + { + const float posDelta = camPos.distance_to(state.prevCameraPos); + const float dirDot = std::clamp(camDir.dotproduct(state.prevCameraDir), -1.f, 1.f); + if (posDelta > 8.f || dirDot < 0.7f) + state.hasHistory = false; + } + state.prevCameraPos = camPos; + state.prevCameraDir = camDir; + state.hasCameraHistory = true; + } + + const u32 readIdx = state.historyIndex; + const u32 writeIdx = 1u - readIdx; + + ResourceDesc outDesc; + outDesc.type = ResourceDesc::Type::Texture2D; + outDesc.width = width; + outDesc.height = height; + outDesc.format = nvrhi::Format::RGBA16_FLOAT; + outDesc.isRenderTarget = true; + outDesc.isTransient = true; + outDesc.debugName = "rt_TAA"; + auto output = fg.CreateTexture("rt_TAA", outDesc); + + ResourceDesc histDesc; + histDesc.type = ResourceDesc::Type::Texture2D; + histDesc.width = width; + histDesc.height = height; + histDesc.format = nvrhi::Format::RGBA16_FLOAT; + histDesc.isImported = true; + histDesc.debugName = "rt_TAA_HistoryRead"; + auto historyRead = fg.ImportTexture("rt_TAA_HistoryRead", state.history[readIdx].Get(), histDesc); + + const bool useTemporal = motionVectors.is_valid() && hasPrevFrame && state.hasHistory; + + struct PassData + { + VirtualResourceHandle current, history, motion, depth, worldPos, output; + TAAPassState* passState = nullptr; + fg::RenderDevice* device = nullptr; + u32 width = 0, height = 0; + u32 writeIdx = 0; + bool useTemporal = false; + }; + + auto& passData = fg.addCallbackPass( + "TAA", + [&, output, historyRead, useTemporal, writeIdx, worldPos](FrameGraph& b, PassHandle ph, PassData& data) { + RenderPassBuilder pb(b, ph); + data.passState = &state; + data.device = device; + data.width = width; + data.height = height; + data.writeIdx = writeIdx; + data.useTemporal = useTemporal; + data.current = pb.read(sceneColor, ResourceState::ShaderResource); + data.history = pb.read(historyRead, ResourceState::ShaderResource); + data.depth = pb.read(depth, ResourceState::ShaderResource); + if (worldPos.is_valid()) + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); + if (useTemporal) + data.motion = pb.read(motionVectors, ResourceState::ShaderResource); + data.output = pb.write(output, ResourceState::RenderTarget); + pb.sideEffects(); + }, + [](const PassData& data, const FrameGraph& graph, fg::RenderContext* ctx) { + auto* cmd = ctx->GetCommandList(); + auto* nv = cmd ? cmd->getDevice() : nullptr; + auto* cur = graph.GetPhysicalTexture(data.current); + auto* hist = graph.GetPhysicalTexture(data.history); + auto* depthTex = graph.GetPhysicalTexture(data.depth); + auto* out = graph.GetPhysicalTexture(data.output); + if (!cmd || !nv || !cur || !hist || !depthTex || !out || !data.passState) + return; + + TAAPassState& st = *data.passState; + auto& cache = GetPassResourceCache(); + auto* loader = GEnv.Render->GetShaderLoader(); + auto* vsR = loader->GetCachedReflection("fullscreen", ".vs"); + auto* psR = loader->GetCachedReflection("taa", ".ps"); + if (!vsR || !psR || !st.pipeline || !st.layout) + return; + + nvrhi::ITexture* motionTex = nullptr; + if (data.useTemporal && data.motion.is_valid()) + motionTex = graph.GetPhysicalTexture(data.motion); + if (!motionTex) + { + // Zero motion → treat as first frame (history unused effectively via high blend) + static nvrhi::TextureHandle s_zeroMotion; + if (!s_zeroMotion) + { + nvrhi::TextureDesc td; + td.width = 1; + td.height = 1; + td.format = nvrhi::Format::RG16_FLOAT; + td.initialState = nvrhi::ResourceStates::ShaderResource; + td.keepInitialState = true; + td.debugName = "DummyZeroMotion"; + s_zeroMotion = nv->createTexture(td); + float zeros[2] = {0.f, 0.f}; + cmd->writeTexture(s_zeroMotion, 0, 0, zeros, sizeof(zeros)); + } + motionTex = s_zeroMotion; + } + + auto* cb = cache.GetOrCreateVolatileCB("TAA", "TAAParams_v3", sizeof(TAAParamsCB), data.device); + TAAParamsCB params{}; + params.screenSizeX = float(data.width); + params.screenSizeY = float(data.height); + params.blendAlpha = data.useTemporal ? 0.38f : 1.0f; + params.sharpness = ps_r_taa_sharpness; + params.jitterX = g_taa_jitter_px; + params.jitterY = g_taa_jitter_py; + params.prevJitterX = g_taa_jitter_prev_px; + params.prevJitterY = g_taa_jitter_prev_py; + if (cb) + cmd->writeBuffer(cb, ¶ms, sizeof(params)); + + BindingSetBuilder bsb(*vsR, *psR, nv, "TAA"); + if (cb) + bsb.ConstantBuffer("TAAParams", cb); + nvrhi::ITexture* worldPosTex = nullptr; + if (data.worldPos.is_valid()) + worldPosTex = graph.GetPhysicalTexture(data.worldPos); + if (!worldPosTex) + worldPosTex = cache.GetDummyContactHistory(nv); + + bsb.Texture("g_Current", cur) + .Texture("g_History", hist) + .Texture("g_Motion", motionTex) + .Texture("g_Depth", depthTex) + .Texture("g_WorldPos", worldPosTex); + auto set = cache.GetOrCreateBindingSet(bsb.Build(), st.layout, nv); + if (!set) + return; + + nvrhi::FramebufferDesc fbDesc; + fbDesc.addColorAttachment(out); + auto fb = cache.GetOrCreateFramebuffer("TAA", fbDesc, nv); + + nvrhi::GraphicsState gs; + gs.pipeline = st.pipeline; + gs.framebuffer = fb; + gs.bindings = {set}; + gs.viewport.addViewportAndScissorRect( + nvrhi::Viewport(float(data.width), float(data.height))); + cmd->setGraphicsState(gs); + cmd->draw(nvrhi::DrawArguments().setVertexCount(3)); + + // Persist resolved frame into history[writeIdx] + if (st.history[data.writeIdx]) + { + cmd->copyTexture( + st.history[data.writeIdx], nvrhi::TextureSlice(), + out, nvrhi::TextureSlice()); + st.historyIndex = data.writeIdx; + st.hasHistory = true; + } + }); + + return passData.output; +} + +} // namespace xray::render::fg::passes diff --git a/src/Layers/xrRender/FrameGraphPasses/TAAPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/TAAPassSetup.h new file mode 100644 index 00000000000..9fb5b2d8bc4 --- /dev/null +++ b/src/Layers/xrRender/FrameGraphPasses/TAAPassSetup.h @@ -0,0 +1,64 @@ +#pragma once + +#include "Layers/xrRender/FrameGraph/FGTypes.h" +#include "Layers/xrRender/FrameGraph/FGResource.h" +#include + +namespace xray::render::framegraph { class FrameGraph; } +namespace xray::render::fg { class RenderDevice; } + +namespace xray::render::fg::passes +{ + +struct TAAPassState +{ + nvrhi::GraphicsPipelineHandle pipeline; + nvrhi::BindingLayoutHandle layout; + nvrhi::TextureHandle history[2]; + u32 historyW = 0; + u32 historyH = 0; + u32 historyIndex = 0; + bool hasHistory = false; + bool initialized = false; + u32 pipeVersion = 0; + Fvector prevCameraPos = {0, 0, 0}; + Fvector prevCameraDir = {0, 0, 1}; + bool hasCameraHistory = false; +}; + +struct alignas(16) TAAParamsCB +{ + float screenSizeX, screenSizeY; + float blendAlpha; + float sharpness; + float jitterX, jitterY; // current frame Halton offset in pixels [-0.5, +0.5] + float prevJitterX, prevJitterY; // previous frame (for history reprojection) +}; + +// Pixel-space Halton jitter (filled by ApplyTAAJitter) +extern float g_taa_jitter_px; +extern float g_taa_jitter_py; +extern float g_taa_jitter_prev_px; +extern float g_taa_jitter_prev_py; + +// View-proj without subpixel jitter — use for motion vectors / prev-frame storage +extern Fmatrix g_taa_unjittered_full_transform; +extern Fmatrix g_taa_unjittered_inv_full_transform; + +void ApplyTAAJitter(); + +framegraph::VirtualResourceHandle setupTAAPass( + framegraph::FrameGraph& fg, + fg::RenderDevice* device, + framegraph::VirtualResourceHandle sceneColor, + framegraph::VirtualResourceHandle depth, + framegraph::VirtualResourceHandle motionVectors, + u32 width, + u32 height, + bool hasPrevFrame, + TAAPassState& state, + framegraph::VirtualResourceHandle worldPos = {}); + +void ShutdownTAAPass(TAAPassState& state); + +} // namespace xray::render::fg::passes diff --git a/src/Layers/xrRender/FrameGraphPasses/ThunderboltPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/ThunderboltPassSetup.cpp index 582a37b3fe6..da8aad530fd 100644 --- a/src/Layers/xrRender/FrameGraphPasses/ThunderboltPassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/ThunderboltPassSetup.cpp @@ -33,6 +33,10 @@ framegraph::VirtualResourceHandle setupThunderboltPass(framegraph::FrameGraph& f if (!cmdList || !outputRT) return; + if (depth && + (depth->getDesc().width != outputRT->getDesc().width || + depth->getDesc().height != outputRT->getDesc().height)) + return; nvrhi::FramebufferDesc fbDesc; fbDesc.addColorAttachment(outputRT); if (depth) diff --git a/src/Layers/xrRender/FrameGraphPasses/TonemapPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/TonemapPassSetup.cpp index 3a85e7dfe6c..6c24726b039 100644 --- a/src/Layers/xrRender/FrameGraphPasses/TonemapPassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/TonemapPassSetup.cpp @@ -1,4 +1,3 @@ -// xrRender/FrameGraphPasses/TonemapPassSetup.cpp #include "stdafx.h" #include "TonemapPassSetup.h" #include "ExposurePassSetup.h" @@ -10,6 +9,29 @@ #include "Layers/xrRender/RenderContext/RenderContext.h" #include "Layers/xrRender/FrameGraph/ShaderLoader.h" #include "Layers/xrRender/RenderContext/RenderDevice.h" +#include "Layers/xrRender/xrRender_console.h" +#include "xrEngine/IGame_Persistent.h" +#include "xrEngine/device.h" +#include +#include +#include +#include + +extern ENGINE_API int ps_r_hdr10; +extern ENGINE_API float ps_r_hdr10_hud; +extern ENGINE_API float ps_r_hdr10_paper_white; +extern ENGINE_API float ps_r_hdr10_peak; +extern ENGINE_API int ps_r_hdr_debug; +extern ENGINE_API float ps_r_hdr_exposure_bias; +extern ENGINE_API float ps_r_hdr_contrast; +extern ENGINE_API float ps_r_hdr_saturation; +extern ENGINE_API float ps_r_hdr_white; +extern ENGINE_API float ps_r_hdr_lift; +extern ENGINE_API float ps_r_hdr_gamma; +extern ENGINE_API float ps_r_hdr_gain; +extern ENGINE_API float ps_r_hdr_temp; +extern ENGINE_API float ps_r_hdr_tint; +extern ENGINE_API float ps_r_hdr_bloom; namespace xray::render::fg { extern xray::render::FrameGraphRenderer RImplementation; @@ -17,34 +39,89 @@ namespace xray::render::fg { namespace xray::render::fg::passes { -void InitializeTonemapPass(nvrhi::IDevice* device, TonemapPassState& state) { - if (state.initialized || !device) return; +namespace { +constexpr u32 kTonemapPipeVersion = 32; + +struct TonemapHDRCB { + float hdr10, paperWhite, peakNits, hudNits; + Fvector4 dofParams; + Fvector4 dofMblur; + Fvector4 eyePos; + Fmatrix invVP; + Fmatrix prevVP; + float exposureBias, contrast, saturation, whitePoint; + float lift, gamma, gain, bloomScale; + float temp, tint, pad0, pad1; + Fvector4 padGrade; +}; +static_assert(sizeof(TonemapHDRCB) == 256); + +struct BloomCB { + float srcW, srcH, dstW, dstH; + float threshold, intensity, dirX, dirY; +}; +static_assert(sizeof(BloomCB) == 32); +} - nvrhi::TextureDesc texDesc; - texDesc.debugName = "FallbackExposure"; - texDesc.width = 1; - texDesc.height = 1; - texDesc.format = nvrhi::Format::R32_FLOAT; - texDesc.initialState = nvrhi::ResourceStates::ShaderResource; - texDesc.keepInitialState = true; +void InitializeTonemapPass(nvrhi::IDevice* device, TonemapPassState& state) { + const u32 hdr10 = (GEnv.Backend && GEnv.Backend->IsHdr10()) ? 1u : 0u; + if (state.initialized && state.pipeVersion == kTonemapPipeVersion && state.pipeline && state.hdr10 == hdr10) + return; - state.fallbackExposureTexture = device->createTexture(texDesc); + state.initialized = false; + state.pipeVersion = 0; + state.pipeline = nullptr; + state.bindingLayout = nullptr; - nvrhi::CommandListHandle cmdList = device->createCommandList(); - cmdList->open(); - float defaultExposure = 1.0f; - cmdList->writeTexture(state.fallbackExposureTexture, 0, 0, &defaultExposure, sizeof(float)); - cmdList->close(); - device->executeCommandList(cmdList); + if (!device) + return; + + if (!state.fallbackExposureTexture) + { + nvrhi::TextureDesc texDesc; + texDesc.debugName = "FallbackExposure"; + texDesc.width = 1; + texDesc.height = 1; + texDesc.format = nvrhi::Format::R32_FLOAT; + texDesc.initialState = nvrhi::ResourceStates::ShaderResource; + texDesc.keepInitialState = true; + + state.fallbackExposureTexture = device->createTexture(texDesc); + nvrhi::TextureDesc depthFb; + depthFb.debugName = "FallbackTonemapDepth"; + depthFb.width = 1; + depthFb.height = 1; + depthFb.format = nvrhi::Format::R32_FLOAT; + depthFb.initialState = nvrhi::ResourceStates::ShaderResource; + depthFb.keepInitialState = true; + state.fallbackDepthTexture = device->createTexture(depthFb); + nvrhi::TextureDesc bloomFb; + bloomFb.debugName = "FallbackBloom"; + bloomFb.width = 1; + bloomFb.height = 1; + bloomFb.format = nvrhi::Format::RGBA16_FLOAT; + bloomFb.initialState = nvrhi::ResourceStates::ShaderResource; + bloomFb.keepInitialState = true; + state.fallbackBloom = device->createTexture(bloomFb); + + nvrhi::CommandListHandle cmdList = device->createCommandList(); + cmdList->open(); + float defaultExposure = 1.0f; + cmdList->writeTexture(state.fallbackExposureTexture, 0, 0, &defaultExposure, sizeof(float)); + float defaultDepth = 1.0f; + cmdList->writeTexture(state.fallbackDepthTexture, 0, 0, &defaultDepth, sizeof(float)); + cmdList->close(); + device->executeCommandList(cmdList); + } if (GEnv.Render->GetShaderLoader()) { + framegraph::BindingSetBuilder::InvalidateReflectionCache(); + auto& cache = framegraph::GetPassResourceCache(); auto vsResult = GEnv.Render->GetShaderLoader()->LoadVertexShader("tonemap"); auto psResult = GEnv.Render->GetShaderLoader()->LoadPixelShader("tonemap"); if (vsResult.handle && psResult.handle) { - auto& cache = framegraph::GetPassResourceCache(); - state.bindingLayout = cache.GetOrCreateBindingLayoutFromReflection( - "TonemapPass", *vsResult.reflection, *psResult.reflection, device); + "TonemapPass_CoP_v29", *vsResult.reflection, *psResult.reflection, device); if (state.bindingLayout) { nvrhi::GraphicsPipelineDesc pipeDesc; @@ -59,23 +136,95 @@ void InitializeTonemapPass(nvrhi::IDevice* device, TonemapPassState& state) { nvrhi::FramebufferInfoEx fbInfo; nvrhi::Format fbFmt = nvrhi::Format::RGBA8_UNORM; - if (GEnv.Backend && GEnv.Backend->GetBackBuffer()) + if (hdr10) + fbFmt = nvrhi::Format::RGBA16_FLOAT; + else if (GEnv.Backend && GEnv.Backend->GetBackBuffer()) fbFmt = GEnv.Backend->GetBackBuffer()->getDesc().format; fbInfo.addColorFormat(fbFmt); - state.pipeline = cache.GetOrCreatePipeline("TonemapPass", pipeDesc, fbInfo, device); + state.pipeline = cache.GetOrCreatePipeline("TonemapPass_CoP_v29", pipeDesc, fbInfo, device); + } + } + + auto extract = GEnv.Render->GetShaderLoader()->LoadComputeShader("bloom_extract"); + auto blur = GEnv.Render->GetShaderLoader()->LoadComputeShader("bloom_blur"); + if (extract.handle && extract.reflection) { + state.bloomExtractLayout = cache.GetOrCreateBindingLayoutFromReflection( + "BloomExtract_v9", *extract.reflection, device); + nvrhi::ComputePipelineDesc desc; + desc.CS = extract.handle; + desc.bindingLayouts = { state.bloomExtractLayout }; + state.bloomExtractPipeline = device->createComputePipeline(desc); + } + if (blur.handle && blur.reflection) { + state.bloomBlurLayout = cache.GetOrCreateBindingLayoutFromReflection( + "BloomBlur_v2", *blur.reflection, device); + nvrhi::ComputePipelineDesc desc; + desc.CS = blur.handle; + desc.bindingLayouts = { state.bloomBlurLayout }; + state.bloomBlurPipeline = device->createComputePipeline(desc); + } + nvrhi::BufferDesc cbDesc; + cbDesc.byteSize = sizeof(BloomCB); + cbDesc.isConstantBuffer = true; + cbDesc.isVolatile = true; + cbDesc.maxVersions = 16; + cbDesc.debugName = "BloomCB"; + state.bloomCB = device->createBuffer(cbDesc); + cbDesc.byteSize = sizeof(TonemapHDRCB); + cbDesc.debugName = "TonemapHDRCB"; + state.hdrCB = device->createBuffer(cbDesc); + + auto encVs = GEnv.Render->GetShaderLoader()->LoadVertexShader("tonemap"); + auto encPs = GEnv.Render->GetShaderLoader()->LoadPixelShader("hdr10_encode"); + if (encVs.handle && encPs.handle) { + state.encodeLayout = cache.GetOrCreateBindingLayoutFromReflection( + "Hdr10Encode_v4", *encVs.reflection, *encPs.reflection, device); + if (state.encodeLayout) { + nvrhi::GraphicsPipelineDesc encDesc; + encDesc.setVertexShader(encVs.handle); + encDesc.setPixelShader(encPs.handle); + encDesc.addBindingLayout(state.encodeLayout); + encDesc.setPrimType(nvrhi::PrimitiveType::TriangleList); + encDesc.renderState.blendState.targets[0].setBlendEnable(false); + encDesc.renderState.depthStencilState.setDepthTestEnable(false); + encDesc.renderState.depthStencilState.setDepthWriteEnable(false); + encDesc.renderState.rasterState.setCullMode(nvrhi::RasterCullMode::None); + nvrhi::FramebufferInfoEx encFb; + nvrhi::Format encFmt = nvrhi::Format::R10G10B10A2_UNORM; + if (GEnv.Backend && GEnv.Backend->GetBackBuffer()) + encFmt = GEnv.Backend->GetBackBuffer()->getDesc().format; + encFb.addColorFormat(encFmt); + state.encodePipeline = cache.GetOrCreatePipeline("Hdr10Encode_v4", encDesc, encFb, device); } } } state.initialized = true; + state.pipeVersion = kTonemapPipeVersion; + state.hdr10 = hdr10; } void ShutdownTonemapPass(TonemapPassState& state) { state.fallbackExposureTexture = nullptr; + state.fallbackDepthTexture = nullptr; + state.fallbackBloom = nullptr; + state.bloom0 = nullptr; + state.bloom1 = nullptr; state.pipeline = nullptr; state.bindingLayout = nullptr; + state.bloomExtractPipeline = nullptr; + state.bloomExtractLayout = nullptr; + state.bloomBlurPipeline = nullptr; + state.bloomBlurLayout = nullptr; + state.bloomCB = nullptr; + state.hdrCB = nullptr; + state.encodePipeline = nullptr; + state.encodeLayout = nullptr; + state.bloomW = 0; + state.bloomH = 0; state.initialized = false; + state.pipeVersion = 0; } framegraph::VirtualResourceHandle setupTonemapPass( @@ -87,7 +236,9 @@ framegraph::VirtualResourceHandle setupTonemapPass( u32 width, u32 height, TonemapPassState& tonemapState, - const ExposurePassState* exposureState) + const ExposurePassState* exposureState, + framegraph::VirtualResourceHandle depthTexture, + framegraph::VirtualResourceHandle worldPosTexture) { using namespace framegraph; @@ -96,33 +247,48 @@ framegraph::VirtualResourceHandle setupTonemapPass( bool hasExposure = exposureTexture.is_valid(); bool hasOutputTarget = outputTarget.is_valid(); + bool hasDepth = depthTexture.is_valid(); + bool hasWorldPos = worldPosTexture.is_valid(); auto& passData = fg.addCallbackPass( "Tonemap", - [hdrInput, exposureTexture, outputTarget, hasExposure, hasOutputTarget, width, height, &tonemapState, exposureState](FrameGraph& builder, PassHandle passHandle, TonemapPassData& data) { + [hdrInput, exposureTexture, outputTarget, depthTexture, worldPosTexture, hasExposure, hasOutputTarget, hasDepth, hasWorldPos, width, height, &tonemapState, exposureState, device](FrameGraph& builder, PassHandle passHandle, TonemapPassData& data) { RenderPassBuilder passBuilder(builder, passHandle); data.width = width; data.height = height; data.hasExposure = hasExposure; + data.hasDepth = hasDepth; + data.hasWorldPos = hasWorldPos; data.passState = &tonemapState; data.exposurePassState = exposureState; + data.device = device; data.hdrInput = passBuilder.read(hdrInput, ResourceState::ShaderResource); if (hasExposure) { data.exposureInput = passBuilder.read(exposureTexture, ResourceState::ShaderResource); } + if (hasDepth) { + data.depthInput = passBuilder.read(depthTexture, ResourceState::ShaderResource); + } + if (hasWorldPos) { + data.worldPosInput = passBuilder.read(worldPosTexture, ResourceState::ShaderResource); + } if (hasOutputTarget) { data.ldrOutput = passBuilder.write(outputTarget, ResourceState::RenderTarget); } else { + nvrhi::Format ldrFmt = nvrhi::Format::RGBA8_UNORM; + if (GEnv.Backend && GEnv.Backend->GetBackBuffer()) + ldrFmt = GEnv.Backend->GetBackBuffer()->getDesc().format; + framegraph::ResourceDesc ldrDesc; ldrDesc.type = framegraph::ResourceDesc::Type::Texture2D; ldrDesc.width = width; ldrDesc.height = height; - ldrDesc.format = nvrhi::Format::RGBA8_UNORM; + ldrDesc.format = ldrFmt; ldrDesc.isRenderTarget = true; ldrDesc.isTransient = false; ldrDesc.debugName = "rt_Final"; @@ -147,22 +313,166 @@ framegraph::VirtualResourceHandle setupTonemapPass( if (!hdrTexture || !ldrTexture) return; + nvrhi::ITexture* exposureTex = ps->fallbackExposureTexture; + if (data.hasExposure && data.exposureInput.is_valid()) + { + if (auto* e = fg.GetPhysicalTexture(data.exposureInput)) + exposureTex = e; + } + if (!exposureTex && data.exposurePassState) + exposureTex = GetExposureTexture(*data.exposurePassState); + if (!exposureTex) + exposureTex = ps->fallbackExposureTexture; + if (!exposureTex) + return; + auto& cache = framegraph::GetPassResourceCache(); + nvrhi::ITexture* depthTex = ps->fallbackDepthTexture; + if (data.hasDepth && data.depthInput.is_valid()) + { + if (auto* d = fg.GetPhysicalTexture(data.depthInput)) + depthTex = d; + } + if (!depthTex) + depthTex = ps->fallbackDepthTexture; + + nvrhi::ITexture* bloomTex = ps->fallbackBloom; + const u32 bw = std::max(1u, data.width / 4); + const u32 bh = std::max(1u, data.height / 4); + if (ps->bloomExtractPipeline && ps->bloomBlurPipeline && ps->bloomCB) { + if (!ps->bloom0 || ps->bloomW != bw || ps->bloomH != bh) { + nvrhi::TextureDesc td; + td.debugName = "Bloom0"; + td.width = bw; + td.height = bh; + td.format = nvrhi::Format::RGBA16_FLOAT; + td.isUAV = true; + td.initialState = nvrhi::ResourceStates::UnorderedAccess; + td.keepInitialState = true; + ps->bloom0 = device->createTexture(td); + td.debugName = "Bloom1"; + ps->bloom1 = device->createTexture(td); + ps->bloomW = bw; + ps->bloomH = bh; + } + if (ps->bloom0 && ps->bloom1) { + BloomCB cb{}; + cb.srcW = float(data.width); + cb.srcH = float(data.height); + cb.dstW = float(bw); + cb.dstH = float(bh); + cb.threshold = ps_r2_ls_bloom_threshold; + cb.intensity = ps_r2_ls_bloom_kernel_scale * std::max(ps_r2_ls_bloom_kernel_b, 0.25f); + cb.dirX = 0.f; + cb.dirY = 0.f; + cmdList->writeBuffer(ps->bloomCB, &cb, sizeof(cb)); + + auto* exRefl = GEnv.Render->GetShaderLoader()->GetCachedReflection("bloom_extract", ".cs"); + if (exRefl) { + framegraph::BindingSetBuilder bsb(*exRefl, device, "Bloom.Extract"); + bsb.ConstantBuffer("BloomParams", ps->bloomCB); + bsb.Texture("t_Hdr", hdrTexture); + bsb.Texture("t_exposure", exposureTex); + if (depthTex) + bsb.TextureSlot(2, depthTex); + nvrhi::ITexture* worldPosTex = nullptr; + if (data.hasWorldPos && data.worldPosInput.is_valid()) + worldPosTex = fg.GetPhysicalTexture(data.worldPosInput); + if (!worldPosTex) + worldPosTex = cache.GetDummyContactHistory(device); + if (worldPosTex) + bsb.Texture("t_WorldPos", worldPosTex); + bsb.TextureUAV("u_Bloom", ps->bloom0); + auto bs = cache.GetOrCreateBindingSet(bsb.Build(), ps->bloomExtractLayout, device); + if (bs) { + nvrhi::ComputeState cs; + cs.pipeline = ps->bloomExtractPipeline; + cs.bindings = { bs }; + cmdList->setComputeState(cs); + cmdList->dispatch((bw + 7) / 8, (bh + 7) / 8, 1); + } + } + + auto dispatchBlur = [&](nvrhi::ITexture* src, nvrhi::ITexture* dst, float dx, float dy) { + BloomCB bcb = cb; + bcb.srcW = float(bw); + bcb.srcH = float(bh); + bcb.intensity = ps_r2_ls_bloom_kernel_scale; + bcb.dirX = dx; + bcb.dirY = dy; + cmdList->writeBuffer(ps->bloomCB, &bcb, sizeof(bcb)); + auto* blRefl = GEnv.Render->GetShaderLoader()->GetCachedReflection("bloom_blur", ".cs"); + if (!blRefl) + return; + framegraph::BindingSetBuilder bsb(*blRefl, device, "Bloom.Blur"); + bsb.ConstantBuffer("BloomParams", ps->bloomCB); + bsb.Texture("t_In", src); + bsb.TextureUAV("u_Out", dst); + auto bs = cache.GetOrCreateBindingSet(bsb.Build(), ps->bloomBlurLayout, device); + if (!bs) + return; + nvrhi::ComputeState cs; + cs.pipeline = ps->bloomBlurPipeline; + cs.bindings = { bs }; + cmdList->setComputeState(cs); + cmdList->dispatch((bw + 7) / 8, (bh + 7) / 8, 1); + }; + dispatchBlur(ps->bloom0, ps->bloom1, 1.f, 0.f); + dispatchBlur(ps->bloom1, ps->bloom0, 0.f, 1.f); + bloomTex = ps->bloom0; + } + } + auto* vsRefl = GEnv.Render->GetShaderLoader()->GetCachedReflection("tonemap", ".vs"); auto* psRefl = GEnv.Render->GetShaderLoader()->GetCachedReflection("tonemap", ".ps"); if (!vsRefl || !psRefl) return; + if (ps->hdrCB) { + TonemapHDRCB hcb{}; + hcb.hdr10 = (GEnv.Backend && GEnv.Backend->IsHdr10()) ? 1.f : 0.f; + hcb.paperWhite = ps_r_hdr10_paper_white; + hcb.peakNits = ps_r_hdr10_peak; + hcb.hudNits = ps_r_hdr10_hud; + Fvector3 dof = ps_r2_dof; + if (g_pGamePersistent) + g_pGamePersistent->GetCurrentDof(dof); + hcb.dofParams.set(dof.x, dof.y, dof.z, ps_r2_dof_sky); + const bool dofOn = RImplementation.o.advancedpp && ps_r2_ls_flags.test(R2FLAG_DOF); + const bool mblurOn = ps_r2_mblur > 0.001f; + hcb.dofMblur.set(ps_r2_dof_kernel_size, ps_r2_mblur, dofOn ? 1.f : 0.f, mblurOn ? 1.f : 0.f); + hcb.eyePos.set(Device.vCameraPosition.x, Device.vCameraPosition.y, Device.vCameraPosition.z, 0.f); + hcb.invVP = Device.mInvFullTransform; + hcb.prevVP = Device.mFullTransformSaved; + hcb.exposureBias = ps_r_hdr_exposure_bias; + hcb.contrast = ps_r_hdr_contrast; + hcb.saturation = ps_r_hdr_saturation; + hcb.whitePoint = ps_r_hdr_white; + hcb.lift = ps_r_hdr_lift; + hcb.gamma = ps_r_hdr_gamma; + hcb.gain = ps_r_hdr_gain; + hcb.bloomScale = ps_r_hdr_bloom; + hcb.temp = ps_r_hdr_temp; + hcb.tint = ps_r_hdr_tint; + cmdList->writeBuffer(ps->hdrCB, &hcb, sizeof(hcb)); + } + framegraph::BindingSetBuilder bsb(*vsRefl, *psRefl, device, "Tonemap"); bsb.Texture("t_hdr", hdrTexture); + bsb.Texture("t_exposure", exposureTex); + bsb.Texture("t_bloom", bloomTex); + if (depthTex) + bsb.TextureSlot(3, depthTex); + if (ps->hdrCB) + bsb.ConstantBuffer("TonemapHDR", ps->hdrCB); auto bindingSet = cache.GetOrCreateBindingSet(bsb.Build(), ps->bindingLayout, device); if (!bindingSet) return; nvrhi::FramebufferDesc fbDesc; fbDesc.addColorAttachment(ldrTexture); - auto framebuffer = cache.GetOrCreateFramebuffer("TonemapPass", fbDesc, device); + auto framebuffer = cache.GetOrCreateFramebuffer("TonemapPass_CoP", fbDesc, device); nvrhi::Viewport viewport; viewport.minX = 0; @@ -186,4 +496,182 @@ framegraph::VirtualResourceHandle setupTonemapPass( return passData.ldrOutput; } +framegraph::VirtualResourceHandle setupHdr10EncodePass( + framegraph::FrameGraph& fg, + fg::RenderDevice* device, + framegraph::VirtualResourceHandle src, + framegraph::VirtualResourceHandle dst, + u32 width, + u32 height, + TonemapPassState& tonemapState) +{ + using namespace framegraph; + if (device && device->GetNVRHIDevice()) + InitializeTonemapPass(device->GetNVRHIDevice(), tonemapState); + + struct EncodeData { + VirtualResourceHandle src; + VirtualResourceHandle dst; + TonemapPassState* state = nullptr; + u32 width = 0; + u32 height = 0; + }; + + auto& passData = fg.addCallbackPass( + "HDR10 Encode", + [src, dst, width, height, &tonemapState](FrameGraph& builder, PassHandle passHandle, EncodeData& data) { + RenderPassBuilder pb(builder, passHandle); + data.src = pb.read(src, ResourceState::ShaderResource); + data.dst = pb.write(dst, ResourceState::RenderTarget); + data.state = &tonemapState; + data.width = width; + data.height = height; + }, + [](const EncodeData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto* ps = data.state; + if (!ps || !ps->encodePipeline || !ps->encodeLayout || !ps->hdrCB) + return; + auto* srcTex = fgGraph.GetPhysicalTexture(data.src); + auto* dstTex = fgGraph.GetPhysicalTexture(data.dst); + if (!srcTex || !dstTex) + return; + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + nvrhi::IDevice* nv = cmd->getDevice(); + TonemapHDRCB hcb{}; + hcb.hdr10 = 1.f; + hcb.paperWhite = ps_r_hdr10_paper_white; + hcb.peakNits = ps_r_hdr10_peak; + hcb.hudNits = ps_r_hdr10_hud; + cmd->writeBuffer(ps->hdrCB, &hcb, sizeof(hcb)); + auto* vsRefl = GEnv.Render->GetShaderLoader()->GetCachedReflection("tonemap", ".vs"); + auto* psRefl = GEnv.Render->GetShaderLoader()->GetCachedReflection("hdr10_encode", ".ps"); + if (!vsRefl || !psRefl) + return; + auto& cache = framegraph::GetPassResourceCache(); + framegraph::BindingSetBuilder bsb(*vsRefl, *psRefl, nv, "Hdr10Encode"); + bsb.Texture("t_src", srcTex); + bsb.ConstantBuffer("TonemapHDR", ps->hdrCB); + auto bs = cache.GetOrCreateBindingSet(bsb.Build(), ps->encodeLayout, nv); + if (!bs) + return; + nvrhi::FramebufferDesc fbDesc; + fbDesc.addColorAttachment(dstTex); + auto fb = cache.GetOrCreateFramebuffer("Hdr10Encode", fbDesc, nv); + nvrhi::Viewport vp; + vp.maxX = float(data.width); + vp.maxY = float(data.height); + vp.maxZ = 1.f; + nvrhi::GraphicsState gs; + gs.pipeline = ps->encodePipeline; + gs.framebuffer = fb; + gs.viewport.addViewportAndScissorRect(vp); + gs.addBindingSet(bs); + cmd->setGraphicsState(gs); + cmd->draw(nvrhi::DrawArguments().setVertexCount(3)); + } + ); + return passData.dst; +} + +void RenderHdrDebugUI(const ExposurePassState* exposure) +{ + if (!ps_r_hdr_debug) + return; + if (!Device.GetImGuiContext()) + return; + ImGui::SetCurrentContext(Device.GetImGuiContext()); + ImGui::SetNextWindowSize(ImVec2(420, 620), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(20, 40), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("HDR Debug")) + { + ImGui::End(); + return; + } + + ImGui::SliderFloat("Exposure bias", &ps_r_hdr_exposure_bias, -4.f, 4.f, "%.2f"); + ImGui::SliderFloat("Contrast", &ps_r_hdr_contrast, 0.2f, 3.f, "%.2f"); + ImGui::SliderFloat("Saturation", &ps_r_hdr_saturation, 0.f, 3.f, "%.2f"); + ImGui::SliderFloat("White point", &ps_r_hdr_white, 0.4f, 8.f, "%.2f"); + ImGui::SliderFloat("Lift", &ps_r_hdr_lift, -0.5f, 0.5f, "%.3f"); + ImGui::SliderFloat("Gamma", &ps_r_hdr_gamma, 0.3f, 2.6f, "%.2f"); + ImGui::SliderFloat("Gain", &ps_r_hdr_gain, 0.2f, 3.f, "%.2f"); + ImGui::SliderFloat("Temperature", &ps_r_hdr_temp, -1.f, 1.f, "%.2f"); + ImGui::SliderFloat("Tint", &ps_r_hdr_tint, -1.f, 1.f, "%.2f"); + ImGui::SliderFloat("Bloom", &ps_r_hdr_bloom, 0.f, 4.f, "%.2f"); + ImGui::Separator(); + ImGui::SliderFloat("Paper white", &ps_r_hdr10_paper_white, 80.f, 1000.f, "%.0f"); + ImGui::SliderFloat("Peak nits", &ps_r_hdr10_peak, 200.f, 10000.f, "%.0f"); + ImGui::SliderFloat("HUD nits", &ps_r_hdr10_hud, 80.f, 1000.f, "%.0f"); + ImGui::SliderFloat("Middle gray", &ps_r2_tonemap_middlegray, 0.f, 2.f, "%.3f"); + ImGui::SliderFloat("Adapt", &ps_r2_tonemap_adaptation, 0.01f, 10.f, "%.2f"); + ImGui::SliderFloat("Low lum", &ps_r2_tonemap_low_lum, 0.0001f, 1.f, "%.4f"); + ImGui::SliderFloat("TM amount", &ps_r2_tonemap_amount, 0.f, 1.f, "%.3f"); + ImGui::SliderFloat("Bloom thresh", &ps_r2_ls_bloom_threshold, 0.f, 1.f, "%.4f"); + ImGui::SliderFloat("Bloom scale", &ps_r2_ls_bloom_kernel_scale, 0.5f, 2.f, "%.2f"); + + if (ImGui::Button("Reset grade")) + { + ps_r_hdr_exposure_bias = 0.f; + ps_r_hdr_contrast = 1.f; + ps_r_hdr_saturation = 1.f; + ps_r_hdr_white = 1.7f; + ps_r_hdr_lift = 0.f; + ps_r_hdr_gamma = 1.f; + ps_r_hdr_gain = 1.f; + ps_r_hdr_temp = 0.f; + ps_r_hdr_tint = 0.f; + ps_r_hdr_bloom = 1.f; + } + ImGui::SameLine(); + if (ImGui::Button("Copy cvars")) + { + char buf[768]; + snprintf(buf, sizeof(buf), + "r_hdr_exposure_bias %.3f\n" + "r_hdr_contrast %.3f\n" + "r_hdr_saturation %.3f\n" + "r_hdr_white %.3f\n" + "r_hdr_lift %.3f\n" + "r_hdr_gamma %.3f\n" + "r_hdr_gain %.3f\n" + "r_hdr_temp %.3f\n" + "r_hdr_tint %.3f\n" + "r_hdr_bloom %.3f\n" + "r_hdr10_paper_white %.1f\n" + "r_hdr10_peak %.1f\n" + "r_hdr10_hud %.1f\n" + "r2_tonemap_middlegray %.3f\n" + "r2_tonemap_adaptation %.3f\n" + "r2_tonemap_lowlum %.4f\n" + "r2_tonemap_amount %.3f\n" + "r2_ls_bloom_threshold %.4f\n" + "r2_ls_bloom_kernel_scale %.3f\n", + ps_r_hdr_exposure_bias, ps_r_hdr_contrast, ps_r_hdr_saturation, ps_r_hdr_white, + ps_r_hdr_lift, ps_r_hdr_gamma, ps_r_hdr_gain, ps_r_hdr_temp, ps_r_hdr_tint, ps_r_hdr_bloom, + ps_r_hdr10_paper_white, ps_r_hdr10_peak, ps_r_hdr10_hud, + ps_r2_tonemap_middlegray, ps_r2_tonemap_adaptation, ps_r2_tonemap_low_lum, ps_r2_tonemap_amount, + ps_r2_ls_bloom_threshold, ps_r2_ls_bloom_kernel_scale); + ImGui::SetClipboardText(buf); + } + + if (exposure) + { + ImGui::Separator(); + ImGui::Text("Exposure: %.3f", exposure->currentExposure); + float hist[64]; + float peak = 1.f; + for (int i = 0; i < 64; i++) + { + hist[i] = (float)exposure->histBins[i]; + peak = std::max(peak, hist[i]); + } + for (int i = 0; i < 64; i++) + hist[i] = log2f(1.f + hist[i]) / log2f(1.f + peak); + ImGui::Text("Luminance histogram (log2, -10 .. +4)"); + ImGui::PlotHistogram("##hdrhist", hist, 64, 0, nullptr, 0.f, 1.f, ImVec2(-1, 120)); + } + + ImGui::End(); +} + } // namespace xray::render::fg::passes diff --git a/src/Layers/xrRender/FrameGraphPasses/TonemapPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/TonemapPassSetup.h index a9b662b666e..0a67e5af66e 100644 --- a/src/Layers/xrRender/FrameGraphPasses/TonemapPassSetup.h +++ b/src/Layers/xrRender/FrameGraphPasses/TonemapPassSetup.h @@ -1,4 +1,3 @@ -// xrRender/FrameGraphPasses/TonemapPassSetup.h #pragma once #include "Layers/xrRender/FrameGraph/FGTypes.h" @@ -19,27 +18,43 @@ struct ExposurePassState; struct TonemapPassState { nvrhi::TextureHandle fallbackExposureTexture; + nvrhi::TextureHandle fallbackDepthTexture; + nvrhi::TextureHandle fallbackBloom; + nvrhi::TextureHandle bloom0; + nvrhi::TextureHandle bloom1; nvrhi::GraphicsPipelineHandle pipeline; nvrhi::BindingLayoutHandle bindingLayout; + nvrhi::ComputePipelineHandle bloomExtractPipeline; + nvrhi::BindingLayoutHandle bloomExtractLayout; + nvrhi::ComputePipelineHandle bloomBlurPipeline; + nvrhi::BindingLayoutHandle bloomBlurLayout; + nvrhi::BufferHandle bloomCB; + nvrhi::BufferHandle hdrCB; + nvrhi::GraphicsPipelineHandle encodePipeline; + nvrhi::BindingLayoutHandle encodeLayout; + u32 bloomW = 0; + u32 bloomH = 0; bool initialized = false; + u32 pipeVersion = 0; + u32 hdr10 = 0; }; struct TonemapPassData { framegraph::VirtualResourceHandle hdrInput; framegraph::VirtualResourceHandle exposureInput; + framegraph::VirtualResourceHandle depthInput; + framegraph::VirtualResourceHandle worldPosInput; framegraph::VirtualResourceHandle ldrOutput; bool hasExposure; + bool hasDepth; + bool hasWorldPos; u32 width; u32 height; TonemapPassState* passState; const ExposurePassState* exposurePassState; + fg::RenderDevice* device = nullptr; }; -// Lambda-based tonemap pass setup -// Converts HDR scene color (RGBA16_FLOAT) to LDR output (RGBA8_UNORM) using ACES filmic tonemap -// Now accepts exposure texture from ExposurePass for auto-exposure -// If outputTarget is valid, writes directly to it (e.g., imported backbuffer) -// If outputTarget is invalid, creates internal rt_Final texture framegraph::VirtualResourceHandle setupTonemapPass( framegraph::FrameGraph& fg, fg::RenderDevice* device, @@ -49,10 +64,23 @@ framegraph::VirtualResourceHandle setupTonemapPass( u32 width, u32 height, TonemapPassState& state, - const ExposurePassState* exposureState = nullptr + const ExposurePassState* exposureState = nullptr, + framegraph::VirtualResourceHandle depthTexture = {}, + framegraph::VirtualResourceHandle worldPosTexture = {} ); void InitializeTonemapPass(nvrhi::IDevice* device, TonemapPassState& state); void ShutdownTonemapPass(TonemapPassState& state); +void RenderHdrDebugUI(const ExposurePassState* exposure); + +framegraph::VirtualResourceHandle setupHdr10EncodePass( + framegraph::FrameGraph& fg, + fg::RenderDevice* device, + framegraph::VirtualResourceHandle src, + framegraph::VirtualResourceHandle dst, + u32 width, + u32 height, + TonemapPassState& state +); } // namespace xray::render::fg::passes diff --git a/src/Layers/xrRender/FrameGraphPasses/TransparentPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/TransparentPassSetup.cpp index 182de8a4439..ff7d9112c81 100644 --- a/src/Layers/xrRender/FrameGraphPasses/TransparentPassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/TransparentPassSetup.cpp @@ -15,13 +15,30 @@ #include "Layers/xrRender/FrameGraph/BindingSetBuilder.h" #include "PassCommon.h" #include "Layers/xrRender/ClusteredLightManager.h" +#include "Layers/xrRender/ResourceManager/FGResourceManager.h" +#include "Layers/xrRender/ResourceManager/TextureManager.h" +#include "xrEngine/Environment.h" +#include "xrEngine/IGame_Persistent.h" +#include "Layers/xrRender/xrRender_console.h" +#include "Layers/xrRender/fgEnvironmentRender.h" namespace xray::render::fg::passes { void InitializeTransparentResources(fg::RenderDevice* device, const nvrhi::FramebufferInfoEx& fbInfo, TransparentPassState& state) { - if (state.initialized) + constexpr u32 kWaterVersion = 27; + if (state.initialized && state.waterVersion == kWaterVersion) return; + state.initialized = false; + state.waterVersion = kWaterVersion; + state.waterDistortPipeline = nullptr; + state.waterDistortPipeDescValid = false; + state.glassDistortPipeline = nullptr; + state.glassDistortPipeDescValid = false; + state.waterPipeline = nullptr; + state.depthCopyPipeline = nullptr; + state.depthCopyLayout = nullptr; + framegraph::BindingSetBuilder::InvalidateReflectionCache(); nvrhi::IDevice* nvDevice = device->GetNVRHIDevice(); if (!nvDevice) @@ -40,7 +57,7 @@ void InitializeTransparentResources(fg::RenderDevice* device, const nvrhi::Frame state.ps = psResult.handle; auto& cache = framegraph::GetPassResourceCache(); - state.layout = cache.GetOrCreateBindingLayoutFromReflection("TransparentPass", *vsResult.reflection, *psResult.reflection, nvDevice); + state.layout = cache.GetOrCreateBindingLayoutFromReflection("TransparentPass_v2_Water", *vsResult.reflection, *psResult.reflection, nvDevice); if (!state.layout) return; @@ -69,7 +86,7 @@ void InitializeTransparentResources(fg::RenderDevice* device, const nvrhi::Frame pipeDesc.renderState.depthStencilState.depthWriteEnable = false; pipeDesc.renderState.depthStencilState.depthFunc = nvrhi::ComparisonFunc::GreaterOrEqual; pipeDesc.renderState.rasterState.frontCounterClockwise = false; - pipeDesc.renderState.rasterState.cullMode = nvrhi::RasterCullMode::Back; + pipeDesc.renderState.rasterState.cullMode = nvrhi::RasterCullMode::None; auto& rt0 = pipeDesc.renderState.blendState.targets[0]; rt0.blendEnable = true; @@ -79,15 +96,173 @@ void InitializeTransparentResources(fg::RenderDevice* device, const nvrhi::Frame rt0.srcBlendAlpha = nvrhi::BlendFactor::One; rt0.destBlendAlpha = nvrhi::BlendFactor::InvSrcAlpha; rt0.blendOpAlpha = nvrhi::BlendOp::Add; + for (u32 rt = 1; rt < 4; ++rt) + pipeDesc.renderState.blendState.targets[rt].setColorWriteMask(nvrhi::ColorMask(0)); - state.pipeline = cache.GetOrCreatePipeline("TransparentPass", pipeDesc, fbInfo, nvDevice); + state.pipeline = cache.GetOrCreatePipeline("TransparentPass_v2_Water", pipeDesc, fbInfo, nvDevice); if (!state.pipeline) return; QueryBindingLayoutFromPipeline(state.pipeline, state.layout); + { + auto waterVs = shaderLoader->LoadVertexShader("water", "main"); + auto waterPs = shaderLoader->LoadPixelShader("water", "main"); + if (waterVs.handle && waterPs.handle && waterVs.reflection && waterPs.reflection) + { + state.waterVs = waterVs.handle; + state.waterPs = waterPs.handle; + state.waterLayout = cache.GetOrCreateBindingLayoutFromReflection( + "TransparentWater_v45_NoSSR", *waterVs.reflection, *waterPs.reflection, nvDevice); + if (state.waterLayout) + { + nvrhi::GraphicsPipelineDesc waterDesc = pipeDesc; + waterDesc.VS = state.waterVs; + waterDesc.PS = state.waterPs; + waterDesc.inputLayout = nvDevice->createInputLayout(attrs, attrCount, state.waterVs); + if (bindlessLayout) + waterDesc.bindingLayouts = {state.waterLayout, bindlessLayout}; + else + waterDesc.bindingLayouts = {state.waterLayout}; + waterDesc.renderState.rasterState.cullMode = nvrhi::RasterCullMode::None; + waterDesc.renderState.depthStencilState.depthTestEnable = true; + waterDesc.renderState.depthStencilState.depthWriteEnable = false; + waterDesc.renderState.depthStencilState.depthFunc = nvrhi::ComparisonFunc::GreaterOrEqual; + waterDesc.renderState.rasterState.depthBias = 0; + waterDesc.renderState.rasterState.slopeScaledDepthBias = 0.f; + auto& wrt0 = waterDesc.renderState.blendState.targets[0]; + wrt0.blendEnable = true; + wrt0.srcBlend = nvrhi::BlendFactor::SrcAlpha; + wrt0.destBlend = nvrhi::BlendFactor::InvSrcAlpha; + wrt0.blendOp = nvrhi::BlendOp::Add; + wrt0.srcBlendAlpha = nvrhi::BlendFactor::One; + wrt0.destBlendAlpha = nvrhi::BlendFactor::InvSrcAlpha; + wrt0.blendOpAlpha = nvrhi::BlendOp::Add; + for (u32 rt = 1; rt < 4; ++rt) + { + waterDesc.renderState.blendState.targets[rt].blendEnable = false; + waterDesc.renderState.blendState.targets[rt].setColorWriteMask( + nvrhi::ColorMask::Red | nvrhi::ColorMask::Green | + nvrhi::ColorMask::Blue | nvrhi::ColorMask::Alpha); + } + state.waterPipeline = cache.GetOrCreatePipeline("TransparentWater_v45_NoSSR", waterDesc, fbInfo, nvDevice); + } + waterVs.reflection = nullptr; + waterPs.reflection = nullptr; + } + if (!state.waterPipeline) + Msg("! [TransparentPass] Water PSO unavailable — water clipped from forward"); + } + + { + auto waterdVs = shaderLoader->LoadVertexShader("waterd", "main"); + auto waterdPs = shaderLoader->LoadPixelShader("waterd", "main"); + if (waterdVs.handle && waterdPs.handle && waterdVs.reflection && waterdPs.reflection) + { + state.waterDistortVs = waterdVs.handle; + state.waterDistortPs = waterdPs.handle; + state.waterDistortLayout = cache.GetOrCreateBindingLayoutFromReflection( + "TransparentWaterDistort_v23_R3Fmt", *waterdVs.reflection, *waterdPs.reflection, nvDevice); + if (state.waterDistortLayout) + { + state.waterDistortInputLayout = + nvDevice->createInputLayout(attrs, attrCount, state.waterDistortVs); + nvrhi::GraphicsPipelineDesc distortDesc; + distortDesc.VS = state.waterDistortVs; + distortDesc.PS = state.waterDistortPs; + distortDesc.inputLayout = state.waterDistortInputLayout; + if (bindlessLayout) + distortDesc.bindingLayouts = {state.waterDistortLayout, bindlessLayout}; + else + distortDesc.bindingLayouts = {state.waterDistortLayout}; + distortDesc.primType = nvrhi::PrimitiveType::TriangleList; + distortDesc.renderState.depthStencilState.depthTestEnable = true; + distortDesc.renderState.depthStencilState.depthWriteEnable = false; + distortDesc.renderState.depthStencilState.depthFunc = nvrhi::ComparisonFunc::GreaterOrEqual; + distortDesc.renderState.rasterState.cullMode = nvrhi::RasterCullMode::None; + auto& drt = distortDesc.renderState.blendState.targets[0]; + drt.blendEnable = false; + state.waterDistortPipeDesc = distortDesc; + state.waterDistortPipeDescValid = true; + state.waterDistortPipeline = nullptr; + } + waterdVs.reflection = nullptr; + waterdPs.reflection = nullptr; + } + if (!state.waterDistortPipeDescValid) + Msg("! [TransparentPass] Water distort PSO unavailable"); + } + + { + auto glassPs = shaderLoader->LoadPixelShader("glass_distort", "main"); + if (glassPs.handle && glassPs.reflection && vsResult.reflection) + { + state.glassDistortPs = glassPs.handle; + state.glassDistortLayout = cache.GetOrCreateBindingLayoutFromReflection( + "TransparentGlassDistort_v1", *vsResult.reflection, *glassPs.reflection, nvDevice); + if (state.glassDistortLayout) + { + state.glassDistortInputLayout = nvDevice->createInputLayout(attrs, attrCount, state.vs); + nvrhi::GraphicsPipelineDesc distortDesc; + distortDesc.VS = state.vs; + distortDesc.PS = state.glassDistortPs; + distortDesc.inputLayout = state.glassDistortInputLayout; + if (bindlessLayout) + distortDesc.bindingLayouts = {state.glassDistortLayout, bindlessLayout}; + else + distortDesc.bindingLayouts = {state.glassDistortLayout}; + distortDesc.primType = nvrhi::PrimitiveType::TriangleList; + distortDesc.renderState.depthStencilState.depthTestEnable = true; + distortDesc.renderState.depthStencilState.depthWriteEnable = false; + distortDesc.renderState.depthStencilState.depthFunc = nvrhi::ComparisonFunc::GreaterOrEqual; + distortDesc.renderState.rasterState.cullMode = nvrhi::RasterCullMode::None; + auto& drt = distortDesc.renderState.blendState.targets[0]; + drt.blendEnable = true; + drt.srcBlend = nvrhi::BlendFactor::SrcAlpha; + drt.destBlend = nvrhi::BlendFactor::InvSrcAlpha; + state.glassDistortPipeDesc = distortDesc; + state.glassDistortPipeDescValid = true; + state.glassDistortPipeline = nullptr; + } + glassPs.reflection = nullptr; + } + } + + { + auto csResult = shaderLoader->LoadComputeShader("copy_depth_r32"); + if (csResult.handle && csResult.reflection) + { + state.depthCopyLayout = cache.GetOrCreateBindingLayoutFromReflection( + "TransparentWater_DepthCopy", *csResult.reflection, nvDevice); + if (state.depthCopyLayout) + { + nvrhi::ComputePipelineDesc cpd; + cpd.CS = csResult.handle; + cpd.bindingLayouts = {state.depthCopyLayout}; + state.depthCopyPipeline = cache.GetOrCreateComputePipeline( + "TransparentWater_DepthCopy", cpd, nvDevice); + } + state.depthCopyCB = cache.GetOrCreateVolatileCB( + "TransparentWater", "CopyDepthParams", 16, device); + csResult.reflection = nullptr; + } + if (!state.depthCopyPipeline) + Msg("! [TransparentPass] Depth copy for soft water unavailable"); + } + + if (auto* resMgr = device->GetFGResourceManager()) + { + if (auto* texMgr = resMgr->GetTextureManager()) + { + auto foam = texMgr->LoadTexture("water" DELIMITER "water_foam"); + state.foamTexture = texMgr->GetNVRHITexture(foam); + } + } + state.initialized = true; - Msg("* [TransparentPass] Pipeline initialized"); + Msg("* [TransparentPass] Pipeline initialized (water=%d waterd=%d depthCopy=%d)", + state.waterPipeline ? 1 : 0, state.waterDistortPipeDescValid ? 1 : 0, + state.depthCopyPipeline ? 1 : 0); } framegraph::DefaultOutputLayout setupTransparentPass( @@ -108,6 +283,7 @@ framegraph::DefaultOutputLayout setupTransparentPass( fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); fbInfo.colorFormats.push_back(nvrhi::Format::RGBA8_UNORM); + fbInfo.colorFormats.push_back(nvrhi::Format::RGBA32_FLOAT); fbInfo.depthFormat = nvrhi::Format::D32; InitializeTransparentResources(device, fbInfo, state); @@ -127,6 +303,38 @@ framegraph::DefaultOutputLayout setupTransparentPass( data.depth = passBuilder.read(inputs.depth, ResourceState::DepthStencilRead); if (inputs.baseColor.is_valid()) data.baseColor = passBuilder.readWrite(inputs.baseColor, ResourceState::RenderTarget); + + if (inputs.worldPos.is_valid()) + { + data.worldPos = passBuilder.readWrite(inputs.worldPos, ResourceState::RenderTarget); + data.clearWorldPos = false; + } + else + { + ResourceDesc wpDesc; + wpDesc.type = ResourceDesc::Type::Texture2D; + wpDesc.width = width; + wpDesc.height = height; + wpDesc.format = nvrhi::Format::RGBA32_FLOAT; + wpDesc.isRenderTarget = true; + wpDesc.isTransient = true; + wpDesc.debugName = "rt_TransparentWorldPos"; + data.worldPos = passBuilder.createTexture("rt_TransparentWorldPos", wpDesc); + data.clearWorldPos = true; + } + + if (state.waterDistortPipeDescValid || state.glassDistortPipeDescValid) + { + ResourceDesc distDesc; + distDesc.type = ResourceDesc::Type::Texture2D; + distDesc.width = width; + distDesc.height = height; + distDesc.format = nvrhi::Format::RGBA16_FLOAT; + distDesc.isRenderTarget = true; + distDesc.isTransient = true; + distDesc.debugName = "rt_Distortion"; + data.distortion = passBuilder.createTexture("rt_Distortion", distDesc); + } }, [](const TransparentPassData& data, @@ -145,13 +353,15 @@ framegraph::DefaultOutputLayout setupTransparentPass( return; auto* baseColorRT = data.baseColor.is_valid() ? fg.GetPhysicalTexture(data.baseColor) : nullptr; + auto* worldPosRT = data.worldPos.is_valid() ? fg.GetPhysicalTexture(data.worldPos) : nullptr; + if (!normalRT || !baseColorRT || !worldPosRT) + return; nvrhi::FramebufferDesc fbDesc; fbDesc.addColorAttachment(colorRT); - if (normalRT) - fbDesc.addColorAttachment(normalRT); - if (baseColorRT) - fbDesc.addColorAttachment(baseColorRT); + fbDesc.addColorAttachment(normalRT); + fbDesc.addColorAttachment(baseColorRT); + fbDesc.addColorAttachment(worldPosRT); fbDesc.setDepthAttachment(depthRT); auto& cache = framegraph::GetPassResourceCache(); auto framebuffer = cache.GetOrCreateFramebuffer("TransparentPass", fbDesc, nvDevice); @@ -161,6 +371,19 @@ framegraph::DefaultOutputLayout setupTransparentPass( if (!data.passState->initialized || !data.passState->pipeline) return; + if (data.distortion.is_valid()) + { + if (auto* distortRT = fg.GetPhysicalTexture(data.distortion)) + cmdList->clearTextureFloat( + distortRT, nvrhi::AllSubresources, nvrhi::Color(0.5f, 0.5f, 0.f, 0.f)); + } + + if (data.clearWorldPos) + { + cmdList->clearTextureFloat( + worldPosRT, nvrhi::AllSubresources, nvrhi::Color(0.f, 0.f, 0.f, 0.f)); + } + using namespace fg::bindless; auto& matBuffer = MaterialBuffer::Instance(); @@ -168,55 +391,71 @@ framegraph::DefaultOutputLayout setupTransparentPass( auto staticGlobalsCB = cache.GetOrCreateVolatileCB("Frame", "StaticGlobals", sizeof(StaticGlobals), data.device); auto drawIndexBuffer = GetOrCreateDrawIndexBuffer("TransparentPass", nvDevice); + { + StaticGlobals sg = BuildStaticGlobals(); + cmdList->writeBuffer(staticGlobalsCB, &sg, sizeof(sg)); + } + auto lightingData = FillLightingConstants(); cmdList->writeBuffer(lightingCB, &lightingData, sizeof(lightingData)); const auto& cfg = data.config; + auto& passCache = framegraph::GetPassResourceCache(); + auto& clm = ClusteredLightManager::Instance(); - auto& variantTexBuffer = bindless::VariantTextureBuffer::Instance(); + nvrhi::ITexture* sky0 = cfg.envSky0 + ? cfg.envSky0 + : passCache.GetDummyCubeMap(nvDevice); + nvrhi::ITexture* sky1 = cfg.envSky1 + ? cfg.envSky1 + : passCache.GetDummyCubeMap(nvDevice); auto* shaderLoader = GEnv.Render->GetShaderLoader(); auto* vsReflection = shaderLoader->GetCachedReflection("bindless_forward", ".vs"); auto* psReflection = shaderLoader->GetCachedReflection("bindless_forward", ".ps"); + if (!vsReflection || !psReflection) + return; + + auto* backend = data.device->GetBackend(); + nvrhi::IBindingSet* bindlessTable = nullptr; + if (backend) + bindlessTable = backend->GetBindlessDescriptorTable(); framegraph::BindingSetBuilder bsb(*vsReflection, *psReflection, nvDevice, "Transparent"); bsb.ConstantBuffer("static_globals", staticGlobalsCB); - bsb.BufferSRV("g_Materials", matBuffer.GetBuffer()); + BindBindlessMaterialTables(bsb); + BindEnvIblCubes(bsb, data.device); bsb.BufferSRV("g_InstanceData", cfg.instanceBuffer); bsb.BufferSRV("g_CompactBatchIndices", cfg.compactBatchIndicesBuffer); bsb.BufferSRV("g_CompactMaterialIDs", cfg.compactMaterialIDBuffer); - bsb.BufferSRV("g_LightData", ClusteredLightManager::Instance().GetLightDataBuffer()); - bsb.BufferSRV("g_ClusterGrid", ClusteredLightManager::Instance().GetClusterGridBuffer()); - bsb.BufferSRV("g_LightIndexList", ClusteredLightManager::Instance().GetLightIndexListBuffer()); + bsb.BufferSRV("g_LightData", clm.GetLightDataBuffer()); + bsb.BufferSRV("g_ClusterGrid", clm.GetClusterGridBuffer()); + bsb.BufferSRV("g_LightIndexList", clm.GetLightIndexListBuffer()); auto transparentBindDesc = bsb.Build(); - auto bindingSet = framegraph::GetPassResourceCache().GetOrCreateBindingSet(transparentBindDesc, data.passState->layout, nvDevice); - R_ASSERT2(bindingSet, "Transparent binding set creation failed"); - - nvrhi::GraphicsState state; - state.pipeline = data.passState->pipeline; - state.framebuffer = framebuffer; - state.bindings = { bindingSet }; + auto bindingSet = passCache.GetOrCreateBindingSet(transparentBindDesc, data.passState->layout, nvDevice); + if (!bindingSet) + return; - auto* backend = data.device->GetBackend(); - if (backend) { - auto* bindlessTable = backend->GetBindlessDescriptorTable(); - if (bindlessTable) - state.addBindingSet(bindlessTable); - } + nvrhi::GraphicsState gfxState; + gfxState.pipeline = data.passState->pipeline; + gfxState.framebuffer = framebuffer; + gfxState.bindings = { bindingSet }; + if (bindlessTable) + gfxState.addBindingSet(bindlessTable); - state.vertexBuffers = { + gfxState.vertexBuffers = { {cfg.megaVertexBuffer, 0, 0}, {drawIndexBuffer, 1, 0} }; - state.indexBuffer = { cfg.megaIndexBuffer, nvrhi::Format::R32_UINT, 0 }; - state.indirectParams = cfg.compactDrawArgsBuffer; - state.indirectCountBuffer = cfg.compactCountBuffer; + gfxState.indexBuffer = { cfg.megaIndexBuffer, nvrhi::Format::R32_UINT, 0 }; + gfxState.indirectParams = cfg.compactDrawArgsBuffer; + gfxState.indirectCountBuffer = cfg.compactCountBuffer; const auto& rtDesc = colorRT->getDesc(); nvrhi::Viewport viewport(0.0f, static_cast(rtDesc.width), 0.0f, static_cast(rtDesc.height), 0.0f, 1.0f); - state.viewport.addViewport(viewport); - state.viewport.addScissorRect(nvrhi::Rect(rtDesc.width, rtDesc.height)); + gfxState.viewport.addViewport(viewport); + gfxState.viewport.addScissorRect(nvrhi::Rect(rtDesc.width, rtDesc.height)); if (cfg.variantPartition.Enabled()) { auto* backendDev = data.device->GetBackend(); @@ -226,18 +465,337 @@ framegraph::DefaultOutputLayout setupTransparentPass( vpCfg.inputLayout = data.passState->inputLayout; vpCfg.passLayout = data.passState->layout; vpCfg.bindlessLayout = backendDev ? backendDev->GetBindlessLayout() : nullptr; - vpCfg.bindlessTable = backend ? backend->GetBindlessDescriptorTable() : nullptr; + vpCfg.bindlessTable = bindlessTable; vpCfg.megaVertexBuffer = cfg.megaVertexBuffer; vpCfg.baseBindings = transparentBindDesc; vpCfg.objectCount = cfg.objectCount; vpCfg.partition = cfg.variantPartition; vpCfg.selectTransparent = true; + vpCfg.skipWmark = cfg.skipWmark; - DrawVariantPartition(cmdList, nvDevice, framebuffer, state, vpCfg); + DrawVariantPartition(cmdList, nvDevice, framebuffer, gfxState, vpCfg); } else { - cmdList->setGraphicsState(state); + cmdList->setGraphicsState(gfxState); DrawIndexedIndirectCountOrFallback(cmdList, 0, 0, cfg.objectCount); } + + if (data.passState->waterPipeline && data.passState->waterLayout) + { + auto* waterVsRefl = shaderLoader->GetCachedReflection("water", ".vs"); + auto* waterPsRefl = shaderLoader->GetCachedReflection("water", ".ps"); + if (waterVsRefl && waterPsRefl) + { + const auto& cdesc = colorRT->getDesc(); + const auto& ddesc = depthRT->getDesc(); + if (!data.passState->waterSsrColor || + data.passState->waterSsrColor->getDesc().width != cdesc.width || + data.passState->waterSsrColor->getDesc().height != cdesc.height || + data.passState->waterSsrColor->getDesc().format != cdesc.format) + { + nvrhi::TextureDesc td{}; + td.width = cdesc.width; + td.height = cdesc.height; + td.format = cdesc.format; + td.mipLevels = 1; + td.arraySize = 1; + td.sampleCount = 1; + td.dimension = nvrhi::TextureDimension::Texture2D; + td.debugName = "WaterSSR_Color"; + td.isShaderResource = true; + td.initialState = nvrhi::ResourceStates::ShaderResource; + td.keepInitialState = true; + data.passState->waterSsrColor = nvDevice->createTexture(td); + } + if (!data.passState->waterSceneDepth || + data.passState->waterSceneDepth->getDesc().width != ddesc.width || + data.passState->waterSceneDepth->getDesc().height != ddesc.height || + data.passState->waterSceneDepth->getDesc().format != nvrhi::Format::R32_FLOAT) + { + nvrhi::TextureDesc td{}; + td.width = ddesc.width; + td.height = ddesc.height; + td.format = nvrhi::Format::R32_FLOAT; + td.mipLevels = 1; + td.arraySize = 1; + td.sampleCount = 1; + td.dimension = nvrhi::TextureDimension::Texture2D; + td.debugName = "WaterSoft_DepthR32"; + td.isShaderResource = true; + td.isUAV = true; + td.initialState = nvrhi::ResourceStates::UnorderedAccess; + td.keepInitialState = true; + data.passState->waterSceneDepth = nvDevice->createTexture(td); + } + nvrhi::ITexture* ssrColor = data.passState->waterSsrColor; + nvrhi::ITexture* softDepthR32 = data.passState->waterSceneDepth; + nvrhi::ITexture* underWP = passCache.GetDummyContactHistory(nvDevice); + cmdList->clearState(); + if (ssrColor) + { + cmdList->setTextureState(colorRT, nvrhi::AllSubresources, nvrhi::ResourceStates::CopySource); + cmdList->setTextureState(ssrColor, nvrhi::AllSubresources, nvrhi::ResourceStates::CopyDest); + cmdList->copyTexture(ssrColor, nvrhi::TextureSlice(), colorRT, nvrhi::TextureSlice()); + } + if (softDepthR32 && data.passState->depthCopyPipeline && data.passState->depthCopyLayout && + data.passState->depthCopyCB) + { + cmdList->setTextureState(depthRT, nvrhi::AllSubresources, nvrhi::ResourceStates::ShaderResource); + cmdList->setTextureState(softDepthR32, nvrhi::AllSubresources, nvrhi::ResourceStates::UnorderedAccess); + struct CopyDepthCB { u32 w, h, p0, p1; } cb{}; + cb.w = ddesc.width; + cb.h = ddesc.height; + cmdList->writeBuffer(data.passState->depthCopyCB, &cb, sizeof(cb)); + auto* csRefl = shaderLoader->GetCachedReflection("copy_depth_r32", ".cs"); + if (csRefl) + { + framegraph::BindingSetBuilder dbsb(*csRefl, nvDevice, "Transparent.DepthCopy"); + dbsb.ConstantBuffer("CopyDepthParams", data.passState->depthCopyCB); + dbsb.Texture("t_Depth", depthRT); + dbsb.TextureUAV("u_DepthR32", softDepthR32); + auto set = passCache.GetOrCreateBindingSet( + dbsb.Build(), data.passState->depthCopyLayout, nvDevice); + if (set) + { + nvrhi::ComputeState cs; + cs.pipeline = data.passState->depthCopyPipeline; + cs.bindings = {set}; + cmdList->setComputeState(cs); + cmdList->dispatch((ddesc.width + 7) / 8, (ddesc.height + 7) / 8, 1); + } + } + cmdList->setTextureState(softDepthR32, nvrhi::AllSubresources, nvrhi::ResourceStates::ShaderResource); + } + if (worldPosRT) + { + const auto& wpDesc = worldPosRT->getDesc(); + if (!data.passState->waterSceneWorldPos || + data.passState->waterSceneWorldPos->getDesc().width != wpDesc.width || + data.passState->waterSceneWorldPos->getDesc().height != wpDesc.height) + { + nvrhi::TextureDesc td{}; + td.width = wpDesc.width; + td.height = wpDesc.height; + td.format = wpDesc.format; + td.mipLevels = 1; + td.arraySize = 1; + td.sampleCount = 1; + td.dimension = nvrhi::TextureDimension::Texture2D; + td.debugName = "WaterSSR_WorldPos"; + td.isShaderResource = true; + td.initialState = nvrhi::ResourceStates::ShaderResource; + td.keepInitialState = true; + data.passState->waterSceneWorldPos = nvDevice->createTexture(td); + } + if (data.passState->waterSceneWorldPos) + { + underWP = data.passState->waterSceneWorldPos; + cmdList->setTextureState(worldPosRT, nvrhi::AllSubresources, nvrhi::ResourceStates::CopySource); + cmdList->setTextureState(underWP, nvrhi::AllSubresources, nvrhi::ResourceStates::CopyDest); + cmdList->copyTexture(underWP, nvrhi::TextureSlice(), worldPosRT, nvrhi::TextureSlice()); + cmdList->setTextureState(underWP, nvrhi::AllSubresources, nvrhi::ResourceStates::ShaderResource); + } + } + if (ssrColor) + cmdList->setTextureState(ssrColor, nvrhi::AllSubresources, nvrhi::ResourceStates::ShaderResource); + + cmdList->setTextureState(colorRT, nvrhi::AllSubresources, nvrhi::ResourceStates::RenderTarget); + cmdList->setTextureState(depthRT, nvrhi::AllSubresources, nvrhi::ResourceStates::DepthRead); + if (normalRT) + cmdList->setTextureState(normalRT, nvrhi::AllSubresources, nvrhi::ResourceStates::RenderTarget); + if (baseColorRT) + cmdList->setTextureState(baseColorRT, nvrhi::AllSubresources, nvrhi::ResourceStates::RenderTarget); + if (worldPosRT) + cmdList->setTextureState( + worldPosRT, nvrhi::AllSubresources, nvrhi::ResourceStates::RenderTarget); + + auto waterCB = cache.GetOrCreateVolatileCB( + "TransparentWater", "WaterParams", sizeof(Fvector4), data.device); + Fvector4 wi{}; + float intens = 1.f; + if (g_pGamePersistent) + intens = g_pGamePersistent->Environment().CurrentEnv.m_fWaterIntensity; + intens = std::clamp(intens, 0.f, 1.f); + const float softOn = 1.f; + wi.set(intens, intens, intens, softOn); + cmdList->writeBuffer(waterCB, &wi, sizeof(wi)); + + nvrhi::ITexture* foam = data.passState->foamTexture; + if (!foam) + foam = passCache.GetDummyShadowMap2D(nvDevice); + + nvrhi::ITexture* ssrColorBind = ssrColor + ? ssrColor + : passCache.GetDummyContactHistory(nvDevice); + nvrhi::ITexture* sceneDepthBind = softDepthR32 + ? softDepthR32 + : passCache.GetDummyContactDepth(nvDevice); + if (!underWP) + underWP = passCache.GetDummyContactHistory(nvDevice); + + framegraph::BindingSetBuilder wbsb(*waterVsRefl, *waterPsRefl, nvDevice, "Transparent.Water"); + wbsb.ConstantBuffer("static_globals", staticGlobalsCB) + .ConstantBuffer("WaterParams", waterCB); + BindBindlessMaterialTables(wbsb); + wbsb.BufferSRV("g_InstanceData", cfg.instanceBuffer) + .BufferSRV("g_CompactBatchIndices", cfg.compactBatchIndicesBuffer) + .BufferSRV("g_CompactMaterialIDs", cfg.compactMaterialIDBuffer); + wbsb.Texture("s_env0", sky0); + wbsb.Texture("s_env1", sky1); + wbsb.Texture("s_leaves", foam); + wbsb.TextureSlot(20, ssrColorBind); + wbsb.TextureSlot(21, sceneDepthBind); + wbsb.TextureSlot(23, underWP); + + auto waterSet = passCache.GetOrCreateBindingSet( + wbsb.Build(), data.passState->waterLayout, nvDevice); + if (waterSet) + { + nvrhi::GraphicsState waterGfx; + waterGfx.pipeline = data.passState->waterPipeline; + waterGfx.framebuffer = framebuffer; + waterGfx.bindings = {waterSet}; + if (bindlessTable) + waterGfx.addBindingSet(bindlessTable); + waterGfx.vertexBuffers = { + {cfg.megaVertexBuffer, 0, 0}, + {drawIndexBuffer, 1, 0} + }; + waterGfx.indexBuffer = { cfg.megaIndexBuffer, nvrhi::Format::R32_UINT, 0 }; + waterGfx.indirectParams = cfg.compactDrawArgsBuffer; + waterGfx.indirectCountBuffer = cfg.compactCountBuffer; + waterGfx.viewport.addViewport(viewport); + waterGfx.viewport.addScissorRect(nvrhi::Rect(rtDesc.width, rtDesc.height)); + cmdList->setGraphicsState(waterGfx); + DrawIndexedIndirectCountOrFallback(cmdList, 0, 0, cfg.objectCount); + } + + if (data.passState->waterDistortPipeDescValid && data.passState->waterDistortLayout && + data.distortion.is_valid()) + { + auto* distortRT = fg.GetPhysicalTexture(data.distortion); + auto* waterdVsRefl = shaderLoader->GetCachedReflection("waterd", ".vs"); + auto* waterdPsRefl = shaderLoader->GetCachedReflection("waterd", ".ps"); + if (distortRT && waterdVsRefl && waterdPsRefl) + { + nvrhi::FramebufferDesc distortFbDesc; + distortFbDesc.addColorAttachment(distortRT); + distortFbDesc.setDepthAttachment(depthRT); + auto distortFB = passCache.GetOrCreateFramebuffer( + "TransparentWaterDistort_Depth", distortFbDesc, nvDevice); + if (distortFB) + { + const auto& distortFbi = distortFB->getFramebufferInfo(); + if (!data.passState->waterDistortPipeline || + data.passState->waterDistortPipeline->getFramebufferInfo() != distortFbi) + { + data.passState->waterDistortPipeline = passCache.GetOrCreatePipeline( + "TransparentWaterDistort_v23_R3Fmt", + data.passState->waterDistortPipeDesc, + distortFbi, + nvDevice); + } + if (!data.passState->waterDistortPipeline) + Msg("! [TransparentPass] Water distort PSO create failed"); + + framegraph::BindingSetBuilder dbsb( + *waterdVsRefl, *waterdPsRefl, nvDevice, "Transparent.WaterDistort"); + dbsb.ConstantBuffer("static_globals", staticGlobalsCB) + .ConstantBuffer("WaterParams", waterCB); + BindBindlessMaterialTables(dbsb); + dbsb.BufferSRV("g_InstanceData", cfg.instanceBuffer) + .BufferSRV("g_CompactBatchIndices", cfg.compactBatchIndicesBuffer) + .BufferSRV("g_CompactMaterialIDs", cfg.compactMaterialIDBuffer); + dbsb.TextureSlot(21, sceneDepthBind); + dbsb.TextureSlot(23, underWP); + + auto distortSet = passCache.GetOrCreateBindingSet( + dbsb.Build(), data.passState->waterDistortLayout, nvDevice); + if (distortSet && data.passState->waterDistortPipeline) + { + nvrhi::GraphicsState dgfx; + dgfx.pipeline = data.passState->waterDistortPipeline; + dgfx.framebuffer = distortFB; + dgfx.bindings = {distortSet}; + if (bindlessTable) + dgfx.addBindingSet(bindlessTable); + dgfx.vertexBuffers = { + {cfg.megaVertexBuffer, 0, 0}, + {drawIndexBuffer, 1, 0} + }; + dgfx.indexBuffer = { + cfg.megaIndexBuffer, nvrhi::Format::R32_UINT, 0}; + dgfx.indirectParams = cfg.compactDrawArgsBuffer; + dgfx.indirectCountBuffer = cfg.compactCountBuffer; + dgfx.viewport.addViewport(viewport); + dgfx.viewport.addScissorRect( + nvrhi::Rect(rtDesc.width, rtDesc.height)); + cmdList->setGraphicsState(dgfx); + DrawIndexedIndirectCountOrFallback( + cmdList, 0, 0, cfg.objectCount); + } + } + } + } + } + + if (data.passState->glassDistortPipeDescValid && data.passState->glassDistortLayout && + data.distortion.is_valid() && cfg.IsValid()) + { + auto* distortRT = fg.GetPhysicalTexture(data.distortion); + auto* glassPsRefl = shaderLoader->GetCachedReflection("glass_distort", ".ps"); + if (distortRT && vsReflection && glassPsRefl) + { + nvrhi::FramebufferDesc distortFbDesc; + distortFbDesc.addColorAttachment(distortRT); + distortFbDesc.setDepthAttachment(depthRT); + auto distortFB = passCache.GetOrCreateFramebuffer( + "TransparentGlassDistort", distortFbDesc, nvDevice); + if (distortFB) + { + const auto& distortFbi = distortFB->getFramebufferInfo(); + if (!data.passState->glassDistortPipeline || + data.passState->glassDistortPipeline->getFramebufferInfo() != distortFbi) + { + data.passState->glassDistortPipeline = passCache.GetOrCreatePipeline( + "TransparentGlassDistort_v1", + data.passState->glassDistortPipeDesc, + distortFbi, + nvDevice); + } + framegraph::BindingSetBuilder gbsb( + *vsReflection, *glassPsRefl, nvDevice, "Transparent.GlassDistort"); + gbsb.ConstantBuffer("static_globals", staticGlobalsCB); + BindBindlessMaterialTables(gbsb); + gbsb.BufferSRV("g_InstanceData", cfg.instanceBuffer) + .BufferSRV("g_CompactBatchIndices", cfg.compactBatchIndicesBuffer) + .BufferSRV("g_CompactMaterialIDs", cfg.compactMaterialIDBuffer); + auto glassSet = passCache.GetOrCreateBindingSet( + gbsb.Build(), data.passState->glassDistortLayout, nvDevice); + if (glassSet && data.passState->glassDistortPipeline) + { + nvrhi::GraphicsState dgfx; + dgfx.pipeline = data.passState->glassDistortPipeline; + dgfx.framebuffer = distortFB; + dgfx.bindings = {glassSet}; + if (bindlessTable) + dgfx.addBindingSet(bindlessTable); + dgfx.vertexBuffers = { + {cfg.megaVertexBuffer, 0, 0}, + {drawIndexBuffer, 1, 0} + }; + dgfx.indexBuffer = {cfg.megaIndexBuffer, nvrhi::Format::R32_UINT, 0}; + dgfx.indirectParams = cfg.compactDrawArgsBuffer; + dgfx.indirectCountBuffer = cfg.compactCountBuffer; + dgfx.viewport.addViewport(viewport); + dgfx.viewport.addScissorRect(nvrhi::Rect(rtDesc.width, rtDesc.height)); + cmdList->setGraphicsState(dgfx); + DrawIndexedIndirectCountOrFallback(cmdList, 0, 0, cfg.objectCount); + } + } + } + } + + } } ); @@ -245,6 +803,133 @@ framegraph::DefaultOutputLayout setupTransparentPass( outputs.albedo = passData.color; outputs.normal = passData.normal; outputs.baseColor = passData.baseColor; + outputs.worldPos = passData.worldPos; + outputs.depth = passData.depth; + outputs.distortion = passData.distortion; + return outputs; +} + +framegraph::DefaultOutputLayout setupWallmarkPass( + framegraph::FrameGraph& fg, + fg::RenderDevice* device, + const framegraph::DefaultOutputLayout& inputs, + const TransparentPassConfig& config, + u32 width, u32 height, + TransparentPassState& state) +{ + using namespace framegraph; + + if (!config.IsValid() || !config.variantPartition.Enabled()) + return inputs; + + nvrhi::FramebufferInfoEx fbInfo; + fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); + fbInfo.depthFormat = nvrhi::Format::D32; + InitializeTransparentResources(device, fbInfo, state); + + struct WallmarkPassData { + VirtualResourceHandle color; + VirtualResourceHandle depth; + fg::RenderDevice* device = nullptr; + TransparentPassConfig config; + TransparentPassState* passState = nullptr; + }; + + auto& passData = fg.addCallbackPass( + "Wallmarks", + [&, config](FrameGraph& builder, PassHandle passHandle, WallmarkPassData& data) { + data.device = device; + data.config = config; + data.passState = &state; + RenderPassBuilder pb(builder, passHandle); + data.color = pb.readWrite(inputs.albedo, ResourceState::RenderTarget); + data.depth = pb.read(inputs.depth, ResourceState::DepthStencilRead); + }, + [](const WallmarkPassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto* colorRT = fgGraph.GetPhysicalTexture(data.color); + auto* depthRT = fgGraph.GetPhysicalTexture(data.depth); + if (!colorRT || !depthRT || !data.passState || !data.passState->initialized) + return; + + nvrhi::IDevice* nvDevice = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmdList = ctx->GetCommandList(); + if (!nvDevice || !cmdList) + return; + + auto& cache = framegraph::GetPassResourceCache(); + nvrhi::FramebufferDesc fbDesc; + fbDesc.addColorAttachment(colorRT); + fbDesc.setDepthAttachment(depthRT); + auto framebuffer = cache.GetOrCreateFramebuffer("WallmarkPass", fbDesc, nvDevice); + if (!framebuffer) + return; + + using namespace fg::bindless; + auto staticGlobalsCB = cache.GetOrCreateVolatileCB("Frame", "StaticGlobals", sizeof(StaticGlobals), data.device); + { + StaticGlobals sg = BuildStaticGlobals(); + cmdList->writeBuffer(staticGlobalsCB, &sg, sizeof(sg)); + } + + auto* shaderLoader = GEnv.Render->GetShaderLoader(); + auto* vsReflection = shaderLoader->GetCachedReflection("bindless_forward", ".vs"); + auto* psReflection = shaderLoader->GetCachedReflection("bindless_forward", ".ps"); + if (!vsReflection || !psReflection) + return; + + auto* backend = data.device->GetBackend(); + nvrhi::IDescriptorTable* bindlessTable = backend ? backend->GetBindlessDescriptorTable() : nullptr; + auto& clm = ClusteredLightManager::Instance(); + const auto& cfg = data.config; + + framegraph::BindingSetBuilder bsb(*vsReflection, *psReflection, nvDevice, "Wallmark"); + bsb.ConstantBuffer("static_globals", staticGlobalsCB); + BindBindlessMaterialTables(bsb); + BindEnvIblCubes(bsb, data.device); + bsb.BufferSRV("g_InstanceData", cfg.instanceBuffer); + bsb.BufferSRV("g_CompactBatchIndices", cfg.compactBatchIndicesBuffer); + bsb.BufferSRV("g_CompactMaterialIDs", cfg.compactMaterialIDBuffer); + bsb.BufferSRV("g_LightData", clm.GetLightDataBuffer()); + bsb.BufferSRV("g_ClusterGrid", clm.GetClusterGridBuffer()); + bsb.BufferSRV("g_LightIndexList", clm.GetLightIndexListBuffer()); + auto bindDesc = bsb.Build(); + auto bindingSet = cache.GetOrCreateBindingSet(bindDesc, data.passState->layout, nvDevice); + if (!bindingSet) + return; + + auto drawIndexBuffer = GetOrCreateDrawIndexBuffer("TransparentPass", nvDevice); + nvrhi::GraphicsState gfxState; + gfxState.framebuffer = framebuffer; + gfxState.bindings = { bindingSet }; + if (bindlessTable) + gfxState.addBindingSet(bindlessTable); + gfxState.vertexBuffers = { + {cfg.megaVertexBuffer, 0, 0}, + {drawIndexBuffer, 1, 0} + }; + gfxState.indexBuffer = { cfg.megaIndexBuffer, nvrhi::Format::R32_UINT, 0 }; + const auto& rtDesc = colorRT->getDesc(); + nvrhi::Viewport viewport(0.0f, static_cast(rtDesc.width), 0.0f, static_cast(rtDesc.height), 0.0f, 1.0f); + gfxState.viewport.addViewport(viewport); + gfxState.viewport.addScissorRect(nvrhi::Rect(rtDesc.width, rtDesc.height)); + + VariantPartitionDrawConfig vpCfg; + vpCfg.inputLayout = data.passState->inputLayout; + vpCfg.passLayout = data.passState->layout; + vpCfg.bindlessLayout = backend ? backend->GetBindlessLayout() : nullptr; + vpCfg.bindlessTable = bindlessTable; + vpCfg.megaVertexBuffer = cfg.megaVertexBuffer; + vpCfg.baseBindings = bindDesc; + vpCfg.objectCount = cfg.objectCount; + vpCfg.partition = cfg.variantPartition; + vpCfg.selectTransparent = true; + vpCfg.onlyWmark = true; + DrawVariantPartition(cmdList, nvDevice, framebuffer, gfxState, vpCfg); + } + ); + + DefaultOutputLayout outputs = inputs; + outputs.albedo = passData.color; outputs.depth = passData.depth; return outputs; } diff --git a/src/Layers/xrRender/FrameGraphPasses/TransparentPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/TransparentPassSetup.h index 81501646c49..8682827583b 100644 --- a/src/Layers/xrRender/FrameGraphPasses/TransparentPassSetup.h +++ b/src/Layers/xrRender/FrameGraphPasses/TransparentPassSetup.h @@ -30,6 +30,10 @@ struct TransparentPassConfig { VariantPartitionConfig variantPartition; + nvrhi::ITexture* envSky0 = nullptr; + nvrhi::ITexture* envSky1 = nullptr; + bool skipWmark = false; + bool IsValid() const { return objectCount > 0 && compactDrawArgsBuffer && megaVertexBuffer && megaIndexBuffer; } @@ -38,10 +42,35 @@ struct TransparentPassConfig { struct TransparentPassState { nvrhi::GraphicsPipelineHandle pipeline; nvrhi::BindingLayoutHandle layout; + nvrhi::GraphicsPipelineHandle waterPipeline; + nvrhi::BindingLayoutHandle waterLayout; + nvrhi::GraphicsPipelineHandle waterDistortPipeline; + nvrhi::BindingLayoutHandle waterDistortLayout; nvrhi::InputLayoutHandle inputLayout; + nvrhi::InputLayoutHandle waterDistortInputLayout; nvrhi::SamplerHandle sampler; nvrhi::ShaderHandle vs; nvrhi::ShaderHandle ps; + nvrhi::ShaderHandle waterVs; + nvrhi::ShaderHandle waterPs; + nvrhi::ShaderHandle waterDistortVs; + nvrhi::ShaderHandle waterDistortPs; + nvrhi::GraphicsPipelineHandle glassDistortPipeline; + nvrhi::BindingLayoutHandle glassDistortLayout; + nvrhi::InputLayoutHandle glassDistortInputLayout; + nvrhi::ShaderHandle glassDistortPs; + nvrhi::GraphicsPipelineDesc glassDistortPipeDesc; + bool glassDistortPipeDescValid = false; + nvrhi::ITexture* foamTexture = nullptr; + nvrhi::TextureHandle waterSsrColor; + nvrhi::TextureHandle waterSceneDepth; + nvrhi::TextureHandle waterSceneWorldPos; + nvrhi::ComputePipelineHandle depthCopyPipeline; + nvrhi::BindingLayoutHandle depthCopyLayout; + nvrhi::BufferHandle depthCopyCB; + nvrhi::GraphicsPipelineDesc waterDistortPipeDesc; + bool waterDistortPipeDescValid = false; + u32 waterVersion = 0; bool initialized = false; }; @@ -50,10 +79,13 @@ struct TransparentPassData { framegraph::VirtualResourceHandle color; framegraph::VirtualResourceHandle normal; framegraph::VirtualResourceHandle baseColor; + framegraph::VirtualResourceHandle worldPos; + framegraph::VirtualResourceHandle distortion; fg::RenderDevice* device; TransparentPassConfig config; TransparentPassState* passState; u32 width, height; + bool clearWorldPos = true; }; void InitializeTransparentResources(fg::RenderDevice* device, const nvrhi::FramebufferInfoEx& fbInfo, TransparentPassState& state); @@ -67,4 +99,13 @@ framegraph::DefaultOutputLayout setupTransparentPass( TransparentPassState& state ); +framegraph::DefaultOutputLayout setupWallmarkPass( + framegraph::FrameGraph& fg, + fg::RenderDevice* device, + const framegraph::DefaultOutputLayout& inputs, + const TransparentPassConfig& config, + u32 width, u32 height, + TransparentPassState& state +); + } // namespace xray::render::fg::passes diff --git a/src/Layers/xrRender/FrameGraphPasses/UIPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/UIPassSetup.cpp index f390522141a..8a88ff38e64 100644 --- a/src/Layers/xrRender/FrameGraphPasses/UIPassSetup.cpp +++ b/src/Layers/xrRender/FrameGraphPasses/UIPassSetup.cpp @@ -17,29 +17,24 @@ #include "Layers/xrRender/Shader.h" #include "Layers/xrRender/SH_Atomic.h" #include "Layers/xrRender/FrameGraph/ShaderLoader.h" -#include "Layers/xrRender/ConstantSystem/FGConstantSystem.h" namespace xray::render::fg::passes { -using namespace xray::render::fgconstants; - -static void UploadStaticGlobals(FGConstantSystem& constants, const StaticGlobals& cb) { - constants.SetStatic("m_V", cb.m_V); - constants.SetStatic("m_P", cb.m_P); - constants.SetStatic("m_VP", cb.m_VP); - constants.SetStatic("timers", cb.timers); - constants.SetStatic("fog_plane", cb.fog_plane); - constants.SetStatic("fog_params", cb.fog_params); - constants.SetStatic("fog_color", cb.fog_color); - constants.SetStatic("L_ambient", cb.L_ambient); - constants.SetStatic("L_sun_color", Fvector4(cb.L_sun_color.x, cb.L_sun_color.y, cb.L_sun_color.z, 0.0f)); - constants.SetStatic("L_sun_dir_w", Fvector4(cb.L_sun_dir_w.x, cb.L_sun_dir_w.y, cb.L_sun_dir_w.z, 0.0f)); - constants.SetStatic("L_hemi_color", cb.L_hemi_color); - constants.SetStatic("eye_position", Fvector4(cb.eye_position.x, cb.eye_position.y, cb.eye_position.z, 0.0f)); - constants.SetStatic("pos_decompression_params", cb.pos_decompression_params); - constants.SetStatic("pos_decompression_params2", cb.pos_decompression_params2); - constants.SetStatic("parallax", cb.parallax); - constants.SetStatic("screen_res", cb.screen_res); +static void UploadStaticGlobals(nvrhi::ICommandList* cmdList, MaterialPSO* matPSO, const StaticGlobals& cb) +{ + if (!cmdList || !matPSO) + return; + for (auto& cbInfo : matPSO->constantBuffers) + { + if (!cbInfo.nvrhiBuffer) + continue; + if (cbInfo.name != "static_globals") + continue; + const u32 bytes = std::min(cbInfo.size, (u32)sizeof(StaticGlobals)); + if (bytes == 0) + continue; + cmdList->writeBuffer(cbInfo.nvrhiBuffer, &cb, bytes); + } } static fg::PrimitiveTopology GetBatchTopology(const ui::UIGeometryBatch& batch) @@ -110,6 +105,7 @@ framegraph::VirtualResourceHandle setupUIPass( } g_pGamePersistent->OnRenderPPUI_main(); + g_pGamePersistent->OnRenderPPUI_PP(); g_pGamePersistent->OnRenderInGameUI(); if (g_pGamePersistent->IsLoadingScreenShown()) { g_pGamePersistent->load_draw_internal(); @@ -119,6 +115,9 @@ framegraph::VirtualResourceHandle setupUIPass( if (!uiRender->GetBatches().empty()) { StaticGlobals staticGlobalsCB = {}; FillGlobalConstants(staticGlobalsCB); + const float uiW = float(std::max(1u, data.width)); + const float uiH = float(std::max(1u, data.height)); + staticGlobalsCB.screen_res.set(uiW, uiH, 1.0f / uiW, 1.0f / uiH); for (const auto& batch : uiRender->GetBatches()) { if (batch.uiShader && uiMatCache) { @@ -128,12 +127,8 @@ framegraph::VirtualResourceHandle setupUIPass( framebuffer, GetBatchTopology(batch) ); - - if (matPSO) { - FGConstantSystem constants(matPSO); - UploadStaticGlobals(constants, staticGlobalsCB); - constants.CommitStatic(ctx); - } + if (matPSO) + UploadStaticGlobals(cmdList, matPSO, staticGlobalsCB); } } @@ -207,6 +202,9 @@ framegraph::VirtualResourceHandle setupCursorPass( if (!uiRender->GetBatches().empty()) { StaticGlobals staticGlobalsCB = {}; FillGlobalConstants(staticGlobalsCB); + const float uiW = float(std::max(1u, data.width)); + const float uiH = float(std::max(1u, data.height)); + staticGlobalsCB.screen_res.set(uiW, uiH, 1.0f / uiW, 1.0f / uiH); for (const auto& batch : uiRender->GetBatches()) { if (batch.uiShader && uiMatCache) { @@ -216,12 +214,8 @@ framegraph::VirtualResourceHandle setupCursorPass( framebuffer, GetBatchTopology(batch) ); - - if (matPSO) { - FGConstantSystem constants(matPSO); - UploadStaticGlobals(constants, staticGlobalsCB); - constants.CommitStatic(ctx); - } + if (matPSO) + UploadStaticGlobals(cmdList, matPSO, staticGlobalsCB); } } diff --git a/src/Layers/xrRender/FrameGraphPasses/VolumetricFogPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/VolumetricFogPassSetup.cpp new file mode 100644 index 00000000000..db3eaa95cac --- /dev/null +++ b/src/Layers/xrRender/FrameGraphPasses/VolumetricFogPassSetup.cpp @@ -0,0 +1,439 @@ +#include "stdafx.h" +#include "VolumetricFogPassSetup.h" +#include "Layers/xrRender/FrameGraph/FrameGraph.h" +#include "Layers/xrRender/FrameGraph/IPass.h" +#include "Layers/xrRender/FrameGraph/PassResourceCache.h" +#include "Layers/xrRender/FrameGraph/RenderPassBuilder.h" +#include "Layers/xrRender/FrameGraph/BindingSetBuilder.h" +#include "Layers/xrRender/FrameGraph/ShaderLoader.h" +#include "Layers/xrRender/RenderContext/RenderContext.h" +#include "Layers/xrRender/RenderContext/RenderDevice.h" +#include "Layers/xrRender/Volumetrics/VolumetricFogManager.h" +#include "Layers/xrRender/RayTracing/ReSTIRMemoryManager.h" +#include "Layers/xrRender/RayTracing/RTAccelStructManager.h" +#include "Layers/xrRender/ClusteredLightManager.h" +#include "Layers/xrRender/FrameGraphPasses/PassCommon.h" +#include "xrEngine/Environment.h" +#include "xrEngine/IGame_Persistent.h" +#include + +extern ENGINE_API int ps_r_vol_fog; +extern ENGINE_API float ps_r_vol_fog_density; +extern ENGINE_API float ps_r_vol_fog_height; +extern ENGINE_API float ps_r_vol_fog_falloff; +extern ENGINE_API float ps_r_vol_fog_g; +extern ENGINE_API float ps_r_vol_fog_noise; +extern ENGINE_API int ps_r_vol_fog_gi; +extern ENGINE_API int ps_r_vol_fog_sun; +extern ENGINE_API int ps_r_vol_fog_rt; +extern ENGINE_API int ps_r_vol_fog_lights; +extern ENGINE_API int ps_r_vol_fog_temporal; +extern ENGINE_API int ps_r_vol_fog_spot; +extern ENGINE_API int ps_r_atmosphere; +extern ENGINE_API float ps_r_atmosphere_strength; + +namespace xray::render::fg::passes { + +using namespace framegraph; + +struct VolFogCB { + Fmatrix invViewProj; + Fmatrix prevViewProj; + Fvector4 cameraPos; + Fvector4 sunDir; + Fvector4 sunColor; + Fvector4 fogTune; + Fvector4 fogTune2; + Fvector4 fogColor; + float screenWidth; + float screenHeight; + float zNear; + float zFar; + u32 frameIndex; + u32 enableGI; + u32 enableSun; + u32 enableRT; + u32 enableLights; + u32 enableTemporal; + u32 spotMode; + u32 numLights; + float atmosphereStrength; + u32 enableAtmosphere; + u32 playerLight; + u32 padFog; + Fvector4 skyColor; + Fvector4 hemiColor; +}; +static_assert(sizeof(VolFogCB) == 320, "VolFogCB must be 320 bytes"); + +static const u32 kVolFogPipeVersion = 22; + +void ShutdownVolumetricFog(VolumetricFogPassState& state) +{ + state.densityPipeline = nullptr; + state.densityLayout = nullptr; + state.injectPipeline = nullptr; + state.injectLayout = nullptr; + state.accumulatePipeline = nullptr; + state.accumulateLayout = nullptr; + state.applyPipeline = nullptr; + state.applyLayout = nullptr; + state.cb = nullptr; + state.sampler = nullptr; + state.initialized = false; + state.enabled = false; + state.pipeVersion = 0; + VolumetricFogManager::Instance().Shutdown(); +} + +static void InitializeVolFog(fg::RenderDevice* device, VolumetricFogPassState& state) +{ + if (state.initialized && state.pipeVersion == kVolFogPipeVersion) + return; + if (state.initialized) + ShutdownVolumetricFog(state); + + auto& cache = GetPassResourceCache(); + nvrhi::IDevice* nv = device->GetNVRHIDevice(); + VolumetricFogManager::Instance().Init(nv); + + nvrhi::SamplerDesc samplerDesc; + samplerDesc.setAllFilters(true); + samplerDesc.setAllAddressModes(nvrhi::SamplerAddressMode::Clamp); + state.sampler = cache.GetOrCreateSampler("VolFog", samplerDesc, nv); + state.cb = cache.GetOrCreateVolatileCB("VolFog", "VolFog_CB", sizeof(VolFogCB), device, 64); + + auto loadPipe = [&](const char* name, nvrhi::ComputePipelineHandle& pipe, nvrhi::BindingLayoutHandle& layout) { + auto cs = GEnv.Render->GetShaderLoader()->LoadComputeShader(name); + if (!cs.handle) + return; + char layoutName[64]; + xr_sprintf(layoutName, "%s_v%u", name, kVolFogPipeVersion); + layout = cache.GetOrCreateBindingLayoutFromReflection(layoutName, *cs.reflection, nv); + nvrhi::ComputePipelineDesc desc; + desc.CS = cs.handle; + desc.bindingLayouts = { layout }; + pipe = nv->createComputePipeline(desc); + }; + loadPipe("vol_fog_density", state.densityPipeline, state.densityLayout); + { + auto cs = GEnv.Render->GetShaderLoader()->LoadComputeShader("vol_fog_inject"); + if (cs.handle) { + char layoutName[64]; + xr_sprintf(layoutName, "vol_fog_inject_v%u", kVolFogPipeVersion); + state.injectLayout = cache.GetOrCreateBindingLayoutFromReflection(layoutName, *cs.reflection, nv); + nvrhi::ComputePipelineDesc desc; + desc.CS = cs.handle; + desc.bindingLayouts = { state.injectLayout }; + state.injectPipeline = nv->createComputePipeline(desc); + } + } + loadPipe("vol_fog_accumulate", state.accumulatePipeline, state.accumulateLayout); + loadPipe("vol_fog_apply", state.applyPipeline, state.applyLayout); + + state.enabled = state.densityPipeline && state.injectPipeline && state.accumulatePipeline && state.applyPipeline; + state.initialized = true; + state.pipeVersion = kVolFogPipeVersion; +} + +VirtualResourceHandle setupVolumetricFogPass( + FrameGraph& fg, + fg::RenderDevice* device, + VirtualResourceHandle sceneColor, + VirtualResourceHandle depth, + VirtualResourceHandle worldPos, + const Fmatrix& invViewProj, + const Fmatrix& prevViewProj, + const Fvector& cameraPos, + u32 width, + u32 height, + VolumetricFogPassState& state, + RTAccelStructManager* accelMgr) +{ + if (!ps_r_vol_fog) + return sceneColor; + + InitializeVolFog(device, state); + auto& fog = VolumetricFogManager::Instance(); + fog.Ensure(); + if (!state.enabled || !fog.IsReady()) + return sceneColor; + + CEnvironment& env = g_pGamePersistent->Environment(); + Fvector sunDir = env.CurrentEnv.sun_dir; + Fvector3 sc = { env.CurrentEnv.sun_color.x, env.CurrentEnv.sun_color.y, env.CurrentEnv.sun_color.z }; + const float sunI = std::max({ sc.x, sc.y, sc.z }); + + VolFogCB cb{}; + cb.invViewProj = invViewProj; + cb.prevViewProj = prevViewProj; + cb.cameraPos = { cameraPos.x, cameraPos.y, cameraPos.z, 0 }; + cb.sunDir = { sunDir.x, sunDir.y, sunDir.z, 0 }; + cb.sunColor = { sc.x, sc.y, sc.z, sunI > 1e-4f ? 1.f : 0.f }; + const float envDens = std::max(env.CurrentEnv.fog_density, 0.01f); + const float envDist = std::max(env.CurrentEnv.fog_distance, 1.f); + cb.fogTune = { ps_r_vol_fog_height, ps_r_vol_fog_falloff, ps_r_vol_fog_density * envDens, ps_r_vol_fog_noise }; + (void)envDist; + { + const Fvector3& fog = env.CurrentEnv.fog_color; + const Fvector3& sky = env.CurrentEnv.sky_color; + const Fvector4& envc = env.CurrentEnv.env_color; + cb.fogColor = { fog.x, fog.y, fog.z, 1.f }; + cb.skyColor = { sky.x, sky.y, sky.z, env.CurrentEnv.weight }; + cb.hemiColor = { envc.x, envc.y, envc.z, 1.f }; + } + auto& mem = ReSTIRMemoryManager::Instance(); + cb.fogTune2 = { + ps_r_vol_fog_g, + Device.fTimeGlobal, + mem.GetIrradianceCache() ? 1.f : 0.f, + (float)mem.GetIrradianceCacheSize() + }; + cb.screenWidth = (float)width; + cb.screenHeight = (float)height; + cb.zNear = 0.2f; + cb.zFar = std::max(env.CurrentEnv.far_plane, 80.f); + cb.frameIndex = Device.dwFrame; + cb.enableGI = ps_r_vol_fog_gi ? 1u : 0u; + cb.enableSun = ps_r_vol_fog_sun ? 1u : 0u; + cb.enableRT = (ps_r_vol_fog_rt && accelMgr && accelMgr->GetTLAS()) ? 1u : 0u; + cb.enableLights = (u32)std::max(0, ps_r_vol_fog_lights); + cb.enableTemporal = ps_r_vol_fog_temporal ? 1u : 0u; + cb.spotMode = (u32)std::clamp(ps_r_vol_fog_spot, 0, 2); + cb.numLights = ClusteredLightManager::Instance().GetLightCount(); + cb.atmosphereStrength = std::clamp(ps_r_atmosphere_strength, 0.f, 4.f); + cb.enableAtmosphere = ps_r_atmosphere ? 1u : 0u; + cb.playerLight = 0; + + nvrhi::ITexture* sky0Tex = nullptr; + nvrhi::ITexture* sky1Tex = nullptr; + ResolveEnvSkyCubes(device, sky0Tex, sky1Tex); + if (!sky0Tex) + sky0Tex = mem.GetPlaceholderCube(); + if (!sky1Tex) + sky1Tex = mem.GetPlaceholderCube(); + + ResourceDesc volDesc; + volDesc.type = ResourceDesc::Type::Texture3D; + volDesc.width = VolumetricFogManager::kWidth; + volDesc.height = VolumetricFogManager::kHeight; + volDesc.depth = VolumetricFogManager::kDepth; + volDesc.format = nvrhi::Format::RGBA16_FLOAT; + volDesc.isUAV = true; + volDesc.isImported = true; + volDesc.isTransient = false; + VirtualResourceHandle fgDensity = fg.ImportTexture("volfog_Density", fog.GetDensity(), volDesc); + VirtualResourceHandle fgLighting = fg.ImportTexture("volfog_Lighting", fog.GetLighting(), volDesc); + VirtualResourceHandle fgAccum = fg.ImportTexture("volfog_Accum", fog.GetAccumulated(), volDesc); + + struct DensData { + fg::RenderDevice* device; + VolumetricFogPassState* state; + VolFogCB cb; + }; + fg.addCallbackPass( + "VolFog Density", + [&, cb, fgDensity](FrameGraph& builder, PassHandle passHandle, DensData& data) { + RenderPassBuilder pb(builder, passHandle); + pb.write(fgDensity, ResourceState::UnorderedAccess); + pb.sideEffects(); + data.device = device; + data.state = &state; + data.cb = cb; + }, + [](const DensData& data, const FrameGraph&, fg::RenderContext* ctx) { + auto& fogMgr = VolumetricFogManager::Instance(); + if (!data.state->densityPipeline || !fogMgr.GetDensity()) + return; + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + cmd->writeBuffer(data.state->cb, &data.cb, sizeof(VolFogCB)); + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("vol_fog_density", ".cs"); + if (!refl) return; + BindingSetBuilder bsb(*refl, nv, "VolFog.Density"); + bsb.ConstantBuffer("VolFogParams", data.state->cb); + bsb.TextureUAV("u_Density", fogMgr.GetDensity()); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->densityLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->densityPipeline; + cs.bindings = { bs }; + cmd->setComputeState(cs); + cmd->dispatch((VolumetricFogManager::kWidth + 7) / 8, (VolumetricFogManager::kHeight + 7) / 8, (VolumetricFogManager::kDepth + 3) / 4); + }); + + struct InjData { + fg::RenderDevice* device; + VolumetricFogPassState* state; + RTAccelStructManager* accelMgr; + VolFogCB cb; + nvrhi::ITexture* sky0; + nvrhi::ITexture* sky1; + }; + fg.addCallbackPass( + "VolFog Inject", + [&, cb, fgDensity, fgLighting, accelMgr, sky0Tex, sky1Tex](FrameGraph& builder, PassHandle passHandle, InjData& data) { + RenderPassBuilder pb(builder, passHandle); + pb.read(fgDensity, ResourceState::ShaderResource); + pb.write(fgLighting, ResourceState::UnorderedAccess); + pb.sideEffects(); + data.device = device; + data.state = &state; + data.accelMgr = accelMgr; + data.cb = cb; + data.sky0 = sky0Tex; + data.sky1 = sky1Tex; + }, + [](const InjData& data, const FrameGraph&, fg::RenderContext* ctx) { + auto& fogMgr = VolumetricFogManager::Instance(); + auto& mem = ReSTIRMemoryManager::Instance(); + auto& clm = ClusteredLightManager::Instance(); + if (!data.state->injectPipeline || !fogMgr.GetLighting()) + return; + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + cmd->writeBuffer(data.state->cb, &data.cb, sizeof(VolFogCB)); + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("vol_fog_inject", ".cs"); + if (!refl) return; + nvrhi::IBuffer* cache = mem.GetIrradianceCache(); + if (!cache) cache = mem.GetPlaceholderBuffer(); + BindingSetBuilder bsb(*refl, nv, "VolFog.Inject"); + bsb.ConstantBuffer("VolFogParams", data.state->cb); + bsb.Texture("t_Density", fogMgr.GetDensity()); + bsb.BufferSRV("t_IrradianceCache", cache); + nvrhi::rt::IAccelStruct* tlas = data.accelMgr ? data.accelMgr->GetTLAS() : nullptr; + if (!tlas && data.accelMgr) + tlas = data.accelMgr->GetOrCreateEmptyTLAS(cmd); + if (!tlas) + return; + bsb.AccelStruct("g_SceneTLAS", tlas); + nvrhi::IBuffer* lights = clm.GetLightDataBuffer(); + if (!lights) lights = mem.GetPlaceholderBuffer(); + if (bsb.HasSRV("g_Lights")) + bsb.BufferSRV("g_Lights", lights); + if (bsb.HasSRV("t_BlueNoise")) + bsb.Texture("t_BlueNoise", mem.GetBlueNoise() ? mem.GetBlueNoise() : mem.GetPlaceholderTex3D()); + if (bsb.HasSRV("t_PrevLighting")) + bsb.Texture("t_PrevLighting", fogMgr.GetLightingHist() ? fogMgr.GetLightingHist() : mem.GetPlaceholderTex3D()); + if (bsb.HasSRV("g_Sky0")) + bsb.Texture("g_Sky0", data.sky0 ? data.sky0 : mem.GetPlaceholderCube()); + if (bsb.HasSRV("g_Sky1")) + bsb.Texture("g_Sky1", data.sky1 ? data.sky1 : mem.GetPlaceholderCube()); + bsb.TextureUAV("u_Lighting", fogMgr.GetLighting()); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->injectLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->injectPipeline; + cs.bindings = { bs }; + cmd->setComputeState(cs); + cmd->dispatch((VolumetricFogManager::kWidth + 7) / 8, (VolumetricFogManager::kHeight + 7) / 8, (VolumetricFogManager::kDepth + 3) / 4); + }); + + struct AccData { + fg::RenderDevice* device; + VolumetricFogPassState* state; + VolFogCB cb; + }; + fg.addCallbackPass( + "VolFog Accumulate", + [&, cb, fgLighting, fgAccum](FrameGraph& builder, PassHandle passHandle, AccData& data) { + RenderPassBuilder pb(builder, passHandle); + pb.read(fgLighting, ResourceState::ShaderResource); + pb.write(fgAccum, ResourceState::UnorderedAccess); + pb.sideEffects(); + data.device = device; + data.state = &state; + data.cb = cb; + }, + [](const AccData& data, const FrameGraph&, fg::RenderContext* ctx) { + auto& fogMgr = VolumetricFogManager::Instance(); + if (!data.state->accumulatePipeline || !fogMgr.GetAccumulated()) + return; + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + cmd->writeBuffer(data.state->cb, &data.cb, sizeof(VolFogCB)); + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("vol_fog_accumulate", ".cs"); + if (!refl) return; + BindingSetBuilder bsb(*refl, nv, "VolFog.Accum"); + bsb.ConstantBuffer("VolFogParams", data.state->cb); + bsb.Texture("t_Lighting", fogMgr.GetLighting()); + bsb.TextureUAV("u_Accum", fogMgr.GetAccumulated()); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->accumulateLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->accumulatePipeline; + cs.bindings = { bs }; + cmd->setComputeState(cs); + cmd->dispatch((VolumetricFogManager::kWidth + 7) / 8, (VolumetricFogManager::kHeight + 7) / 8, 1); + if (fogMgr.GetLighting() && fogMgr.GetLightingHist()) + cmd->copyTexture(fogMgr.GetLightingHist(), nvrhi::TextureSlice(), fogMgr.GetLighting(), nvrhi::TextureSlice()); + }); + + struct ApplyData { + fg::RenderDevice* device; + VolumetricFogPassState* state; + VirtualResourceHandle depth; + VirtualResourceHandle worldPos; + VirtualResourceHandle sceneColor; + VolFogCB cb; + nvrhi::ITexture* sky0; + nvrhi::ITexture* sky1; + u32 width, height; + }; + auto& apply = fg.addCallbackPass( + "VolFog Apply", + [&, cb, fgAccum, worldPos, sky0Tex, sky1Tex](FrameGraph& builder, PassHandle passHandle, ApplyData& data) { + RenderPassBuilder pb(builder, passHandle); + data.depth = pb.read(depth, ResourceState::ShaderResource); + if (worldPos.is_valid()) + data.worldPos = pb.read(worldPos, ResourceState::ShaderResource); + pb.read(fgAccum, ResourceState::ShaderResource); + data.sceneColor = pb.readWrite(sceneColor, ResourceState::UnorderedAccess); + data.device = device; + data.state = &state; + data.cb = cb; + data.sky0 = sky0Tex; + data.sky1 = sky1Tex; + data.width = width; + data.height = height; + }, + [](const ApplyData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto& fogMgr = VolumetricFogManager::Instance(); + auto& mem = ReSTIRMemoryManager::Instance(); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* colorTex = fgGraph.GetPhysicalTexture(data.sceneColor); + nvrhi::ITexture* worldPosTex = data.worldPos.is_valid() + ? fgGraph.GetPhysicalTexture(data.worldPos) + : nullptr; + if (!worldPosTex) + worldPosTex = mem.GetPlaceholderTex(); + if (!data.state->applyPipeline || !depthTex || !colorTex) + return; + nvrhi::IDevice* nv = data.device->GetNVRHIDevice(); + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + cmd->writeBuffer(data.state->cb, &data.cb, sizeof(VolFogCB)); + auto* refl = GEnv.Render->GetShaderLoader()->GetCachedReflection("vol_fog_apply", ".cs"); + if (!refl) return; + BindingSetBuilder bsb(*refl, nv, "VolFog.Apply"); + bsb.ConstantBuffer("VolFogParams", data.state->cb); + bsb.Texture("t_Depth", depthTex); + bsb.Texture("t_Accum", fogMgr.GetAccumulated()); + bsb.Texture("t_WorldPos", worldPosTex); + if (bsb.HasSRV("g_Sky0")) + bsb.Texture("g_Sky0", data.sky0 ? data.sky0 : mem.GetPlaceholderCube()); + if (bsb.HasSRV("g_Sky1")) + bsb.Texture("g_Sky1", data.sky1 ? data.sky1 : mem.GetPlaceholderCube()); + bsb.TextureUAV("u_SceneColor", colorTex); + auto bs = GetPassResourceCache().GetOrCreateBindingSet(bsb.Build(), data.state->applyLayout, nv); + if (!bs) return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->applyPipeline; + cs.bindings = { bs }; + cmd->setComputeState(cs); + cmd->dispatch((data.width + 7) / 8, (data.height + 7) / 8, 1); + }); + + return apply.sceneColor; +} + +} diff --git a/src/Layers/xrRender/FrameGraphPasses/VolumetricFogPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/VolumetricFogPassSetup.h new file mode 100644 index 00000000000..f0cf09321b6 --- /dev/null +++ b/src/Layers/xrRender/FrameGraphPasses/VolumetricFogPassSetup.h @@ -0,0 +1,44 @@ +#pragma once + +#include "Layers/xrRender/FrameGraph/FGTypes.h" +#include "Layers/xrRender/FrameGraph/FGResource.h" +#include + +namespace xray::render::fg { class RenderDevice; class RTAccelStructManager; } +namespace xray::render::framegraph { class FrameGraph; } + +namespace xray::render::fg::passes { + +struct VolumetricFogPassState { + nvrhi::ComputePipelineHandle densityPipeline; + nvrhi::BindingLayoutHandle densityLayout; + nvrhi::ComputePipelineHandle injectPipeline; + nvrhi::BindingLayoutHandle injectLayout; + nvrhi::ComputePipelineHandle accumulatePipeline; + nvrhi::BindingLayoutHandle accumulateLayout; + nvrhi::ComputePipelineHandle applyPipeline; + nvrhi::BindingLayoutHandle applyLayout; + nvrhi::IBuffer* cb = nullptr; + nvrhi::SamplerHandle sampler; + bool initialized = false; + bool enabled = false; + u32 pipeVersion = 0; +}; + +framegraph::VirtualResourceHandle setupVolumetricFogPass( + framegraph::FrameGraph& fg, + fg::RenderDevice* device, + framegraph::VirtualResourceHandle sceneColor, + framegraph::VirtualResourceHandle depth, + framegraph::VirtualResourceHandle worldPos, + const Fmatrix& invViewProj, + const Fmatrix& prevViewProj, + const Fvector& cameraPos, + u32 width, + u32 height, + VolumetricFogPassState& state, + RTAccelStructManager* accelMgr = nullptr); + +void ShutdownVolumetricFog(VolumetricFogPassState& state); + +} diff --git a/src/Layers/xrRender/FrameGraphPasses/WetSurfacesPassSetup.cpp b/src/Layers/xrRender/FrameGraphPasses/WetSurfacesPassSetup.cpp new file mode 100644 index 00000000000..7b253b4d436 --- /dev/null +++ b/src/Layers/xrRender/FrameGraphPasses/WetSurfacesPassSetup.cpp @@ -0,0 +1,598 @@ +#include "stdafx.h" +#include "WetSurfacesPassSetup.h" +#include "Layers/xrRender/FrameGraph/BindingSetBuilder.h" +#include "Layers/xrRender/FrameGraph/FrameGraph.h" +#include "Layers/xrRender/FrameGraph/PassResourceCache.h" +#include "Layers/xrRender/FrameGraph/RenderPassBuilder.h" +#include "Layers/xrRender/FrameGraph/ShaderLoader.h" +#include "Layers/xrRender/RenderContext/RenderContext.h" +#include "Layers/xrRender/RenderContext/RenderDevice.h" +#include "Layers/xrRender/ResourceManager/FGResourceManager.h" +#include "Layers/xrRender/ResourceManager/TextureManager.h" +#include "Layers/xrRender/xrRender_console.h" +#include "Layers/xrRender/FrameGraphPasses/TAAPassSetup.h" +#include "Layers/xrRender/RayTracing/ReSTIRMemoryManager.h" +#include "xrEngine/Environment.h" +#include "xrEngine/IGame_Persistent.h" + +extern ENGINE_API int ps_r_rt_gi; +extern ENGINE_API int ps_r_path_tracer; + +namespace xray::render::fg::passes { + +using namespace framegraph; + +// Phase 2 CB — matrices for wet SSR + rain SM projection +struct alignas(16) WetConstantsGPU +{ + Fvector4 RainDensity; // x = y = rain density + Fvector4 EyePos; + Fvector4 SunColor; + Fvector4 Timers; // x = time + Fmatrix m_VP; + Fmatrix m_RainSampleVP; +}; +static_assert(sizeof(WetConstantsGPU) == 192, "WetConstantsGPU size mismatch"); + +struct WetSurfacesDrawData +{ + VirtualResourceHandle colorIn; + VirtualResourceHandle normal; + VirtualResourceHandle worldPos; + VirtualResourceHandle baseColor; + VirtualResourceHandle patched; + VirtualResourceHandle colorOut; + VirtualResourceHandle normalOut; + VirtualResourceHandle rainSM; + VirtualResourceHandle sceneReflection; + u32 width = 0; + u32 height = 0; + WetSurfacesPassState* passState = nullptr; + WetSurfacesExtras extras; + bool hasRainSM = false; + bool hasSceneReflection = false; + bool hasBaseColor = false; +}; + +static void BlitColor(nvrhi::ICommandList* cmdList, nvrhi::ITexture* dst, nvrhi::ITexture* src) +{ + if (!cmdList || !dst || !src) + return; + cmdList->setTextureState(src, nvrhi::AllSubresources, nvrhi::ResourceStates::CopySource); + cmdList->setTextureState(dst, nvrhi::AllSubresources, nvrhi::ResourceStates::CopyDest); + cmdList->copyTexture(dst, nvrhi::TextureSlice(), src, nvrhi::TextureSlice()); + cmdList->setTextureState(dst, nvrhi::AllSubresources, nvrhi::ResourceStates::RenderTarget); +} + +static WetConstantsGPU MakeWetConstants(const Fmatrix& rainSampleVP) +{ + WetConstantsGPU wet{}; + float rainDensity = g_pGamePersistent + ? g_pGamePersistent->Environment().CurrentEnv.rain_density + : 0.f; + wet.RainDensity.set(rainDensity, rainDensity, 0.f, 0.f); + const bool rtNoDistFade = (ps_r_rt_gi != 0) || (ps_r_path_tracer != 0); + wet.EyePos.set( + Device.vCameraPosition.x, Device.vCameraPosition.y, Device.vCameraPosition.z, + rtNoDistFade ? 1.f : 0.f); + if (g_pGamePersistent) + { + const auto& env = g_pGamePersistent->Environment().CurrentEnv; + wet.SunColor.set(env.sun_color.x, env.sun_color.y, env.sun_color.z, 0.f); + } + const float t = Device.fTimeGlobal; + wet.Timers.set(t, t * 10.f, t / 10.f, _sin(t)); + wet.m_VP = g_taa_unjittered_full_transform; + wet.m_RainSampleVP = rainSampleVP; + return wet; +} + +constexpr u32 kWetPipeVersion = 28; + +void InitializeWetSurfacesPass(nvrhi::IDevice* device, WetSurfacesPassState& state) +{ + if (!device) + return; + if (state.initialized && state.pipeVersion == kWetPipeVersion) + return; + state.initialized = false; + state.wetTexResolved = false; + state.waterRippleTex = nullptr; + state.waterFallTex = nullptr; + state.puddlesPerlinTex = nullptr; + + auto* loader = GEnv.Render ? GEnv.Render->GetShaderLoader() : nullptr; + if (!loader) + { + state.initialized = true; + return; + } + + auto vs = loader->LoadVertexShader("fullscreen"); + auto patchPs = loader->LoadPixelShader("wet/rain_patch_normal"); + auto applyPs = loader->LoadPixelShader("wet/rain_apply"); + auto writePs = loader->LoadPixelShader("wet/rain_write_normal"); + + // Apply is mandatory; patch/write are optional (Slang often crashes on patch). + if (!vs.handle || !vs.reflection || !applyPs.handle || !applyPs.reflection) + { + Msg("! [WetSurfaces] Failed to load apply shader vs=%d apply=%d — wet disabled", + vs.handle && vs.reflection, applyPs.handle && applyPs.reflection); + state.initialized = true; + state.pipeVersion = kWetPipeVersion; + return; + } + + auto& cache = GetPassResourceCache(); + state.layout = cache.GetOrCreateBindingLayoutFromReflection( + "WetApply_v25_SpecUnion", *vs.reflection, *applyPs.reflection, device); + + if (patchPs.handle && patchPs.reflection) + { + state.patchLayout = cache.GetOrCreateBindingLayoutFromReflection( + "WetPatch_v27", *vs.reflection, *patchPs.reflection, device); + } + else + { + Msg("! [WetSurfaces] patch shader missing — apply-only mode"); + } + + if (writePs.handle && writePs.reflection) + { + state.writeNormalLayout = cache.GetOrCreateBindingLayoutFromReflection( + "WetWriteNormal_v24", *vs.reflection, *writePs.reflection, device); + } + + nvrhi::FramebufferInfoEx fbInfo; + fbInfo.addColorFormat(nvrhi::Format::RGBA16_FLOAT); + + auto makePipe = [&](auto& ps, nvrhi::BindingLayoutHandle layout, + nvrhi::GraphicsPipelineHandle& pipe, const char* name) { + if (!layout || !ps.handle) + return; + nvrhi::GraphicsPipelineDesc desc; + desc.setVertexShader(vs.handle); + desc.setPixelShader(ps.handle); + desc.addBindingLayout(layout); + desc.setPrimType(nvrhi::PrimitiveType::TriangleList); + desc.renderState.depthStencilState.setDepthTestEnable(false); + desc.renderState.depthStencilState.setDepthWriteEnable(false); + desc.renderState.rasterState.setCullMode(nvrhi::RasterCullMode::None); + pipe = cache.GetOrCreatePipeline(name, desc, fbInfo, device); + }; + + makePipe(applyPs, state.layout, state.pipeline, "WetApply_v28_ActorWet"); + makePipe(patchPs, state.patchLayout, state.patchPipeline, "WetPatch_v28_ActorWet"); + makePipe(writePs, state.writeNormalLayout, state.writeNormalPipeline, "WetWriteNormal_v28"); + + state.initialized = true; + state.pipeVersion = kWetPipeVersion; + if (state.pipeline && state.layout) + { + Msg("* [WetSurfaces] Rebuild v28: apply=%d patch=%d write=%d", + !!state.pipeline, !!state.patchPipeline, !!state.writeNormalPipeline); + } + else + Msg("! [WetSurfaces] Pipeline create failed — wet disabled"); +} + +namespace +{ +bool EnsureWetTarget( + nvrhi::IDevice* nvDevice, + nvrhi::TextureHandle& slot, + u32 width, + u32 height, + const char* name) +{ + if (!nvDevice || width == 0 || height == 0) + return false; + + if (slot) + { + const auto& d = slot->getDesc(); + if (d.width == width && d.height == height) + return true; + } + + nvrhi::TextureDesc desc; + desc.width = width; + desc.height = height; + desc.format = nvrhi::Format::RGBA16_FLOAT; + desc.isRenderTarget = true; + desc.isShaderResource = true; + desc.debugName = name; + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + slot = nvDevice->createTexture(desc); + if (!slot) + { + Msg("! [WetSurfaces] Failed to create %s %ux%u RGBA16F", name, width, height); + return false; + } + return true; +} +} // namespace + +DefaultOutputLayout setupWetSurfacesPass( + FrameGraph& fg, + fg::RenderDevice* device, + const DefaultOutputLayout& inputs, + u32 width, + u32 height, + WetSurfacesPassState& passState, + const WetSurfacesExtras& extras) +{ + DefaultOutputLayout outputs = inputs; + static bool s_loggedNoFlag = false; + static bool s_loggedMissingInputs = false; + static bool s_loggedNoPipelines = false; + static bool s_loggedNoTargets = false; + + if (!ps_r2_ls_flags.test(R3FLAG_DYN_WET_SURF)) + { + if (!s_loggedNoFlag) + { + Msg("! [WetSurfaces] Disabled by r3_dynamic_wet_surfaces flag"); + s_loggedNoFlag = true; + } + return outputs; + } + + const float rainDensity = g_pGamePersistent + ? g_pGamePersistent->Environment().CurrentEnv.rain_density + : 0.f; + if (rainDensity < 0.001f) + return outputs; + + if (!inputs.albedo.is_valid() || !inputs.normal.is_valid() || + !inputs.worldPos.is_valid()) + { + if (!s_loggedMissingInputs) + { + Msg("! [WetSurfaces] Missing inputs: albedo=%d normal=%d worldPos=%d", + inputs.albedo.is_valid(), inputs.normal.is_valid(), + inputs.worldPos.is_valid()); + s_loggedMissingInputs = true; + } + return outputs; + } + + if (!device || !device->GetNVRHIDevice()) + return outputs; + + nvrhi::IDevice* nvDevice = device->GetNVRHIDevice(); + InitializeWetSurfacesPass(nvDevice, passState); + + if (!passState.pipeline || !passState.layout) + { + if (!s_loggedNoPipelines) + { + Msg("! [WetSurfaces] Missing apply pipeline/layout (patch=%d write=%d)", + !!passState.patchPipeline, !!passState.writeNormalPipeline); + s_loggedNoPipelines = true; + } + return outputs; + } + + const bool doWriteNormal = passState.writeNormalPipeline && passState.writeNormalLayout + && passState.patchPipeline && passState.patchLayout; + + const bool needResize = passState.texWidth != width || passState.texHeight != height; + if (needResize) + { + passState.patched = nullptr; + passState.color = nullptr; + passState.normal = nullptr; + passState.texWidth = 0; + passState.texHeight = 0; + } + + if (!EnsureWetTarget(nvDevice, passState.patched, width, height, "rt_WetPatched") || + !EnsureWetTarget(nvDevice, passState.color, width, height, "rt_WetColor") || + (doWriteNormal && !EnsureWetTarget(nvDevice, passState.normal, width, height, "rt_WetNormal"))) + { + passState.patched = nullptr; + passState.color = nullptr; + passState.normal = nullptr; + passState.texWidth = 0; + passState.texHeight = 0; + if (!s_loggedNoTargets) + { + Msg("! [WetSurfaces] RT alloc failed — wet disabled this frame"); + s_loggedNoTargets = true; + } + return outputs; + } + passState.texWidth = width; + passState.texHeight = height; + s_loggedNoTargets = false; + + ResourceDesc texDesc; + texDesc.type = ResourceDesc::Type::Texture2D; + texDesc.width = width; + texDesc.height = height; + texDesc.format = nvrhi::Format::RGBA16_FLOAT; + texDesc.isRenderTarget = true; + texDesc.isTransient = false; + + texDesc.debugName = "rt_WetPatched"; + auto patched = fg.ImportTexture("rt_WetPatched", passState.patched.Get(), texDesc); + texDesc.debugName = "rt_WetColor"; + auto outColor = fg.ImportTexture("rt_WetColor", passState.color.Get(), texDesc); + + VirtualResourceHandle outNormal{}; + if (doWriteNormal) + { + texDesc.debugName = "rt_WetNormal"; + outNormal = fg.ImportTexture("rt_WetNormal", passState.normal.Get(), texDesc); + } + + const bool hasRainSM = extras.rainSMValid && extras.rainSM.is_valid(); + const bool hasSceneReflection = extras.sceneReflectionValid && extras.sceneReflection.is_valid(); + + (void)fg.addCallbackPass( + "WetSurfaces", + [inputs, patched, outColor, outNormal, doWriteNormal, hasRainSM, hasSceneReflection, + width, height, &passState, extras]( + FrameGraph& builder, PassHandle passHandle, WetSurfacesDrawData& data) + { + RenderPassBuilder passBuilder(builder, passHandle); + data.width = width; + data.height = height; + data.passState = &passState; + data.extras = extras; + data.hasRainSM = hasRainSM; + data.hasSceneReflection = hasSceneReflection; + data.colorIn = passBuilder.read(inputs.albedo, ResourceState::ShaderResource); + data.normal = passBuilder.read(inputs.normal, ResourceState::ShaderResource); + data.worldPos = passBuilder.read(inputs.worldPos, ResourceState::ShaderResource); + data.hasBaseColor = inputs.baseColor.is_valid(); + if (data.hasBaseColor) + data.baseColor = passBuilder.read(inputs.baseColor, ResourceState::ShaderResource); + if (hasRainSM) + data.rainSM = passBuilder.read(extras.rainSM, ResourceState::ShaderResource); + if (hasSceneReflection) + data.sceneReflection = passBuilder.read(extras.sceneReflection, ResourceState::ShaderResource); + data.patched = passBuilder.write(patched, ResourceState::RenderTarget); + data.colorOut = passBuilder.write(outColor, ResourceState::RenderTarget); + if (doWriteNormal && outNormal.is_valid()) + data.normalOut = passBuilder.write(outNormal, ResourceState::RenderTarget); + passBuilder.sideEffects(); + }, + [](const WetSurfacesDrawData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) + { + nvrhi::ICommandList* cmdList = ctx->GetCommandList(); + nvrhi::IDevice* nvDevice = cmdList->getDevice(); + auto* ps = data.passState; + + auto* colorIn = fgGraph.GetPhysicalTexture(data.colorIn); + auto* colorOut = fgGraph.GetPhysicalTexture(data.colorOut); + BlitColor(cmdList, colorOut, colorIn); + + if (!ps || !ps->pipeline || !ps->layout) + return; + + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* worldPosTex = fgGraph.GetPhysicalTexture(data.worldPos); + auto* patchedTex = fgGraph.GetPhysicalTexture(data.patched); + auto* baseTex = data.hasBaseColor ? fgGraph.GetPhysicalTexture(data.baseColor) : nullptr; + if (!colorIn || !normalTex || !worldPosTex || !patchedTex || !colorOut) + return; + + if (colorIn == colorOut) + { + static bool s_loggedAlias = false; + if (!s_loggedAlias) + { + Msg("! [WetSurfaces] colorIn == colorOut — wet cannot composite"); + s_loggedAlias = true; + } + return; + } + + auto& cache = GetPassResourceCache(); + auto* rd = ctx->GetDevice(); + auto wetCB = cache.GetOrCreateVolatileCB( + "WetSurfaces", "WetConstants_v1n_RainVP", sizeof(WetConstantsGPU), rd); + WetConstantsGPU wet = MakeWetConstants(data.extras.rainSampleVP); + cmdList->writeBuffer(wetCB, &wet, sizeof(wet)); + + static int s_wetExecLog = 0; + if ((s_wetExecLog++ % 120) == 0) + { + Msg("* [WetSurfaces] exec rain=%.3f ssrSrc=%d in=%p out=%p", + wet.RainDensity.x, data.hasSceneReflection, + (void*)colorIn, (void*)colorOut); + } + + auto* vsRefl = GEnv.Render->GetShaderLoader()->GetCachedReflection("fullscreen", ".vs"); + auto* applyRefl = GEnv.Render->GetShaderLoader()->GetCachedReflection("wet/rain_apply", ".ps"); + if (!vsRefl || !applyRefl) + return; + auto* patchRefl = GEnv.Render->GetShaderLoader()->GetCachedReflection("wet/rain_patch_normal", ".ps"); + auto* writeRefl = GEnv.Render->GetShaderLoader()->GetCachedReflection("wet/rain_write_normal", ".ps"); + + nvrhi::ITexture* rainSM = data.hasRainSM + ? fgGraph.GetPhysicalTexture(data.rainSM) + : nullptr; + if (!rainSM) + rainSM = data.extras.rainSMTex; + if (!rainSM) + rainSM = cache.GetDummyShadowMap2D(nvDevice); + + if (!ps->wetTexResolved) + { + auto* texMgr = rd && rd->GetFGResourceManager() + ? rd->GetFGResourceManager()->GetTextureManager() + : nullptr; + auto loadTex = [&](const char* name) -> nvrhi::ITexture* { + if (!texMgr) + return nullptr; + auto h = texMgr->LoadTexture(name); + return texMgr->GetNVRHITexture(h); + }; + nvrhi::ITexture* waterFall = loadTex("water" DELIMITER "water_normal"); + if (!waterFall) + waterFall = cache.GetDummyContactHistory(nvDevice); + ps->waterFallTex = waterFall; + ps->waterRippleTex = waterFall; + ps->puddlesPerlinTex = waterFall; + ps->wetTexResolved = true; + } + nvrhi::ITexture* waterRipple = ps->waterRippleTex; + nvrhi::ITexture* waterFall = ps->waterFallTex; + nvrhi::ITexture* puddlesPerlin = ps->puddlesPerlinTex; + if (!waterRipple) + waterRipple = cache.GetDummyContactHistory(nvDevice); + if (!waterFall) + waterFall = waterRipple; + if (!puddlesPerlin) + puddlesPerlin = waterRipple; + if (!baseTex) + baseTex = cache.GetDummyContactHistory(nvDevice); + + nvrhi::Viewport viewport; + viewport.minX = 0; + viewport.minY = 0; + viewport.maxX = static_cast(data.width); + viewport.maxY = static_cast(data.height); + viewport.minZ = 0.0f; + viewport.maxZ = 1.0f; + + // 1) Patch normals + wetness (optional) + bool havePatched = false; + if (ps->patchPipeline && ps->patchLayout && patchRefl) + { + BindingSetBuilder bsb(*vsRefl, *patchRefl, nvDevice, "WetPatch"); + bsb.ConstantBuffer("WetConstants", wetCB) + .Texture("g_Normal", normalTex) + .Texture("g_WorldPos", worldPosTex) + .Texture("g_RainShadow", rainSM) + .Texture("g_Water", waterRipple) + .Texture("g_WaterFall", waterFall) + .Texture("g_PuddlesPerlin", puddlesPerlin) + .Texture("g_Base", baseTex) + .Texture("g_SkyOpen", data.extras.skyOpenTex + ? data.extras.skyOpenTex + : ReSTIRMemoryManager::Instance().GetPlaceholderTex()); + auto bindingSet = cache.GetOrCreateBindingSet(bsb.Build(), ps->patchLayout, nvDevice); + if (bindingSet) + { + nvrhi::FramebufferDesc fbDesc; + fbDesc.addColorAttachment(patchedTex); + auto framebuffer = cache.GetOrCreateFramebuffer("WetPatch_v1d", fbDesc, nvDevice); + if (framebuffer) + { + nvrhi::GraphicsState state; + state.pipeline = ps->patchPipeline; + state.framebuffer = framebuffer; + state.viewport.addViewportAndScissorRect(viewport); + state.addBindingSet(bindingSet); + cmdList->setGraphicsState(state); + cmdList->draw(nvrhi::DrawArguments().setVertexCount(3)); + cmdList->setTextureState(patchedTex, nvrhi::AllSubresources, + nvrhi::ResourceStates::ShaderResource); + havePatched = true; + } + } + } + + // Apply-only: feed scene normals; wet amount comes from rain in PS + nvrhi::ITexture* patchedForApply = havePatched ? patchedTex : normalTex; + + // 2) Write normals for AO (optional) + if (ps->writeNormalPipeline && ps->writeNormalLayout && writeRefl + && data.normalOut.is_valid() && havePatched) + { + auto* normalOut = fgGraph.GetPhysicalTexture(data.normalOut); + if (normalOut) + { + BindingSetBuilder bsb(*vsRefl, *writeRefl, nvDevice, "WetWriteNormal"); + bsb.Texture("g_Normal", normalTex).Texture("g_Patched", patchedTex); + auto bindingSet = cache.GetOrCreateBindingSet(bsb.Build(), ps->writeNormalLayout, nvDevice); + if (bindingSet) + { + nvrhi::FramebufferDesc fbDesc; + fbDesc.addColorAttachment(normalOut); + auto framebuffer = cache.GetOrCreateFramebuffer("WetWriteNormal_v1f", fbDesc, nvDevice); + if (framebuffer) + { + nvrhi::GraphicsState state; + state.pipeline = ps->writeNormalPipeline; + state.framebuffer = framebuffer; + state.viewport.addViewportAndScissorRect(viewport); + state.addBindingSet(bindingSet); + cmdList->setGraphicsState(state); + cmdList->draw(nvrhi::DrawArguments().setVertexCount(3)); + cmdList->setTextureState(normalOut, nvrhi::AllSubresources, + nvrhi::ResourceStates::ShaderResource); + } + } + } + } + + // 3) Apply darken + streaks + wet SSR + { + nvrhi::ITexture* sceneRefl = data.hasSceneReflection + ? fgGraph.GetPhysicalTexture(data.sceneReflection) + : nullptr; + if (!sceneRefl) + sceneRefl = colorIn; // fallback: still safe (no write feedback to colorIn) + + cmdList->setTextureState(colorIn, nvrhi::AllSubresources, + nvrhi::ResourceStates::ShaderResource); + cmdList->setTextureState(patchedForApply, nvrhi::AllSubresources, + nvrhi::ResourceStates::ShaderResource); + cmdList->setTextureState(worldPosTex, nvrhi::AllSubresources, + nvrhi::ResourceStates::ShaderResource); + cmdList->setTextureState(sceneRefl, nvrhi::AllSubresources, + nvrhi::ResourceStates::ShaderResource); + cmdList->setTextureState(colorOut, nvrhi::AllSubresources, + nvrhi::ResourceStates::RenderTarget); + + BindingSetBuilder bsb(*vsRefl, *applyRefl, nvDevice, "WetApply"); + bsb.ConstantBuffer("WetConstants", wetCB) + .Texture("g_Color", colorIn) + .Texture("g_Patched", patchedForApply) + .Texture("g_WorldPos", worldPosTex) + .Texture("g_WaterFall", waterFall) + .Texture("g_SceneReflection", sceneRefl) + .Texture("g_Base", baseTex); + auto bindingSet = cache.GetOrCreateBindingSet(bsb.Build(), ps->layout, nvDevice); + if (!bindingSet) + { + static bool s_loggedApplyBind = false; + if (!s_loggedApplyBind) + { + Msg("! [WetSurfaces] Apply binding failed — passthrough blit kept"); + s_loggedApplyBind = true; + } + return; + } + + nvrhi::FramebufferDesc fbDesc; + fbDesc.addColorAttachment(colorOut); + auto framebuffer = cache.GetOrCreateFramebuffer("WetApply_v7", fbDesc, nvDevice); + if (!framebuffer) + return; + + nvrhi::GraphicsState state; + state.pipeline = ps->pipeline; + state.framebuffer = framebuffer; + state.viewport.addViewportAndScissorRect(viewport); + state.addBindingSet(bindingSet); + cmdList->setGraphicsState(state); + cmdList->draw(nvrhi::DrawArguments().setVertexCount(3)); + cmdList->setTextureState(colorOut, nvrhi::AllSubresources, + nvrhi::ResourceStates::ShaderResource); + } + }); + + outputs.albedo = outColor; + if (doWriteNormal && outNormal.is_valid()) + outputs.normal = outNormal; + return outputs; +} + +} // namespace xray::render::fg::passes diff --git a/src/Layers/xrRender/FrameGraphPasses/WetSurfacesPassSetup.h b/src/Layers/xrRender/FrameGraphPasses/WetSurfacesPassSetup.h new file mode 100644 index 00000000000..04ea3af2249 --- /dev/null +++ b/src/Layers/xrRender/FrameGraphPasses/WetSurfacesPassSetup.h @@ -0,0 +1,63 @@ +#pragma once + +#include "Layers/xrRender/FrameGraph/FGTypes.h" +#include "Layers/xrRender/FrameGraph/FGResource.h" +#include "Layers/xrRender/FrameGraph/IPass.h" +#include + +namespace xray::render::framegraph { + class FrameGraph; +} + +namespace xray::render::fg { + class RenderDevice; +} + +namespace xray::render::fg::passes { + +struct WetSurfacesPassState { + nvrhi::GraphicsPipelineHandle pipeline; + nvrhi::BindingLayoutHandle layout; + nvrhi::GraphicsPipelineHandle patchPipeline; + nvrhi::BindingLayoutHandle patchLayout; + nvrhi::GraphicsPipelineHandle writeNormalPipeline; + nvrhi::BindingLayoutHandle writeNormalLayout; + nvrhi::TextureHandle patched; + nvrhi::TextureHandle color; + nvrhi::TextureHandle normal; + u32 texWidth = 0; + u32 texHeight = 0; + bool initialized = false; + u32 pipeVersion = 0; + nvrhi::ITexture* waterRippleTex = nullptr; + nvrhi::ITexture* waterFallTex = nullptr; + nvrhi::ITexture* puddlesPerlinTex = nullptr; + bool wetTexResolved = false; +}; + +void InitializeWetSurfacesPass(nvrhi::IDevice* device, WetSurfacesPassState& state); + +// Phase 1 wet + phase 2 SSR source (rt_SceneReflection, non-aliased). +struct WetSurfacesExtras +{ + framegraph::VirtualResourceHandle rainSM; + nvrhi::ITexture* rainSMTex = nullptr; + Fmatrix rainSampleVP; + bool rainSMValid = false; + + framegraph::VirtualResourceHandle sceneReflection; + bool sceneReflectionValid = false; + + nvrhi::ITexture* skyOpenTex = nullptr; +}; + +framegraph::DefaultOutputLayout setupWetSurfacesPass( + framegraph::FrameGraph& fg, + fg::RenderDevice* device, + const framegraph::DefaultOutputLayout& inputs, + u32 width, + u32 height, + WetSurfacesPassState& state, + const WetSurfacesExtras& extras = {}); + +} // namespace xray::render::fg::passes diff --git a/src/Layers/xrRender/GPUCullingManager.cpp b/src/Layers/xrRender/GPUCullingManager.cpp index 5c2c46984a7..d345a4a0c21 100644 --- a/src/Layers/xrRender/GPUCullingManager.cpp +++ b/src/Layers/xrRender/GPUCullingManager.cpp @@ -1827,7 +1827,7 @@ void GPUCullingManager::UploadSceneObjects(fg::RenderContext* ctx, const Geometr // ───────────────────────────────────────────────────── GPUObjectData obj; obj.position = batch.worldBoundsCenter; - obj.radius = batch.worldBoundsRadius; + obj.radius = batch.isStatic ? batch.worldBoundsRadius : (batch.worldBoundsRadius * 1.75f); obj.batchIndex = static_cast(objectData.size()); obj.flags = 0; @@ -1837,6 +1837,10 @@ void GPUCullingManager::UploadSceneObjects(fg::RenderContext* ctx, const Geometr obj.flags |= GPU_OBJECT_ALPHA_TEST; if (batch.IsStrictB2F()) obj.flags |= GPU_OBJECT_TRANSPARENT; + if (batch.visual && batch.visual->shaderName.size() + && (strstr(batch.visual->shaderName.c_str(), "wallmark") + || strstr(batch.visual->shaderName.c_str(), "lightplane"))) + obj.flags |= GPU_OBJECT_WMARK; obj.pad0 = 0.0f; obj.pad1 = 0.0f; @@ -1932,6 +1936,20 @@ void GPUCullingManager::UploadSceneObjects(fg::RenderContext* ctx, const Geometr if (batch.IsStrictB2F()) { appendBatch(batch, m_transparentObjectData, m_transparentDrawArgsData, m_transparentMaterialIDData, m_transparentInstanceData); + if (batch.visual && batch.visual->shaderName.size() && + strstr(batch.visual->shaderName.c_str(), "water")) + { + static u32 s_waterBatchLog = 0; + if (s_waterBatchLog < 8) + { + Msg("* [GPUCull] Water transparent batch mat=%u shader='%s' idx=%u verts=%u", + batch.bindlessMaterialID, + batch.visual->shaderName.c_str(), + static_cast(m_transparentObjectData.size() - 1), + batch.indexCount); + ++s_waterBatchLog; + } + } continue; } @@ -2266,8 +2284,8 @@ void GPUCullingManager::UploadSkinnedObjects(fg::RenderContext* ctx, const Geome GPUObjectData obj; obj.position = batch.worldBoundsCenter; - obj.radius = batch.worldBoundsRadius; - obj.flags = 0; // Skinned meshes use their own alpha handling + obj.radius = batch.worldBoundsRadius * 1.75f; + obj.flags = 0; obj.pad0 = 0.0f; obj.pad1 = 0.0f; @@ -2617,14 +2635,11 @@ GPUCullOutput GPUCullingManager::SetupCullingPass( bindless::MaterialBuffer::Instance().Upload(ctx); - if (mgr->m_rtAccelMgr) { - mgr->m_rtAccelMgr->BuildIfNeeded(cmdList, mgr); - if (mgr->m_rtAccelMgr->IsReady()) { - if (!mgr->m_rtAccelMgr->GetMaterialBuffer()) - mgr->m_rtAccelMgr->SetMaterialBuffer(bindless::MaterialBuffer::Instance().GetBuffer()); - if (!mgr->m_rtAccelMgr->GetTerrainMaterialBuffer()) - mgr->m_rtAccelMgr->SetTerrainMaterialBuffer(bindless::TerrainMaterialBuffer::Instance().GetBuffer()); - } + if (mgr->m_rtAccelMgr && mgr->m_rtAccelMgr->IsReady()) { + if (!mgr->m_rtAccelMgr->GetMaterialBuffer()) + mgr->m_rtAccelMgr->SetMaterialBuffer(bindless::MaterialBuffer::Instance().GetBuffer()); + if (!mgr->m_rtAccelMgr->GetTerrainMaterialBuffer()) + mgr->m_rtAccelMgr->SetTerrainMaterialBuffer(bindless::TerrainMaterialBuffer::Instance().GetBuffer()); } // Get Hi-Z texture @@ -3694,6 +3709,9 @@ void GPUCullingManager::SetupDebugVisualizationPass( nvrhi::BindingSetHandle bindingSet = nvDevice->createBindingSet(bsb.Build(), mgr->m_debugGraphicsLayout); + if (depthTexture->getDesc().width != colorTexture->getDesc().width || + depthTexture->getDesc().height != colorTexture->getDesc().height) + return; nvrhi::FramebufferDesc fbDesc; fbDesc.addColorAttachment(colorTexture); fbDesc.setDepthAttachment(depthTexture); @@ -3764,6 +3782,9 @@ void GPUCullingManager::BeginLevelLoad(u32 estimatedVertices, u32 estimatedIndic InvalidateStaticCullingData(); + if (m_rtAccelMgr) + m_rtAccelMgr->Invalidate(); + // Clear and pre-allocate mega-buffers m_megaVertices.clear(); m_megaVertices.reserve(estimatedVertices); diff --git a/src/Layers/xrRender/GPUCullingManager.h b/src/Layers/xrRender/GPUCullingManager.h index 6e7f3593d47..0ac6e68b2ed 100644 --- a/src/Layers/xrRender/GPUCullingManager.h +++ b/src/Layers/xrRender/GPUCullingManager.h @@ -64,6 +64,7 @@ enum GPUObjectFlags : u32 { GPU_OBJECT_OPAQUE = 0x1, GPU_OBJECT_ALPHA_TEST = 0x2, GPU_OBJECT_TRANSPARENT = 0x4, + GPU_OBJECT_WMARK = 0x8, }; // ═══════════════════════════════════════════════════════ @@ -260,6 +261,7 @@ class GPUCullingManager { const xr_vector& GetTerrainMaterialIDData() const { return m_terrainMaterialIDData; } const xr_vector& GetTransparentDrawArgsData() const { return m_transparentDrawArgsData; } const xr_vector& GetTransparentMaterialIDData() const { return m_transparentMaterialIDData; } + const xr_vector& GetTransparentInstanceData() const { return m_transparentInstanceData; } const xr_vector& GetStaticInstanceData() const { return m_staticInstanceData; } void SetRTAccelStructManager(RTAccelStructManager* mgr) { m_rtAccelMgr = mgr; } diff --git a/src/Layers/xrRender/Geometry/GeometryBatch.h b/src/Layers/xrRender/Geometry/GeometryBatch.h index 4a6821455d7..e7a885a786f 100644 --- a/src/Layers/xrRender/Geometry/GeometryBatch.h +++ b/src/Layers/xrRender/Geometry/GeometryBatch.h @@ -132,6 +132,11 @@ struct GeometryBatch { if (!visual || !visual->shaderName.size()) return false; + if (strstr(visual->shaderName.c_str(), "water") != nullptr) + return true; + if (strstr(visual->shaderName.c_str(), "wallmark") != nullptr) + return true; + return MaterialSystem::Instance() .GetMaterialInfo(visual->shaderName) .transparent; diff --git a/src/Layers/xrRender/Geometry/MaterialCache.cpp b/src/Layers/xrRender/Geometry/MaterialCache.cpp index 23097cd67fe..0b848bd425f 100644 --- a/src/Layers/xrRender/Geometry/MaterialCache.cpp +++ b/src/Layers/xrRender/Geometry/MaterialCache.cpp @@ -41,6 +41,25 @@ namespace xray::render { using namespace xray::render::fg; +static bool ParticleTexLooksEmissive(const char* tex) +{ + if (!tex) + return false; + return strstr(tex, "explosion") || strstr(tex, "grenade") || strstr(tex, "blast") + || strstr(tex, "ani-explosion") || strstr(tex, "glow") || strstr(tex, "fire") + || strstr(tex, "flame") || strstr(tex, "ani-fire") || strstr(tex, "flash") + || strstr(tex, "flare") || strstr(tex, "spark") || strstr(tex, "anomaly") + || strstr(tex, "heat") || strstr(tex, "zhar"); +} + +static float ParticleEmissiveIntensity(const char* tex) +{ + if (tex && (strstr(tex, "explosion") || strstr(tex, "grenade") + || strstr(tex, "blast") || strstr(tex, "ani-explosion"))) + return 6.0f; + return 4.5f; +} + @@ -633,11 +652,8 @@ nvrhi::BindingSetHandle MaterialCache::GetOrCreateBindingSet(MaterialPSO* matPSO return matPSO->vsBindingSet; } -u32 MaterialCache::GetVertexFormatID(dxRender_Visual* visual) +u32 MaterialCache::GetVertexFormatID(dxRender_Visual* /*visual*/) { - if (!visual) - return 0; - return 0; } @@ -712,12 +728,15 @@ MaterialPSO* MaterialCache::GetOrCreateUIPSO( (reinterpret_cast(dxShader->m_psHandle.Get()) << 1); } + const auto& fbInfo = framebuffer->getFramebufferInfo(); MaterialKey key; key.psoType = PSOType::UI; key.shader = nullptr; key.textureHash = shaderHash ^ (static_cast(topology) << 56); key.element = elementIndex; key.framebuffer = framebuffer; + key.colorFormat = fbInfo.colorFormats.empty() ? nvrhi::Format::UNKNOWN : fbInfo.colorFormats[0]; + key.depthFormat = fbInfo.depthFormat; auto it = m_cache.find(key); if (it != m_cache.end()) { @@ -836,16 +855,22 @@ MaterialPSO* MaterialCache::CreateUIPSO( } } + xr_map maxCbSizes; + for (const auto& cbInfo : pso->constantBuffers) + maxCbSizes[cbInfo.name] = std::max(maxCbSizes[cbInfo.name], cbInfo.size); + xr_map createdBuffers; for (auto& cbInfo : pso->constantBuffers) { auto it = createdBuffers.find(cbInfo.name); if (it != createdBuffers.end()) { cbInfo.nvrhiBuffer = it->second; + cbInfo.size = maxCbSizes[cbInfo.name]; continue; } + const u32 bufSize = maxCbSizes[cbInfo.name]; nvrhi::BufferDesc bufferDesc; - bufferDesc.byteSize = cbInfo.size; + bufferDesc.byteSize = bufSize; bufferDesc.isConstantBuffer = true; bufferDesc.debugName = make_string("UI_CB_%s", cbInfo.name.c_str()).c_str(); bufferDesc.keepInitialState = true; @@ -858,6 +883,7 @@ MaterialPSO* MaterialCache::CreateUIPSO( } cbInfo.nvrhiBuffer = buffer; + cbInfo.size = bufSize; createdBuffers[cbInfo.name] = buffer; } @@ -1059,6 +1085,7 @@ u32 MaterialCache::RegisterBindlessMaterial(MaterialPSO* matPSO) matData.alphaRef = 0.5f; matData.flags = 0; matData.shaderVariant = 0; + matData.lmapIndex = INVALID_TEXTURE_INDEX; if (matPSO->pass) { fg::STextureList* texList = matPSO->pass->T._get(); @@ -1128,7 +1155,6 @@ u32 MaterialCache::PreRegisterTerrainMaterial(dxRender_Visual* visual) matData.pbrG_Index = INVALID_TEXTURE_INDEX; matData.pbrB_Index = INVALID_TEXTURE_INDEX; matData.pbrA_Index = INVALID_TEXTURE_INDEX; - matData.flags = MAT_FLAG_TERRAIN; if (visual->textureName.size() > 0) { @@ -1297,6 +1323,8 @@ void MaterialCache::FinalizePendingTerrainMaterials(fg::RenderContext* ctx) if (idx != INVALID_TEXTURE_INDEX) { matData.pbrR_Index = idx; matData.flags |= MAT_FLAG_HAS_PBR_LAYER; + if (texDescMgr.UseSteepParallax(detailR)) + matData.flags |= MAT_FLAG_STEEP_PARALLAX; updated = true; } } @@ -1393,9 +1421,15 @@ u32 MaterialCache::PreRegisterBindlessMaterial(dxRender_Visual* visual) matData.alphaRef = 0.5f; matData.flags = 0; matData.shaderVariant = 0; + matData.lmapIndex = INVALID_TEXTURE_INDEX; if (visual->textureName.size() > 0) { - matData.detailScale = GetDetailScale(visual->textureName); + shared_str baseForScale = visual->textureName; + const char* p = baseForScale.c_str(); + if (const char* comma = strchr(p, ',')) + baseForScale = shared_str(xr_string(p, comma - p).c_str()); + if (baseForScale.size()) + matData.detailScale = GetDetailScale(baseForScale); } if (visual->shaderName.size() > 0) { @@ -1408,7 +1442,69 @@ u32 MaterialCache::PreRegisterBindlessMaterial(dxRender_Visual* visual) matData.flags |= MAT_FLAG_ALPHA_BLEND; } if (strstr(visual->shaderName.c_str(), "water") != nullptr) + { matData.flags |= MAT_FLAG_WATER; + matData.flags |= MAT_FLAG_ALPHA_BLEND; + } + const char* sh = visual->shaderName.c_str(); + const char* tex = visual->textureName.c_str(); + auto hasName = [](const char* s, const char* k) { return s && strstr(s, k) != nullptr; }; + const bool metalAlpha = hasName(sh, "metall") || hasName(sh, "metal") || hasName(tex, "metall") + || hasName(tex, "metal") || hasName(tex, "grate") || hasName(tex, "fence") + || hasName(tex, "grid") || hasName(tex, "setka") || hasName(tex, "zabor") + || hasName(tex, "rebar") || hasName(tex, "lattice") || hasName(tex, "netting") + || hasName(tex, "rast_"); + const bool foliageName = hasName(sh, "tree") || hasName(sh, "bush") || hasName(sh, "leaf") + || hasName(sh, "flora") || hasName(tex, "tree") || hasName(tex, "trees") + || hasName(tex, "leaf") || hasName(tex, "leaves") || hasName(tex, "bush") + || hasName(tex, "kust") || hasName(tex, "vetka") || hasName(tex, "flora") + || hasName(tex, "elka") || hasName(tex, "sosna"); + if (foliageName && !metalAlpha) + matData.flags |= MAT_FLAG_FOLIAGE; + const bool glassName = hasName(tex, "glas\\") || hasName(tex, "glas/") + || hasName(tex, "glass\\") || hasName(tex, "glass/") + || hasName(tex, "glasses") || hasName(tex, "head_glass") + || hasName(tex, "wind_transp") || hasName(sh, "xwindows") + || hasName(sh, "xmonolith") || hasName(sh, "xanomaly") + || hasName(sh, "pautina") || hasName(sh, "xdistort"); + if (glassName) + { + matData.flags |= MAT_FLAG_GLASS; + matData.flags |= MAT_FLAG_ALPHA_BLEND; + matData.flags |= MAT_FLAG_TWO_SIDED; + } + if (hasName(sh, "scope") || hasName(sh, "lense")) + matData.flags |= MAT_FLAG_SCOPE; + if (hasName(sh, "hud3d") || hasName(sh, "hud_p3d")) + { + matData.flags |= MAT_FLAG_HUD3D; + matData.flags |= MAT_FLAG_ALPHA_BLEND; + } + if (hasName(sh, "wallmark")) + { + matData.flags |= MAT_FLAG_WMARK; + matData.flags |= MAT_FLAG_ALPHA_BLEND; + } + if (strstr(sh, "lightplane") != nullptr) + { + matData.flags |= MAT_FLAG_ALPHA_BLEND; + matData.flags |= MAT_FLAG_ALPHA_TEST; + if (matData.alphaRef <= 0.0f) + matData.alphaRef = 0.5f / 255.0f; + } + const bool emissiveName = strstr(sh, "selflight") != nullptr + || strstr(sh, "glow") != nullptr + || (tex && (strstr(tex, "glow") != nullptr || strstr(tex, "selflight") != nullptr)); + if (emissiveName) + { + matData.flags |= MAT_FLAG_EMISSIVE; + float intens = 2.5f; + if (strstr(sh, "selflight") || (tex && strstr(tex, "selflight"))) + intens = 3.5f; + if (strstr(sh, "glow") || (tex && strstr(tex, "glow"))) + intens = 4.5f; + matData.emissiveIntensity = intens; + } matData.shaderVariant = matInfo.shaderVariant; matData.flags |= MAT_FLAG_HAS_NORMAL; } @@ -1457,8 +1553,15 @@ u32 MaterialCache::PreRegisterParticleMaterial(const shared_str& textureName) matData.pbrIndex = INVALID_TEXTURE_INDEX; matData.detailScale = 1.0f; matData.alphaRef = 0.01f / 255.0f; + matData.lmapIndex = INVALID_TEXTURE_INDEX; matData.flags = 0; matData.shaderVariant = 0; + const char* tex = textureName.c_str(); + if (ParticleTexLooksEmissive(tex)) + { + matData.flags |= MAT_FLAG_EMISSIVE; + matData.emissiveIntensity = ParticleEmissiveIntensity(tex); + } u32 materialID = materialBuffer.RegisterMaterial(matData); @@ -1515,6 +1618,16 @@ void MaterialCache::FinalizePendingMaterials(fg::RenderContext* ctx) MaterialData matData = *existingMat; bool updated = false; + if (!visual && pending.textureName.size()) { + const char* tex = pending.textureName.c_str(); + if (ParticleTexLooksEmissive(tex)) + { + matData.flags |= MAT_FLAG_EMISSIVE; + matData.emissiveIntensity = ParticleEmissiveIntensity(tex); + updated = true; + } + } + shared_str diffuseName; if (visual) { diffuseName = visual->textureName; @@ -1522,27 +1635,113 @@ void MaterialCache::FinalizePendingMaterials(fg::RenderContext* ctx) diffuseName = pending.textureName; } + const bool isWater = (matData.flags & MAT_FLAG_WATER) != 0; + if (isWater) + { + const char* shader = visual && visual->shaderName.size() ? visual->shaderName.c_str() : ""; + if (strstr(shader, "studen")) + diffuseName = "water\\water_studen"; + else if (strstr(shader, "ryaska")) + diffuseName = "water\\water_ryaska1"; + else + diffuseName = "water\\water_water"; + } + if (!diffuseName.size() || !diffuseName[0]) continue; + xr_vector texSlots; { - resources::TextureHandle handle = texManager->LoadTexture(diffuseName.c_str()); - if (handle.IsValid()) { - nvrhi::ITexture* nvrhiTex = texManager->GetNVRHITexture(handle); - if (nvrhiTex) { - u32 descriptorIndex = backend->RegisterBindlessTexture(nvrhiTex); - if (descriptorIndex != INVALID_TEXTURE_INDEX) { - matData.diffuseIndex = descriptorIndex; - updated = true; - } + const char* p = diffuseName.c_str(); + while (p && *p) + { + const char* comma = strchr(p, ','); + if (comma) + { + texSlots.emplace_back(p, comma - p); + p = comma + 1; + } + else + { + texSlots.emplace_back(p); + break; + } + } + } + + shared_str baseName = !texSlots.empty() ? shared_str(texSlots[0].c_str()) : diffuseName; + if (!baseName.size() || !baseName[0] || 0 == xr_strcmp(baseName.c_str(), "$null")) + { + if (texSlots.size() > 1 && texSlots[1].size() && 0 != xr_strcmp(texSlots[1].c_str(), "$null")) + baseName = shared_str(texSlots[1].c_str()); + } + + auto RegisterTex = [&](const char* name) -> u32 { + if (!name || !name[0] || 0 == xr_strcmp(name, "$null")) + return INVALID_TEXTURE_INDEX; + resources::TextureHandle handle = texManager->LoadTexture(name); + if (!handle.IsValid()) + return INVALID_TEXTURE_INDEX; + nvrhi::ITexture* nvrhiTex = texManager->GetNVRHITexture(handle); + if (!nvrhiTex) + return INVALID_TEXTURE_INDEX; + return backend->RegisterBindlessTexture(nvrhiTex); + }; + + { + u32 descriptorIndex = RegisterTex(baseName.c_str()); + if (descriptorIndex != INVALID_TEXTURE_INDEX) { + matData.diffuseIndex = descriptorIndex; + updated = true; + if (isWater) + Msg("* [MaterialCache] Water diffuse '%s' idx=%u", baseName.c_str(), descriptorIndex); + } else if (isWater) { + Msg("! [MaterialCache] Water diffuse FAILED '%s'", baseName.c_str()); + } + } + + if (!isWater) + { + const char* lmapName = nullptr; + for (const auto& slot : texSlots) + { + if (slot.size() >= 4 && 0 == strncmp(slot.c_str(), "lmap", 4)) + { + lmapName = slot.c_str(); + break; } } + if (lmapName) + { + u32 descriptorIndex = RegisterTex(lmapName); + if (descriptorIndex != INVALID_TEXTURE_INDEX) { + matData.lmapIndex = descriptorIndex; + matData.flags |= MAT_FLAG_HAS_LMAP; + updated = true; + } + } + } + + if ((matData.flags & MAT_FLAG_GLASS) != 0) + { + u32 descriptorIndex = RegisterTex("pfx" DELIMITER "pfx_dist_glass"); + if (descriptorIndex != INVALID_TEXTURE_INDEX) + { + matData.detailIndex = descriptorIndex; + matData.flags |= MAT_FLAG_HAS_DETAIL; + updated = true; + } } auto& texDescMgr = TextureDescr; - shared_str bumpName = texDescMgr.GetBumpName(diffuseName); + shared_str bumpName = isWater ? shared_str("water\\water_water_bump") + : texDescMgr.GetBumpName(baseName); if (bumpName.size() && bumpName[0]) { resources::TextureHandle handle = texManager->LoadTexture(bumpName.c_str()); + if (isWater && !handle.IsValid()) + handle = texManager->LoadTexture("water\\water_normal"); + if (isWater && !handle.IsValid()) + handle = texManager->LoadTexture("fx\\water_normal"); if (handle.IsValid()) { nvrhi::ITexture* nvrhiTex = texManager->GetNVRHITexture(handle); if (nvrhiTex) { @@ -1556,39 +1755,38 @@ void MaterialCache::FinalizePendingMaterials(fg::RenderContext* ctx) } } - LPCSTR detailTexName = nullptr; - if (texDescMgr.GetDetailTexture(diffuseName, detailTexName)) { - if (detailTexName && detailTexName[0]) { - resources::TextureHandle handle = texManager->LoadTexture(detailTexName); - if (handle.IsValid()) { - nvrhi::ITexture* nvrhiTex = texManager->GetNVRHITexture(handle); - if (nvrhiTex) { - u32 descriptorIndex = backend->RegisterBindlessTexture(nvrhiTex); - if (descriptorIndex != INVALID_TEXTURE_INDEX) { - matData.detailIndex = descriptorIndex; - matData.detailScale = texDescMgr.GetDetailScale(diffuseName); - matData.flags |= MAT_FLAG_HAS_DETAIL; - updated = true; - } + if (isWater) { + u32 descriptorIndex = RegisterTex("water\\water_dudv"); + if (descriptorIndex != INVALID_TEXTURE_INDEX) { + matData.detailIndex = descriptorIndex; + matData.flags |= MAT_FLAG_HAS_DETAIL; + updated = true; + } + } else { + LPCSTR detailTexName = nullptr; + if (texDescMgr.GetDetailTexture(baseName, detailTexName)) { + if (detailTexName && detailTexName[0]) { + u32 descriptorIndex = RegisterTex(detailTexName); + if (descriptorIndex != INVALID_TEXTURE_INDEX) { + matData.detailIndex = descriptorIndex; + matData.detailScale = texDescMgr.GetDetailScale(baseName); + matData.flags |= MAT_FLAG_HAS_DETAIL; + updated = true; } } } } - if (diffuseName.c_str() && diffuseName[0]) { - shared_str pbrName = texDescMgr.GetPBRName(diffuseName); + if (!isWater && baseName.c_str() && baseName[0]) { + shared_str pbrName = texDescMgr.GetPBRName(baseName); if (!pbrName.empty()) { - resources::TextureHandle handle = texManager->LoadTexture(pbrName.c_str()); - if (handle.IsValid()) { - nvrhi::ITexture* nvrhiTex = texManager->GetNVRHITexture(handle); - if (nvrhiTex) { - u32 descriptorIndex = backend->RegisterBindlessTexture(nvrhiTex); - if (descriptorIndex != INVALID_TEXTURE_INDEX) { - matData.pbrIndex = descriptorIndex; - matData.flags |= MAT_FLAG_HAS_PBR; - updated = true; - } - } + u32 descriptorIndex = RegisterTex(pbrName.c_str()); + if (descriptorIndex != INVALID_TEXTURE_INDEX) { + matData.pbrIndex = descriptorIndex; + matData.flags |= MAT_FLAG_HAS_PBR; + if (texDescMgr.UseSteepParallax(baseName)) + matData.flags |= MAT_FLAG_STEEP_PARALLAX; + updated = true; } } } diff --git a/src/Layers/xrRender/Geometry/MaterialCache.h b/src/Layers/xrRender/Geometry/MaterialCache.h index e370362fe87..604ccfc99ac 100644 --- a/src/Layers/xrRender/Geometry/MaterialCache.h +++ b/src/Layers/xrRender/Geometry/MaterialCache.h @@ -69,6 +69,8 @@ struct MaterialKey { u32 element; nvrhi::IFramebuffer* framebuffer; + nvrhi::Format colorFormat; + nvrhi::Format depthFormat; MaterialKey() : psoType(PSOType::Material) @@ -77,6 +79,8 @@ struct MaterialKey { , stateHash(0) , element(0) , framebuffer(nullptr) + , colorFormat(nvrhi::Format::UNKNOWN) + , depthFormat(nvrhi::Format::UNKNOWN) { } @@ -87,6 +91,8 @@ struct MaterialKey { , stateHash(stHash) , element(0) , framebuffer(nullptr) + , colorFormat(nvrhi::Format::UNKNOWN) + , depthFormat(nvrhi::Format::UNKNOWN) { } @@ -95,7 +101,9 @@ struct MaterialKey { if (psoType == PSOType::UI) { if (textureHash != other.textureHash) return textureHash < other.textureHash; - return element < other.element; + if (element != other.element) return element < other.element; + if (colorFormat != other.colorFormat) return colorFormat < other.colorFormat; + return depthFormat < other.depthFormat; } if (psoType == PSOType::Depth) { @@ -114,7 +122,9 @@ struct MaterialKey { if (psoType == PSOType::UI) { return textureHash == other.textureHash && - element == other.element; + element == other.element && + colorFormat == other.colorFormat && + depthFormat == other.depthFormat; } if (psoType == PSOType::Depth) { diff --git a/src/Layers/xrRender/ImGuiRendererNVRHI.h b/src/Layers/xrRender/ImGuiRendererNVRHI.h index 44d606bc820..2dc100146e9 100644 --- a/src/Layers/xrRender/ImGuiRendererNVRHI.h +++ b/src/Layers/xrRender/ImGuiRendererNVRHI.h @@ -71,6 +71,8 @@ class ImGuiRendererNVRHI : public IImGuiRender { // Constants structure for projection matrix struct ImGuiConstants { float mvpMatrix[4][4]; + float uiScale; + float uiPad[3]; }; protected: diff --git a/src/Layers/xrRender/ImGuiRendererNVRHI_Render.cpp b/src/Layers/xrRender/ImGuiRendererNVRHI_Render.cpp index ef34328ac92..fa959c02957 100644 --- a/src/Layers/xrRender/ImGuiRendererNVRHI_Render.cpp +++ b/src/Layers/xrRender/ImGuiRendererNVRHI_Render.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "ImGuiRendererNVRHI.h" +#include #include "Layers/xrRender/FrameGraphPasses/ShaderConstants.h" #include "FrameGraph/BindingSetBuilder.h" #include "FrameGraph/ShaderCache.h" @@ -123,7 +124,10 @@ void ImGuiRendererNVRHI::SetupRenderState(ImDrawData* drawData, nvrhi::ICommandL float T = drawData->DisplayPos.y; float B = drawData->DisplayPos.y + drawData->DisplaySize.y; - ImGuiConstants constants; + ImGuiConstants constants{}; + constants.uiScale = 1.f; + if (GEnv.Backend && GEnv.Backend->IsHdr10()) + constants.uiScale = ps_r_hdr10_hud / std::max(ps_r_hdr10_paper_white, 1.f); constants.mvpMatrix[0][0] = 2.0f / (R - L); constants.mvpMatrix[0][1] = 0.0f; constants.mvpMatrix[0][2] = 0.0f; diff --git a/src/Layers/xrRender/Materials/MaterialSystem.cpp b/src/Layers/xrRender/Materials/MaterialSystem.cpp index 673506b6fef..bf02d33094b 100644 --- a/src/Layers/xrRender/Materials/MaterialSystem.cpp +++ b/src/Layers/xrRender/Materials/MaterialSystem.cpp @@ -135,6 +135,18 @@ const MaterialSystem::MaterialInfo& MaterialSystem::GetMaterialInfo(const shared } } + if (strstr(shaderName, "water")) + info.transparent = true; + if (strstr(shaderName, "lightplane")) + info.transparent = true; + if (strstr(shaderName, "xwindows") || strstr(shaderName, "xmonolith") + || strstr(shaderName, "xanomaly") || strstr(shaderName, "pautina") + || strstr(shaderName, "xdistort") || strstr(shaderName, "hud3d") + || strstr(shaderName, "hud_p3d")) + info.transparent = true; + if (strstr(shaderName, "wallmark")) + info.transparent = true; + m_materialCache[key] = info; return m_materialCache[key]; } diff --git a/src/Layers/xrRender/Profiler/StatsOverlay.cpp b/src/Layers/xrRender/Profiler/StatsOverlay.cpp index 76c8706d1e4..c22aec082cc 100644 --- a/src/Layers/xrRender/Profiler/StatsOverlay.cpp +++ b/src/Layers/xrRender/Profiler/StatsOverlay.cpp @@ -4,10 +4,14 @@ #include "xrCore/MemoryStats.h" #include "xrEngine/device.h" #include "xrEngine/IRenderBackend.h" +#include "Layers/xrRender/Upscaling/StreamlineDLSS.h" #include #include #include +extern ENGINE_API int ps_r_upscale; +extern ENGINE_API int ps_r_dlss_fg; + static bool FormatSubmitThreadLine(char* buf, size_t size) { IRenderBackend::SubmitThreadTimings t; @@ -124,6 +128,22 @@ void StatsOverlay::Render() float fps = cpuFrameTime > 0.0f ? 1000.0f / cpuFrameTime : 0.0f; ImGui::Text("Frame: %s (%.1f FPS)", FormatTime(cpuFrameTime), fps); + { + const bool fgWant = ps_r_upscale == 2 && ps_r_dlss_fg != 0; + const bool fgAvail = xray::render::fg::Streamline_IsFGAvailable(); + const bool fgEval = xray::render::fg::Streamline_FgEvaluatedLastFrame(); + const bool fgPres = xray::render::fg::Streamline_FgPresentedLastFrame(); + const float fpsFg = Device.GetStats().fFPS_FG; + if (fgWant || fgAvail || fgEval || fgPres || fpsFg > 1.f) + { + ImGui::Text("DLSS-FG: %s | after FG: %.1f FPS", + !fgAvail ? "unavailable" : + !fgWant ? "off" : + fgPres ? "presenting x2" : + fgEval ? "eval (present failed)" : "enabled", + fpsFg > 1.f ? fpsFg : (fgPres ? fps * 2.f : fps)); + } + } if (!ideActive) { ImGui::TextDisabled("(Press editor key to interact)"); diff --git a/src/Layers/xrRender/RayTracing/RTAccelStructManager.cpp b/src/Layers/xrRender/RayTracing/RTAccelStructManager.cpp index 55623c3165a..b8ff9f66972 100644 --- a/src/Layers/xrRender/RayTracing/RTAccelStructManager.cpp +++ b/src/Layers/xrRender/RayTracing/RTAccelStructManager.cpp @@ -11,6 +11,7 @@ #include "Layers/xrRender/SkeletonCustom.h" #include "Layers/xrRender/ShaderVariant/VariantPSOCache.h" #include "Layers/xrRender/FGDetailManager.h" +#include "Layers/xrRender/FrameGraphPasses/ParticlePassSetup.h" #include "Layers/xrRender/FrameGraph/ShaderLoader.h" #include "xrEngine/IGame_Persistent.h" #include "xrEngine/Environment.h" @@ -19,6 +20,7 @@ extern ENGINE_API float ps_r3_grass_blade_width; extern ENGINE_API float ps_r3_grass_blade_height; extern ENGINE_API float ps_r3_grass_wind_displacement; +extern ENGINE_API float ps_r_rt_detail_dist; namespace xray::render::fg { extern xray::render::FrameGraphRenderer RImplementation; @@ -28,6 +30,19 @@ namespace xray::render::fg { extern int ps_r__detail_gpu; +static bool MatrixEquals(const Fmatrix& a, const Fmatrix& b) +{ + for (int r = 0; r < 4; ++r) + { + for (int c = 0; c < 4; ++c) + { + if (a.m[r][c] != b.m[r][c]) + return false; + } + } + return true; +} + nvrhi::ComputePipelineHandle RTAccelStructManager::s_skinPipeline; nvrhi::BindingLayoutHandle RTAccelStructManager::s_skinLayout; fg::BufferHandle RTAccelStructManager::s_skinCB; @@ -42,6 +57,7 @@ bool RTAccelStructManager::s_grassInitialized = false; nvrhi::ComputePipelineHandle RTAccelStructManager::s_billboardPipeline; nvrhi::BindingLayoutHandle RTAccelStructManager::s_billboardLayout; fg::BufferHandle RTAccelStructManager::s_billboardCB; +nvrhi::BufferHandle s_billboardCount; bool RTAccelStructManager::s_billboardInitialized = false; struct RTSkinningCB { @@ -75,9 +91,16 @@ static_assert(sizeof(GrassRTCB) == 96, "GrassRTCB must be 96 bytes"); struct BillboardRTCB { u32 maxVertsPerBillboard; - u32 pad[3]; + u32 maxBillboards; + float windAngleDeg; + float windSpeed; + float time; + float windDisplacement; + u32 pad[2]; + float cameraPos[3]; + float maxDistanceSq; }; -static_assert(sizeof(BillboardRTCB) == 16, "BillboardRTCB must be 16 bytes"); +static_assert(sizeof(BillboardRTCB) == 48, "BillboardRTCB must be 48 bytes"); static void FmatrixToRTTransform(const Fmatrix& m, nvrhi::rt::AffineTransform& out) { @@ -100,16 +123,49 @@ void RTAccelStructManager::Initialize(fg::RenderDevice* device) Msg("* [RT] Ray tracing NOT supported on this device"); } +nvrhi::rt::IAccelStruct* RTAccelStructManager::GetOrCreateEmptyTLAS(nvrhi::ICommandList* cmd) +{ + if (m_emptyTlas) + return m_emptyTlas; + if (!m_rtSupported || !m_device) + return nullptr; + nvrhi::IDevice* nv = m_device->GetNVRHIDevice(); + if (!nv) + return nullptr; + nvrhi::rt::AccelStructDesc desc; + desc.debugName = "EmptyTLAS"; + desc.isTopLevel = true; + desc.topLevelMaxInstances = 1; + desc.buildFlags = nvrhi::rt::AccelStructBuildFlags::PreferFastTrace; + m_emptyTlas = nv->createAccelStruct(desc); + if (m_emptyTlas && cmd) + cmd->buildTopLevelAccelStruct(m_emptyTlas, nullptr, 0); + return m_emptyTlas; +} + void RTAccelStructManager::Shutdown() { m_staticBlas = nullptr; m_uniqueGeometries.clear(); m_tlas = nullptr; + m_emptyTlas = nullptr; + for (u32 i = 0; i < 3; ++i) { + m_tlasSlots[i] = nullptr; + m_tlasMaxInstances[i] = 0; + m_batchInfoSlots[i] = nullptr; + m_skinnedSlots[i] = {}; + m_grassSlots[i] = {}; + } + m_tlasSlot = 0; + m_batchInfoSlot = 0; + m_skinnedSlot = 0; + m_grassSlot = 0; m_batchInfoBuffer = nullptr; - m_skinnedOutputVB = nullptr; - m_skinnedIB = nullptr; m_skinnedBlas = nullptr; m_skinnedBatchData.clear(); + m_skinnedTotalVerts = 0; + m_skinnedTotalIndices = 0; + m_skinnedTopoHash = 0; m_skinnedReady = false; m_grassOutputVB = nullptr; m_grassIB = nullptr; @@ -117,6 +173,12 @@ void RTAccelStructManager::Shutdown() m_grassTotalVerts = 0; m_grassTotalIndices = 0; m_grassReady = false; + m_particleOutputVB = nullptr; + m_particleIB = nullptr; + m_particleBlas = nullptr; + m_particleTotalVerts = 0; + m_particleTotalIndices = 0; + m_particleReady = false; m_isReady = false; m_batchCount = 0; m_batchCounts = {}; @@ -135,6 +197,7 @@ void RTAccelStructManager::Shutdown() s_billboardPipeline = nullptr; s_billboardLayout = nullptr; s_billboardCB = fg::BufferHandle(); + s_billboardCount = nullptr; s_billboardInitialized = false; } @@ -154,30 +217,99 @@ void RTAccelStructManager::InvalidateShaderPipelines() s_billboardPipeline = nullptr; s_billboardLayout = nullptr; s_billboardCB = fg::BufferHandle(); + s_billboardCount = nullptr; s_billboardInitialized = false; Msg("* [RTAccel] Shader pipelines invalidated for hot-reload"); } +void RTAccelStructManager::Invalidate() +{ + m_staticBlas = nullptr; + m_uniqueGeometries.clear(); + m_tlas = nullptr; + for (u32 i = 0; i < 3; ++i) { + m_tlasSlots[i] = nullptr; + m_tlasMaxInstances[i] = 0; + m_batchInfoSlots[i] = nullptr; + m_skinnedSlots[i] = {}; + m_grassSlots[i] = {}; + } + m_tlasSlot = 0; + m_batchInfoSlot = 0; + m_skinnedSlot = 0; + m_grassSlot = 0; + m_batchInfoBuffer = nullptr; + m_megaVB = nullptr; + m_megaIB = nullptr; + m_skinnedBlas = nullptr; + m_skinnedBatchData.clear(); + m_skinnedTotalVerts = 0; + m_skinnedTotalIndices = 0; + m_skinnedTopoHash = 0; + m_skinnedReady = false; + m_grassOutputVB = nullptr; + m_grassIB = nullptr; + m_grassBlas = nullptr; + m_grassTotalVerts = 0; + m_grassTotalIndices = 0; + m_grassReady = false; + m_particleOutputVB = nullptr; + m_particleIB = nullptr; + m_particleBlas = nullptr; + m_particleTotalVerts = 0; + m_particleTotalIndices = 0; + m_particleReady = false; + m_isReady = false; + m_batchCount = 0; + m_batchCounts = {}; + Msg("* [RT] Acceleration structures invalidated"); +} + void RTAccelStructManager::BuildIfNeeded(nvrhi::ICommandList* cmdList, GPUCullingManager* gpuCulling) { if (m_isReady || !m_rtSupported || !gpuCulling) return; - if (!gpuCulling->IsMegaDataUploaded()) + if (!gpuCulling->IsMegaDataUploaded()) { + static bool s_logged = false; + if (!s_logged) { + s_logged = true; + Msg("* [RT] BuildIfNeeded waiting: mega data not uploaded yet"); + } return; + } const auto& drawArgs = gpuCulling->GetStaticDrawArgsData(); - if (drawArgs.empty()) + if (drawArgs.empty()) { + static bool s_logged = false; + if (!s_logged) { + s_logged = true; + Msg("* [RT] BuildIfNeeded waiting: static draw args empty"); + } return; + } m_megaVB = gpuCulling->GetMegaVertexBuffer(); m_megaIB = gpuCulling->GetMegaIndexBuffer(); + if (!m_megaVB || !m_megaIB) { + Msg("! [RT] BuildIfNeeded: mega VB/IB null"); + return; + } + + cmdList->setBufferState(m_megaVB, nvrhi::ResourceStates::AccelStructBuildInput); + cmdList->setBufferState(m_megaIB, nvrhi::ResourceStates::AccelStructBuildInput); + cmdList->commitBarriers(); BuildStaticBLAS(cmdList, gpuCulling); BuildInstancedBLAS(cmdList, gpuCulling); BuildTLAS(cmdList); - if (!m_tlas) return; + if (!m_tlas) { + m_staticBlas = nullptr; + m_uniqueGeometries.clear(); + Msg("! [RT] BuildIfNeeded: TLAS create/build failed"); + return; + } CreateBatchInfoBuffer(cmdList, gpuCulling); @@ -199,7 +331,11 @@ static u32 AddBatchesToBLAS( nvrhi::IBuffer* megaVB, nvrhi::IBuffer* megaIB, u32 totalVertexCount, u32 vertexStride, const xr_vector* vertexCounts = nullptr, - const xr_vector* skipMask = nullptr) + const xr_vector* skipMask = nullptr, + const xr_vector* instances = nullptr, + bool forceNonOpaque = false, + const xr_vector* materialIDs = nullptr, + xr_vector* outInfos = nullptr) { u32 count = 0; for (u32 i = 0; i < (u32)drawArgs.size(); i++) { @@ -207,11 +343,13 @@ static u32 AddBatchesToBLAS( continue; const auto& args = drawArgs[i]; - if (args.indexCountPerInstance == 0) + if (args.indexCountPerInstance < 3) continue; u32 baseVert = static_cast(args.baseVertexLocation); u32 batchVertexCount = (vertexCounts && i < vertexCounts->size()) ? (*vertexCounts)[i] : (totalVertexCount - baseVert); + if (batchVertexCount == 0 || baseVert >= totalVertexCount || batchVertexCount > (totalVertexCount - baseVert)) + continue; nvrhi::rt::GeometryTriangles tri; tri.setIndexBuffer(megaIB) @@ -224,11 +362,29 @@ static u32 AddBatchesToBLAS( .setVertexOffset(static_cast(baseVert) * vertexStride) .setVertexCount(batchVertexCount); + if (instances && i < instances->size() + && ((*instances)[i].flags & GPU_OBJECT_WMARK) != 0) + continue; + + bool nonOpaque = forceNonOpaque; + if (!nonOpaque && instances && i < instances->size()) { + const u32 flags = (*instances)[i].flags; + nonOpaque = (flags & (GPU_OBJECT_ALPHA_TEST | GPU_OBJECT_TRANSPARENT)) != 0; + } + nvrhi::rt::GeometryDesc geom; geom.setTriangles(tri) - .setFlags(nvrhi::rt::GeometryFlags::Opaque); + .setFlags(nonOpaque ? nvrhi::rt::GeometryFlags::None : nvrhi::rt::GeometryFlags::Opaque); blasDesc.addBottomLevelGeometry(geom); + if (outInfos) { + RTBatchInfo info; + info.materialID = (materialIDs && i < materialIDs->size()) ? (*materialIDs)[i] : 0; + info.startIndex = args.startIndexLocation; + info.baseVertex = args.baseVertexLocation; + info.indexCount = args.indexCountPerInstance; + outInfos->push_back(info); + } count++; } return count; @@ -245,21 +401,35 @@ void RTAccelStructManager::BuildStaticBLAS(nvrhi::ICommandList* cmdList, GPUCull xr_vector isTransformed(staticDrawArgs.size(), false); for (u32 i = 0; i < (u32)staticDrawArgs.size(); i++) { - if (i < staticInstances.size() && memcmp(&staticInstances[i].world, &Fidentity, sizeof(Fmatrix)) != 0) + if (i < staticInstances.size() && !MatrixEquals(staticInstances[i].world, Fidentity)) isTransformed[i] = true; } nvrhi::rt::AccelStructDesc blasDesc; blasDesc.debugName = "StaticSceneBLAS"; - blasDesc.buildFlags = nvrhi::rt::AccelStructBuildFlags::PreferFastTrace | - nvrhi::rt::AccelStructBuildFlags::AllowCompaction; + blasDesc.buildFlags = nvrhi::rt::AccelStructBuildFlags::PreferFastTrace; + m_staticGeomInfos.clear(); m_batchCounts.identityStatic = AddBatchesToBLAS(blasDesc, staticDrawArgs, - m_megaVB, m_megaIB, totalVerts, vertexStride, &staticVertCounts, &isTransformed); + m_megaVB, m_megaIB, totalVerts, vertexStride, &staticVertCounts, &isTransformed, + &staticInstances, false, &gpuCulling->GetStaticMaterialIDData(), &m_staticGeomInfos); m_batchCounts.terrain = AddBatchesToBLAS(blasDesc, gpuCulling->GetTerrainDrawArgsData(), - m_megaVB, m_megaIB, totalVerts, vertexStride); + m_megaVB, m_megaIB, totalVerts, vertexStride, nullptr, nullptr, nullptr, false, + &gpuCulling->GetTerrainMaterialIDData(), &m_staticGeomInfos); m_batchCounts.transparent = AddBatchesToBLAS(blasDesc, gpuCulling->GetTransparentDrawArgsData(), - m_megaVB, m_megaIB, totalVerts, vertexStride); + m_megaVB, m_megaIB, totalVerts, vertexStride, nullptr, nullptr, + &gpuCulling->GetTransparentInstanceData(), true, + &gpuCulling->GetTransparentMaterialIDData(), &m_staticGeomInfos); + + m_staticHasAlpha = m_batchCounts.transparent > 0; + for (u32 i = 0; i < (u32)staticInstances.size(); i++) { + if (i < isTransformed.size() && isTransformed[i]) + continue; + if ((staticInstances[i].flags & (GPU_OBJECT_ALPHA_TEST | GPU_OBJECT_TRANSPARENT)) != 0) { + m_staticHasAlpha = true; + break; + } + } u32 staticTotal = m_batchCounts.identityStatic + m_batchCounts.terrain + m_batchCounts.transparent; if (staticTotal == 0) return; @@ -268,7 +438,6 @@ void RTAccelStructManager::BuildStaticBLAS(nvrhi::ICommandList* cmdList, GPUCull if (!m_staticBlas) return; nvrhi::utils::BuildBottomLevelAccelStruct(cmdList, m_staticBlas, blasDesc); - cmdList->compactBottomLevelAccelStructs(); } void RTAccelStructManager::BuildInstancedBLAS(nvrhi::ICommandList* cmdList, GPUCullingManager* gpuCulling) @@ -286,7 +455,8 @@ void RTAccelStructManager::BuildInstancedBLAS(nvrhi::ICommandList* cmdList, GPUC for (u32 i = 0; i < (u32)staticDrawArgs.size(); i++) { if (i >= staticInstances.size()) continue; - if (memcmp(&staticInstances[i].world, &Fidentity, sizeof(Fmatrix)) == 0) continue; + if ((staticInstances[i].flags & GPU_OBJECT_WMARK) != 0) continue; + if (MatrixEquals(staticInstances[i].world, Fidentity)) continue; const auto& args = staticDrawArgs[i]; if (args.indexCountPerInstance == 0) continue; @@ -295,17 +465,25 @@ void RTAccelStructManager::BuildInstancedBLAS(nvrhi::ICommandList* cmdList, GPUC InstanceInfo inst; inst.world = staticInstances[i].world; inst.materialID = (i < staticMaterialIDs.size()) ? staticMaterialIDs[i] : 0; + inst.flags = staticInstances[i].flags; auto it = keyToIndex.find(key); if (it != keyToIndex.end()) { m_uniqueGeometries[it->second].instances.push_back(inst); + if ((inst.flags & (GPU_OBJECT_ALPHA_TEST | GPU_OBJECT_TRANSPARENT)) != 0) + m_uniqueGeometries[it->second].alphaGeometry = true; } else { u32 baseVert = static_cast(args.baseVertexLocation); u32 batchVertexCount = (i < staticVertCounts.size()) ? staticVertCounts[i] : (totalVerts - baseVert); + if (batchVertexCount == 0 || args.indexCountPerInstance < 3) + continue; + if (baseVert >= totalVerts || batchVertexCount > (totalVerts - baseVert)) + continue; UniqueGeometry ug; ug.key = key; ug.vertexCount = batchVertexCount; + ug.alphaGeometry = (inst.flags & (GPU_OBJECT_ALPHA_TEST | GPU_OBJECT_TRANSPARENT)) != 0; ug.instances.push_back(inst); keyToIndex[key] = (u32)m_uniqueGeometries.size(); m_uniqueGeometries.push_back(std::move(ug)); @@ -315,8 +493,7 @@ void RTAccelStructManager::BuildInstancedBLAS(nvrhi::ICommandList* cmdList, GPUC for (auto& ug : m_uniqueGeometries) { nvrhi::rt::AccelStructDesc blasDesc; blasDesc.debugName = "InstancedBLAS"; - blasDesc.buildFlags = nvrhi::rt::AccelStructBuildFlags::PreferFastTrace | - nvrhi::rt::AccelStructBuildFlags::AllowCompaction; + blasDesc.buildFlags = nvrhi::rt::AccelStructBuildFlags::PreferFastTrace; nvrhi::rt::GeometryTriangles tri; tri.setIndexBuffer(m_megaIB) @@ -330,7 +507,7 @@ void RTAccelStructManager::BuildInstancedBLAS(nvrhi::ICommandList* cmdList, GPUC .setVertexCount(ug.vertexCount); nvrhi::rt::GeometryDesc geom; - geom.setTriangles(tri).setFlags(nvrhi::rt::GeometryFlags::Opaque); + geom.setTriangles(tri).setFlags(ug.alphaGeometry ? nvrhi::rt::GeometryFlags::None : nvrhi::rt::GeometryFlags::Opaque); blasDesc.addBottomLevelGeometry(geom); ug.blas = nvDevice->createAccelStruct(blasDesc); @@ -339,8 +516,6 @@ void RTAccelStructManager::BuildInstancedBLAS(nvrhi::ICommandList* cmdList, GPUC nvrhi::utils::BuildBottomLevelAccelStruct(cmdList, ug.blas, blasDesc); } - cmdList->compactBottomLevelAccelStructs(); - u32 totalInstances = 0; for (const auto& ug : m_uniqueGeometries) totalInstances += (u32)ug.instances.size(); @@ -360,22 +535,33 @@ void RTAccelStructManager::BuildTLAS(nvrhi::ICommandList* cmdList) u32 skinnedCount = m_skinnedReady ? (u32)m_skinnedBatchData.size() : 0; u32 grassCount = m_grassReady ? 1 : 0; - u32 instanceCount = (m_staticBlas ? 1 : 0) + totalInstances + (m_skinnedBlas ? 1 : 0) + (m_grassBlas ? 1 : 0); + u32 particleCount = m_particleReady ? 1 : 0; + u32 instanceCount = (m_staticBlas ? 1 : 0) + totalInstances + (m_skinnedBlas ? 1 : 0) + + (m_grassBlas ? 1 : 0) + (m_particleBlas ? 1 : 0); if (instanceCount == 0) { Msg("! [RT] No BLAS to build TLAS from"); return; } - nvrhi::rt::AccelStructDesc tlasDesc; - tlasDesc.debugName = "SceneTLAS"; - tlasDesc.isTopLevel = true; - tlasDesc.topLevelMaxInstances = instanceCount; - tlasDesc.buildFlags = nvrhi::rt::AccelStructBuildFlags::PreferFastTrace; - - m_tlas = nvDevice->createAccelStruct(tlasDesc); - if (!m_tlas) { - Msg("! [RT] Failed to create TLAS"); - return; + m_tlasSlot = (m_tlasSlot + 1u) % 3u; + auto& tlasSlot = m_tlasSlots[m_tlasSlot]; + u32& maxInst = m_tlasMaxInstances[m_tlasSlot]; + u32 neededMax = instanceCount + 64u; + if (neededMax < 4096u) + neededMax = 4096u; + if (!tlasSlot || maxInst < instanceCount) { + maxInst = neededMax; + nvrhi::rt::AccelStructDesc tlasDesc; + tlasDesc.debugName = "SceneTLAS"; + tlasDesc.isTopLevel = true; + tlasDesc.topLevelMaxInstances = maxInst; + tlasDesc.buildFlags = nvrhi::rt::AccelStructBuildFlags::PreferFastBuild; + tlasSlot = nvDevice->createAccelStruct(tlasDesc); + if (!tlasSlot) { + Msg("! [RT] Failed to create TLAS"); + m_tlas = nullptr; + return; + } } xr_vector instances; @@ -387,8 +573,8 @@ void RTAccelStructManager::BuildTLAS(nvrhi::ICommandList* cmdList) nvrhi::rt::InstanceDesc inst; inst.setTransform(nvrhi::rt::c_IdentityTransform) .setInstanceID(0) - .setInstanceMask(0x01) - .setFlags(nvrhi::rt::InstanceFlags::ForceOpaque) + .setInstanceMask(kRTMaskScene) + .setFlags(nvrhi::rt::InstanceFlags::TriangleCullDisable) .setBLAS(m_staticBlas); instances.push_back(inst); } @@ -405,8 +591,8 @@ void RTAccelStructManager::BuildTLAS(nvrhi::ICommandList* cmdList) FmatrixToRTTransform(instInfo.world, xform); inst.setTransform(xform) .setInstanceID(instancedBatchOffset) - .setInstanceMask(0x01) - .setFlags(nvrhi::rt::InstanceFlags::ForceOpaque) + .setInstanceMask(kRTMaskScene) + .setFlags(nvrhi::rt::InstanceFlags::TriangleCullDisable) .setBLAS(ug.blas); instances.push_back(inst); instancedBatchOffset++; @@ -418,7 +604,7 @@ void RTAccelStructManager::BuildTLAS(nvrhi::ICommandList* cmdList) nvrhi::rt::InstanceDesc inst; inst.setTransform(nvrhi::rt::c_IdentityTransform) .setInstanceID(skinnedBatchOffset) - .setInstanceMask(0x01) + .setInstanceMask(kRTMaskScene) .setFlags(nvrhi::rt::InstanceFlags::ForceOpaque) .setBLAS(m_skinnedBlas); instances.push_back(inst); @@ -426,61 +612,40 @@ void RTAccelStructManager::BuildTLAS(nvrhi::ICommandList* cmdList) u32 grassBatchOffset = skinnedBatchOffset + skinnedCount; if (m_grassBlas && grassCount > 0) { - auto grassInstFlags = m_grassBillboardMode - ? nvrhi::rt::InstanceFlags::TriangleCullDisable - : nvrhi::rt::InstanceFlags::ForceOpaque; nvrhi::rt::InstanceDesc inst; inst.setTransform(nvrhi::rt::c_IdentityTransform) .setInstanceID(grassBatchOffset) - .setInstanceMask(0x01) - .setFlags(grassInstFlags) + .setInstanceMask(kRTMaskGrass) + .setFlags(nvrhi::rt::InstanceFlags::TriangleCullDisable) .setBLAS(m_grassBlas); instances.push_back(inst); } - m_batchCount = grassBatchOffset + grassCount; - cmdList->buildTopLevelAccelStruct(m_tlas, instances.data(), (u32)instances.size()); -} - -static void AppendBatchInfos( - xr_vector& out, - const xr_vector& drawArgs, - const xr_vector& materialIDs, - const xr_vector* skipMask = nullptr) -{ - for (u32 i = 0; i < (u32)drawArgs.size(); i++) { - if (skipMask && i < skipMask->size() && (*skipMask)[i]) - continue; - if (drawArgs[i].indexCountPerInstance == 0) - continue; - RTBatchInfo info; - info.materialID = (i < materialIDs.size()) ? materialIDs[i] : 0; - info.startIndex = drawArgs[i].startIndexLocation; - info.baseVertex = drawArgs[i].baseVertexLocation; - info.indexCount = drawArgs[i].indexCountPerInstance; - out.push_back(info); + u32 particleBatchOffset = grassBatchOffset + grassCount; + if (m_particleBlas && particleCount > 0) { + nvrhi::rt::InstanceDesc inst; + inst.setTransform(nvrhi::rt::c_IdentityTransform) + .setInstanceID(particleBatchOffset) + .setInstanceMask(kRTMaskParticles) + .setFlags(nvrhi::rt::InstanceFlags::TriangleCullDisable) + .setBLAS(m_particleBlas); + instances.push_back(inst); } + + m_batchCount = particleBatchOffset + particleCount; + cmdList->buildTopLevelAccelStruct(tlasSlot, instances.data(), (u32)instances.size()); + m_tlas = tlasSlot; } void RTAccelStructManager::CreateBatchInfoBuffer(nvrhi::ICommandList* cmdList, GPUCullingManager* gpuCulling) { + (void)gpuCulling; nvrhi::IDevice* nvDevice = m_device->GetNVRHIDevice(); - const auto& staticDrawArgs = gpuCulling->GetStaticDrawArgsData(); - const auto& staticInstances = gpuCulling->GetStaticInstanceData(); - - xr_vector isTransformed(staticDrawArgs.size(), false); - for (u32 i = 0; i < (u32)staticDrawArgs.size(); i++) { - if (i < staticInstances.size() && memcmp(&staticInstances[i].world, &Fidentity, sizeof(Fmatrix)) != 0) - isTransformed[i] = true; - } - xr_vector batchInfos; batchInfos.reserve(m_batchCount); - AppendBatchInfos(batchInfos, staticDrawArgs, gpuCulling->GetStaticMaterialIDData(), &isTransformed); - AppendBatchInfos(batchInfos, gpuCulling->GetTerrainDrawArgsData(), gpuCulling->GetTerrainMaterialIDData()); - AppendBatchInfos(batchInfos, gpuCulling->GetTransparentDrawArgsData(), gpuCulling->GetTransparentMaterialIDData()); + batchInfos.insert(batchInfos.end(), m_staticGeomInfos.begin(), m_staticGeomInfos.end()); for (const auto& ug : m_uniqueGeometries) { for (const auto& instInfo : ug.instances) { @@ -511,17 +676,37 @@ void RTAccelStructManager::CreateBatchInfoBuffer(nvrhi::ICommandList* cmdList, G batchInfos.push_back(info); } + if (m_particleReady && m_particleTotalIndices > 0) { + RTBatchInfo info; + info.materialID = 0; + info.startIndex = 0; + info.baseVertex = 0; + info.indexCount = m_particleTotalIndices; + batchInfos.push_back(info); + } + if (batchInfos.empty()) return; - nvrhi::BufferDesc desc; - desc.debugName = "RTBatchInfoBuffer"; - desc.byteSize = batchInfos.size() * sizeof(RTBatchInfo); - desc.structStride = sizeof(RTBatchInfo); - desc.initialState = nvrhi::ResourceStates::ShaderResource; - desc.keepInitialState = true; + m_batchInfoSlot = (m_batchInfoSlot + 1u) % 3u; + auto& slot = m_batchInfoSlots[m_batchInfoSlot]; + const u64 needed = batchInfos.size() * sizeof(RTBatchInfo); + if (!slot || slot->getDesc().byteSize < needed) { + nvrhi::BufferDesc desc; + desc.debugName = "RTBatchInfoBuffer"; + desc.byteSize = needed; + desc.structStride = sizeof(RTBatchInfo); + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + slot = nvDevice->createBuffer(desc); + if (!slot) { + Msg("! [RT] BatchInfo buffer create failed"); + m_batchInfoBuffer = nullptr; + return; + } + } - m_batchInfoBuffer = nvDevice->createBuffer(desc); - cmdList->writeBuffer(m_batchInfoBuffer, batchInfos.data(), batchInfos.size() * sizeof(RTBatchInfo)); + cmdList->writeBuffer(slot, batchInfos.data(), needed); + m_batchInfoBuffer = slot; } u32 RTAccelStructManager::GetSkinningFormatID(u16 renderMode, u32 stride) @@ -556,7 +741,7 @@ void RTAccelStructManager::InitSkinningPipeline() cbDesc.byteSize = sizeof(RTSkinningCB); cbDesc.isConstantBuffer = true; cbDesc.isVolatile = true; - cbDesc.maxVersions = fg::RenderDevice::BufferDesc::VOLATILE_CB_MAX_VERSIONS; + cbDesc.maxVersions = 8192; s_skinCB = m_device->CreateBuffer(cbDesc); s_skinLayout = cache.GetOrCreateBindingLayoutFromReflection("RTSkinning", *skinResult.reflection, nvDevice); @@ -595,7 +780,7 @@ void RTAccelStructManager::BuildSkinnedBLAS( InitSkinningPipeline(); if (!s_skinInitialized) return; - if (worldBatches.empty() && hudBatches.empty()) { + if (worldBatches.empty()) { InvalidateSkinned(); return; } @@ -604,13 +789,14 @@ void RTAccelStructManager::BuildSkinnedBLAS( constexpr u32 SKINNED_VERTEX_STRIDE = 24; m_skinnedBatchData.clear(); - m_skinnedBatchData.reserve(worldBatches.size() + hudBatches.size()); + m_skinnedBatchData.reserve(worldBatches.size()); xr_vector consolidatedIndices; u32 currentVertexOffset = 0; u32 currentIndexOffset = 0; u32 totalVerts = 0; u32 totalIndices = 0; + u32 worldBatchCount = 0; auto processBatch = [&](const GeometryBatch& batch) { auto* mesh = static_cast(static_cast(batch.visual)); @@ -644,13 +830,35 @@ void RTAccelStructManager::BuildSkinnedBLAS( totalIndices += batch.indexCount; }; - for (const auto& b : worldBatches) processBatch(b); - for (const auto& b : hudBatches) processBatch(b); + for (const auto& b : worldBatches) { + processBatch(b); + worldBatchCount++; + } + (void)hudBatches; + + constexpr u32 kMaxSkinnedVerts = 500000u; + if (totalVerts > kMaxSkinnedVerts) { + Msg("! [RT] Skinned verts capped %u -> %u", totalVerts, kMaxSkinnedVerts); + InvalidateSkinned(); + return; + } + + u64 topoHash = m_skinnedBatchData.size(); + topoHash = topoHash * 131ull + 0xA1FBu; + for (const auto& sb : m_skinnedBatchData) { + topoHash = topoHash * 131ull + sb.vertexCount; + topoHash = topoHash * 131ull + sb.indexCount; + topoHash = topoHash * 131ull + sb.materialID; + topoHash = topoHash * 131ull + (u64)(uintptr_t)sb.srcVB; + } + + m_skinnedSlot = (m_skinnedSlot + 1u) % kSkinnedSlots; + auto& slot = m_skinnedSlots[m_skinnedSlot]; u64 vbSize = static_cast(totalVerts) * SKINNED_VERTEX_STRIDE; u64 ibSize = static_cast(totalIndices) * sizeof(u32); - if (!m_skinnedOutputVB || m_skinnedOutputVB->getDesc().byteSize < vbSize) { + if (!slot.vb || slot.vb->getDesc().byteSize < vbSize) { nvrhi::BufferDesc desc; desc.debugName = "SkinnedOutputVB"; desc.byteSize = vbSize; @@ -659,10 +867,15 @@ void RTAccelStructManager::BuildSkinnedBLAS( desc.initialState = nvrhi::ResourceStates::UnorderedAccess; desc.keepInitialState = true; desc.canHaveUAVs = true; - m_skinnedOutputVB = nvDevice->createBuffer(desc); + slot.vb = nvDevice->createBuffer(desc); + if (!slot.vb) { + Msg("! [RT] SkinnedOutputVB create failed"); + InvalidateSkinned(); + return; + } } - if (!m_skinnedIB || m_skinnedIB->getDesc().byteSize < ibSize) { + if (!slot.ib || slot.ib->getDesc().byteSize < ibSize) { nvrhi::BufferDesc desc; desc.debugName = "SkinnedConsolidatedIB"; desc.byteSize = ibSize; @@ -670,16 +883,26 @@ void RTAccelStructManager::BuildSkinnedBLAS( desc.isAccelStructBuildInput = true; desc.initialState = nvrhi::ResourceStates::ShaderResource; desc.keepInitialState = true; - m_skinnedIB = nvDevice->createBuffer(desc); + slot.ib = nvDevice->createBuffer(desc); + if (!slot.ib) { + Msg("! [RT] SkinnedIB create failed"); + InvalidateSkinned(); + return; + } + slot.topoHash = 0; } - cmdList->writeBuffer(m_skinnedIB, consolidatedIndices.data(), consolidatedIndices.size() * sizeof(u32)); + if (slot.topoHash != topoHash) + cmdList->writeBuffer(slot.ib, consolidatedIndices.data(), consolidatedIndices.size() * sizeof(u32)); nvrhi::IBuffer* boneBuffer = gpuCulling->GetGlobalBoneBuffer(); + if (!boneBuffer) + return; xr_map bindingSetCache; nvrhi::ComputeState state; state.pipeline = s_skinPipeline; + auto& cache = framegraph::GetPassResourceCache(); for (const auto& sb : m_skinnedBatchData) { auto it = bindingSetCache.find(sb.srcVB); @@ -688,9 +911,9 @@ void RTAccelStructManager::BuildSkinnedBLAS( framegraph::BindingSetBuilder bsb(*skinRefl, nvDevice, "RT.SkinVertices"); bsb.BufferSRV("g_SrcVB", sb.srcVB) .BufferSRV("g_BoneMatrices", boneBuffer) - .BufferUAV("g_Output", m_skinnedOutputVB) + .BufferUAV("g_Output", slot.vb) .ConstantBuffer("RTSkinningCB", m_device->GetNativeBuffer(s_skinCB)); - it = bindingSetCache.emplace(sb.srcVB, nvDevice->createBindingSet(bsb.Build(), s_skinLayout)).first; + it = bindingSetCache.emplace(sb.srcVB, cache.GetOrCreateBindingSet(bsb.Build(), s_skinLayout, nvDevice)).first; } RTSkinningCB cb; @@ -716,11 +939,11 @@ void RTAccelStructManager::BuildSkinnedBLAS( for (const auto& sb : m_skinnedBatchData) { nvrhi::rt::GeometryTriangles tri; - tri.setIndexBuffer(m_skinnedIB) + tri.setIndexBuffer(slot.ib) .setIndexFormat(nvrhi::Format::R32_UINT) .setIndexOffset(static_cast(sb.indexOffset) * sizeof(u32)) .setIndexCount(sb.indexCount) - .setVertexBuffer(m_skinnedOutputVB) + .setVertexBuffer(slot.vb) .setVertexFormat(nvrhi::Format::RGB32_FLOAT) .setVertexStride(SKINNED_VERTEX_STRIDE) .setVertexOffset(static_cast(sb.vertexOffset) * SKINNED_VERTEX_STRIDE) @@ -731,22 +954,49 @@ void RTAccelStructManager::BuildSkinnedBLAS( blasDesc.addBottomLevelGeometry(geom); } - m_skinnedBlas = nvDevice->createAccelStruct(blasDesc); - if (m_skinnedBlas) - nvrhi::utils::BuildBottomLevelAccelStruct(cmdList, m_skinnedBlas, blasDesc); + const bool needCreate = !slot.blas || slot.topoHash != topoHash || + slot.totalVerts != totalVerts || slot.totalIndices != totalIndices; + if (needCreate) { + slot.blas = nvDevice->createAccelStruct(blasDesc); + if (!slot.blas) { + Msg("! [RT] Skinned BLAS create failed"); + InvalidateSkinned(); + return; + } + static u32 s_skinnedBuildLog = 0; + if ((++s_skinnedBuildLog % 120u) == 1u) + Msg("* [RT] Built %u skinned BLAS (%u verts, %u indices)", + (u32)m_skinnedBatchData.size(), totalVerts, totalIndices); + } + + if (slot.blas) + nvrhi::utils::BuildBottomLevelAccelStruct(cmdList, slot.blas, blasDesc); + slot.totalVerts = totalVerts; + slot.totalIndices = totalIndices; + slot.topoHash = topoHash; + m_skinnedBlas = slot.blas; + m_skinnedTotalVerts = totalVerts; + m_skinnedTotalIndices = totalIndices; + m_skinnedTopoHash = topoHash; m_batchCounts.skinned = (u32)m_skinnedBatchData.size(); + m_batchCounts.skinnedWorld = worldBatchCount; + m_batchCounts.skinnedHud = m_batchCounts.skinned > worldBatchCount + ? m_batchCounts.skinned - worldBatchCount + : 0; m_skinnedReady = true; - - Msg("* [RT] Built %u skinned BLAS (%u verts, %u indices)", - (u32)m_skinnedBatchData.size(), totalVerts, totalIndices); } void RTAccelStructManager::InvalidateSkinned() { m_skinnedBlas = nullptr; m_skinnedBatchData.clear(); + m_skinnedTotalVerts = 0; + m_skinnedTotalIndices = 0; + m_skinnedTopoHash = 0; m_batchCounts.skinned = 0; + m_batchCounts.skinnedWorld = 0; + m_batchCounts.skinnedHud = 0; m_skinnedReady = false; } @@ -761,6 +1011,15 @@ void RTAccelStructManager::InvalidateGrass() m_detailAtlasIndex = 0; } +void RTAccelStructManager::InvalidateParticles() +{ + m_particleBlas = nullptr; + m_particleTotalVerts = 0; + m_particleTotalIndices = 0; + m_batchCounts.particles = 0; + m_particleReady = false; +} + void RTAccelStructManager::RebuildDynamic(nvrhi::ICommandList* cmdList, GPUCullingManager* gpuCulling) { BuildTLAS(cmdList); @@ -827,7 +1086,16 @@ void RTAccelStructManager::InitBillboardPipeline() bbCBDesc.maxVersions = fg::RenderDevice::BufferDesc::VOLATILE_CB_MAX_VERSIONS; s_billboardCB = m_device->CreateBuffer(bbCBDesc); - s_billboardLayout = cache.GetOrCreateBindingLayoutFromReflection("RTBillboard", *billboardResult.reflection, nvDevice); + nvrhi::BufferDesc countDesc; + countDesc.debugName = "BillboardPackedCount"; + countDesc.byteSize = 16; + countDesc.canHaveUAVs = true; + countDesc.canHaveRawViews = true; + countDesc.initialState = nvrhi::ResourceStates::UnorderedAccess; + countDesc.keepInitialState = true; + s_billboardCount = nvDevice->createBuffer(countDesc); + + s_billboardLayout = cache.GetOrCreateBindingLayoutFromReflection("RTBillboardCompact", *billboardResult.reflection, nvDevice); nvrhi::ComputePipelineDesc pipeDesc; pipeDesc.CS = billboardResult.handle; @@ -852,21 +1120,11 @@ void RTAccelStructManager::BuildGrassBLAS(nvrhi::ICommandList* cmdList, FGDetail u32 totalVerts = 0; u32 totalIndices = 0; - if (billboardMode) { - InitBillboardPipeline(); - if (!s_billboardInitialized) return; - - u32 maxVPB = detailMgr->maxPulledIndexCount; - maxVPB = (maxVPB / 3) * 3; - if (maxVPB == 0 || !detailMgr->billboardDrawArgsBuffer) { InvalidateGrass(); return; } - - u32 capacity = detailMgr->visibleBufferCapacity; - totalVerts = capacity * maxVPB; - totalIndices = totalVerts; - u64 vbSize = static_cast(totalVerts) * GRASS_VERTEX_STRIDE; - u64 ibSize = static_cast(totalIndices) * sizeof(u32); + m_grassSlot = (m_grassSlot + 1u) % kGrassSlots; + auto& slot = m_grassSlots[m_grassSlot]; - if (!m_grassOutputVB || m_grassOutputVB->getDesc().byteSize < vbSize) { + auto ensureGrassBuffers = [&](u64 vbSize, u64 ibSize) -> bool { + if (!slot.vb || slot.vb->getDesc().byteSize < vbSize) { nvrhi::BufferDesc desc; desc.debugName = "GrassOutputVB"; desc.byteSize = vbSize; @@ -875,50 +1133,121 @@ void RTAccelStructManager::BuildGrassBLAS(nvrhi::ICommandList* cmdList, FGDetail desc.initialState = nvrhi::ResourceStates::UnorderedAccess; desc.keepInitialState = true; desc.canHaveUAVs = true; - m_grassOutputVB = nvDevice->createBuffer(desc); + slot.vb = nvDevice->createBuffer(desc); + if (!slot.vb) return false; } - - if (!m_grassIB || m_grassIB->getDesc().byteSize < ibSize) { + if (!slot.ib || slot.ib->getDesc().byteSize < ibSize) { nvrhi::BufferDesc desc; desc.debugName = "GrassConsolidatedIB"; desc.byteSize = ibSize; desc.canHaveRawViews = true; desc.isAccelStructBuildInput = true; desc.canHaveUAVs = true; - desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.initialState = nvrhi::ResourceStates::UnorderedAccess; desc.keepInitialState = true; - m_grassIB = nvDevice->createBuffer(desc); + slot.ib = nvDevice->createBuffer(desc); + if (!slot.ib) return false; + } + return true; + }; + + if (billboardMode) { + InitBillboardPipeline(); + if (!s_billboardInitialized) return; + + u32 maxVPB = detailMgr->maxPulledIndexCount; + maxVPB = (maxVPB / 3) * 3; + const bool haveBB = detailMgr->billboardDrawArgsBuffer && detailMgr->visibleBillboardInstancesBuffer; + const bool haveDecal = detailMgr->decalDrawArgsBuffer && detailMgr->visibleDecalInstancesBuffer; + if (maxVPB == 0 || (!haveBB && !haveDecal)) { InvalidateGrass(); return; } + + const float distScale = std::clamp(ps_r_rt_detail_dist / 40.f, 0.2f, 1.f); + constexpr u32 kMaxRtBillboards = 16384u; + u32 visible = (u32)std::max(512u, (u32)(kMaxRtBillboards * distScale)); + totalVerts = visible * maxVPB; + totalIndices = totalVerts; + if (!s_billboardCount || !ensureGrassBuffers( + static_cast(totalVerts) * GRASS_VERTEX_STRIDE, + static_cast(totalIndices) * sizeof(u32))) { + InvalidateGrass(); + return; } BillboardRTCB cb; cb.maxVertsPerBillboard = maxVPB; - cb.pad[0] = cb.pad[1] = cb.pad[2] = 0; + cb.maxBillboards = visible; + cb.windAngleDeg = g_pGamePersistent + ? g_pGamePersistent->Environment().CurrentEnv.wind_direction + : 0.0f; + cb.windSpeed = detailMgr->windSpeed; + cb.time = Device.fTimeGlobal; + cb.windDisplacement = ps_r3_grass_wind_displacement; + cb.pad[0] = cb.pad[1] = 0; + cb.cameraPos[0] = Device.vCameraPosition.x; + cb.cameraPos[1] = Device.vCameraPosition.y; + cb.cameraPos[2] = Device.vCameraPosition.z; + const float casterDist = std::max(ps_r_rt_detail_dist, 32.f); + cb.maxDistanceSq = casterDist * casterDist; auto* billboardRefl = GEnv.Render->GetShaderLoader()->GetCachedReflection("rt_grass_billboard", ".cs"); - framegraph::BindingSetBuilder bsb(*billboardRefl, nvDevice, "RT.Billboard"); - bsb.BufferSRV("g_AllInstances", detailMgr->generatedInstancesBuffer) - .BufferSRV("g_VisibleIndices", detailMgr->visibleBillboardInstancesBuffer) - .BufferSRV("g_DetailModels", detailMgr->detailModelsBuffer) - .BufferSRV("g_PulledVerts", detailMgr->pulledVertexBuffer) - .BufferSRV("g_DrawArgs", detailMgr->billboardDrawArgsBuffer) - .BufferUAV("g_Output", m_grassOutputVB) - .BufferUAV("g_OutputIB", m_grassIB) - .ConstantBuffer("BillboardRTCB", m_device->GetNativeBuffer(s_billboardCB)); - auto bindingSet = nvDevice->createBindingSet(bsb.Build(), s_billboardLayout); + auto dispatchPacked = [&](nvrhi::IBuffer* visibleIdx, nvrhi::IBuffer* drawArgs) { + if (!visibleIdx || !drawArgs) + return; + framegraph::BindingSetBuilder bsb(*billboardRefl, nvDevice, "RT.Billboard"); + bsb.BufferSRV("g_AllInstances", detailMgr->generatedInstancesBuffer) + .BufferSRV("g_VisibleIndices", visibleIdx) + .BufferSRV("g_DetailModels", detailMgr->detailModelsBuffer) + .BufferSRV("g_PulledVerts", detailMgr->pulledVertexBuffer) + .BufferSRV("g_DrawArgs", drawArgs) + .Texture("g_Perlin4D", detailMgr->perlin4dTexture) + .BufferUAV("g_Output", slot.vb) + .BufferUAV("g_OutputIB", slot.ib) + .BufferUAV("g_PackedCount", s_billboardCount) + .ConstantBuffer("BillboardRTCB", m_device->GetNativeBuffer(s_billboardCB)); + auto bindingSet = nvDevice->createBindingSet(bsb.Build(), s_billboardLayout); + nvrhi::ComputeState state; + state.pipeline = s_billboardPipeline; + state.bindings = { bindingSet }; + cmdList->setComputeState(state); + cmdList->dispatch((visible + 255) / 256, 1, 1); + }; cmdList->writeBuffer(m_device->GetNativeBuffer(s_billboardCB), &cb, sizeof(BillboardRTCB)); - nvrhi::ComputeState state; - state.pipeline = s_billboardPipeline; - state.bindings = { bindingSet }; - cmdList->setComputeState(state); - cmdList->dispatch((capacity + 255) / 256, 1, 1); + cmdList->setBufferState(slot.vb, nvrhi::ResourceStates::UnorderedAccess); + cmdList->setBufferState(slot.ib, nvrhi::ResourceStates::UnorderedAccess); + cmdList->setBufferState(s_billboardCount, nvrhi::ResourceStates::UnorderedAccess); + cmdList->commitBarriers(); + cmdList->clearBufferUInt(s_billboardCount, 0); + cmdList->clearBufferUInt(slot.vb, 0); + cmdList->clearBufferUInt(slot.ib, 0); + if (haveBB) + dispatchPacked(detailMgr->visibleBillboardInstancesBuffer, detailMgr->billboardDrawArgsBuffer); + if (haveBB && haveDecal) { + cmdList->setBufferState(slot.vb, nvrhi::ResourceStates::UnorderedAccess); + cmdList->setBufferState(slot.ib, nvrhi::ResourceStates::UnorderedAccess); + cmdList->setBufferState(s_billboardCount, nvrhi::ResourceStates::UnorderedAccess); + cmdList->commitBarriers(); + } + if (haveDecal) + dispatchPacked(detailMgr->visibleDecalInstancesBuffer, detailMgr->decalDrawArgsBuffer); } else { InitGrassPipeline(); if (!s_grassInitialized) return; - u32 lodCounts[FGDetailManager::LOD_COUNT] = { - stats.visibleLOD0Count, stats.visibleLOD1Count, stats.visibleLOD2Count - }; + const float distScale = std::clamp(ps_r_rt_detail_dist / 40.f, 0.2f, 1.f); + constexpr u32 kMaxRtGrassBlades = 6144u; + u32 budget = (u32)std::max(512u, (u32)(kMaxRtGrassBlades * distScale)); + + u32 lodCounts[FGDetailManager::LOD_COUNT] = { 0, 0, 0 }; + u32 take0 = std::min(stats.visibleLOD0Count, budget); + lodCounts[0] = take0; + budget -= take0; + u32 take1 = std::min(stats.visibleLOD1Count, budget); + lodCounts[1] = take1; + budget -= take1; + u32 take2 = std::min(stats.visibleLOD2Count, budget); + lodCounts[2] = take2; + u32 totalBlades = lodCounts[0] + lodCounts[1] + lodCounts[2]; if (totalBlades == 0) { InvalidateGrass(); return; } @@ -932,31 +1261,11 @@ void RTAccelStructManager::BuildGrassBLAS(nvrhi::ICommandList* cmdList, FGDetail totalIndices += lodCounts[lod] * lodIndicesPerBlade[lod]; } - u64 vbSize = static_cast(totalVerts) * GRASS_VERTEX_STRIDE; - u64 ibSize = static_cast(totalIndices) * sizeof(u32); - - if (!m_grassOutputVB || m_grassOutputVB->getDesc().byteSize < vbSize) { - nvrhi::BufferDesc desc; - desc.debugName = "GrassOutputVB"; - desc.byteSize = vbSize; - desc.canHaveRawViews = true; - desc.isAccelStructBuildInput = true; - desc.initialState = nvrhi::ResourceStates::UnorderedAccess; - desc.keepInitialState = true; - desc.canHaveUAVs = true; - m_grassOutputVB = nvDevice->createBuffer(desc); - } - - if (!m_grassIB || m_grassIB->getDesc().byteSize < ibSize) { - nvrhi::BufferDesc desc; - desc.debugName = "GrassConsolidatedIB"; - desc.byteSize = ibSize; - desc.canHaveRawViews = true; - desc.isAccelStructBuildInput = true; - desc.canHaveUAVs = true; - desc.initialState = nvrhi::ResourceStates::ShaderResource; - desc.keepInitialState = true; - m_grassIB = nvDevice->createBuffer(desc); + if (!ensureGrassBuffers( + static_cast(totalVerts) * GRASS_VERTEX_STRIDE, + static_cast(totalIndices) * sizeof(u32))) { + InvalidateGrass(); + return; } float windAngleDeg = 0.0f; @@ -989,8 +1298,8 @@ void RTAccelStructManager::BuildGrassBLAS(nvrhi::ICommandList* cmdList, FGDetail .BufferSRV("g_SlotData", detailMgr->slotDataBuffer) .BufferSRV("g_VisibleIndices", detailMgr->visibleInstancesBuffer[lod]) .Texture("g_WindTexture", detailMgr->perlin4dTexture) - .BufferUAV("g_Output", m_grassOutputVB) - .BufferUAV("g_OutputIB", m_grassIB) + .BufferUAV("g_Output", slot.vb) + .BufferUAV("g_OutputIB", slot.ib) .ConstantBuffer("GrassRTCB", m_device->GetNativeBuffer(s_grassCB)); auto bindingSet = nvDevice->createBindingSet(bsb.Build(), s_grassLayout); @@ -1012,43 +1321,144 @@ void RTAccelStructManager::BuildGrassBLAS(nvrhi::ICommandList* cmdList, FGDetail vertexOffset += totalVertsThisLod; indexOffset += lodCounts[lod] * lodIndicesPerBlade[lod]; } - - Msg("* [RT] Built blade grass BLAS (%u blades, %u verts, %u indices, LODs: %u/%u/%u)", - totalBlades, totalVerts, totalIndices, lodCounts[0], lodCounts[1], lodCounts[2]); } + cmdList->setBufferState(slot.vb, nvrhi::ResourceStates::AccelStructBuildInput); + cmdList->setBufferState(slot.ib, nvrhi::ResourceStates::AccelStructBuildInput); + cmdList->commitBarriers(); + nvrhi::rt::AccelStructDesc blasDesc; blasDesc.debugName = "GrassBLAS"; blasDesc.buildFlags = nvrhi::rt::AccelStructBuildFlags::PreferFastBuild; nvrhi::rt::GeometryTriangles tri; - tri.setIndexBuffer(m_grassIB) + tri.setIndexBuffer(slot.ib) .setIndexFormat(nvrhi::Format::R32_UINT) .setIndexOffset(0) .setIndexCount(totalIndices) - .setVertexBuffer(m_grassOutputVB) + .setVertexBuffer(slot.vb) .setVertexFormat(nvrhi::Format::RGB32_FLOAT) .setVertexStride(GRASS_VERTEX_STRIDE) .setVertexOffset(0) .setVertexCount(totalVerts); - auto geomFlags = billboardMode ? nvrhi::rt::GeometryFlags::None : nvrhi::rt::GeometryFlags::Opaque; nvrhi::rt::GeometryDesc geom; - geom.setTriangles(tri).setFlags(geomFlags); + geom.setTriangles(tri).setFlags(nvrhi::rt::GeometryFlags::None); blasDesc.addBottomLevelGeometry(geom); - if (!m_grassBlas || m_grassTotalVerts != totalVerts || m_grassTotalIndices != totalIndices) - m_grassBlas = nvDevice->createAccelStruct(blasDesc); + if (!slot.blas || slot.totalVerts != totalVerts || slot.totalIndices != totalIndices) + slot.blas = nvDevice->createAccelStruct(blasDesc); - if (m_grassBlas) - nvrhi::utils::BuildBottomLevelAccelStruct(cmdList, m_grassBlas, blasDesc); + if (slot.blas) + nvrhi::utils::BuildBottomLevelAccelStruct(cmdList, slot.blas, blasDesc); + cmdList->setBufferState(slot.vb, nvrhi::ResourceStates::ShaderResource); + cmdList->setBufferState(slot.ib, nvrhi::ResourceStates::ShaderResource); + cmdList->commitBarriers(); + + slot.totalVerts = totalVerts; + slot.totalIndices = totalIndices; + m_grassOutputVB = slot.vb; + m_grassIB = slot.ib; + m_grassBlas = slot.blas; m_grassTotalVerts = totalVerts; m_grassTotalIndices = totalIndices; m_batchCounts.grass = 1; m_grassReady = true; m_grassBillboardMode = billboardMode; m_detailAtlasIndex = billboardMode ? detailMgr->buildDetailsBindlessIndex : 0; + static u32 s_lastGrassVerts = 0; + static u32 s_lastGrassAtlas = UINT32_MAX; + if (s_lastGrassVerts != totalVerts || s_lastGrassAtlas != m_detailAtlasIndex) { + Msg("* [RT] Detail BLAS ready: mode=%s capacity=%u verts, visible~%u (bb=%u decal=%u) atlas=%u", + billboardMode ? "billboard" : "blade", totalVerts, + stats.visibleBillboardCount + stats.visibleDecalCount, + stats.visibleBillboardCount, stats.visibleDecalCount, m_detailAtlasIndex); + s_lastGrassVerts = totalVerts; + s_lastGrassAtlas = m_detailAtlasIndex; + } +} + +void RTAccelStructManager::BuildParticleBLAS( + nvrhi::ICommandList* cmdList, + const xr_vector& worldBatches) +{ + if (!m_rtSupported || !m_isReady || !cmdList) + return; + if (worldBatches.empty()) { + InvalidateParticles(); + return; + } + + xr_vector vertices; + xr_vector indices; + const u32 quads = passes::BuildEmissiveParticleRTGeometry(worldBatches, vertices, indices, 8192u); + if (quads == 0 || vertices.empty() || indices.empty()) { + InvalidateParticles(); + return; + } + + nvrhi::IDevice* nvDevice = m_device->GetNVRHIDevice(); + const u64 vbSize = vertices.size() * sizeof(passes::ParticleVertex); + const u64 ibSize = indices.size() * sizeof(u32); + if (!m_particleOutputVB || m_particleOutputVB->getDesc().byteSize < vbSize) { + nvrhi::BufferDesc desc; + desc.debugName = "ParticleRTVB"; + desc.byteSize = std::max(vbSize, 65536); + desc.canHaveRawViews = true; + desc.isAccelStructBuildInput = true; + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + m_particleOutputVB = nvDevice->createBuffer(desc); + } + if (!m_particleIB || m_particleIB->getDesc().byteSize < ibSize) { + nvrhi::BufferDesc desc; + desc.debugName = "ParticleRTIB"; + desc.byteSize = std::max(ibSize, 65536); + desc.canHaveRawViews = true; + desc.isAccelStructBuildInput = true; + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + m_particleIB = nvDevice->createBuffer(desc); + } + if (!m_particleOutputVB || !m_particleIB) { + InvalidateParticles(); + return; + } + + cmdList->writeBuffer(m_particleOutputVB, vertices.data(), vbSize); + cmdList->writeBuffer(m_particleIB, indices.data(), ibSize); + + nvrhi::rt::AccelStructDesc blasDesc; + blasDesc.debugName = "ParticleBLAS"; + blasDesc.buildFlags = nvrhi::rt::AccelStructBuildFlags::PreferFastBuild; + + nvrhi::rt::GeometryTriangles tri; + tri.setIndexBuffer(m_particleIB) + .setIndexFormat(nvrhi::Format::R32_UINT) + .setIndexOffset(0) + .setIndexCount((u32)indices.size()) + .setVertexBuffer(m_particleOutputVB) + .setVertexFormat(nvrhi::Format::RGB32_FLOAT) + .setVertexStride(sizeof(passes::ParticleVertex)) + .setVertexOffset(0) + .setVertexCount((u32)vertices.size()); + + nvrhi::rt::GeometryDesc geom; + geom.setTriangles(tri).setFlags(nvrhi::rt::GeometryFlags::None); + blasDesc.addBottomLevelGeometry(geom); + + if (!m_particleBlas || m_particleTotalVerts != (u32)vertices.size() || + m_particleTotalIndices != (u32)indices.size()) + m_particleBlas = nvDevice->createAccelStruct(blasDesc); + + if (m_particleBlas) + nvrhi::utils::BuildBottomLevelAccelStruct(cmdList, m_particleBlas, blasDesc); + + m_particleTotalVerts = (u32)vertices.size(); + m_particleTotalIndices = (u32)indices.size(); + m_batchCounts.particles = 1; + m_particleReady = m_particleBlas != nullptr; } } diff --git a/src/Layers/xrRender/RayTracing/RTAccelStructManager.h b/src/Layers/xrRender/RayTracing/RTAccelStructManager.h index 1fa0f51d057..de7b3b381b5 100644 --- a/src/Layers/xrRender/RayTracing/RTAccelStructManager.h +++ b/src/Layers/xrRender/RayTracing/RTAccelStructManager.h @@ -11,6 +11,10 @@ namespace xray::render::fg { class FGDetailManager; } +namespace xray::render::fg::passes { + struct ParticleBatch; +} + namespace xray::render::fg { class RenderDevice; class RenderContext; @@ -27,13 +31,22 @@ struct RTBatchInfo { }; static_assert(sizeof(RTBatchInfo) == 16, "RTBatchInfo must be 16 bytes"); +enum : u32 { + kRTMaskScene = 0x01, + kRTMaskParticles = 0x02, + kRTMaskGrass = 0x04, +}; + struct RTBatchCounts { u32 identityStatic = 0; u32 terrain = 0; u32 transparent = 0; u32 instancedTotal = 0; u32 skinned = 0; + u32 skinnedWorld = 0; + u32 skinnedHud = 0; u32 grass = 0; + u32 particles = 0; }; class RTAccelStructManager { @@ -42,33 +55,38 @@ class RTAccelStructManager { void Shutdown(); void BuildIfNeeded(nvrhi::ICommandList* cmdList, GPUCullingManager* gpuCulling); + void Invalidate(); void BuildSkinnedBLAS(nvrhi::ICommandList* cmdList, GPUCullingManager* gpuCulling, const xr_vector& worldBatches, const xr_vector& hudBatches); void BuildGrassBLAS(nvrhi::ICommandList* cmdList, FGDetailManager* detailMgr); + void BuildParticleBLAS(nvrhi::ICommandList* cmdList, const xr_vector& worldBatches); void RebuildDynamic(nvrhi::ICommandList* cmdList, GPUCullingManager* gpuCulling); void InvalidateSkinned(); void InvalidateGrass(); + void InvalidateParticles(); static void InvalidateShaderPipelines(); bool IsReady() const { return m_isReady; } bool IsSupported() const { return m_rtSupported; } nvrhi::rt::IAccelStruct* GetTLAS() const { return m_tlas.Get(); } + nvrhi::rt::IAccelStruct* GetOrCreateEmptyTLAS(nvrhi::ICommandList* cmd); nvrhi::IBuffer* GetBatchInfoBuffer() const { return m_batchInfoBuffer.Get(); } nvrhi::IBuffer* GetMegaVB() const { return m_megaVB; } nvrhi::IBuffer* GetMegaIB() const { return m_megaIB; } nvrhi::IBuffer* GetMaterialBuffer() const { return m_materialBuffer; } nvrhi::IBuffer* GetTerrainMaterialBuffer() const { return m_terrainMaterialBuffer; } - nvrhi::IBuffer* GetSkinnedOutputVB() const { return m_skinnedOutputVB.Get(); } - nvrhi::IBuffer* GetSkinnedIB() const { return m_skinnedIB.Get(); } - nvrhi::IBuffer* GetGrassOutputVB() const { return m_grassOutputVB.Get(); } - nvrhi::IBuffer* GetGrassIB() const { return m_grassIB.Get(); } + nvrhi::IBuffer* GetSkinnedOutputVB() const { return m_skinnedSlots[m_skinnedSlot].vb.Get(); } + nvrhi::IBuffer* GetSkinnedIB() const { return m_skinnedSlots[m_skinnedSlot].ib.Get(); } + nvrhi::IBuffer* GetGrassOutputVB() const { return m_grassSlots[m_grassSlot].vb.Get(); } + nvrhi::IBuffer* GetGrassIB() const { return m_grassSlots[m_grassSlot].ib.Get(); } + nvrhi::IBuffer* GetParticleOutputVB() const { return m_particleOutputVB.Get(); } + nvrhi::IBuffer* GetParticleIB() const { return m_particleIB.Get(); } u32 GetBatchCount() const { return m_batchCount; } const RTBatchCounts& GetBatchCounts() const { return m_batchCounts; } u32 GetDetailAtlasIndex() const { return m_detailAtlasIndex; } - void SetMaterialBuffer(nvrhi::IBuffer* buf) { m_materialBuffer = buf; } void SetTerrainMaterialBuffer(nvrhi::IBuffer* buf) { m_terrainMaterialBuffer = buf; } @@ -87,11 +105,13 @@ class RTAccelStructManager { struct InstanceInfo { Fmatrix world; u32 materialID; + u32 flags = 0; }; struct UniqueGeometry { GeometryKey key; u32 vertexCount; + bool alphaGeometry = false; nvrhi::rt::AccelStructHandle blas; xr_vector instances; }; @@ -126,23 +146,53 @@ class RTAccelStructManager { bool m_isReady = false; u32 m_batchCount = 0; RTBatchCounts m_batchCounts = {}; + bool m_staticHasAlpha = false; nvrhi::rt::AccelStructHandle m_staticBlas; + xr_vector m_staticGeomInfos; xr_vector m_uniqueGeometries; nvrhi::rt::AccelStructHandle m_tlas; + nvrhi::rt::AccelStructHandle m_emptyTlas; + nvrhi::rt::AccelStructHandle m_tlasSlots[3]; + u32 m_tlasSlot = 0; + u32 m_tlasMaxInstances[3] = {}; nvrhi::BufferHandle m_batchInfoBuffer; + nvrhi::BufferHandle m_batchInfoSlots[3]; + u32 m_batchInfoSlot = 0; nvrhi::IBuffer* m_megaVB = nullptr; nvrhi::IBuffer* m_megaIB = nullptr; nvrhi::IBuffer* m_materialBuffer = nullptr; nvrhi::IBuffer* m_terrainMaterialBuffer = nullptr; - nvrhi::BufferHandle m_skinnedOutputVB; - nvrhi::BufferHandle m_skinnedIB; + struct SkinnedFrameSlot { + nvrhi::BufferHandle vb; + nvrhi::BufferHandle ib; + nvrhi::rt::AccelStructHandle blas; + u64 topoHash = 0; + u32 totalVerts = 0; + u32 totalIndices = 0; + }; + static constexpr u32 kSkinnedSlots = 3; + SkinnedFrameSlot m_skinnedSlots[kSkinnedSlots]; + u32 m_skinnedSlot = 0; nvrhi::rt::AccelStructHandle m_skinnedBlas; xr_vector m_skinnedBatchData; + u32 m_skinnedTotalVerts = 0; + u32 m_skinnedTotalIndices = 0; + u64 m_skinnedTopoHash = 0; bool m_skinnedReady = false; + struct GrassFrameSlot { + nvrhi::BufferHandle vb; + nvrhi::BufferHandle ib; + nvrhi::rt::AccelStructHandle blas; + u32 totalVerts = 0; + u32 totalIndices = 0; + }; + static constexpr u32 kGrassSlots = 3; + GrassFrameSlot m_grassSlots[kGrassSlots]; + u32 m_grassSlot = 0; nvrhi::BufferHandle m_grassOutputVB; nvrhi::BufferHandle m_grassIB; nvrhi::rt::AccelStructHandle m_grassBlas; @@ -151,6 +201,12 @@ class RTAccelStructManager { bool m_grassReady = false; bool m_grassBillboardMode = false; u32 m_detailAtlasIndex = 0; + nvrhi::BufferHandle m_particleOutputVB; + nvrhi::BufferHandle m_particleIB; + nvrhi::rt::AccelStructHandle m_particleBlas; + u32 m_particleTotalVerts = 0; + u32 m_particleTotalIndices = 0; + bool m_particleReady = false; static nvrhi::ComputePipelineHandle s_skinPipeline; static nvrhi::BindingLayoutHandle s_skinLayout; diff --git a/src/Layers/xrRender/RayTracing/ReSTIRMemoryManager.cpp b/src/Layers/xrRender/RayTracing/ReSTIRMemoryManager.cpp new file mode 100644 index 00000000000..1b4bca48656 --- /dev/null +++ b/src/Layers/xrRender/RayTracing/ReSTIRMemoryManager.cpp @@ -0,0 +1,540 @@ +#include "stdafx.h" +#include "ReSTIRMemoryManager.h" +#include +#include +#include +#include + +extern ENGINE_API int ps_r_rt_gi_half; + +namespace xray::render::fg { + +static constexpr int kSTBNSize = 128; +static constexpr int kSTBNDepth = 16; + +static void GenerateBlueNoise128(xr_vector& dst) +{ + constexpr int N = kSTBNSize; + constexpr int Count = N * N; + dst.assign(Count, 0); + xr_vector energy(Count, 0.f); + xr_vector occupied(Count, 0); + auto addImpulse = [&](int cx, int cy, float sign) { + for (int y = -16; y <= 16; ++y) { + for (int x = -16; x <= 16; ++x) { + const float d2 = float(x * x + y * y); + const float g = expf(-d2 * 0.08f); + const int ix = (cx + x + N * 4) % N; + const int iy = (cy + y + N * 4) % N; + energy[iy * N + ix] += sign * g; + } + } + }; + for (int i = 0; i < Count; ++i) { + int best = 0; + float bestE = 1e30f; + for (int p = 0; p < Count; ++p) { + if (occupied[p]) + continue; + if (energy[p] < bestE) { + bestE = energy[p]; + best = p; + } + } + occupied[best] = 1; + dst[best] = u8((i * 255) / (Count - 1)); + addImpulse(best % N, best / N, 1.f); + } +} + +static void GenerateSTBN(xr_vector& dst) +{ + constexpr int N = kSTBNSize; + constexpr int D = kSTBNDepth; + constexpr int Slice = N * N; + xr_vector slice; + GenerateBlueNoise128(slice); + dst.assign((size_t)Slice * D, 0); + memcpy(dst.data(), slice.data(), (size_t)Slice); + for (int z = 1; z < D; ++z) { + const int sx = (z * 47 + 13) & (N - 1); + const int sy = (z * 119 + 31) & (N - 1); + u8* cur = dst.data() + (size_t)z * Slice; + for (int y = 0; y < N; ++y) { + for (int x = 0; x < N; ++x) { + const int srcx = (x + sx) ^ (z * 17); + const int srcy = (y + sy) ^ (z * 41); + cur[y * N + x] = slice[(srcy & (N - 1)) * N + (srcx & (N - 1))]; + } + } + const u8* prev = dst.data() + (size_t)(z - 1) * Slice; + for (int i = 0; i < Slice; ++i) { + if (abs((int)cur[i] - (int)prev[i]) < 8) { + const int j = (i + 37 * z + 11) % Slice; + const u8 tmp = cur[i]; + cur[i] = cur[j]; + cur[j] = tmp; + } + } + } +} + +static void RestirGiResolution(u32 width, u32 height, u32& giW, u32& giH) +{ + if (ps_r_rt_gi_half) + { + giW = std::max(1u, (width + 1u) / 2u); + giH = std::max(1u, (height + 1u) / 2u); + } + else + { + giW = width; + giH = height; + } +} + +ReSTIRMemoryManager& ReSTIRMemoryManager::Instance() +{ + static ReSTIRMemoryManager instance; + return instance; +} + +void ReSTIRMemoryManager::ClearBindingSets() +{ + m_temporalBS[0] = nullptr; + m_temporalBS[1] = nullptr; + m_spatialBS[0] = nullptr; + m_spatialBS[1] = nullptr; +} + +void ReSTIRMemoryManager::Init(nvrhi::IDevice* device) +{ + m_device = device; + if (!m_placeholderBuffer) + { + nvrhi::BufferDesc desc; + desc.debugName = "ReSTIR_PlaceholderBuf"; + desc.byteSize = 16; + desc.canHaveRawViews = true; + desc.canHaveUAVs = true; + desc.structStride = 16; + desc.initialState = nvrhi::ResourceStates::UnorderedAccess; + desc.keepInitialState = true; + m_placeholderBuffer = device->createBuffer(desc); + } + if (!m_placeholderCube) + { + nvrhi::TextureDesc desc; + desc.debugName = "ReSTIR_PlaceholderCube"; + desc.width = 1; + desc.height = 1; + desc.dimension = nvrhi::TextureDimension::TextureCube; + desc.arraySize = 6; + desc.format = nvrhi::Format::RGBA8_UNORM; + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + m_placeholderCube = device->createTexture(desc); + } + if (!m_placeholderTex) + { + nvrhi::TextureDesc desc; + desc.debugName = "ReSTIR_PlaceholderTex"; + desc.width = 1; + desc.height = 1; + desc.format = nvrhi::Format::R16_FLOAT; + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + m_placeholderTex = device->createTexture(desc); + } + if (!m_placeholderColorTex) + { + nvrhi::TextureDesc desc; + desc.debugName = "ReSTIR_PlaceholderColor"; + desc.width = 1; + desc.height = 1; + desc.format = nvrhi::Format::RGBA16_FLOAT; + desc.isUAV = true; + desc.initialState = nvrhi::ResourceStates::UnorderedAccess; + desc.keepInitialState = true; + m_placeholderColorTex = device->createTexture(desc); + } + if (!m_lightData) + { + nvrhi::BufferDesc desc; + desc.debugName = "ReSTIR_LightData"; + desc.byteSize = RESTIR_MAX_LIGHTS * 128u; + desc.structStride = 128; + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + m_lightData = device->createBuffer(desc); + } + if (!m_placeholderTex3D) + { + nvrhi::TextureDesc desc; + desc.debugName = "ReSTIR_PlaceholderTex3D"; + desc.width = 1; + desc.height = 1; + desc.depth = 1; + desc.dimension = nvrhi::TextureDimension::Texture3D; + desc.format = nvrhi::Format::R8_UNORM; + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + m_placeholderTex3D = device->createTexture(desc); + } + if (!m_blueNoise) + { + GenerateSTBN(m_blueNoisePixels); + nvrhi::TextureDesc desc; + desc.debugName = "ReSTIR_STBN"; + desc.width = kSTBNSize; + desc.height = kSTBNSize; + desc.depth = kSTBNDepth; + desc.dimension = nvrhi::TextureDimension::Texture3D; + desc.format = nvrhi::Format::R8_UNORM; + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + m_blueNoise = device->createTexture(desc); + m_blueNoiseUploaded = false; + } +} + +void ReSTIRMemoryManager::Shutdown() +{ + DestroyResources(); + ClearBindingSets(); + m_lightData = nullptr; + m_placeholderBuffer = nullptr; + m_placeholderCube = nullptr; + m_placeholderTex = nullptr; + m_placeholderColorTex = nullptr; + m_placeholderTex3D = nullptr; + m_blueNoise = nullptr; + m_blueNoisePixels.clear(); + m_blueNoiseUploaded = false; + m_device = nullptr; +} + +void ReSTIRMemoryManager::DestroyResources() +{ + if (!m_device) + return; + + ClearBindingSets(); + for (int i = 0; i < 2; ++i) { + m_reservoir[i] = nullptr; + m_diReservoir[i] = nullptr; + m_specReservoirA[i] = nullptr; + m_specReservoirB[i] = nullptr; + m_ptReservoirA[i] = nullptr; + m_ptReservoirB[i] = nullptr; + } + m_ptDupMap = nullptr; + m_pairingTex[0] = nullptr; + m_pairingTex[1] = nullptr; + m_pairingTex[2] = nullptr; + m_blurTemp = nullptr; + m_blurTempSpec = nullptr; + m_histDiffuse = nullptr; + m_histSpecular = nullptr; + m_sunVis[0] = nullptr; + m_sunVis[1] = nullptr; + m_irradianceCache = nullptr; + m_irradianceCacheSize = 0; + m_directLighting = nullptr; + m_noisyDiffuse = nullptr; + m_noisySpecular = nullptr; + m_hitDistance = nullptr; + m_wetAccum = nullptr; + m_skyOpen = nullptr; + m_sunshafts = nullptr; + m_sunshaftsHist = nullptr; + m_ddgiAmbient = nullptr; + m_ddgiProbes = nullptr; + m_width = 0; + m_height = 0; + m_giWidth = 0; + m_giHeight = 0; + m_shaftWidth = 0; + m_shaftHeight = 0; + + m_device->waitForIdle(); + m_device->runGarbageCollection(); +} + +void ReSTIRMemoryManager::CreatePersistent(u32 width, u32 height) +{ + u32 giW = width; + u32 giH = height; + RestirGiResolution(width, height, giW, giH); + const u32 shaftW = std::max(1u, (width + 1u) / 2u); + const u32 shaftH = std::max(1u, (height + 1u) / 2u); + const u32 pixelCount = giW * giH; + for (int i = 0; i < 2; ++i) + { + nvrhi::BufferDesc desc; + desc.debugName = i == 0 ? "ReSTIR_Reservoir_0" : "ReSTIR_Reservoir_1"; + desc.byteSize = (size_t)pixelCount * RESTIR_RESERVOIR_STRIDE; + desc.structStride = RESTIR_RESERVOIR_STRIDE; + desc.canHaveUAVs = true; + desc.canHaveRawViews = true; + desc.initialState = nvrhi::ResourceStates::UnorderedAccess; + desc.keepInitialState = true; + m_reservoir[i] = m_device->createBuffer(desc); + } + + auto makeUAVTex = [&](const char* name, nvrhi::Format fmt, u32 w, u32 h) -> nvrhi::TextureHandle { + nvrhi::TextureDesc desc; + desc.debugName = name; + desc.width = w; + desc.height = h; + desc.format = fmt; + desc.isUAV = true; + desc.initialState = nvrhi::ResourceStates::UnorderedAccess; + desc.keepInitialState = true; + nvrhi::TextureHandle tex = m_device->createTexture(desc); + if (!tex) + Msg("! [ReSTIR] Failed to create UAV texture '%s' %ux%u fmt=%d", name, w, h, (int)fmt); + return tex; + }; + + m_directLighting = makeUAVTex("ReSTIR_DirectLighting", nvrhi::Format::RGBA16_FLOAT, giW, giH); + m_noisyDiffuse = makeUAVTex("ReSTIR_NoisyDiffuse", nvrhi::Format::RGBA16_FLOAT, giW, giH); + m_noisySpecular = makeUAVTex("ReSTIR_NoisySpecular", nvrhi::Format::RGBA16_FLOAT, giW, giH); + m_hitDistance = makeUAVTex("ReSTIR_HitDistance", nvrhi::Format::R16_FLOAT, giW, giH); + m_wetAccum = makeUAVTex("ReSTIR_WetAccum", nvrhi::Format::R16_FLOAT, width, height); + m_skyOpen = makeUAVTex("ReSTIR_SkyOpen", nvrhi::Format::R16_FLOAT, width, height); + m_sunshafts = makeUAVTex("ReSTIR_Sunshafts", nvrhi::Format::RGBA16_FLOAT, shaftW, shaftH); + m_sunshaftsHist = makeUAVTex("ReSTIR_SunshaftsHist", nvrhi::Format::RGBA16_FLOAT, shaftW, shaftH); + m_ddgiAmbient = makeUAVTex("ReSTIR_DDGIAmbient", nvrhi::Format::RGBA16_FLOAT, giW, giH); + m_diReservoir[0] = makeUAVTex("ReSTIR_DI_0", nvrhi::Format::RGBA32_FLOAT, giW, giH); + m_diReservoir[1] = makeUAVTex("ReSTIR_DI_1", nvrhi::Format::RGBA32_FLOAT, giW, giH); + m_specReservoirA[0] = makeUAVTex("ReSTIR_SpecA_0", nvrhi::Format::RGBA32_FLOAT, giW, giH); + m_specReservoirA[1] = makeUAVTex("ReSTIR_SpecA_1", nvrhi::Format::RGBA32_FLOAT, giW, giH); + m_specReservoirB[0] = makeUAVTex("ReSTIR_SpecB_0", nvrhi::Format::RGBA32_FLOAT, giW, giH); + m_specReservoirB[1] = makeUAVTex("ReSTIR_SpecB_1", nvrhi::Format::RGBA32_FLOAT, giW, giH); + if (!m_diReservoir[0]) + m_diReservoir[0] = makeUAVTex("ReSTIR_DI_0", nvrhi::Format::RGBA16_FLOAT, giW, giH); + if (!m_diReservoir[1]) + m_diReservoir[1] = makeUAVTex("ReSTIR_DI_1", nvrhi::Format::RGBA16_FLOAT, giW, giH); + if (!m_specReservoirA[0]) + m_specReservoirA[0] = makeUAVTex("ReSTIR_SpecA_0", nvrhi::Format::RGBA16_FLOAT, giW, giH); + if (!m_specReservoirA[1]) + m_specReservoirA[1] = makeUAVTex("ReSTIR_SpecA_1", nvrhi::Format::RGBA16_FLOAT, giW, giH); + if (!m_specReservoirB[0]) + m_specReservoirB[0] = makeUAVTex("ReSTIR_SpecB_0", nvrhi::Format::RGBA16_FLOAT, giW, giH); + if (!m_specReservoirB[1]) + m_specReservoirB[1] = makeUAVTex("ReSTIR_SpecB_1", nvrhi::Format::RGBA16_FLOAT, giW, giH); + m_blurTemp = makeUAVTex("ReSTIR_BlurTemp", nvrhi::Format::RGBA16_FLOAT, giW, giH); + m_blurTempSpec = makeUAVTex("ReSTIR_BlurTempSpec", nvrhi::Format::RGBA16_FLOAT, giW, giH); + m_histDiffuse = makeUAVTex("ReSTIR_HistDiffuse", nvrhi::Format::RGBA16_FLOAT, giW, giH); + m_histSpecular = makeUAVTex("ReSTIR_HistSpecular", nvrhi::Format::RGBA16_FLOAT, giW, giH); + m_sunVis[0] = makeUAVTex("ReSTIR_SunVis_0", nvrhi::Format::R16_FLOAT, giW, giH); + m_sunVis[1] = makeUAVTex("ReSTIR_SunVis_1", nvrhi::Format::R16_FLOAT, giW, giH); + m_ptReservoirA[0] = makeUAVTex("ReSTIR_PTA_0", nvrhi::Format::RGBA32_UINT, giW, giH); + m_ptReservoirA[1] = makeUAVTex("ReSTIR_PTA_1", nvrhi::Format::RGBA32_UINT, giW, giH); + m_ptReservoirB[0] = makeUAVTex("ReSTIR_PTB_0", nvrhi::Format::RGBA32_UINT, giW, giH); + m_ptReservoirB[1] = makeUAVTex("ReSTIR_PTB_1", nvrhi::Format::RGBA32_UINT, giW, giH); + m_ptDupMap = makeUAVTex("ReSTIR_PTDup", nvrhi::Format::R8_UNORM, giW, giH); + { + auto makePair = [&](const char* name, int dim, int shuffles) { + nvrhi::TextureDesc desc; + desc.debugName = name; + desc.width = (u32)dim; + desc.height = (u32)dim; + desc.format = nvrhi::Format::RG8_SNORM; + desc.initialState = nvrhi::ResourceStates::ShaderResource; + desc.keepInitialState = true; + nvrhi::TextureHandle tex = m_device->createTexture(desc); + xr_vector idx((size_t)dim * dim); + for (int i = 0; i < dim * dim; ++i) + idx[i] = i; + u32 rng = 0xA341316Cu + (u32)shuffles * 747796405u; + auto next = [&]() { + rng = rng * 1664525u + 1013904223u; + return rng; + }; + for (int s = 0; s < shuffles; ++s) { + for (int y = 0; y + 1 < dim; y += 2) { + for (int x = 0; x + 1 < dim; x += 2) { + int p[4] = { y * dim + x, y * dim + x + 1, (y + 1) * dim + x, (y + 1) * dim + x + 1 }; + for (int k = 3; k > 0; --k) { + int j = (int)(next() % (u32)(k + 1)); + std::swap(p[k], p[j]); + } + int a = idx[p[0]], b = idx[p[1]], c = idx[p[2]], d = idx[p[3]]; + idx[p[0]] = b; idx[p[1]] = a; idx[p[2]] = d; idx[p[3]] = c; + } + } + } + xr_vector pix((size_t)dim * dim * 2, 0); + for (int y = 0; y < dim; ++y) { + for (int x = 0; x < dim; ++x) { + int i = y * dim + x; + int p = idx[i]; + int px = p % dim; + int py = p / dim; + int dx = std::clamp(px - x, -127, 127); + int dy = std::clamp(py - y, -127, 127); + pix[(size_t)i * 2] = (int8_t)dx; + pix[(size_t)i * 2 + 1] = (int8_t)dy; + } + } + if (tex) { + nvrhi::CommandListHandle cmd = m_device->createCommandList(); + cmd->open(); + cmd->writeTexture(tex, 0, 0, pix.data(), (size_t)dim * 2); + cmd->close(); + m_device->executeCommandList(cmd); + } + return tex; + }; + m_pairingTex[0] = makePair("ReSTIR_Pair254", 254, 8); + m_pairingTex[1] = makePair("ReSTIR_Pair230", 230, 6); + m_pairingTex[2] = makePair("ReSTIR_Pair210", 210, 5); + } + + { + nvrhi::TextureDesc desc; + desc.debugName = "ReSTIR_DDGIProbes"; + desc.width = 16; + desc.height = 8; + desc.depth = 16; + desc.dimension = nvrhi::TextureDimension::Texture3D; + desc.format = nvrhi::Format::RGBA16_FLOAT; + desc.isUAV = true; + desc.initialState = nvrhi::ResourceStates::UnorderedAccess; + desc.keepInitialState = true; + m_ddgiProbes = m_device->createTexture(desc); + } + + m_width = width; + m_height = height; + m_giWidth = giW; + m_giHeight = giH; + m_shaftWidth = shaftW; + m_shaftHeight = shaftH; + m_resetHistory = true; + Msg("* [ReSTIR] Persistent resources full %ux%u gi %ux%u%s shafts %ux%u (lights max %u)", + width, height, giW, giH, ps_r_rt_gi_half ? " half" : " full", shaftW, shaftH, RESTIR_MAX_LIGHTS); +} + +void ReSTIRMemoryManager::Ensure(u32 width, u32 height) +{ + if (!m_device || width == 0 || height == 0) + return; + u32 giW = width; + u32 giH = height; + RestirGiResolution(width, height, giW, giH); + const u32 shaftW = std::max(1u, (width + 1u) / 2u); + const u32 shaftH = std::max(1u, (height + 1u) / 2u); + const bool sizeOk = m_reservoir[0] && m_width == width && m_height == height + && m_giWidth == giW && m_giHeight == giH + && m_shaftWidth == shaftW && m_shaftHeight == shaftH; + const bool complete = sizeOk && m_diReservoir[0] && m_specReservoirA[0] && m_specReservoirB[0] + && m_blurTemp && m_sunshafts && m_sunshaftsHist && m_directLighting && m_skyOpen + && m_sunVis[0] && m_sunVis[1]; + if (complete) + return; + + DestroyResources(); + CreatePersistent(width, height); +} + +nvrhi::IBuffer* ReSTIRMemoryManager::GetReservoirBuffer(u32 index) const +{ + return m_reservoir[index & 1]; +} + +void ReSTIRMemoryManager::RequestHistoryReset() +{ + m_resetHistory = true; +} + +bool ReSTIRMemoryManager::ConsumeHistoryReset() +{ + const bool reset = m_resetHistory; + m_resetHistory = false; + return reset; +} + +void ReSTIRMemoryManager::ClearHistoryTargets(nvrhi::ICommandList* cmd) +{ + if (!cmd) + return; + const nvrhi::Color z(0.f); + if (m_histDiffuse) + cmd->clearTextureFloat(m_histDiffuse, nvrhi::AllSubresources, z); + if (m_histSpecular) + cmd->clearTextureFloat(m_histSpecular, nvrhi::AllSubresources, z); + if (m_ddgiAmbient) + cmd->clearTextureFloat(m_ddgiAmbient, nvrhi::AllSubresources, z); + if (m_ddgiProbes) + cmd->clearTextureFloat(m_ddgiProbes, nvrhi::AllSubresources, z); + if (m_diReservoir[0]) + cmd->clearTextureFloat(m_diReservoir[0], nvrhi::AllSubresources, z); + if (m_diReservoir[1]) + cmd->clearTextureFloat(m_diReservoir[1], nvrhi::AllSubresources, z); + if (m_noisyDiffuse) + cmd->clearTextureFloat(m_noisyDiffuse, nvrhi::AllSubresources, z); + if (m_noisySpecular) + cmd->clearTextureFloat(m_noisySpecular, nvrhi::AllSubresources, z); + if (m_irradianceCache) + cmd->clearBufferUInt(m_irradianceCache, 0); + if (m_reservoir[0]) + cmd->clearBufferUInt(m_reservoir[0], 0); + if (m_reservoir[1]) + cmd->clearBufferUInt(m_reservoir[1], 0); + if (m_sunVis[0]) + cmd->clearTextureFloat(m_sunVis[0], nvrhi::AllSubresources, z); + if (m_sunVis[1]) + cmd->clearTextureFloat(m_sunVis[1], nvrhi::AllSubresources, z); + if (m_sunshaftsHist) + cmd->clearTextureFloat(m_sunshaftsHist, nvrhi::AllSubresources, z); + if (m_sunshafts) + cmd->clearTextureFloat(m_sunshafts, nvrhi::AllSubresources, z); + if (m_skyOpen) + cmd->clearTextureFloat(m_skyOpen, nvrhi::AllSubresources, z); +} + +void ReSTIRMemoryManager::EnsureBlueNoiseUploaded(nvrhi::ICommandList* cmd) +{ + if (m_blueNoiseUploaded || !cmd || !m_blueNoise || + m_blueNoisePixels.size() < (size_t)kSTBNSize * kSTBNSize * kSTBNDepth) + return; + cmd->writeTexture(m_blueNoise, 0, 0, m_blueNoisePixels.data(), kSTBNSize, + (size_t)kSTBNSize * kSTBNSize); + m_blueNoiseUploaded = true; +} + +void ReSTIRMemoryManager::EnsureIrradianceCache(u32 entries) +{ + if (!m_device) + return; + if (m_irradianceCache && m_irradianceCacheSize == entries) + return; + nvrhi::BufferDesc desc; + desc.debugName = "RTGI_IrradianceCache"; + const u32 count = entries > 0 ? entries : 1u; + desc.byteSize = (size_t)count * 16u; + desc.structStride = 16; + desc.canHaveUAVs = true; + desc.canHaveRawViews = true; + desc.initialState = nvrhi::ResourceStates::UnorderedAccess; + desc.keepInitialState = true; + m_irradianceCache = m_device->createBuffer(desc); + m_irradianceCacheSize = entries; +} + +void ReSTIRMemoryManager::UploadLights(nvrhi::ICommandList* cmd, const void* lights, u32 lightCount, u32 strideBytes) +{ + if (!cmd || !m_lightData || !lights || strideBytes == 0) + return; + u32 count = lightCount; + if (count > RESTIR_MAX_LIGHTS) + count = RESTIR_MAX_LIGHTS; + if (count == 0) + return; + cmd->writeBuffer(m_lightData, lights, (size_t)count * strideBytes); +} + +} diff --git a/src/Layers/xrRender/RayTracing/ReSTIRMemoryManager.h b/src/Layers/xrRender/RayTracing/ReSTIRMemoryManager.h new file mode 100644 index 00000000000..1a704a75eab --- /dev/null +++ b/src/Layers/xrRender/RayTracing/ReSTIRMemoryManager.h @@ -0,0 +1,135 @@ +#pragma once + +#include +#include "xrCore/xrCore.h" + +namespace xray::render::fg { + +static constexpr u32 RESTIR_MAX_LIGHTS = 2048; +static constexpr u32 RESTIR_RESERVOIR_STRIDE = 16; + +struct ReSTIRPackedReservoir +{ + u32 data[4]; +}; +static_assert(sizeof(ReSTIRPackedReservoir) == RESTIR_RESERVOIR_STRIDE, "Packed reservoir must be 16 bytes"); + +class ReSTIRMemoryManager +{ +public: + static ReSTIRMemoryManager& Instance(); + + void Init(nvrhi::IDevice* device); + void Shutdown(); + + void Ensure(u32 width, u32 height); + void DestroyResources(); + + nvrhi::IBuffer* GetReservoirBuffer(u32 index) const; + nvrhi::ITexture* GetDirectLighting() const { return m_directLighting; } + nvrhi::ITexture* GetNoisyDiffuse() const { return m_noisyDiffuse; } + nvrhi::ITexture* GetNoisySpecular() const { return m_noisySpecular; } + nvrhi::ITexture* GetHitDistance() const { return m_hitDistance; } + nvrhi::ITexture* GetWetAccum() const { return m_wetAccum; } + nvrhi::ITexture* GetSkyOpen() const { return m_skyOpen; } + nvrhi::ITexture* GetSunshafts() const { return m_sunshafts; } + nvrhi::ITexture* GetSunshaftsHist() const { return m_sunshaftsHist; } + u32 GetShaftWidth() const { return m_shaftWidth; } + u32 GetShaftHeight() const { return m_shaftHeight; } + u32 GetGiWidth() const { return m_giWidth ? m_giWidth : m_width; } + u32 GetGiHeight() const { return m_giHeight ? m_giHeight : m_height; } + u32 GetFullWidth() const { return m_width; } + u32 GetFullHeight() const { return m_height; } + nvrhi::ITexture* GetDdgiAmbient() const { return m_ddgiAmbient; } + nvrhi::ITexture* GetDdgiProbes() const { return m_ddgiProbes; } + nvrhi::IBuffer* GetLightDataBuffer() const { return m_lightData; } + nvrhi::IBuffer* GetPlaceholderBuffer() const { return m_placeholderBuffer; } + nvrhi::ITexture* GetPlaceholderCube() const { return m_placeholderCube; } + nvrhi::ITexture* GetPlaceholderTex() const { return m_placeholderTex; } + nvrhi::ITexture* GetPlaceholderTex3D() const { return m_placeholderTex3D; } + nvrhi::ITexture* GetPlaceholderColorTex() const { return m_placeholderColorTex; } + nvrhi::ITexture* GetDIReservoir(u32 index) const { return m_diReservoir[index & 1]; } + nvrhi::ITexture* GetSpecReservoirA(u32 index) const { return m_specReservoirA[index & 1]; } + nvrhi::ITexture* GetSpecReservoirB(u32 index) const { return m_specReservoirB[index & 1]; } + nvrhi::ITexture* GetBlurTemp() const { return m_blurTemp; } + nvrhi::ITexture* GetBlurTempSpec() const { return m_blurTempSpec; } + nvrhi::ITexture* GetHistDiffuse() const { return m_histDiffuse; } + nvrhi::ITexture* GetHistSpecular() const { return m_histSpecular; } + nvrhi::ITexture* GetBlueNoise() const { return m_blueNoise; } + nvrhi::ITexture* GetSunVis(u32 index) const { return m_sunVis[index & 1]; } + nvrhi::ITexture* GetPTReservoirA(u32 index) const { return m_ptReservoirA[index & 1]; } + nvrhi::ITexture* GetPTReservoirB(u32 index) const { return m_ptReservoirB[index & 1]; } + nvrhi::ITexture* GetPTDupMap() const { return m_ptDupMap; } + nvrhi::ITexture* GetPairingTex(u32 index) const { return m_pairingTex[index % 3]; } + void EnsureBlueNoiseUploaded(nvrhi::ICommandList* cmd); + nvrhi::IBuffer* GetIrradianceCache() const { return m_irradianceCache; } + u32 GetIrradianceCacheSize() const { return m_irradianceCacheSize; } + void EnsureIrradianceCache(u32 entries); + void RequestHistoryReset(); + bool ConsumeHistoryReset(); + void ClearHistoryTargets(nvrhi::ICommandList* cmd); + + nvrhi::BindingSetHandle& TemporalBindingSet(u32 idx) { return m_temporalBS[idx & 1]; } + nvrhi::BindingSetHandle& SpatialBindingSet(u32 idx) { return m_spatialBS[idx & 1]; } + void ClearBindingSets(); + + u32 GetWidth() const { return m_width; } + u32 GetHeight() const { return m_height; } + u32 GetPixelCount() const { return GetGiWidth() * GetGiHeight(); } + bool IsReady() const { return m_reservoir[0] != nullptr; } + + void UploadLights(nvrhi::ICommandList* cmd, const void* lights, u32 lightCount, u32 strideBytes); + + static constexpr u32 WorkIndex() { return 0; } + static constexpr u32 HistoryIndex() { return 1; } + +private: + void CreatePersistent(u32 width, u32 height); + + nvrhi::DeviceHandle m_device; + nvrhi::BufferHandle m_reservoir[2]; + nvrhi::TextureHandle m_directLighting; + nvrhi::TextureHandle m_noisyDiffuse; + nvrhi::TextureHandle m_noisySpecular; + nvrhi::TextureHandle m_hitDistance; + nvrhi::TextureHandle m_wetAccum; + nvrhi::TextureHandle m_skyOpen; + nvrhi::TextureHandle m_sunshafts; + nvrhi::TextureHandle m_sunshaftsHist; + nvrhi::TextureHandle m_ddgiAmbient; + nvrhi::TextureHandle m_ddgiProbes; + nvrhi::BufferHandle m_lightData; + nvrhi::BufferHandle m_placeholderBuffer; + nvrhi::TextureHandle m_placeholderCube; + nvrhi::TextureHandle m_placeholderTex; + nvrhi::TextureHandle m_placeholderColorTex; + nvrhi::TextureHandle m_placeholderTex3D; + nvrhi::TextureHandle m_diReservoir[2]; + nvrhi::TextureHandle m_specReservoirA[2]; + nvrhi::TextureHandle m_specReservoirB[2]; + nvrhi::TextureHandle m_blurTemp; + nvrhi::TextureHandle m_blurTempSpec; + nvrhi::TextureHandle m_histDiffuse; + nvrhi::TextureHandle m_histSpecular; + nvrhi::TextureHandle m_blueNoise; + nvrhi::TextureHandle m_sunVis[2]; + nvrhi::TextureHandle m_ptReservoirA[2]; + nvrhi::TextureHandle m_ptReservoirB[2]; + nvrhi::TextureHandle m_ptDupMap; + nvrhi::TextureHandle m_pairingTex[3]; + xr_vector m_blueNoisePixels; + bool m_blueNoiseUploaded = false; + nvrhi::BufferHandle m_irradianceCache; + u32 m_irradianceCacheSize = 0; + nvrhi::BindingSetHandle m_temporalBS[2]; + nvrhi::BindingSetHandle m_spatialBS[2]; + u32 m_width = 0; + u32 m_height = 0; + u32 m_giWidth = 0; + u32 m_giHeight = 0; + u32 m_shaftWidth = 0; + u32 m_shaftHeight = 0; + bool m_resetHistory = false; +}; + +} diff --git a/src/Layers/xrRender/RenderContext/RenderDevice.cpp b/src/Layers/xrRender/RenderContext/RenderDevice.cpp index 4b1da36f258..e38beb450f5 100644 --- a/src/Layers/xrRender/RenderContext/RenderDevice.cpp +++ b/src/Layers/xrRender/RenderContext/RenderDevice.cpp @@ -110,6 +110,10 @@ bool RenderDevice::InitializeFromBackend(IRenderBackend* backend) { nvrhi::ICommandList* GetCommandList() const override { return m_ref->GetCommandList(); } nvrhi::ITexture* GetBackBuffer() override { return m_ref->GetBackBuffer(); } void Present(bool vsync) override { m_ref->Present(vsync); } + bool PresentFrameGeneration(nvrhi::ITexture* interpolated, nvrhi::ITexture* real) override + { + return m_ref->PresentFrameGeneration(interpolated, real); + } std::pair GetBackBufferSize() const override { return m_ref->GetBackBufferSize(); } bool IsInFrame() const override { return m_ref->IsInFrame(); } void BeginFrame() override { m_ref->BeginFrame(); } @@ -594,6 +598,24 @@ nvrhi::BindingSetHandle RenderDevice::CreateBindingSet(const nvrhi::BindingSetDe nvrhi::FramebufferHandle RenderDevice::CreateFramebuffer(const nvrhi::FramebufferDesc& desc) { VERIFY(m_initialized); + u32 attW = 0, attH = 0; + auto checkDim = [&](nvrhi::ITexture* tex) -> bool { + if (!tex) + return true; + const auto& d = tex->getDesc(); + if (!attW) { + attW = d.width; + attH = d.height; + return true; + } + return d.width == attW && d.height == attH; + }; + if (desc.depthAttachment.texture && !checkDim(desc.depthAttachment.texture)) + return nullptr; + for (const auto& attachment : desc.colorAttachments) { + if (attachment.texture && !checkDim(attachment.texture)) + return nullptr; + } return GetNativeDevice()->createFramebuffer(desc); } diff --git a/src/Layers/xrRender/RenderContext/RenderDevice.h b/src/Layers/xrRender/RenderContext/RenderDevice.h index a3f7b2f29ad..6a45ac9b17d 100644 --- a/src/Layers/xrRender/RenderContext/RenderDevice.h +++ b/src/Layers/xrRender/RenderContext/RenderDevice.h @@ -163,7 +163,7 @@ class RenderDevice { bool isVolatile = false; u32 maxVersions = 0; - static constexpr u32 VOLATILE_CB_MAX_VERSIONS = 16; + static constexpr u32 VOLATILE_CB_MAX_VERSIONS = 256; shared_str debugName; }; diff --git a/src/Layers/xrRender/ResourceManager/TextureManager.cpp b/src/Layers/xrRender/ResourceManager/TextureManager.cpp index 103312e9682..187602b5978 100644 --- a/src/Layers/xrRender/ResourceManager/TextureManager.cpp +++ b/src/Layers/xrRender/ResourceManager/TextureManager.cpp @@ -803,11 +803,21 @@ void TextureManager::LoadTextureSync(TextureHandle handle) { // (u32)ddsData.sequenceState->frameData.size()); // } - if (!isVideoTexture && !isSequenceTexture && - strncmp(meta.filePath.c_str(), "trees" DELIMITER, 6) == 0) + if (!isVideoTexture && !isSequenceTexture) { - if (PreserveAlphaCoverage(ddsData)) - Msg("* [TextureManager] Alpha coverage preserved: %s", meta.filePath.c_str()); + const char* path = meta.filePath.c_str(); + const bool arefLikely = + strncmp(path, "trees" DELIMITER, 6) == 0 || + strncmp(path, "mtl" DELIMITER, 4) == 0 || + strncmp(path, "prop" DELIMITER, 5) == 0 || + strncmp(path, "details" DELIMITER, 8) == 0 || + strncmp(path, "flora" DELIMITER, 6) == 0 || + strncmp(path, "fx" DELIMITER, 3) == 0 || + strstr(path, "fence") != nullptr || + strstr(path, "grate") != nullptr || + strstr(path, "wire") != nullptr; + if (arefLikely && PreserveAlphaCoverage(ddsData)) + Msg("* [TextureManager] Alpha coverage preserved: %s", path); } // ═══════════════════════════════════════════════════ diff --git a/src/Layers/xrRender/ShaderVariant/VariantPSOCache.cpp b/src/Layers/xrRender/ShaderVariant/VariantPSOCache.cpp index 14845a14115..29e4c662d40 100644 --- a/src/Layers/xrRender/ShaderVariant/VariantPSOCache.cpp +++ b/src/Layers/xrRender/ShaderVariant/VariantPSOCache.cpp @@ -65,7 +65,13 @@ nvrhi::IGraphicsPipeline* VariantPSOCache::GetOrCreatePSO( nvrhi::IBindingLayout* passBindingLayout, nvrhi::IBindingLayout* bindlessLayout) { - VariantPSOKey key{variantIndex, passIndex, vertexFormat}; + u32 fbSig = 0; + if (framebuffer) + { + const auto& info = framebuffer->getFramebufferInfo(); + fbSig = (u32)info.colorFormats.size() ^ ((u32)info.depthFormat << 8); + } + VariantPSOKey key{variantIndex, passIndex, vertexFormat, fbSig}; auto it = m_cache.find(key); if (it != m_cache.end()) @@ -107,7 +113,21 @@ nvrhi::IGraphicsPipeline* VariantPSOCache::GetOrCreatePSO( if (pass.blendEnabled) { pipeDesc.renderState.blendState.targets[0] = pass.blendRT; + pipeDesc.renderState.blendState.targets[0].setColorWriteMask(pass.colorWriteMask); pipeDesc.renderState.blendState.alphaToCoverageEnable = pass.alphaToCoverage; + const bool additive = + pass.blendRT.destBlend == nvrhi::BlendFactor::One && + (pass.blendRT.srcBlend == nvrhi::BlendFactor::SrcAlpha || + pass.blendRT.srcBlend == nvrhi::BlendFactor::One); + const bool modulate = + pass.blendRT.srcBlend == nvrhi::BlendFactor::DstColor || + pass.blendRT.destBlend == nvrhi::BlendFactor::SrcColor || + pass.blendRT.destBlend == nvrhi::BlendFactor::DstColor; + if (additive || modulate) + { + for (u32 rt = 1; rt < 4; ++rt) + pipeDesc.renderState.blendState.targets[rt].setColorWriteMask(nvrhi::ColorMask(0)); + } } auto pipeline = device->createGraphicsPipeline(pipeDesc, framebuffer); @@ -169,11 +189,17 @@ void DrawVariantPartition( for (u32 v = 0; v < p.variantCount; v++) { nvrhi::IGraphicsPipeline* pso; if (v == 0) { + if (cfg.onlyWmark) + continue; pso = cfg.defaultPipeline; } else { const auto* variant = registry.GetVariantByIndex(v); if (!variant) continue; if (cfg.selectTransparent ? !variant->transparent : variant->transparent) continue; + if (cfg.skipWmark && (variant->wmark || variant->emissive)) + continue; + if (cfg.onlyWmark && !variant->wmark && !variant->emissive) + continue; pso = psoCache.GetOrCreatePSO(nvDevice, framebuffer, v, *variant, 0, VF_MDI, cfg.inputLayout, cfg.passLayout, cfg.bindlessLayout); if (!pso) continue; diff --git a/src/Layers/xrRender/ShaderVariant/VariantPSOCache.h b/src/Layers/xrRender/ShaderVariant/VariantPSOCache.h index 11a515dcb8c..10e9873f039 100644 --- a/src/Layers/xrRender/ShaderVariant/VariantPSOCache.h +++ b/src/Layers/xrRender/ShaderVariant/VariantPSOCache.h @@ -13,12 +13,14 @@ struct VariantPSOKey u32 variantIndex; u32 passIndex; u32 vertexFormat; + u32 fbSig; bool operator<(const VariantPSOKey& o) const { if (variantIndex != o.variantIndex) return variantIndex < o.variantIndex; if (passIndex != o.passIndex) return passIndex < o.passIndex; - return vertexFormat < o.vertexFormat; + if (vertexFormat != o.vertexFormat) return vertexFormat < o.vertexFormat; + return fbSig < o.fbSig; } }; @@ -72,6 +74,8 @@ struct VariantPartitionDrawConfig u32 objectCount = 0; VariantPartitionConfig partition; bool selectTransparent = false; + bool skipWmark = false; + bool onlyWmark = false; }; void DrawVariantPartition( diff --git a/src/Layers/xrRender/SkeletonRigid.cpp b/src/Layers/xrRender/SkeletonRigid.cpp index 6884ab81f73..70c00a8c0f0 100644 --- a/src/Layers/xrRender/SkeletonRigid.cpp +++ b/src/Layers/xrRender/SkeletonRigid.cpp @@ -221,17 +221,27 @@ void CKinematics::CLBone(const CBoneData* bd, CBoneInstance& bi, const Fmatrix* else { BuildBoneMatrix(bd, bi, parent, channel_mask); -#ifndef MASTER_GOLD - R_ASSERT2(_valid(bi.mTransform), "anim kils bone matrix"); -#endif // #ifndef MASTER_GOLD + if (!_valid(bi.mTransform)) + { + if (parent && _valid(*parent)) + bi.mTransform.set(*parent); + else + bi.mTransform.identity(); + } if (bi.callback()) { bi.callback()(&bi); -#ifndef MASTER_GOLD - R_ASSERT2(_valid(bi.mTransform), make_string("callback kils bone matrix bone: %s ", bd->name.c_str())); -#endif // #ifndef MASTER_GOLD + if (!_valid(bi.mTransform)) + { + if (parent && _valid(*parent)) + bi.mTransform.set(*parent); + else + bi.mTransform.identity(); + } } } + if (!_valid(bi.mTransform)) + bi.mTransform.identity(); bi.mRenderTransform.mul_43(bi.mTransform, bd->m2b_transform); } } @@ -243,9 +253,8 @@ void CKinematics::Bone_GetAnimPos(Fmatrix& pos, u16 id, u8 mask_channel, bool ig R_ASSERT(id < LL_BoneCount()); CBoneInstance bi = LL_GetBoneInstance(id); BoneChain_Calculate(&LL_GetData(id), bi, mask_channel, ignore_callbacks); -#ifndef MASTER_GOLD - R_ASSERT(_valid(bi.mTransform)); -#endif + if (!_valid(bi.mTransform)) + bi.mTransform.identity(); pos.set(bi.mTransform); } diff --git a/src/Layers/xrRender/Upscaling/DlssFgPassSetup.cpp b/src/Layers/xrRender/Upscaling/DlssFgPassSetup.cpp new file mode 100644 index 00000000000..2429730577f --- /dev/null +++ b/src/Layers/xrRender/Upscaling/DlssFgPassSetup.cpp @@ -0,0 +1,143 @@ +#include "stdafx.h" +#include "DlssFgPassSetup.h" +#include "StreamlineDLSS.h" +#include "Layers/xrRender/FrameGraph/FrameGraph.h" +#include "Layers/xrRender/FrameGraph/RenderPassBuilder.h" +#include "Layers/xrRender/RenderContext/RenderContext.h" +#include "Layers/xrRender/FrameGraphPasses/TAAPassSetup.h" + +extern ENGINE_API int ps_r_dlss_fg; +extern ENGINE_API int ps_r_upscale; + +namespace xray::render::fg::passes { +using namespace framegraph; + +namespace { + +void DlssFgCopyMatrix(float dst[16], const Fmatrix& m) +{ + dst[0] = m._11; dst[1] = m._12; dst[2] = m._13; dst[3] = m._14; + dst[4] = m._21; dst[5] = m._22; dst[6] = m._23; dst[7] = m._24; + dst[8] = m._31; dst[9] = m._32; dst[10] = m._33; dst[11] = m._34; + dst[12] = m._41; dst[13] = m._42; dst[14] = m._43; dst[15] = m._44; +} + +} + +void setupDlssFgPass( + FrameGraph& fg, + VirtualResourceHandle colorWithUI, + VirtualResourceHandle hudlessColor, + VirtualResourceHandle depth, + VirtualResourceHandle motionVectors, + const Fmatrix& viewToClip, + const Fmatrix& prevViewProj, + const Fmatrix& currViewProj, + u32 renderW, + u32 renderH, + u32 displayW, + u32 displayH, + bool reset) +{ + if (ps_r_upscale != 2 || !ps_r_dlss_fg || !Streamline_IsFGAvailable()) + return; + if (!colorWithUI.is_valid() || !depth.is_valid() || !motionVectors.is_valid()) + return; + + struct PassData + { + VirtualResourceHandle color; + VirtualResourceHandle hudless; + VirtualResourceHandle depth; + VirtualResourceHandle motion; + Fmatrix viewToClip{}; + Fmatrix clipToView{}; + Fmatrix clipToPrev{}; + Fmatrix prevToClip{}; + u32 renderW = 0, renderH = 0; + u32 displayW = 0, displayH = 0; + bool reset = false; + float jitterX = 0.f, jitterY = 0.f; + }; + + Fmatrix invCurr; + invCurr.invert(currViewProj); + Fmatrix clipToPrev; + clipToPrev.mul(prevViewProj, invCurr); + Fmatrix prevToClip; + prevToClip.invert(clipToPrev); + Fmatrix clipToView; + clipToView.invert(viewToClip); + + fg.addCallbackPass( + "DLSS-FG", + [&](FrameGraph& builder, PassHandle passHandle, PassData& data) { + RenderPassBuilder pb(builder, passHandle); + data.color = pb.read(colorWithUI, ResourceState::ShaderResource); + if (hudlessColor.is_valid()) + data.hudless = pb.read(hudlessColor, ResourceState::ShaderResource); + data.depth = pb.read(depth, ResourceState::ShaderResource); + data.motion = pb.read(motionVectors, ResourceState::ShaderResource); + pb.sideEffects(); + data.viewToClip = viewToClip; + data.clipToView = clipToView; + data.clipToPrev = clipToPrev; + data.prevToClip = prevToClip; + data.renderW = renderW; + data.renderH = renderH; + data.displayW = displayW; + data.displayH = displayH; + data.reset = reset; + data.jitterX = g_taa_jitter_px; + data.jitterY = g_taa_jitter_py; + }, + [](const PassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + if (!ctx) + return; + auto* color = fgGraph.GetPhysicalTexture(data.color); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* mv = fgGraph.GetPhysicalTexture(data.motion); + if (!color || !depthTex || !mv) + return; + + DlssFgInputs in{}; + in.backbuffer = color; + in.hudless = data.hudless.is_valid() ? fgGraph.GetPhysicalTexture(data.hudless) : nullptr; + in.depth = depthTex; + in.motionVectors = mv; + in.displayWidth = data.displayW; + in.displayHeight = data.displayH; + in.renderWidth = data.renderW; + in.renderHeight = data.renderH; + in.jitterX = data.jitterX; + in.jitterY = data.jitterY; + in.reset = data.reset; + DlssFgCopyMatrix(in.viewToClip, data.viewToClip); + DlssFgCopyMatrix(in.clipToView, data.clipToView); + DlssFgCopyMatrix(in.clipToPrevClip, data.clipToPrev); + DlssFgCopyMatrix(in.prevClipToClip, data.prevToClip); + in.cameraPos[0] = Device.vCameraPosition.x; + in.cameraPos[1] = Device.vCameraPosition.y; + in.cameraPos[2] = Device.vCameraPosition.z; + in.cameraUp[0] = Device.vCameraTop.x; + in.cameraUp[1] = Device.vCameraTop.y; + in.cameraUp[2] = Device.vCameraTop.z; + in.cameraFwd[0] = Device.vCameraDirection.x; + in.cameraFwd[1] = Device.vCameraDirection.y; + in.cameraFwd[2] = Device.vCameraDirection.z; + Fvector right; + right.crossproduct(Device.vCameraTop, Device.vCameraDirection); + right.normalize_safe(); + in.cameraRight[0] = right.x; + in.cameraRight[1] = right.y; + in.cameraRight[2] = right.z; + in.cameraNear = RENDER_VIEWPORT_NEAR; + in.cameraFar = g_pGamePersistent ? g_pGamePersistent->Environment().CurrentEnv.far_plane : 600.f; + in.cameraFOV = deg2rad(Device.fFOV); + in.cameraAspect = (data.displayH > 0) ? ((float)data.displayW / (float)data.displayH) : 1.f; + + Streamline_EvaluateDLSSG(ctx->GetCommandList(), in); + }); +} + +} diff --git a/src/Layers/xrRender/Upscaling/DlssFgPassSetup.h b/src/Layers/xrRender/Upscaling/DlssFgPassSetup.h new file mode 100644 index 00000000000..f2396d59e0f --- /dev/null +++ b/src/Layers/xrRender/Upscaling/DlssFgPassSetup.h @@ -0,0 +1,26 @@ +#pragma once + +#include "Layers/xrRender/FrameGraph/FGTypes.h" +#include "Layers/xrRender/FrameGraph/FGResource.h" + +namespace xray::render::framegraph { class FrameGraph; } +namespace xray::render::fg { class RenderDevice; } + +namespace xray::render::fg::passes { + +void setupDlssFgPass( + framegraph::FrameGraph& fg, + framegraph::VirtualResourceHandle colorWithUI, + framegraph::VirtualResourceHandle hudlessColor, + framegraph::VirtualResourceHandle depth, + framegraph::VirtualResourceHandle motionVectors, + const Fmatrix& viewToClip, + const Fmatrix& prevViewProj, + const Fmatrix& currViewProj, + u32 renderW, + u32 renderH, + u32 displayW, + u32 displayH, + bool reset); + +} diff --git a/src/Layers/xrRender/Upscaling/IUpscaleBackend.h b/src/Layers/xrRender/Upscaling/IUpscaleBackend.h new file mode 100644 index 00000000000..e9a04284196 --- /dev/null +++ b/src/Layers/xrRender/Upscaling/IUpscaleBackend.h @@ -0,0 +1,72 @@ +#pragma once + +#include + +namespace xray::render::fg { + +enum class UpscaleBackendType : u32 +{ + None = 0, + DLSS +}; + +enum class UpscaleQuality : u32 +{ + UltraPerformance = 0, + Performance, + Balanced, + Quality, + Native, + DLAA +}; + +struct UpscaleInputs +{ + nvrhi::ITexture* color = nullptr; + nvrhi::ITexture* depth = nullptr; + nvrhi::ITexture* motionVectors = nullptr; + nvrhi::ITexture* exposure = nullptr; + nvrhi::ITexture* reactiveMask = nullptr; + nvrhi::ITexture* output = nullptr; + nvrhi::ITexture* normals = nullptr; + nvrhi::ITexture* diffuseAlbedo = nullptr; + nvrhi::ITexture* specularAlbedo = nullptr; + nvrhi::ITexture* roughness = nullptr; + nvrhi::ITexture* diffuseHitDistance = nullptr; + nvrhi::ITexture* specularHitDistance = nullptr; + nvrhi::ITexture* noisyDiffuse = nullptr; + nvrhi::ITexture* noisySpecular = nullptr; + float worldToView[16]{}; + float viewToClip[16]{}; + u32 renderWidth = 0; + u32 renderHeight = 0; + u32 displayWidth = 0; + u32 displayHeight = 0; + float jitterX = 0.f; + float jitterY = 0.f; + float sharpness = 0.f; + float frameTimeMs = 16.f; + u32 frameIndex = 0; + bool reset = false; + bool enableFG = false; + bool enableRR = false; +}; + +class IUpscaleBackend +{ +public: + virtual ~IUpscaleBackend() = default; + virtual bool Init(nvrhi::IDevice* device) = 0; + virtual void Shutdown() = 0; + virtual bool IsAvailable() const = 0; + virtual UpscaleBackendType GetType() const = 0; + virtual bool SupportsFG() const { return false; } + virtual bool SupportsRR() const { return false; } + virtual bool Evaluate(nvrhi::ICommandList* cmd, const UpscaleInputs& inputs) = 0; +}; + +IUpscaleBackend* CreateNullUpscaleBackend(); +IUpscaleBackend* CreateDLSSUpscaleBackend(); +IUpscaleBackend* CreateUpscaleBackendAuto(); + +} diff --git a/src/Layers/xrRender/Upscaling/NgxDLSS.cpp b/src/Layers/xrRender/Upscaling/NgxDLSS.cpp new file mode 100644 index 00000000000..a16b3e2b7ad --- /dev/null +++ b/src/Layers/xrRender/Upscaling/NgxDLSS.cpp @@ -0,0 +1,879 @@ +#include "stdafx.h" +#include "StreamlineDLSS.h" +#include "Layers/xrRender/Backend/VulkanBackend.h" + +#if defined(XRAY_USE_DLSS) + +#include +#include +#include +#include +#include +#include +#if defined(XR_PLATFORM_LINUX) +#include +#include +#include +#endif + +#include "nvsdk_ngx.h" +#include "nvsdk_ngx_defs_dlssg.h" +#include "nvsdk_ngx_helpers.h" +#include "nvsdk_ngx_helpers_vk.h" +#include "nvsdk_ngx_helpers_dlssd_vk.h" +#include "nvsdk_ngx_helpers_dlssg_vk.h" + +extern ENGINE_API int ps_r_dlss_quality; +extern ENGINE_API int ps_r_dlss_fg; +extern ENGINE_API int ps_r_dlss_rr; +extern ENGINE_API int ps_r_dlss_auto_exposure; + +namespace xray::render::fg { +namespace { + +constexpr const char* kNgxProjectId = "a0f57b54-1daf-4934-90ae-c4035c19df04"; +constexpr unsigned long long kNgxApplicationId = 231313132ull; + +bool g_slPreInit = false; +bool g_ngxReady = false; +bool g_dlssAvailable = false; +bool g_rrAvailable = false; +bool g_fgAvailable = false; +NVSDK_NGX_Parameter* g_params = nullptr; +NVSDK_NGX_Handle* g_dlssHandle = nullptr; +NVSDK_NGX_Handle* g_rrHandle = nullptr; +NVSDK_NGX_Handle* g_fgHandle = nullptr; +u32 g_featW = 0, g_featH = 0, g_outW = 0, g_outH = 0; +int g_featQuality = -1; +int g_featCreateFlags = -1; +bool g_featRR = false; +bool g_featNeedsSubmit = false; +bool g_featRecreated = false; +bool g_featForceSR = false; +u32 g_fgW = 0, g_fgH = 0, g_fgRenderW = 0, g_fgRenderH = 0; +u32 g_fgFormat = 0; +nvrhi::TextureHandle g_fgInterp; +nvrhi::TextureHandle g_fgReal; +bool g_fgPresentPending = false; +bool g_fgEvaluatedLastFrame = false; +bool g_fgPresentedLastFrame = false; +std::wstring g_appDataPathW; +std::vector g_searchPathsW; +std::vector g_searchPathPtrs; +NVSDK_NGX_FeatureCommonInfo g_featureInfo{}; + +std::wstring Utf8ToWide(const xr_string& path) +{ +#if defined(XR_PLATFORM_WINDOWS) + if (path.empty()) + return L"."; + int n = MultiByteToWideChar(CP_UTF8, 0, path.c_str(), -1, nullptr, 0); + std::wstring out(n > 0 ? n - 1 : 0, L'\0'); + if (n > 1) + MultiByteToWideChar(CP_UTF8, 0, path.c_str(), -1, out.data(), n); + return out.empty() ? L"." : out; +#else + std::wstring out; + out.reserve(path.size()); + for (unsigned char c : path) + out.push_back((wchar_t)c); + return out.empty() ? L"." : out; +#endif +} + +xr_string NormalizeFsPath(xr_string path) +{ + for (char& c : path) + { + if (c == '\\') + c = '/'; + } + while (!path.empty() && (path.back() == '/' || path.back() == '\\')) + path.pop_back(); + return path; +} + +xr_string ResolveNgxAppDataPath() +{ + xr_string path; + if (auto* p = FS.get_path("$app_data_root$")) + path = NormalizeFsPath(p->m_Path); + if (path.empty()) + { +#if defined(XR_PLATFORM_LINUX) + char cwd[PATH_MAX] = {}; + if (getcwd(cwd, sizeof(cwd))) + path = cwd; +#endif + } + if (path.empty() && Core.ApplicationPath[0]) + path = NormalizeFsPath(Core.ApplicationPath); + if (path.empty()) + path = "."; + path += "/ngx"; +#if defined(XR_PLATFORM_LINUX) + mkdir(path.c_str(), 0755); +#endif + return path; +} + +void CollectDllSearchPaths() +{ + g_searchPathsW.clear(); + g_searchPathPtrs.clear(); + auto addPath = [&](const xr_string& p) { + xr_string n = NormalizeFsPath(p); + if (n.empty()) + return; + std::wstring w = Utf8ToWide(n); + for (const auto& existing : g_searchPathsW) + if (existing == w) + return; + g_searchPathsW.push_back(std::move(w)); + }; + +#if defined(XR_PLATFORM_LINUX) + char exePath[PATH_MAX] = {}; + const ssize_t n = readlink("/proc/self/exe", exePath, sizeof(exePath) - 1); + if (n > 0) + { + exePath[n] = 0; + char* slash = strrchr(exePath, '/'); + if (slash) + { + *slash = 0; + addPath(xr_string(exePath)); + } + } + char cwd[PATH_MAX] = {}; + if (getcwd(cwd, sizeof(cwd))) + addPath(xr_string(cwd)); +#endif + if (Core.ApplicationPath[0]) + addPath(xr_string(Core.ApplicationPath)); + + g_searchPathPtrs.reserve(g_searchPathsW.size()); + for (const auto& w : g_searchPathsW) + g_searchPathPtrs.push_back(w.c_str()); + + memset(&g_featureInfo, 0, sizeof(g_featureInfo)); + g_featureInfo.PathListInfo.Path = g_searchPathPtrs.data(); + g_featureInfo.PathListInfo.Length = (unsigned int)g_searchPathPtrs.size(); +} + +NVSDK_NGX_PerfQuality_Value MapQuality(int q) +{ + switch (q) + { + case 0: return NVSDK_NGX_PerfQuality_Value_UltraPerformance; + case 1: return NVSDK_NGX_PerfQuality_Value_MaxPerf; + case 2: return NVSDK_NGX_PerfQuality_Value_Balanced; + case 3: return NVSDK_NGX_PerfQuality_Value_MaxQuality; + case 5: return NVSDK_NGX_PerfQuality_Value_DLAA; + default: return NVSDK_NGX_PerfQuality_Value_MaxQuality; + } +} + +bool MakeResourceVK(nvrhi::ITexture* tex, bool readWrite, NVSDK_NGX_Resource_VK& out) +{ + if (!tex) + return false; + const auto img = tex->getNativeObject(nvrhi::ObjectTypes::VK_Image); + const auto view = tex->getNativeView(nvrhi::ObjectTypes::VK_ImageView); + if (!img.integer || !view.integer) + return false; + const nvrhi::Format fmt = tex->getDesc().format; + VkImageSubresourceRange range{}; + range.aspectMask = (fmt == nvrhi::Format::D32 || + fmt == nvrhi::Format::D16 || + fmt == nvrhi::Format::D24S8 || + fmt == nvrhi::Format::D32S8) + ? VK_IMAGE_ASPECT_DEPTH_BIT + : VK_IMAGE_ASPECT_COLOR_BIT; + range.levelCount = 1; + range.layerCount = 1; + out = NVSDK_NGX_Create_ImageView_Resource_VK( + (VkImageView)view.integer, + (VkImage)img.integer, + range, + (VkFormat)nvrhi::vulkan::convertFormat(tex->getDesc().format), + tex->getDesc().width, + tex->getDesc().height, + readWrite); + return true; +} + +void WaitGpuForNgx() +{ + if (GEnv.Backend) + GEnv.Backend->WaitForIdle(); +} + +void DestroyFgFeature() +{ + const bool hadFg = g_fgHandle != nullptr; + if (hadFg && g_params) + { + WaitGpuForNgx(); + NVSDK_NGX_VULKAN_ReleaseFeature(g_fgHandle); + g_fgHandle = nullptr; + } + g_fgInterp = nullptr; + g_fgReal = nullptr; + g_fgPresentPending = false; + g_fgW = g_fgH = g_fgRenderW = g_fgRenderH = 0; + g_fgFormat = 0; +} + +void DestroyFeatures() +{ + const bool hadFeat = g_dlssHandle || g_rrHandle; + if (hadFeat && g_params) + { + WaitGpuForNgx(); + if (g_dlssHandle) + { + NVSDK_NGX_VULKAN_ReleaseFeature(g_dlssHandle); + g_dlssHandle = nullptr; + } + if (g_rrHandle) + { + NVSDK_NGX_VULKAN_ReleaseFeature(g_rrHandle); + g_rrHandle = nullptr; + } + } + g_featW = g_featH = g_outW = g_outH = 0; + g_featQuality = -1; + g_featCreateFlags = -1; + g_featRR = false; + g_featNeedsSubmit = false; +} + +void CopyTo44(float dst[4][4], const float src[16]) +{ + for (int r = 0; r < 4; ++r) + for (int c = 0; c < 4; ++c) + dst[r][c] = src[r * 4 + c]; +} + +bool EnsureFeature(nvrhi::ICommandList* cmd, const UpscaleInputs& inputs, bool wantRR) +{ + if (!g_ngxReady || !g_params || !cmd) + return false; + int createFlags = + NVSDK_NGX_DLSS_Feature_Flags_IsHDR | + NVSDK_NGX_DLSS_Feature_Flags_DepthInverted; + if (inputs.renderWidth < inputs.displayWidth || inputs.renderHeight < inputs.displayHeight) + createFlags |= NVSDK_NGX_DLSS_Feature_Flags_MVLowRes; + if (ps_r_dlss_auto_exposure || !inputs.exposure) + createFlags |= NVSDK_NGX_DLSS_Feature_Flags_AutoExposure; + + if (g_featW == inputs.renderWidth && g_featH == inputs.renderHeight && + g_outW == inputs.displayWidth && g_outH == inputs.displayHeight && + g_featQuality == ps_r_dlss_quality && g_featRR == wantRR && + g_featCreateFlags == createFlags && + (wantRR ? g_rrHandle : g_dlssHandle)) + return true; + + DestroyFeatures(); + + auto* vkCmd = (VkCommandBuffer)cmd->getNativeObject(nvrhi::ObjectTypes::VK_CommandBuffer).integer; + auto* backend = dynamic_cast(GEnv.Backend); + if (!vkCmd || !backend) + return false; + + NVSDK_NGX_DLSS_Create_Params create{}; + create.Feature.InWidth = inputs.renderWidth; + create.Feature.InHeight = inputs.renderHeight; + create.Feature.InTargetWidth = inputs.displayWidth; + create.Feature.InTargetHeight = inputs.displayHeight; + create.Feature.InPerfQualityValue = MapQuality(ps_r_dlss_quality); + create.InFeatureCreateFlags = createFlags; + + NVSDK_NGX_Result res; + if (wantRR && g_rrAvailable) + { + NVSDK_NGX_DLSSD_Create_Params rrCreate{}; + rrCreate.InDenoiseMode = NVSDK_NGX_DLSS_Denoise_Mode_DLUnified; + rrCreate.InRoughnessMode = NVSDK_NGX_DLSS_Roughness_Mode_Packed; + rrCreate.InUseHWDepth = NVSDK_NGX_DLSS_Depth_Type_HW; + rrCreate.InWidth = inputs.renderWidth; + rrCreate.InHeight = inputs.renderHeight; + rrCreate.InTargetWidth = inputs.displayWidth; + rrCreate.InTargetHeight = inputs.displayHeight; + rrCreate.InPerfQualityValue = MapQuality(ps_r_dlss_quality); + rrCreate.InFeatureCreateFlags = create.InFeatureCreateFlags; + res = NGX_VULKAN_CREATE_DLSSD_EXT1( + backend->GetVkDevice(), vkCmd, 1, 1, &g_rrHandle, g_params, &rrCreate); + if (NVSDK_NGX_FAILED(res)) + { + Msg("! [Upscale] DLSS-RR CreateFeature failed 0x%08x — falling back to DLSS SR", (u32)res); + wantRR = false; + } + } + + if (!wantRR) + { + res = NGX_VULKAN_CREATE_DLSS_EXT1( + backend->GetVkDevice(), vkCmd, 1, 1, &g_dlssHandle, g_params, &create); + if (NVSDK_NGX_FAILED(res)) + { + Msg("! [Upscale] DLSS CreateFeature failed 0x%08x", (u32)res); + return false; + } + } + + g_featW = inputs.renderWidth; + g_featH = inputs.renderHeight; + g_outW = inputs.displayWidth; + g_outH = inputs.displayHeight; + g_featQuality = ps_r_dlss_quality; + g_featCreateFlags = createFlags; + g_featRR = wantRR && g_rrHandle != nullptr; + g_featNeedsSubmit = true; + g_featRecreated = true; + return true; +} + +} + +bool Streamline_PreInstanceInit() +{ + g_slPreInit = true; + return true; +} + +void Streamline_GetRequiredInstanceExtensions(xr_vector& outExts) +{ + unsigned int instCount = 0, devCount = 0; + const char** instExts = nullptr; + const char** devExts = nullptr; + if (NVSDK_NGX_SUCCEED(NVSDK_NGX_VULKAN_RequiredExtensions(&instCount, &instExts, &devCount, &devExts))) + { + for (unsigned int i = 0; i < instCount; ++i) + outExts.push_back(instExts[i]); + (void)devExts; + (void)devCount; + } +} + +void Streamline_GetRequiredDeviceExtensions(void* /*physicalDevice*/, xr_vector& outExts) +{ + unsigned int instCount = 0, devCount = 0; + const char** instExts = nullptr; + const char** devExts = nullptr; + if (NVSDK_NGX_SUCCEED(NVSDK_NGX_VULKAN_RequiredExtensions(&instCount, &instExts, &devCount, &devExts))) + { + for (unsigned int i = 0; i < devCount; ++i) + outExts.push_back(devExts[i]); + (void)instExts; + (void)instCount; + } +} + +bool Streamline_SetVulkanInfo(const StreamlineVulkanInfo& info) +{ + if (!info.instance || !info.physicalDevice || !info.device) + return false; + if (g_ngxReady) + return g_dlssAvailable; + + const xr_string appData = ResolveNgxAppDataPath(); + g_appDataPathW = Utf8ToWide(appData); + CollectDllSearchPaths(); + Msg("* [Upscale] NGX init appData=%s dllPaths=%u", appData.c_str(), (u32)g_searchPathPtrs.size()); + FlushLog(); + + Msg("* [Upscale] NGX calling VULKAN_Init (ApplicationId)..."); + FlushLog(); + NVSDK_NGX_Result res = NVSDK_NGX_VULKAN_Init( + kNgxApplicationId, + g_appDataPathW.c_str(), + (VkInstance)info.instance, + (VkPhysicalDevice)info.physicalDevice, + (VkDevice)info.device, + vkGetInstanceProcAddr, + vkGetDeviceProcAddr, + g_searchPathPtrs.empty() ? nullptr : &g_featureInfo, + NVSDK_NGX_Version_API); + + if (NVSDK_NGX_FAILED(res)) + { + Msg("! [Upscale] NGX ApplicationId Init failed 0x%08x — trying ProjectID", (u32)res); + FlushLog(); + res = NVSDK_NGX_VULKAN_Init_with_ProjectID( + kNgxProjectId, + NVSDK_NGX_ENGINE_TYPE_CUSTOM, + "1.6.0", + g_appDataPathW.c_str(), + (VkInstance)info.instance, + (VkPhysicalDevice)info.physicalDevice, + (VkDevice)info.device, + vkGetInstanceProcAddr, + vkGetDeviceProcAddr, + g_searchPathPtrs.empty() ? nullptr : &g_featureInfo, + NVSDK_NGX_Version_API); + } + + if (NVSDK_NGX_FAILED(res)) + { + Msg("! [Upscale] NGX Vulkan Init failed 0x%08x", (u32)res); + FlushLog(); + return false; + } + Msg("* [Upscale] NGX Init OK (0x%08x)", (u32)res); + FlushLog(); + + res = NVSDK_NGX_VULKAN_GetCapabilityParameters(&g_params); + if (NVSDK_NGX_FAILED(res) || !g_params) + { + Msg("! [Upscale] NGX GetCapabilityParameters failed"); + NVSDK_NGX_VULKAN_Shutdown1((VkDevice)info.device); + return false; + } + + int needsUpdatedDriver = 0; + unsigned int minDriver = 0; + NVSDK_NGX_Parameter_GetI(g_params, NVSDK_NGX_Parameter_SuperSampling_NeedsUpdatedDriver, &needsUpdatedDriver); + NVSDK_NGX_Parameter_GetUI(g_params, NVSDK_NGX_Parameter_SuperSampling_MinDriverVersionMajor, &minDriver); + int dlssSupported = 0; + NVSDK_NGX_Parameter_GetI(g_params, NVSDK_NGX_Parameter_SuperSampling_Available, &dlssSupported); + g_dlssAvailable = dlssSupported != 0; + + int rrSupported = 0; + NVSDK_NGX_Parameter_GetI(g_params, NVSDK_NGX_Parameter_SuperSamplingDenoising_Available, &rrSupported); + g_rrAvailable = rrSupported != 0; + + int fgSupported = 0; + NVSDK_NGX_Parameter_GetI(g_params, NVSDK_NGX_Parameter_FrameGeneration_Available, &fgSupported); + if (!fgSupported) + NVSDK_NGX_Parameter_GetI(g_params, NVSDK_NGX_Parameter_FrameInterpolation_Available, &fgSupported); + g_fgAvailable = fgSupported != 0; + if (!g_fgAvailable) + { + int fgInit = 0; + unsigned int fgMinMaj = 0, fgMinMin = 0; + int fgNeedsDriver = 0; + NVSDK_NGX_Parameter_GetI(g_params, NVSDK_NGX_Parameter_FrameGeneration_FeatureInitResult, &fgInit); + NVSDK_NGX_Parameter_GetUI(g_params, NVSDK_NGX_Parameter_FrameGeneration_MinDriverVersionMajor, &fgMinMaj); + NVSDK_NGX_Parameter_GetUI(g_params, NVSDK_NGX_Parameter_FrameGeneration_MinDriverVersionMinor, &fgMinMin); + NVSDK_NGX_Parameter_GetI(g_params, NVSDK_NGX_Parameter_FrameGeneration_NeedsUpdatedDriver, &fgNeedsDriver); + Msg("! [Upscale] DLSS-FG unavailable initResult=0x%08x needsDriver=%d minDriver=%u.%u (RTX 40+ required)", + (u32)fgInit, fgNeedsDriver, fgMinMaj, fgMinMin); + } + + g_ngxReady = true; + Msg("* [Upscale] NGX ready dlss=%d rr=%d fg=%d needsDriver=%d minMajor=%u", + g_dlssAvailable ? 1 : 0, g_rrAvailable ? 1 : 0, g_fgAvailable ? 1 : 0, + needsUpdatedDriver, minDriver); + if (!g_dlssAvailable) + Msg("! [Upscale] DLSS plugin unavailable — check libnvidia-ngx-dlss.so next to xr_3da / LD_LIBRARY_PATH"); + return g_dlssAvailable; +} + +void Streamline_Shutdown() +{ + DestroyFgFeature(); + DestroyFeatures(); + if (g_params || g_ngxReady) + WaitGpuForNgx(); + if (g_params) + { + NVSDK_NGX_VULKAN_DestroyParameters(g_params); + g_params = nullptr; + } + if (g_ngxReady) + { + auto* backend = dynamic_cast(GEnv.Backend); + if (backend) + NVSDK_NGX_VULKAN_Shutdown1(backend->GetVkDevice()); + } + g_ngxReady = false; + g_dlssAvailable = g_rrAvailable = g_fgAvailable = false; + g_featRecreated = false; + g_featForceSR = false; +} + +bool Streamline_IsDLSSAvailable() { return g_dlssAvailable; } +bool Streamline_IsFGAvailable() { return g_fgAvailable; } +bool Streamline_IsRRAvailable() { return g_rrAvailable; } + +bool Streamline_ConsumeFeatureReset() +{ + const bool r = g_featRecreated; + g_featRecreated = false; + return r; +} + +void Streamline_ReleaseFeatures() +{ + DestroyFgFeature(); + DestroyFeatures(); + g_featForceSR = false; +} + +bool EvaluateDLSSSR(VkCommandBuffer vkCmd, const UpscaleInputs& inputs, + NVSDK_NGX_Resource_VK& color, NVSDK_NGX_Resource_VK& depth, NVSDK_NGX_Resource_VK& mv, + NVSDK_NGX_Resource_VK& out, NVSDK_NGX_Resource_VK* exposure) +{ + if (!g_dlssHandle) + return false; + NVSDK_NGX_VK_DLSS_Eval_Params eval{}; + eval.Feature.pInColor = &color; + eval.Feature.pInOutput = &out; + eval.Feature.InSharpness = inputs.sharpness; + eval.pInDepth = &depth; + eval.pInMotionVectors = &mv; + eval.InJitterOffsetX = inputs.jitterX; + eval.InJitterOffsetY = inputs.jitterY; + eval.InRenderSubrectDimensions = { inputs.renderWidth, inputs.renderHeight }; + eval.InReset = inputs.reset ? 1 : 0; + eval.InMVScaleX = (float)inputs.renderWidth; + eval.InMVScaleY = (float)inputs.renderHeight; + eval.InPreExposure = 1.f; + eval.InExposureScale = 1.f; + if (exposure) + eval.pInExposureTexture = exposure; + const NVSDK_NGX_Result res = NGX_VULKAN_EVALUATE_DLSS_EXT(vkCmd, g_dlssHandle, g_params, &eval); + if (NVSDK_NGX_FAILED(res)) + { + Msg("! [Upscale] DLSS SR Evaluate failed 0x%08x", (u32)res); + return false; + } + return true; +} + +bool Streamline_EvaluateDLSS(nvrhi::ICommandList* cmd, const UpscaleInputs& inputs) +{ + if (!g_ngxReady || !cmd || !inputs.color || !inputs.output || !inputs.depth || !inputs.motionVectors) + return false; + + bool wantRR = !g_featForceSR && inputs.enableRR && g_rrAvailable && + inputs.normals && inputs.diffuseAlbedo && inputs.specularAlbedo; + if (!EnsureFeature(cmd, inputs, wantRR)) + return false; + UpscaleInputs evalIn = inputs; + if (g_featNeedsSubmit) + { + g_featNeedsSubmit = false; + evalIn.reset = true; + } + wantRR = g_featRR && wantRR; + + NVSDK_NGX_Resource_VK color{}, depth{}, mv{}, out{}, exposure{}; + if (!MakeResourceVK(evalIn.color, false, color) || + !MakeResourceVK(evalIn.depth, false, depth) || + !MakeResourceVK(evalIn.motionVectors, false, mv) || + !MakeResourceVK(evalIn.output, true, out)) + return false; + const bool hasExposure = evalIn.exposure && MakeResourceVK(evalIn.exposure, false, exposure); + + auto* vkCmd = (VkCommandBuffer)cmd->getNativeObject(nvrhi::ObjectTypes::VK_CommandBuffer).integer; + if (!vkCmd) + return false; + + bool ok = false; + if (g_featRR && g_rrHandle && wantRR) + { + NVSDK_NGX_Resource_VK normals{}, diffAlb{}, specAlb{}, diffHit{}, specHit{}; + if (!MakeResourceVK(evalIn.normals, false, normals) || + !MakeResourceVK(evalIn.diffuseAlbedo, false, diffAlb) || + !MakeResourceVK(evalIn.specularAlbedo, false, specAlb)) + { + Msg("! [Upscale] DLSS-RR missing G-buffer — falling back to SR"); + } + else + { + const bool hasDiffHit = evalIn.diffuseHitDistance && MakeResourceVK(evalIn.diffuseHitDistance, false, diffHit); + const bool hasSpecHit = evalIn.specularHitDistance && MakeResourceVK(evalIn.specularHitDistance, false, specHit); + + NVSDK_NGX_VK_DLSSD_Eval_Params eval{}; + eval.pInColor = &color; + eval.pInOutput = &out; + eval.pInDepth = &depth; + eval.pInMotionVectors = &mv; + eval.pInDiffuseAlbedo = &diffAlb; + eval.pInSpecularAlbedo = &specAlb; + eval.pInNormals = &normals; + eval.pInRoughness = &normals; + if (hasDiffHit) + eval.pInDiffuseHitDistance = &diffHit; + if (hasSpecHit) + eval.pInSpecularHitDistance = &specHit; + eval.pInWorldToViewMatrix = const_cast(evalIn.worldToView); + eval.pInViewToClipMatrix = const_cast(evalIn.viewToClip); + eval.InJitterOffsetX = evalIn.jitterX; + eval.InJitterOffsetY = evalIn.jitterY; + eval.InRenderSubrectDimensions = { evalIn.renderWidth, evalIn.renderHeight }; + eval.InReset = evalIn.reset ? 1 : 0; + eval.InMVScaleX = (float)evalIn.renderWidth; + eval.InMVScaleY = (float)evalIn.renderHeight; + eval.InFrameTimeDeltaInMsec = evalIn.frameTimeMs; + eval.InPreExposure = 1.f; + eval.InExposureScale = 1.f; + if (hasExposure) + eval.pInExposureTexture = &exposure; + + const NVSDK_NGX_Result res = NGX_VULKAN_EVALUATE_DLSSD_EXT(vkCmd, g_rrHandle, g_params, &eval); + if (NVSDK_NGX_SUCCEED(res)) + { + ok = true; + static bool s_rrOk = false; + if (!s_rrOk) + { + s_rrOk = true; + Msg("* [Upscale] DLSS-RR Evaluate OK"); + } + } + else + { + Msg("! [Upscale] DLSS-RR Evaluate failed 0x%08x — falling back to SR next frame", (u32)res); + g_featForceSR = true; + } + } + } + + if (!ok && g_dlssHandle) + ok = EvaluateDLSSSR(vkCmd, evalIn, color, depth, mv, out, hasExposure ? &exposure : nullptr); + + return ok; +} + +bool Streamline_EvaluateDLSSRR(nvrhi::ICommandList* cmd, const UpscaleInputs& inputs) +{ + UpscaleInputs rrInputs = inputs; + rrInputs.enableRR = true; + return Streamline_EvaluateDLSS(cmd, rrInputs); +} + +bool EnsureFgFeature(nvrhi::ICommandList* cmd, const DlssFgInputs& inputs, u32 vkFormat) +{ + if (!g_ngxReady || !g_params || !g_fgAvailable || !cmd) + return false; + if (g_fgHandle && g_fgW == inputs.displayWidth && g_fgH == inputs.displayHeight && + g_fgRenderW == inputs.renderWidth && g_fgRenderH == inputs.renderHeight && + g_fgFormat == vkFormat && g_fgInterp && g_fgReal) + return true; + + DestroyFgFeature(); + + auto* vkCmd = (VkCommandBuffer)cmd->getNativeObject(nvrhi::ObjectTypes::VK_CommandBuffer).integer; + if (!vkCmd) + return false; + + NVSDK_NGX_DLSSG_Create_Params create{}; + create.Width = inputs.displayWidth; + create.Height = inputs.displayHeight; + create.NativeBackbufferFormat = vkFormat; + create.RenderWidth = inputs.renderWidth; + create.RenderHeight = inputs.renderHeight; + create.DynamicResolutionScaling = false; + + NVSDK_NGX_Parameter_SetUI(g_params, "Enable.OFA", 1); + NVSDK_NGX_Parameter_SetUI(g_params, "DLSSG.EnableInterp", 1); + + const NVSDK_NGX_Result res = NGX_VK_CREATE_DLSSG(vkCmd, 1, 1, &g_fgHandle, g_params, &create); + if (NVSDK_NGX_FAILED(res) || !g_fgHandle) + { + Msg("! [Upscale] DLSS-FG CreateFeature failed 0x%08x", (u32)res); + g_fgHandle = nullptr; + return false; + } + + nvrhi::IDevice* nv = cmd->getDevice(); + nvrhi::TextureDesc td; + td.width = inputs.displayWidth; + td.height = inputs.displayHeight; + td.format = inputs.backbuffer->getDesc().format; + td.isUAV = true; + td.isShaderResource = true; + td.isRenderTarget = true; + td.initialState = nvrhi::ResourceStates::UnorderedAccess; + td.keepInitialState = true; + td.debugName = "rt_DlssFg_Interp"; + g_fgInterp = nv->createTexture(td); + td.debugName = "rt_DlssFg_Real"; + g_fgReal = nv->createTexture(td); + if (!g_fgInterp || !g_fgReal) + { + DestroyFgFeature(); + return false; + } + + g_fgW = inputs.displayWidth; + g_fgH = inputs.displayHeight; + g_fgRenderW = inputs.renderWidth; + g_fgRenderH = inputs.renderHeight; + g_fgFormat = vkFormat; + Msg("* [Upscale] DLSS-FG CreateFeature OK %ux%u (render %ux%u)", + g_fgW, g_fgH, g_fgRenderW, g_fgRenderH); + return true; +} + +bool Streamline_EvaluateDLSSG(nvrhi::ICommandList* cmd, const DlssFgInputs& inputs) +{ + g_fgPresentPending = false; + g_fgEvaluatedLastFrame = false; + g_fgPresentedLastFrame = false; + if (!g_ngxReady || !cmd || !inputs.backbuffer || !inputs.depth || !inputs.motionVectors) + return false; + if (!g_fgAvailable || !ps_r_dlss_fg) + return false; + + const u32 vkFormat = (u32)nvrhi::vulkan::convertFormat(inputs.backbuffer->getDesc().format); + if (!EnsureFgFeature(cmd, inputs, vkFormat)) + return false; + + NVSDK_NGX_Resource_VK color{}, depth{}, mv{}, hudless{}, interp{}, real{}; + if (!MakeResourceVK(inputs.backbuffer, false, color) || + !MakeResourceVK(inputs.depth, false, depth) || + !MakeResourceVK(inputs.motionVectors, false, mv) || + !MakeResourceVK(g_fgInterp.Get(), true, interp) || + !MakeResourceVK(g_fgReal.Get(), true, real)) + return false; + + const bool hasHudless = inputs.hudless && MakeResourceVK(inputs.hudless, false, hudless); + + cmd->setTextureState(inputs.backbuffer, nvrhi::AllSubresources, nvrhi::ResourceStates::ShaderResource); + cmd->setTextureState(inputs.depth, nvrhi::AllSubresources, nvrhi::ResourceStates::ShaderResource); + cmd->setTextureState(inputs.motionVectors, nvrhi::AllSubresources, nvrhi::ResourceStates::ShaderResource); + if (hasHudless) + cmd->setTextureState(inputs.hudless, nvrhi::AllSubresources, nvrhi::ResourceStates::ShaderResource); + cmd->setTextureState(g_fgInterp, nvrhi::AllSubresources, nvrhi::ResourceStates::UnorderedAccess); + cmd->setTextureState(g_fgReal, nvrhi::AllSubresources, nvrhi::ResourceStates::UnorderedAccess); + cmd->commitBarriers(); + + auto* vkCmd = (VkCommandBuffer)cmd->getNativeObject(nvrhi::ObjectTypes::VK_CommandBuffer).integer; + if (!vkCmd || !g_fgHandle) + return false; + + NVSDK_NGX_Parameter_SetUI(g_params, "DLSSG.EnableInterp", 1); + NVSDK_NGX_Parameter_SetI(g_params, "DLSSG.NumFrames", 1); + NVSDK_NGX_Parameter_SetUI(g_params, "DLSSG.IsRecording", 1); + + NVSDK_NGX_VK_DLSSG_Eval_Params eval{}; + eval.pBackbuffer = &color; + eval.pDepth = &depth; + eval.pMVecs = &mv; + if (hasHudless) + eval.pHudless = &hudless; + eval.pOutputInterpFrame = &interp; + eval.pOutputRealFrame = ℜ + + NVSDK_NGX_DLSSG_Opt_Eval_Params opt{}; + opt.multiFrameCount = 1; + opt.multiFrameIndex = 1; + CopyTo44(opt.cameraViewToClip, inputs.viewToClip); + CopyTo44(opt.clipToCameraView, inputs.clipToView); + for (int i = 0; i < 4; ++i) + for (int j = 0; j < 4; ++j) + opt.clipToLensClip[i][j] = (i == j) ? 1.f : 0.f; + CopyTo44(opt.clipToPrevClip, inputs.clipToPrevClip); + CopyTo44(opt.prevClipToClip, inputs.prevClipToClip); + opt.jitterOffset[0] = inputs.jitterX; + opt.jitterOffset[1] = inputs.jitterY; + opt.mvecScale[0] = (float)inputs.renderWidth; + opt.mvecScale[1] = (float)inputs.renderHeight; + opt.cameraPos[0] = inputs.cameraPos[0]; + opt.cameraPos[1] = inputs.cameraPos[1]; + opt.cameraPos[2] = inputs.cameraPos[2]; + opt.cameraUp[0] = inputs.cameraUp[0]; + opt.cameraUp[1] = inputs.cameraUp[1]; + opt.cameraUp[2] = inputs.cameraUp[2]; + opt.cameraRight[0] = inputs.cameraRight[0]; + opt.cameraRight[1] = inputs.cameraRight[1]; + opt.cameraRight[2] = inputs.cameraRight[2]; + opt.cameraFwd[0] = inputs.cameraFwd[0]; + opt.cameraFwd[1] = inputs.cameraFwd[1]; + opt.cameraFwd[2] = inputs.cameraFwd[2]; + opt.cameraNear = inputs.cameraNear; + opt.cameraFar = inputs.cameraFar; + opt.cameraFOV = inputs.cameraFOV; + opt.cameraAspectRatio = inputs.cameraAspect; + { + const auto fmt = inputs.backbuffer->getDesc().format; + opt.colorBuffersHDR = + fmt == nvrhi::Format::RGBA16_FLOAT || fmt == nvrhi::Format::RGBA32_FLOAT || + fmt == nvrhi::Format::RGB32_FLOAT || fmt == nvrhi::Format::RG16_FLOAT; + } + opt.depthInverted = true; + opt.cameraMotionIncluded = true; + opt.reset = inputs.reset; + opt.motionVectorsDilated = false; + opt.mvecsSubrectSize = { inputs.renderWidth, inputs.renderHeight }; + opt.depthSubrectSize = { inputs.renderWidth, inputs.renderHeight }; + opt.backbufferSubrectSize = { inputs.displayWidth, inputs.displayHeight }; + if (hasHudless) + opt.hudLessSubrectSize = { inputs.displayWidth, inputs.displayHeight }; + + const NVSDK_NGX_Result res = NGX_VK_EVALUATE_DLSSG(vkCmd, g_fgHandle, g_params, &eval, &opt); + cmd->clearState(); + if (NVSDK_NGX_FAILED(res)) + { + Msg("! [Upscale] DLSS-FG Evaluate failed 0x%08x", (u32)res); + return false; + } + + g_fgPresentPending = true; + g_fgEvaluatedLastFrame = true; + static bool s_fgOk = false; + if (!s_fgOk) + { + s_fgOk = true; + Msg("* [Upscale] DLSS-FG Evaluate OK"); + } + return true; +} + +bool Streamline_TakeFgPresent(nvrhi::ITexture*& outInterp, nvrhi::ITexture*& outReal) +{ + if (!g_fgPresentPending || !g_fgInterp || !g_fgReal) + return false; + outInterp = g_fgInterp.Get(); + outReal = g_fgReal.Get(); + g_fgPresentPending = false; + return true; +} + +bool Streamline_FgEvaluatedLastFrame() +{ + return g_fgEvaluatedLastFrame; +} + +bool Streamline_FgPresentedLastFrame() +{ + return g_fgPresentedLastFrame; +} + +void Streamline_NotifyFgPresented(bool ok) +{ + g_fgPresentedLastFrame = ok; +} + +} + +#else + +namespace xray::render::fg { + +bool Streamline_PreInstanceInit() { return false; } +void Streamline_GetRequiredInstanceExtensions(xr_vector&) {} +void Streamline_GetRequiredDeviceExtensions(void*, xr_vector&) {} +bool Streamline_SetVulkanInfo(const StreamlineVulkanInfo&) { return false; } +void Streamline_Shutdown() {} +bool Streamline_IsDLSSAvailable() { return false; } +bool Streamline_IsFGAvailable() { return false; } +bool Streamline_IsRRAvailable() { return false; } +bool Streamline_ConsumeFeatureReset() { return false; } +void Streamline_ReleaseFeatures() {} +bool Streamline_EvaluateDLSS(nvrhi::ICommandList*, const UpscaleInputs&) { return false; } +bool Streamline_EvaluateDLSSRR(nvrhi::ICommandList*, const UpscaleInputs&) { return false; } +bool Streamline_EvaluateDLSSG(nvrhi::ICommandList*, const DlssFgInputs&) { return false; } +bool Streamline_TakeFgPresent(nvrhi::ITexture*&, nvrhi::ITexture*&) { return false; } +bool Streamline_FgEvaluatedLastFrame() { return false; } +bool Streamline_FgPresentedLastFrame() { return false; } +void Streamline_NotifyFgPresented(bool) {} + +} + +#endif diff --git a/src/Layers/xrRender/Upscaling/StreamlineDLSS.h b/src/Layers/xrRender/Upscaling/StreamlineDLSS.h new file mode 100644 index 00000000000..ed48378cef8 --- /dev/null +++ b/src/Layers/xrRender/Upscaling/StreamlineDLSS.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include "IUpscaleBackend.h" + +namespace xray::render::fg { + +struct StreamlineVulkanInfo +{ + void* instance = nullptr; + void* physicalDevice = nullptr; + void* device = nullptr; + void* graphicsQueue = nullptr; + u32 graphicsQueueFamily = 0; + void* computeQueue = nullptr; + u32 computeQueueFamily = 0; + void* opticalFlowQueue = nullptr; + u32 opticalFlowQueueFamily = 0; +}; + +struct DlssFgInputs +{ + nvrhi::ITexture* backbuffer = nullptr; + nvrhi::ITexture* hudless = nullptr; + nvrhi::ITexture* depth = nullptr; + nvrhi::ITexture* motionVectors = nullptr; + u32 displayWidth = 0; + u32 displayHeight = 0; + u32 renderWidth = 0; + u32 renderHeight = 0; + float jitterX = 0.f; + float jitterY = 0.f; + bool reset = false; + float viewToClip[16]{}; + float clipToView[16]{}; + float clipToPrevClip[16]{}; + float prevClipToClip[16]{}; + float cameraPos[3]{}; + float cameraUp[3]{}; + float cameraRight[3]{}; + float cameraFwd[3]{}; + float cameraNear = 0.2f; + float cameraFar = 600.f; + float cameraFOV = 1.f; + float cameraAspect = 1.f; +}; + +bool Streamline_PreInstanceInit(); +void Streamline_GetRequiredInstanceExtensions(xr_vector& outExts); +void Streamline_GetRequiredDeviceExtensions(void* physicalDevice, xr_vector& outExts); +bool Streamline_SetVulkanInfo(const StreamlineVulkanInfo& info); +void Streamline_Shutdown(); + +bool Streamline_IsDLSSAvailable(); +bool Streamline_IsFGAvailable(); +bool Streamline_IsRRAvailable(); +bool Streamline_ConsumeFeatureReset(); +void Streamline_ReleaseFeatures(); + +bool Streamline_EvaluateDLSS(nvrhi::ICommandList* cmd, const UpscaleInputs& inputs); +bool Streamline_EvaluateDLSSRR(nvrhi::ICommandList* cmd, const UpscaleInputs& inputs); +bool Streamline_EvaluateDLSSG(nvrhi::ICommandList* cmd, const DlssFgInputs& inputs); +bool Streamline_TakeFgPresent(nvrhi::ITexture*& outInterp, nvrhi::ITexture*& outReal); +bool Streamline_FgEvaluatedLastFrame(); +bool Streamline_FgPresentedLastFrame(); +void Streamline_NotifyFgPresented(bool ok); + +} diff --git a/src/Layers/xrRender/Upscaling/UpscaleBackends.cpp b/src/Layers/xrRender/Upscaling/UpscaleBackends.cpp new file mode 100644 index 00000000000..a7590981b69 --- /dev/null +++ b/src/Layers/xrRender/Upscaling/UpscaleBackends.cpp @@ -0,0 +1,147 @@ +#include "stdafx.h" +#include "IUpscaleBackend.h" +#include "StreamlineDLSS.h" +#include "Layers/xrRender/Backend/VulkanBackend.h" + +extern ENGINE_API int ps_r_upscale; +extern ENGINE_API int ps_r_dlss; +extern ENGINE_API int ps_r_dlss_fg; +extern ENGINE_API int ps_r_dlss_rr; + +namespace xray::render::fg { +namespace { + +class NullUpscaleBackend final : public IUpscaleBackend +{ +public: + bool Init(nvrhi::IDevice*) override { return true; } + void Shutdown() override {} + bool IsAvailable() const override { return false; } + UpscaleBackendType GetType() const override { return UpscaleBackendType::None; } + bool Evaluate(nvrhi::ICommandList*, const UpscaleInputs&) override { return false; } +}; + +class DLSSUpscaleBackend final : public IUpscaleBackend +{ + bool m_ready = false; +public: + bool Init(nvrhi::IDevice*) override + { +#if defined(XRAY_USE_DLSS) + auto* vk = dynamic_cast(GEnv.Backend); + if (!vk) + { + Msg("! [Upscale] DLSS requires Vulkan backend"); + return false; + } + if (Streamline_IsDLSSAvailable()) + { + m_ready = true; + } + else + { + StreamlineVulkanInfo info{}; + info.instance = vk->GetVkInstance(); + info.physicalDevice = vk->GetVkPhysicalDevice(); + info.device = vk->GetVkDevice(); + info.graphicsQueueFamily = vk->GetGraphicsQueueFamily(); + m_ready = Streamline_SetVulkanInfo(info); + } + if (m_ready) + Msg("* [Upscale] DLSS/NGX backend ready (fg=%d rr=%d)", + Streamline_IsFGAvailable() ? 1 : 0, + Streamline_IsRRAvailable() ? 1 : 0); + else + Msg("! [Upscale] DLSS/NGX unavailable"); +#else + m_ready = false; + Msg("* [Upscale] DLSS/NGX SDK not compiled in (XRAY_USE_DLSS)"); +#endif + return m_ready; + } + void Shutdown() override + { +#if defined(XRAY_USE_DLSS) + Streamline_Shutdown(); +#endif + m_ready = false; + } + bool IsAvailable() const override { return m_ready; } + UpscaleBackendType GetType() const override { return UpscaleBackendType::DLSS; } + bool SupportsFG() const override + { +#if defined(XRAY_USE_DLSS) + return m_ready && Streamline_IsFGAvailable(); +#else + return false; +#endif + } + bool SupportsRR() const override + { +#if defined(XRAY_USE_DLSS) + return m_ready && Streamline_IsRRAvailable(); +#else + return false; +#endif + } + bool Evaluate(nvrhi::ICommandList* cmd, const UpscaleInputs& inputs) override + { + if (!m_ready || !cmd || !inputs.color || !inputs.output) + return false; +#if defined(XRAY_USE_DLSS) + UpscaleInputs in = inputs; + in.enableFG = in.enableFG && ps_r_dlss_fg != 0 && Streamline_IsFGAvailable(); + in.enableRR = in.enableRR && ps_r_dlss_rr != 0 && Streamline_IsRRAvailable(); + if (Streamline_EvaluateDLSS(cmd, in)) + return true; +#endif + if (inputs.renderWidth == inputs.displayWidth && + inputs.renderHeight == inputs.displayHeight && + inputs.color != inputs.output) + { + nvrhi::TextureSlice slice; + cmd->copyTexture(inputs.output, slice, inputs.color, slice); + return true; + } + return false; + } +}; + +} + +IUpscaleBackend* CreateNullUpscaleBackend() { return new NullUpscaleBackend(); } +IUpscaleBackend* CreateDLSSUpscaleBackend() { return new DLSSUpscaleBackend(); } + +IUpscaleBackend* CreateUpscaleBackendAuto() +{ + if (ps_r_dlss != 0 && ps_r_upscale == 0) + ps_r_upscale = 2; + + if (ps_r_upscale == 1) + { + Msg("! [Upscale] FSR removed; use r_upscale 2 (DLSS)"); + ps_r_upscale = 0; + } + + if (ps_r_upscale == 3) + { + ps_r_upscale = 2; + Msg("* [Upscale] Auto selected DLSS"); + } + + if (ps_r_upscale == 2) + { + auto* dlss = CreateDLSSUpscaleBackend(); + if (dlss->Init(GEnv.Backend ? GEnv.Backend->GetDevice() : nullptr) && dlss->IsAvailable()) + return dlss; + delete dlss; + Msg("! [Upscale] DLSS unavailable"); + ps_r_upscale = 0; + } + + auto* nullBackend = CreateNullUpscaleBackend(); + nullBackend->Init(nullptr); + return nullBackend; +} + +} diff --git a/src/Layers/xrRender/Upscaling/UpscalePassSetup.cpp b/src/Layers/xrRender/Upscaling/UpscalePassSetup.cpp new file mode 100644 index 00000000000..97379e50e15 --- /dev/null +++ b/src/Layers/xrRender/Upscaling/UpscalePassSetup.cpp @@ -0,0 +1,358 @@ +#include "stdafx.h" +#include "UpscalePassSetup.h" +#include "Layers/xrRender/FrameGraph/FrameGraph.h" +#include "Layers/xrRender/FrameGraph/RenderPassBuilder.h" +#include "Layers/xrRender/FrameGraph/PassResourceCache.h" +#include "Layers/xrRender/FrameGraph/BindingSetBuilder.h" +#include "Layers/xrRender/FrameGraph/ShaderLoader.h" +#include "Layers/xrRender/RenderContext/RenderContext.h" +#include "Layers/xrRender/RenderContext/RenderDevice.h" + +extern ENGINE_API int ps_r_dlss_fg; +extern ENGINE_API int ps_r_dlss_rr; +extern ENGINE_API int ps_r_dlss_auto_exposure; +extern ENGINE_API float ps_r_dlss_sharpness; +extern ENGINE_API int ps_r_upscale; + +namespace xray::render::fg::passes { +using namespace framegraph; + +namespace { + +struct alignas(16) DlssRrGuideCB +{ + float cameraPos[4]; + float screenSize[2]; + float pad[2]; +}; + +void EnsureDisplayColor(nvrhi::IDevice* nv, UpscalePassState& state, u32 w, u32 h) +{ + if (state.displayColor && state.displayW == w && state.displayH == h) + return; + nvrhi::TextureDesc td; + td.width = w; + td.height = h; + td.format = nvrhi::Format::RGBA16_FLOAT; + td.isUAV = true; + td.isShaderResource = true; + td.isRenderTarget = true; + td.initialState = nvrhi::ResourceStates::UnorderedAccess; + td.keepInitialState = true; + td.debugName = "rt_Upscale_DisplayColor"; + state.displayColor = nv->createTexture(td); + state.displayW = w; + state.displayH = h; +} + +void EnsureRRGuideResources(fg::RenderDevice* device, UpscalePassState& state, u32 w, u32 h) +{ + nvrhi::IDevice* nv = device->GetNVRHIDevice(); + if (!state.rrGuidePipeline) + { + auto* loader = RImplementation.GetShaderLoader(); + if (!loader) + return; + auto cs = loader->LoadComputeShader("dlss_rr_guides"); + if (!cs.handle || !cs.reflection) + return; + auto& cache = GetPassResourceCache(); + state.rrGuideLayout = cache.GetOrCreateBindingLayoutFromReflection("DlssRrGuides", *cs.reflection, nv); + nvrhi::ComputePipelineDesc pd; + pd.CS = cs.handle; + pd.bindingLayouts = { state.rrGuideLayout }; + state.rrGuidePipeline = cache.GetOrCreateComputePipeline("DlssRrGuides", pd, nv); + state.rrGuideCB = cache.GetOrCreateVolatileCB("Upscale", "DlssRrGuideCB", sizeof(DlssRrGuideCB), device); + } + + if (state.rrDiffuseAlbedo && state.rrGuideW == w && state.rrGuideH == h) + return; + + auto make = [&](const char* name, nvrhi::Format fmt) { + nvrhi::TextureDesc td; + td.width = w; + td.height = h; + td.format = fmt; + td.isUAV = true; + td.isShaderResource = true; + td.initialState = nvrhi::ResourceStates::UnorderedAccess; + td.keepInitialState = true; + td.debugName = name; + return nv->createTexture(td); + }; + state.rrDiffuseAlbedo = make("rt_DlssRr_DiffuseAlbedo", nvrhi::Format::RGBA16_FLOAT); + state.rrSpecularAlbedo = make("rt_DlssRr_SpecularAlbedo", nvrhi::Format::RGBA16_FLOAT); + state.rrSpecularHitDist = make("rt_DlssRr_SpecularHitDist", nvrhi::Format::R16_FLOAT); + state.rrGuideW = w; + state.rrGuideH = h; +} + +static void UpscaleCopyMatrix(float dst[16], const Fmatrix& m) +{ + dst[0] = m._11; dst[1] = m._12; dst[2] = m._13; dst[3] = m._14; + dst[4] = m._21; dst[5] = m._22; dst[6] = m._23; dst[7] = m._24; + dst[8] = m._31; dst[9] = m._32; dst[10] = m._33; dst[11] = m._34; + dst[12] = m._41; dst[13] = m._42; dst[14] = m._43; dst[15] = m._44; +} + +} + +framegraph::VirtualResourceHandle setupUpscaleOrResolvePass( + FrameGraph& fg, + fg::RenderDevice* device, + VirtualResourceHandle sceneColor, + VirtualResourceHandle depth, + VirtualResourceHandle motionVectors, + VirtualResourceHandle exposure, + const UpscaleState& upscaleState, + IUpscaleBackend* backend, + u32 renderW, + u32 renderH, + UpscalePassState& state, + const UpscaleRRGuides* rrGuides) +{ + if (!backend || !backend->IsAvailable()) + return sceneColor; + if (!upscaleState.upscaleActive && !NeedsResolveToDisplay(upscaleState)) + return sceneColor; + + nvrhi::IDevice* nv = device->GetNVRHIDevice(); + EnsureDisplayColor(nv, state, upscaleState.displayWidth, upscaleState.displayHeight); + + const bool wantRR = rrGuides && ps_r_upscale == 2 && ps_r_dlss_rr != 0 && backend->SupportsRR(); + if (wantRR) + EnsureRRGuideResources(device, state, renderW, renderH); + + ResourceDesc outDesc; + outDesc.type = ResourceDesc::Type::Texture2D; + outDesc.width = upscaleState.displayWidth; + outDesc.height = upscaleState.displayHeight; + outDesc.format = nvrhi::Format::RGBA16_FLOAT; + outDesc.isUAV = true; + outDesc.isRenderTarget = true; + outDesc.isImported = true; + outDesc.isTransient = false; + VirtualResourceHandle outHandle = fg.ImportTexture("rt_Upscale_DisplayColor", state.displayColor.Get(), outDesc); + + VirtualResourceHandle rrDiffHandle{}, rrSpecHandle{}, rrHitHandle{}, rrNoisySpecHandle{}; + if (wantRR && state.rrDiffuseAlbedo) + { + ResourceDesc gd; + gd.type = ResourceDesc::Type::Texture2D; + gd.width = renderW; + gd.height = renderH; + gd.format = nvrhi::Format::RGBA16_FLOAT; + gd.isUAV = true; + gd.isImported = true; + gd.isTransient = false; + rrDiffHandle = fg.ImportTexture("rt_DlssRr_DiffuseAlbedo", state.rrDiffuseAlbedo.Get(), gd); + rrSpecHandle = fg.ImportTexture("rt_DlssRr_SpecularAlbedo", state.rrSpecularAlbedo.Get(), gd); + gd.format = nvrhi::Format::R16_FLOAT; + gd.isUAV = true; + rrHitHandle = fg.ImportTexture("rt_DlssRr_SpecularHitDist", state.rrSpecularHitDist.Get(), gd); + if (rrGuides->noisySpecular) + { + ResourceDesc ns = gd; + ns.format = nvrhi::Format::RGBA16_FLOAT; + ns.isUAV = false; + rrNoisySpecHandle = fg.ImportTexture("rt_DlssRr_NoisySpecularIn", rrGuides->noisySpecular, ns); + } + } + + if (wantRR && state.rrGuidePipeline && rrDiffHandle.is_valid() && rrNoisySpecHandle.is_valid()) + { + struct PackData + { + VirtualResourceHandle baseColor, normals, worldPos, depth, noisySpec; + VirtualResourceHandle outDiff, outSpec, outHit; + UpscalePassState* state = nullptr; + u32 w = 0, h = 0; + }; + fg.addCallbackPass( + "DLSS-RR Guides", + [&](FrameGraph& builder, PassHandle passHandle, PackData& data) { + RenderPassBuilder pb(builder, passHandle); + data.baseColor = pb.read(rrGuides->baseColor, ResourceState::ShaderResource); + data.normals = pb.read(rrGuides->normals, ResourceState::ShaderResource); + data.worldPos = pb.read(rrGuides->worldPos, ResourceState::ShaderResource); + data.depth = pb.read(rrGuides->depth.is_valid() ? rrGuides->depth : depth, ResourceState::ShaderResource); + data.noisySpec = pb.read(rrNoisySpecHandle, ResourceState::ShaderResource); + data.outDiff = pb.write(rrDiffHandle, ResourceState::UnorderedAccess); + data.outSpec = pb.write(rrSpecHandle, ResourceState::UnorderedAccess); + data.outHit = pb.write(rrHitHandle, ResourceState::UnorderedAccess); + pb.sideEffects(); + data.state = &state; + data.w = renderW; + data.h = renderH; + }, + [](const PackData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + if (!data.state || !data.state->rrGuidePipeline || !ctx) + return; + auto* base = fgGraph.GetPhysicalTexture(data.baseColor); + auto* nrm = fgGraph.GetPhysicalTexture(data.normals); + auto* wpos = fgGraph.GetPhysicalTexture(data.worldPos); + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* noisySpec = fgGraph.GetPhysicalTexture(data.noisySpec); + auto* outD = data.state->rrDiffuseAlbedo.Get(); + auto* outS = data.state->rrSpecularAlbedo.Get(); + auto* outH = data.state->rrSpecularHitDist.Get(); + if (!base || !nrm || !wpos || !depthTex || !noisySpec || !outD || !outS || !outH) + return; + + auto* cmd = ctx->GetCommandList(); + auto* nvDev = cmd->getDevice(); + auto& cache = GetPassResourceCache(); + auto* refl = RImplementation.GetShaderLoader()->GetCachedReflection("dlss_rr_guides", ".cs"); + if (!refl || !data.state->rrGuideCB) + return; + + DlssRrGuideCB cb{}; + cb.cameraPos[0] = Device.vCameraPosition.x; + cb.cameraPos[1] = Device.vCameraPosition.y; + cb.cameraPos[2] = Device.vCameraPosition.z; + cb.cameraPos[3] = 1.f; + cb.screenSize[0] = (float)data.w; + cb.screenSize[1] = (float)data.h; + cmd->writeBuffer(data.state->rrGuideCB, &cb, sizeof(cb)); + + BindingSetBuilder bsb(*refl, nvDev, "DlssRrGuides"); + bsb.ConstantBuffer("DlssRrGuideParams", data.state->rrGuideCB) + .Texture("g_BaseColor", base) + .Texture("g_Normal", nrm) + .Texture("g_WorldPos", wpos) + .Texture("g_NoisySpecular", noisySpec) + .Texture("g_Depth", depthTex, nvrhi::Format::R32_FLOAT) + .TextureUAV("u_DiffuseAlbedo", outD) + .TextureUAV("u_SpecularAlbedo", outS) + .TextureUAV("u_SpecularHitDist", outH); + auto set = cache.GetOrCreateBindingSet(bsb.Build(), data.state->rrGuideLayout, nvDev); + if (!set) + return; + nvrhi::ComputeState cs; + cs.pipeline = data.state->rrGuidePipeline; + cs.bindings = { set }; + cmd->setComputeState(cs); + cmd->dispatch((data.w + 7) / 8, (data.h + 7) / 8, 1); + }); + } + + struct PassData { + VirtualResourceHandle src; + VirtualResourceHandle dst; + VirtualResourceHandle depth; + VirtualResourceHandle motion; + VirtualResourceHandle exposure; + VirtualResourceHandle normals; + VirtualResourceHandle diffuseAlbedo; + VirtualResourceHandle specularAlbedo; + VirtualResourceHandle specularHit; + IUpscaleBackend* backend; + UpscalePassState* state; + UpscaleState upscale; + u32 renderW, renderH; + nvrhi::ITexture* noisyDiffuse = nullptr; + nvrhi::ITexture* noisySpecular = nullptr; + nvrhi::ITexture* hitDistance = nullptr; + bool wantRR = false; + }; + + auto& pass = fg.addCallbackPass( + "Upscale / Resolve", + [&](FrameGraph& builder, PassHandle passHandle, PassData& data) { + RenderPassBuilder pb(builder, passHandle); + data.src = pb.read(sceneColor, ResourceState::ShaderResource); + if (depth.is_valid()) + data.depth = pb.read(depth, ResourceState::ShaderResource); + if (motionVectors.is_valid()) + data.motion = pb.read(motionVectors, ResourceState::ShaderResource); + if (exposure.is_valid()) + data.exposure = pb.read(exposure, ResourceState::ShaderResource); + if (wantRR && rrGuides && rrDiffHandle.is_valid() && rrSpecHandle.is_valid() && + rrHitHandle.is_valid() && rrNoisySpecHandle.is_valid() && state.rrGuidePipeline) + { + if (rrGuides->normals.is_valid()) + data.normals = pb.read(rrGuides->normals, ResourceState::ShaderResource); + data.diffuseAlbedo = pb.read(rrDiffHandle, ResourceState::ShaderResource); + data.specularAlbedo = pb.read(rrSpecHandle, ResourceState::ShaderResource); + data.specularHit = pb.read(rrHitHandle, ResourceState::ShaderResource); + data.noisyDiffuse = rrGuides->noisyDiffuse; + data.noisySpecular = rrGuides->noisySpecular; + data.hitDistance = rrGuides->hitDistance; + data.wantRR = true; + } + data.dst = pb.write(outHandle, ResourceState::UnorderedAccess); + pb.sideEffects(); + data.backend = backend; + data.state = &state; + data.upscale = upscaleState; + data.renderW = renderW; + data.renderH = renderH; + }, + [](const PassData& data, const FrameGraph& fgGraph, fg::RenderContext* ctx) { + auto* src = fgGraph.GetPhysicalTexture(data.src); + auto* dst = data.state ? data.state->displayColor.Get() : nullptr; + if (!src || !dst || !ctx) + return; + + nvrhi::ICommandList* cmd = ctx->GetCommandList(); + UpscaleInputs in; + in.color = src; + in.depth = data.depth.is_valid() ? fgGraph.GetPhysicalTexture(data.depth) : nullptr; + in.motionVectors = data.motion.is_valid() ? fgGraph.GetPhysicalTexture(data.motion) : nullptr; + in.exposure = data.exposure.is_valid() ? fgGraph.GetPhysicalTexture(data.exposure) : nullptr; + in.normals = data.normals.is_valid() ? fgGraph.GetPhysicalTexture(data.normals) : nullptr; + in.diffuseAlbedo = data.diffuseAlbedo.is_valid() ? fgGraph.GetPhysicalTexture(data.diffuseAlbedo) : nullptr; + in.specularAlbedo = data.specularAlbedo.is_valid() ? fgGraph.GetPhysicalTexture(data.specularAlbedo) : nullptr; + in.specularHitDistance = data.specularHit.is_valid() ? fgGraph.GetPhysicalTexture(data.specularHit) : nullptr; + in.diffuseHitDistance = data.hitDistance; + in.roughness = in.normals; + in.noisyDiffuse = data.noisyDiffuse; + in.noisySpecular = data.noisySpecular; + in.output = dst; + in.renderWidth = data.renderW; + in.renderHeight = data.renderH; + in.displayWidth = data.upscale.displayWidth; + in.displayHeight = data.upscale.displayHeight; + in.jitterX = data.upscale.jitterX; + in.jitterY = data.upscale.jitterY; + in.sharpness = ps_r_dlss_sharpness; + in.frameIndex = Device.dwFrame; + in.frameTimeMs = Device.fTimeDelta > 1e-5f ? Device.fTimeDelta * 1000.f : 16.f; + in.reset = data.upscale.resetHistory; + in.enableFG = (ps_r_upscale == 2 && ps_r_dlss_fg); + in.enableRR = data.wantRR && (ps_r_upscale == 2 && ps_r_dlss_rr); + if (ps_r_upscale == 2) + in.exposure = nullptr; + UpscaleCopyMatrix(in.worldToView, Device.mView); + UpscaleCopyMatrix(in.viewToClip, Device.mProject); + + bool ok = false; + if (data.backend && data.backend->IsAvailable()) + ok = data.backend->Evaluate(cmd, in); + + if (!ok) { + static bool s_failLogged = false; + if (!s_failLogged) + { + s_failLogged = true; + Msg("! [Upscale] Evaluate failed — render %ux%u → display %ux%u", + data.renderW, data.renderH, data.upscale.displayWidth, data.upscale.displayHeight); + } + if (data.renderW == data.upscale.displayWidth && + data.renderH == data.upscale.displayHeight) + { + nvrhi::TextureSlice slice; + cmd->copyTexture(dst, slice, src, slice); + } + else + { + cmd->clearTextureFloat(dst, nvrhi::TextureSubresourceSet(0, 1, 0, 1), + nvrhi::Color(0.f)); + } + } + }); + + return pass.dst; +} + +} diff --git a/src/Layers/xrRender/Upscaling/UpscalePassSetup.h b/src/Layers/xrRender/Upscaling/UpscalePassSetup.h new file mode 100644 index 00000000000..a9d4f42e3ce --- /dev/null +++ b/src/Layers/xrRender/Upscaling/UpscalePassSetup.h @@ -0,0 +1,54 @@ +#pragma once + +#include "Layers/xrRender/FrameGraph/FGTypes.h" +#include "Layers/xrRender/FrameGraph/FGResource.h" +#include "UpscaleState.h" +#include "IUpscaleBackend.h" +#include + +namespace xray::render::framegraph { class FrameGraph; } +namespace xray::render::fg { class RenderDevice; } + +namespace xray::render::fg::passes { + +struct UpscalePassState +{ + nvrhi::TextureHandle displayColor; + nvrhi::TextureHandle rrDiffuseAlbedo; + nvrhi::TextureHandle rrSpecularAlbedo; + nvrhi::TextureHandle rrSpecularHitDist; + nvrhi::ComputePipelineHandle rrGuidePipeline; + nvrhi::BindingLayoutHandle rrGuideLayout; + nvrhi::IBuffer* rrGuideCB = nullptr; + u32 displayW = 0; + u32 displayH = 0; + u32 rrGuideW = 0; + u32 rrGuideH = 0; +}; + +struct UpscaleRRGuides +{ + framegraph::VirtualResourceHandle normals; + framegraph::VirtualResourceHandle baseColor; + framegraph::VirtualResourceHandle worldPos; + framegraph::VirtualResourceHandle depth; + nvrhi::ITexture* noisyDiffuse = nullptr; + nvrhi::ITexture* noisySpecular = nullptr; + nvrhi::ITexture* hitDistance = nullptr; +}; + +framegraph::VirtualResourceHandle setupUpscaleOrResolvePass( + framegraph::FrameGraph& fg, + fg::RenderDevice* device, + framegraph::VirtualResourceHandle sceneColor, + framegraph::VirtualResourceHandle depth, + framegraph::VirtualResourceHandle motionVectors, + framegraph::VirtualResourceHandle exposure, + const UpscaleState& upscaleState, + IUpscaleBackend* backend, + u32 renderW, + u32 renderH, + UpscalePassState& state, + const UpscaleRRGuides* rrGuides = nullptr); + +} diff --git a/src/Layers/xrRender/Upscaling/UpscaleState.cpp b/src/Layers/xrRender/Upscaling/UpscaleState.cpp new file mode 100644 index 00000000000..ad2a94dc440 --- /dev/null +++ b/src/Layers/xrRender/Upscaling/UpscaleState.cpp @@ -0,0 +1,62 @@ +#include "stdafx.h" +#include "UpscaleState.h" +#include +#include + +extern ENGINE_API float ps_r_render_scale; +extern ENGINE_API int ps_r_upscale; +extern ENGINE_API int ps_r_dlss; +extern ENGINE_API int ps_r_dlss_quality; + +namespace xray::render::fg { +namespace { + +float DlssQualityScale(int quality) +{ + switch (quality) + { + case 0: return 0.33f; + case 1: return 0.50f; + case 2: return 0.58f; + case 3: return 0.67f; + case 5: return 1.00f; + default: return 0.67f; + } +} + +} + +void UpdateUpscaleState(UpscaleState& state, u32 displayW, u32 displayH, bool backendAvailable) +{ + if (backendAvailable && ps_r_dlss != 0 && ps_r_upscale == 0) + ps_r_upscale = 2; + if (ps_r_upscale == 1) + ps_r_upscale = 0; + if (ps_r_upscale == 3) + ps_r_upscale = 2; + + state.displayWidth = std::max(1u, displayW); + state.displayHeight = std::max(1u, displayH); + + float scale = ps_r_render_scale; + if (scale <= 0.f) + scale = 1.0f; + scale = std::clamp(scale, 0.25f, 1.0f); + + if (ps_r_upscale == 0 || !backendAvailable) + scale = 1.0f; + else if (ps_r_upscale == 2 && ps_r_dlss_quality == 5) + scale = 1.0f; + else if (ps_r_upscale == 2 && scale >= 0.999f) + scale = DlssQualityScale(ps_r_dlss_quality); + + state.renderScale = scale; + state.renderWidth = std::max(1u, (u32)std::lround(float(state.displayWidth) * scale)); + state.renderHeight = std::max(1u, (u32)std::lround(float(state.displayHeight) * scale)); + state.upscaleActive = backendAvailable && ps_r_upscale == 2 && + (state.renderWidth != state.displayWidth || + state.renderHeight != state.displayHeight || + ps_r_dlss_quality == 5); +} + +} diff --git a/src/Layers/xrRender/Upscaling/UpscaleState.h b/src/Layers/xrRender/Upscaling/UpscaleState.h new file mode 100644 index 00000000000..a08709764e2 --- /dev/null +++ b/src/Layers/xrRender/Upscaling/UpscaleState.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +namespace xray::render::fg { + +struct UpscaleState +{ + u32 displayWidth = 0; + u32 displayHeight = 0; + u32 renderWidth = 0; + u32 renderHeight = 0; + float renderScale = 1.0f; + float jitterX = 0.f; + float jitterY = 0.f; + float prevJitterX = 0.f; + float prevJitterY = 0.f; + bool upscaleActive = false; + bool resetHistory = false; +}; + +void UpdateUpscaleState(UpscaleState& state, u32 displayW, u32 displayH, bool backendAvailable = true); + +inline bool NeedsResolveToDisplay(const UpscaleState& state) +{ + return state.renderWidth != state.displayWidth || state.renderHeight != state.displayHeight; +} + +} diff --git a/src/Layers/xrRender/Volumetrics/VolumetricFogManager.cpp b/src/Layers/xrRender/Volumetrics/VolumetricFogManager.cpp new file mode 100644 index 00000000000..54de3d2be9c --- /dev/null +++ b/src/Layers/xrRender/Volumetrics/VolumetricFogManager.cpp @@ -0,0 +1,68 @@ +#include "stdafx.h" +#include "VolumetricFogManager.h" +#include + +namespace xray::render::fg { + +VolumetricFogManager& VolumetricFogManager::Instance() +{ + static VolumetricFogManager instance; + return instance; +} + +void VolumetricFogManager::Init(nvrhi::IDevice* device) +{ + m_device = device; + Ensure(); +} + +void VolumetricFogManager::Shutdown() +{ + m_density = nullptr; + m_lighting = nullptr; + m_accumulated = nullptr; + m_lightingHist = nullptr; + if (m_device) { + m_device->waitForIdle(); + m_device->runGarbageCollection(); + } + m_device = nullptr; +} + +void VolumetricFogManager::Ensure() +{ + if (!m_device) + return; + if (m_density && m_lighting && m_accumulated && m_lightingHist) + return; + CreateVolumes(); +} + +void VolumetricFogManager::CreateVolumes() +{ + auto makeVol = [&](const char* name) { + nvrhi::TextureDesc desc; + desc.debugName = name; + desc.width = kWidth; + desc.height = kHeight; + desc.depth = kDepth; + desc.dimension = nvrhi::TextureDimension::Texture3D; + desc.format = nvrhi::Format::RGBA16_FLOAT; + desc.isUAV = true; + desc.initialState = nvrhi::ResourceStates::UnorderedAccess; + desc.keepInitialState = true; + return m_device->createTexture(desc); + }; + m_density = makeVol("VolFog_Density"); + m_lighting = makeVol("VolFog_Lighting"); + m_accumulated = makeVol("VolFog_Accum"); + m_lightingHist = makeVol("VolFog_LightingHist"); + Msg("* [VolFog] Persistent volumes %ux%ux%u RGBA16F", kWidth, kHeight, kDepth); +} + +void VolumetricFogManager::SwapLightingHistory() +{ + std::swap(m_lighting, m_lightingHist); +} + +} diff --git a/src/Layers/xrRender/Volumetrics/VolumetricFogManager.h b/src/Layers/xrRender/Volumetrics/VolumetricFogManager.h new file mode 100644 index 00000000000..e2bcf1f35d0 --- /dev/null +++ b/src/Layers/xrRender/Volumetrics/VolumetricFogManager.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include "xrCore/xrCore.h" + +namespace xray::render::fg { + +class VolumetricFogManager +{ +public: + static constexpr u32 kWidth = 160; + static constexpr u32 kHeight = 90; + static constexpr u32 kDepth = 64; + + static VolumetricFogManager& Instance(); + + void Init(nvrhi::IDevice* device); + void Shutdown(); + void Ensure(); + + nvrhi::ITexture* GetDensity() const { return m_density; } + nvrhi::ITexture* GetLighting() const { return m_lighting; } + nvrhi::ITexture* GetAccumulated() const { return m_accumulated; } + nvrhi::ITexture* GetLightingHist() const { return m_lightingHist; } + bool IsReady() const { return m_density != nullptr && m_lighting != nullptr && m_accumulated != nullptr; } + void SwapLightingHistory(); + +private: + void CreateVolumes(); + + nvrhi::DeviceHandle m_device; + nvrhi::TextureHandle m_density; + nvrhi::TextureHandle m_lighting; + nvrhi::TextureHandle m_accumulated; + nvrhi::TextureHandle m_lightingHist; +}; + +} diff --git a/src/Layers/xrRender/fgEnvironmentRender.cpp b/src/Layers/xrRender/fgEnvironmentRender.cpp index 1771441d481..40c712fd3d6 100644 --- a/src/Layers/xrRender/fgEnvironmentRender.cpp +++ b/src/Layers/xrRender/fgEnvironmentRender.cpp @@ -1,4 +1,5 @@ #include "stdafx.h" +#include #include "fgEnvironmentRender.h" @@ -11,9 +12,12 @@ #include "Layers/xrRender/FrameGraph/BindingSetBuilder.h" #include "Layers/xrRender/FrameGraphPasses/PassVertexFormats.h" #include "Layers/xrRender/FrameGraphPasses/ShaderConstants.h" +#include "Layers/xrRender/xrRender_console.h" #include "xrEngine/Environment.h" #include "xrEngine/IGame_Persistent.h" #include "xrEngine/xr_efflensflare.h" +#include "Layers/xrRender/r__scene.h" +#include "Layers/xrRender/FLOD.h" namespace xray::render::fg { @@ -156,6 +160,9 @@ void FGEnvironmentRender::OnDeviceDestroy() m_skyIndexBuffer = nullptr; m_skyConstantBuffer = nullptr; m_skyPlaceholderCube = nullptr; + m_skyExposureFallback = nullptr; + m_skyDepthFallback = nullptr; + m_skyPassCB = nullptr; m_skySampler = nullptr; m_skyVS = nullptr; m_skyPS = nullptr; @@ -174,6 +181,40 @@ void FGEnvironmentRender::OnDeviceDestroy() m_sunPipeline = nullptr; m_sunInitialized = false; + m_cloudVertexBuffer = nullptr; + m_cloudIndexBuffer = nullptr; + m_cloudPlaceholder = nullptr; + m_cloudSampler = nullptr; + m_cloudVS = nullptr; + m_cloudPS = nullptr; + m_cloudInputLayout = nullptr; + m_cloudBindingLayout = nullptr; + m_cloudPipeline = nullptr; + m_cloudVBCapacity = 0; + m_cloudIBCapacity = 0; + m_cloudInitialized = false; + + m_portalVertexBuffer = nullptr; + m_portalVS = nullptr; + m_portalPS = nullptr; + m_portalInputLayout = nullptr; + m_portalBindingLayout = nullptr; + m_portalPipeline = nullptr; + m_portalVBCapacity = 0; + m_portalInitialized = false; + + m_lodVertexBuffer = nullptr; + m_lodIndexBuffer = nullptr; + m_lodPlaceholder = nullptr; + m_lodSampler = nullptr; + m_lodVS = nullptr; + m_lodPS = nullptr; + m_lodInputLayout = nullptr; + m_lodBindingLayout = nullptr; + m_lodPipeline = nullptr; + m_lodVBCapacity = 0; + m_lodInitialized = false; + m_device = nullptr; } @@ -217,6 +258,32 @@ void FGEnvironmentRender::InitSkyResources() m_skyPlaceholderCube = m_device->createTexture(cubeDesc); R_ASSERT2(m_skyPlaceholderCube, "FGEnv: placeholder cubemap createTexture failed"); + nvrhi::TextureDesc expDesc; + expDesc.width = 1; + expDesc.height = 1; + expDesc.format = nvrhi::Format::R32_FLOAT; + expDesc.debugName = "FGEnv_SkyExposureFallback"; + expDesc.initialState = nvrhi::ResourceStates::ShaderResource; + expDesc.keepInitialState = true; + m_skyExposureFallback = m_device->createTexture(expDesc); + + nvrhi::TextureDesc depthFb; + depthFb.width = 1; + depthFb.height = 1; + depthFb.format = nvrhi::Format::R32_FLOAT; + depthFb.debugName = "FGEnv_SkyDepthFallback"; + depthFb.initialState = nvrhi::ResourceStates::ShaderResource; + depthFb.keepInitialState = true; + m_skyDepthFallback = m_device->createTexture(depthFb); + + nvrhi::BufferDesc skyCbDesc; + skyCbDesc.byteSize = 16; + skyCbDesc.isConstantBuffer = true; + skyCbDesc.isVolatile = true; + skyCbDesc.maxVersions = 16; + skyCbDesc.debugName = "FGEnv_SkyPassCB"; + m_skyPassCB = m_device->createBuffer(skyCbDesc); + nvrhi::SamplerDesc samplerDesc; samplerDesc.setAllAddressModes(nvrhi::SamplerAddressMode::Clamp); samplerDesc.setAllFilters(true); @@ -229,6 +296,12 @@ void FGEnvironmentRender::InitSkyResources() u32 skyBlue = 0xFF8080FF; for (u32 face = 0; face < 6; ++face) uploadCmd->writeTexture(m_skyPlaceholderCube, face, 0, &skyBlue, sizeof(skyBlue)); + float expOne = 1.0f; + if (m_skyExposureFallback) + uploadCmd->writeTexture(m_skyExposureFallback, 0, 0, &expOne, sizeof(expOne)); + float depthZero = 0.0f; + if (m_skyDepthFallback) + uploadCmd->writeTexture(m_skyDepthFallback, 0, 0, &depthZero, sizeof(depthZero)); uploadCmd->close(); m_device->executeCommandList(uploadCmd); } @@ -236,6 +309,7 @@ void FGEnvironmentRender::InitSkyResources() auto* shaderLoader = RImplementation.GetShaderLoader(); R_ASSERT(shaderLoader); + framegraph::BindingSetBuilder::InvalidateReflectionCache(); auto vsResult = shaderLoader->LoadVertexShader("sky_forward"); auto psResult = shaderLoader->LoadPixelShader("sky_forward"); if (!vsResult.handle || !psResult.handle) @@ -249,7 +323,7 @@ void FGEnvironmentRender::InitSkyResources() auto& cache = framegraph::GetPassResourceCache(); m_skyBindingLayout = cache.GetOrCreateBindingLayoutFromReflection( - "FGEnv_Sky", *vsResult.reflection, *psResult.reflection, m_device); + "FGEnv_Sky_v14", *vsResult.reflection, *psResult.reflection, m_device); R_ASSERT2(m_skyBindingLayout, "FGEnv: createBindingLayout failed"); nvrhi::VertexAttributeDesc vertexAttribs[] = { @@ -289,13 +363,13 @@ void FGEnvironmentRender::InitSkyResources() nvrhi::FramebufferInfoEx fbInfo; fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); - m_skyPipeline = cache.GetOrCreatePipeline("FGEnv_Sky", pipelineDesc, fbInfo, m_device); + m_skyPipeline = cache.GetOrCreatePipeline("FGEnv_Sky_v15", pipelineDesc, fbInfo, m_device); R_ASSERT2(m_skyPipeline, "FGEnv: createGraphicsPipeline failed"); m_skyInitialized = true; } -void FGEnvironmentRender::DrawSky(nvrhi::ICommandList* cmdList, nvrhi::IFramebuffer* framebuffer, CEnvironment* environment, u32 width, u32 height) +void FGEnvironmentRender::DrawSky(nvrhi::ICommandList* cmdList, nvrhi::IFramebuffer* framebuffer, CEnvironment* environment, u32 width, u32 height, nvrhi::ITexture* depthTex, bool composite) { if (!environment || !cmdList || !framebuffer) return; @@ -358,13 +432,21 @@ void FGEnvironmentRender::DrawSky(nvrhi::ICommandList* cmdList, nvrhi::IFramebuf framegraph::BindingSetBuilder bsb(*vsRefl, *psRefl, m_device, "FGEnv_Sky"); bsb.ConstantBuffer("dynamic_transforms", dynamicCBBuffer); + { + passes::StaticGlobals sg = passes::BuildStaticGlobals(); + auto staticGlobalsCB = cache.GetOrCreateVolatileCB("Frame", "StaticGlobals", sizeof(passes::StaticGlobals), renderDevice); + cmdList->writeBuffer(staticGlobalsCB, &sg, sizeof(sg)); + bsb.ConstantBuffer("static_globals", staticGlobalsCB); + } bsb.Texture("s_sky0", sky0Tex); bsb.Texture("s_sky1", sky1Tex); auto bindingSet = cache.GetOrCreateBindingSet(bsb.Build(), m_skyBindingLayout, m_device); + if (!bindingSet) + return; auto* colorRT = framebuffer->getDesc().colorAttachments[0].texture; - if (colorRT) + if (colorRT && !composite) cmdList->clearTextureFloat(colorRT, nvrhi::AllSubresources, nvrhi::Color(0.0f)); nvrhi::Viewport viewport; @@ -441,7 +523,7 @@ void FGEnvironmentRender::InitSunResources() auto& cache = framegraph::GetPassResourceCache(); m_sunBindingLayout = cache.GetOrCreateBindingLayoutFromReflection( - "FGEnv_Sun", *vsResult.reflection, *psResult.reflection, m_device); + "FGEnv_Sun_v3", *vsResult.reflection, *psResult.reflection, m_device); R_ASSERT(m_sunBindingLayout); nvrhi::VertexAttributeDesc attribs[] = { @@ -465,7 +547,7 @@ void FGEnvironmentRender::InitSunResources() nvrhi::RenderState renderState; renderState.blendState.targets[0].setBlendEnable(true); - renderState.blendState.targets[0].setSrcBlend(nvrhi::BlendFactor::One); + renderState.blendState.targets[0].setSrcBlend(nvrhi::BlendFactor::SrcAlpha); renderState.blendState.targets[0].setDestBlend(nvrhi::BlendFactor::One); renderState.blendState.targets[0].setBlendOp(nvrhi::BlendOp::Add); renderState.depthStencilState.setDepthTestEnable(false); @@ -483,7 +565,7 @@ void FGEnvironmentRender::InitSunResources() nvrhi::FramebufferInfoEx fbInfo; fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); - m_sunPipeline = cache.GetOrCreatePipeline("FGEnv_Sun", pipelineDesc, fbInfo, m_device); + m_sunPipeline = cache.GetOrCreatePipeline("FGEnv_Sun_v3", pipelineDesc, fbInfo, m_device); R_ASSERT(m_sunPipeline); m_sunInitialized = true; @@ -491,6 +573,13 @@ void FGEnvironmentRender::InitSunResources() void FGEnvironmentRender::DrawSun(nvrhi::ICommandList* cmdList, nvrhi::IFramebuffer* framebuffer, CEnvironment* environment, u32 width, u32 height) { + (void)cmdList; + (void)framebuffer; + (void)environment; + (void)width; + (void)height; + return; + if (!environment || !cmdList || !framebuffer) return; @@ -536,11 +625,6 @@ void FGEnvironmentRender::DrawSun(nvrhi::ICommandList* cmdList, nvrhi::IFramebuf else sunColor.set(env.sun_color.x, env.sun_color.y, env.sun_color.z, 1.0f); - const float intensity = 2.0f; - sunColor.r *= intensity; - sunColor.g *= intensity; - sunColor.b *= intensity; - Fvector vecSx, vecSy; vecSx.mul(vecX, sunRadius * fDistance); vecSy.mul(vecY, sunRadius * fDistance); @@ -574,8 +658,11 @@ void FGEnvironmentRender::DrawSun(nvrhi::ICommandList* cmdList, nvrhi::IFramebuf auto* fgRenderer = static_cast(GEnv.Render); auto* renderDevice = fgRenderer->GetRenderDevice(); + passes::DynamicTransforms dynamicCB = {}; + passes::FillDynamicTransforms(dynamicCB); auto dynamicCBBuffer = cache.GetOrCreateVolatileCB( - "Frame", "DynamicTransforms", sizeof(passes::DynamicTransforms), renderDevice); + "FGEnv_Sun", "DynamicCB", sizeof(passes::DynamicTransforms), renderDevice); + cmdList->writeBuffer(dynamicCBBuffer, &dynamicCB, sizeof(dynamicCB)); nvrhi::ITexture* sunTex = nullptr; const shared_str& sunTexName = flareDesc->m_Source.texture; @@ -611,4 +698,409 @@ void FGEnvironmentRender::DrawSun(nvrhi::ICommandList* cmdList, nvrhi::IFramebuf cmdList->setGraphicsState(state); cmdList->drawIndexed(nvrhi::DrawArguments{6, 1, 0, 0, 0}); } + +void FGEnvironmentRender::InitCloudResources() +{ + if (m_cloudInitialized) + return; + + auto* fgRenderer = static_cast(GEnv.Render); + auto* renderDevice = fgRenderer->GetRenderDevice(); + m_device = renderDevice->GetNVRHIDevice(); + if (!m_device) + return; + + nvrhi::TextureDesc texDesc; + texDesc.width = 1; + texDesc.height = 1; + texDesc.format = nvrhi::Format::RGBA8_UNORM; + texDesc.debugName = "FGEnv_CloudPlaceholder"; + texDesc.initialState = nvrhi::ResourceStates::ShaderResource; + texDesc.keepInitialState = true; + m_cloudPlaceholder = m_device->createTexture(texDesc); + + nvrhi::SamplerDesc samplerDesc; + samplerDesc.setAllAddressModes(nvrhi::SamplerAddressMode::Wrap); + samplerDesc.setAllFilters(true); + m_cloudSampler = m_device->createSampler(samplerDesc); + + { + nvrhi::CommandListHandle uploadCmd = m_device->createCommandList(); + uploadCmd->open(); + u32 white = 0xFFFFFFFF; + uploadCmd->writeTexture(m_cloudPlaceholder, 0, 0, &white, sizeof(white)); + uploadCmd->close(); + m_device->executeCommandList(uploadCmd); + } + + auto* shaderLoader = RImplementation.GetShaderLoader(); + auto vsResult = shaderLoader->LoadVertexShader("clouds"); + auto psResult = shaderLoader->LoadPixelShader("clouds"); + if (!vsResult.handle || !psResult.handle) + return; + m_cloudVS = vsResult.handle; + m_cloudPS = psResult.handle; + + auto& cache = framegraph::GetPassResourceCache(); + m_cloudBindingLayout = cache.GetOrCreateBindingLayoutFromReflection( + "FGEnv_Clouds_v1", *vsResult.reflection, *psResult.reflection, m_device); + if (!m_cloudBindingLayout) + return; + + nvrhi::VertexAttributeDesc attribs[] = { + nvrhi::VertexAttributeDesc() + .setName("POSITION") + .setFormat(nvrhi::Format::RGB32_FLOAT) + .setOffset(offsetof(passes::CloudVertex, p)) + .setElementStride(sizeof(passes::CloudVertex)), + nvrhi::VertexAttributeDesc() + .setName("COLOR") + .setFormat(nvrhi::Format::RGBA8_UNORM) + .setOffset(offsetof(passes::CloudVertex, dir)) + .setElementStride(sizeof(passes::CloudVertex)), + nvrhi::VertexAttributeDesc() + .setName("COLOR") + .setFormat(nvrhi::Format::RGBA8_UNORM) + .setArraySize(1) + .setOffset(offsetof(passes::CloudVertex, color)) + .setElementStride(sizeof(passes::CloudVertex)), + }; + attribs[1].setName("COLOR0"); + attribs[2].setName("COLOR1"); + m_cloudInputLayout = cache.GetOrCreateInputLayout("FGEnv_Clouds", attribs, 3, m_cloudVS, m_device); + + nvrhi::RenderState renderState; + renderState.blendState.targets[0].setBlendEnable(true); + renderState.blendState.targets[0].setSrcBlend(nvrhi::BlendFactor::SrcAlpha); + renderState.blendState.targets[0].setDestBlend(nvrhi::BlendFactor::InvSrcAlpha); + renderState.depthStencilState.setDepthTestEnable(false); + renderState.depthStencilState.setDepthWriteEnable(false); + renderState.rasterState.setCullMode(nvrhi::RasterCullMode::None); + + nvrhi::GraphicsPipelineDesc pipelineDesc; + pipelineDesc.setVertexShader(m_cloudVS); + pipelineDesc.setPixelShader(m_cloudPS); + pipelineDesc.addBindingLayout(m_cloudBindingLayout); + pipelineDesc.setInputLayout(m_cloudInputLayout); + pipelineDesc.setRenderState(renderState); + pipelineDesc.setPrimType(nvrhi::PrimitiveType::TriangleList); + + nvrhi::FramebufferInfoEx fbInfo; + fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); + m_cloudPipeline = cache.GetOrCreatePipeline("FGEnv_Clouds_v1", pipelineDesc, fbInfo, m_device); + m_cloudInitialized = m_cloudPipeline != nullptr; +} + +void FGEnvironmentRender::DrawClouds(nvrhi::ICommandList* cmdList, nvrhi::IFramebuffer* framebuffer, CEnvironment* environment, u32 width, u32 height) +{ + if (!environment || !cmdList || !framebuffer) + return; + if (environment->CloudsVerts.empty() || environment->CloudsIndices.empty()) + return; + + InitCloudResources(); + if (!m_cloudInitialized) + return; + + const CEnvDescriptorMixer& env = environment->CurrentEnv; + const u32 vCount = static_cast(environment->CloudsVerts.size()); + const u32 iCount = static_cast(environment->CloudsIndices.size()); + const u32 vbBytes = vCount * sizeof(passes::CloudVertex); + const u32 ibBytes = iCount * sizeof(u16); + + if (!m_cloudVertexBuffer || m_cloudVBCapacity < vbBytes) + { + nvrhi::BufferDesc vbDesc; + vbDesc.byteSize = std::max(vbBytes, 64u); + vbDesc.debugName = "FGEnv_CloudVB"; + vbDesc.isVertexBuffer = true; + vbDesc.initialState = nvrhi::ResourceStates::VertexBuffer; + vbDesc.keepInitialState = true; + m_cloudVertexBuffer = m_device->createBuffer(vbDesc); + m_cloudVBCapacity = vbDesc.byteSize; + } + if (!m_cloudIndexBuffer || m_cloudIBCapacity < ibBytes) + { + nvrhi::BufferDesc ibDesc; + ibDesc.byteSize = std::max(ibBytes, 64u); + ibDesc.debugName = "FGEnv_CloudIB"; + ibDesc.isIndexBuffer = true; + ibDesc.initialState = nvrhi::ResourceStates::IndexBuffer; + ibDesc.keepInitialState = true; + m_cloudIndexBuffer = m_device->createBuffer(ibDesc); + m_cloudIBCapacity = ibDesc.byteSize; + } + if (!m_cloudVertexBuffer || !m_cloudIndexBuffer) + return; + + Fvector wd0, wd1; + Fvector4 wind_dir; + wd0.setHP(PI_DIV_4, 0); + wd1.setHP(PI_DIV_4 + PI_DIV_8, 0); + wind_dir.set(wd0.x, wd0.z, wd1.x, wd1.z).mul(0.5f).add(0.5f).mul(255.f); + u32 C0 = color_rgba(iFloor(wind_dir.x), iFloor(wind_dir.y), iFloor(wind_dir.w), iFloor(wind_dir.z)); + u32 C1 = color_rgba( + iFloor(env.clouds_color.x * 255.f), + iFloor(env.clouds_color.y * 255.f), + iFloor(env.clouds_color.z * 255.f), + iFloor(env.clouds_color.w * 255.f)); + + xr_vector verts(vCount); + for (u32 i = 0; i < vCount; ++i) + { + verts[i].p = environment->CloudsVerts[i]; + verts[i].dir = C0; + verts[i].color = C1; + } + cmdList->writeBuffer(m_cloudVertexBuffer, verts.data(), vbBytes); + cmdList->writeBuffer(m_cloudIndexBuffer, environment->CloudsIndices.data(), ibBytes); + + Fmatrix mScale, mXFORM; + mScale.scale(10.f, 0.4f, 10.f); + mXFORM.rotateY(env.clouds_rotation); + mXFORM.mulB_43(mScale); + mXFORM.translate_over(Device.vCameraPosition); + + auto& cache = framegraph::GetPassResourceCache(); + auto* fgRenderer = static_cast(GEnv.Render); + auto* renderDevice = fgRenderer->GetRenderDevice(); + + passes::DynamicTransforms dynamicCB = {}; + passes::FillDynamicTransforms(dynamicCB, mXFORM); + auto dynamicCBBuffer = cache.GetOrCreateVolatileCB("FGEnv_Clouds", "DynamicCB", sizeof(passes::DynamicTransforms), renderDevice); + cmdList->writeBuffer(dynamicCBBuffer, &dynamicCB, sizeof(dynamicCB)); + + auto staticGlobalsCB = cache.GetOrCreateVolatileCB("Frame", "StaticGlobals", sizeof(passes::StaticGlobals), renderDevice); + { + passes::StaticGlobals sg = passes::BuildStaticGlobals(); + cmdList->writeBuffer(staticGlobalsCB, &sg, sizeof(sg)); + } + + nvrhi::ITexture* c0 = m_cloudPlaceholder.Get(); + nvrhi::ITexture* c1 = m_cloudPlaceholder.Get(); + auto* texManager = renderDevice->GetFGResourceManager() + ? renderDevice->GetFGResourceManager()->GetTextureManager() : nullptr; + if (texManager && environment->Current[0] && environment->Current[1]) + { + const shared_str& n0 = environment->Current[0]->clouds_texture_name; + const shared_str& n1 = environment->Current[1]->clouds_texture_name; + if (n0.size()) + c0 = texManager->GetNVRHITexture(texManager->LoadTexture(n0.c_str())); + if (n1.size()) + c1 = texManager->GetNVRHITexture(texManager->LoadTexture(n1.c_str())); + if (!c0) c0 = m_cloudPlaceholder.Get(); + if (!c1) c1 = m_cloudPlaceholder.Get(); + } + + auto* vsRefl = RImplementation.GetShaderLoader()->GetCachedReflection("clouds", ".vs"); + auto* psRefl = RImplementation.GetShaderLoader()->GetCachedReflection("clouds", ".ps"); + if (!vsRefl || !psRefl) + return; + + framegraph::BindingSetBuilder bsb(*vsRefl, *psRefl, m_device, "FGEnv_Clouds"); + bsb.ConstantBuffer("dynamic_transforms", dynamicCBBuffer); + bsb.ConstantBuffer("static_globals", staticGlobalsCB); + bsb.Texture("s_clouds0", c0); + bsb.Texture("s_clouds1", c1); + auto bindingSet = cache.GetOrCreateBindingSet(bsb.Build(), m_cloudBindingLayout, m_device); + if (!bindingSet) + return; + + nvrhi::GraphicsState state; + state.pipeline = m_cloudPipeline; + state.framebuffer = framebuffer; + state.viewport.addViewportAndScissorRect( + nvrhi::Viewport(static_cast(width), static_cast(height))); + state.addBindingSet(bindingSet); + state.vertexBuffers = {{m_cloudVertexBuffer, 0, 0}}; + state.indexBuffer = {m_cloudIndexBuffer, nvrhi::Format::R16_UINT, 0}; + cmdList->setGraphicsState(state); + cmdList->drawIndexed(nvrhi::DrawArguments{iCount, 1, 0, 0, 0}); +} + +void FGEnvironmentRender::InitPortalResources() +{ + if (m_portalInitialized) + return; + + auto* fgRenderer = static_cast(GEnv.Render); + auto* renderDevice = fgRenderer->GetRenderDevice(); + m_device = renderDevice->GetNVRHIDevice(); + if (!m_device) + return; + + auto* shaderLoader = RImplementation.GetShaderLoader(); + auto vsResult = shaderLoader->LoadVertexShader("portal"); + auto psResult = shaderLoader->LoadPixelShader("portal"); + if (!vsResult.handle || !psResult.handle) + return; + m_portalVS = vsResult.handle; + m_portalPS = psResult.handle; + + auto& cache = framegraph::GetPassResourceCache(); + m_portalBindingLayout = cache.GetOrCreateBindingLayoutFromReflection( + "FGEnv_Portal_v1", *vsResult.reflection, *psResult.reflection, m_device); + if (!m_portalBindingLayout) + return; + + nvrhi::VertexAttributeDesc attribs[] = { + nvrhi::VertexAttributeDesc() + .setName("POSITION") + .setFormat(nvrhi::Format::RGB32_FLOAT) + .setOffset(offsetof(passes::PortalVertex, p)) + .setElementStride(sizeof(passes::PortalVertex)), + nvrhi::VertexAttributeDesc() + .setName("COLOR") + .setFormat(nvrhi::Format::RGBA8_UNORM) + .setOffset(offsetof(passes::PortalVertex, color)) + .setElementStride(sizeof(passes::PortalVertex)), + }; + m_portalInputLayout = cache.GetOrCreateInputLayout("FGEnv_Portal", attribs, 2, m_portalVS, m_device); + + nvrhi::RenderState renderState; + renderState.blendState.targets[0].setBlendEnable(true); + renderState.blendState.targets[0].setSrcBlend(nvrhi::BlendFactor::SrcAlpha); + renderState.blendState.targets[0].setDestBlend(nvrhi::BlendFactor::InvSrcAlpha); + renderState.depthStencilState.setDepthTestEnable(true); + renderState.depthStencilState.setDepthWriteEnable(false); + renderState.depthStencilState.setDepthFunc(nvrhi::ComparisonFunc::GreaterOrEqual); + renderState.rasterState.setCullMode(nvrhi::RasterCullMode::None); + + nvrhi::GraphicsPipelineDesc pipelineDesc; + pipelineDesc.setVertexShader(m_portalVS); + pipelineDesc.setPixelShader(m_portalPS); + pipelineDesc.addBindingLayout(m_portalBindingLayout); + pipelineDesc.setInputLayout(m_portalInputLayout); + pipelineDesc.setRenderState(renderState); + pipelineDesc.setPrimType(nvrhi::PrimitiveType::TriangleList); + + nvrhi::FramebufferInfoEx fbInfo; + fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); + fbInfo.depthFormat = nvrhi::Format::D32; + m_portalPipeline = cache.GetOrCreatePipeline("FGEnv_Portal_v1", pipelineDesc, fbInfo, m_device); + m_portalInitialized = m_portalPipeline != nullptr; +} + +void FGEnvironmentRender::DrawPortals(nvrhi::ICommandList*, nvrhi::IFramebuffer*, u32, u32) +{ +} + +void FGEnvironmentRender::InitLodResources() +{ + if (m_lodInitialized) + return; + + auto* fgRenderer = static_cast(GEnv.Render); + auto* renderDevice = fgRenderer->GetRenderDevice(); + m_device = renderDevice->GetNVRHIDevice(); + if (!m_device) + return; + + nvrhi::TextureDesc texDesc; + texDesc.width = 1; + texDesc.height = 1; + texDesc.format = nvrhi::Format::RGBA8_UNORM; + texDesc.debugName = "FGEnv_LodPlaceholder"; + texDesc.initialState = nvrhi::ResourceStates::ShaderResource; + texDesc.keepInitialState = true; + m_lodPlaceholder = m_device->createTexture(texDesc); + + nvrhi::SamplerDesc samplerDesc; + samplerDesc.setAllAddressModes(nvrhi::SamplerAddressMode::Wrap); + samplerDesc.setAllFilters(true); + m_lodSampler = m_device->createSampler(samplerDesc); + + { + nvrhi::CommandListHandle uploadCmd = m_device->createCommandList(); + uploadCmd->open(); + u32 white = 0xFFFFFFFF; + uploadCmd->writeTexture(m_lodPlaceholder, 0, 0, &white, sizeof(white)); + u16 idx[6] = {0, 1, 2, 0, 2, 3}; + nvrhi::BufferDesc ibDesc; + ibDesc.byteSize = sizeof(idx); + ibDesc.debugName = "FGEnv_LodIB"; + ibDesc.isIndexBuffer = true; + ibDesc.initialState = nvrhi::ResourceStates::IndexBuffer; + ibDesc.keepInitialState = true; + m_lodIndexBuffer = m_device->createBuffer(ibDesc); + uploadCmd->writeBuffer(m_lodIndexBuffer, idx, sizeof(idx)); + uploadCmd->close(); + m_device->executeCommandList(uploadCmd); + } + + auto* shaderLoader = RImplementation.GetShaderLoader(); + auto vsResult = shaderLoader->LoadVertexShader("lod_forward"); + auto psResult = shaderLoader->LoadPixelShader("lod_forward"); + if (!vsResult.handle || !psResult.handle) + return; + m_lodVS = vsResult.handle; + m_lodPS = psResult.handle; + + auto& cache = framegraph::GetPassResourceCache(); + m_lodBindingLayout = cache.GetOrCreateBindingLayoutFromReflection( + "FGEnv_Lod_v1", *vsResult.reflection, *psResult.reflection, m_device); + if (!m_lodBindingLayout) + return; + + nvrhi::VertexAttributeDesc attribs[] = { + nvrhi::VertexAttributeDesc() + .setName("POSITION") + .setFormat(nvrhi::Format::RGB32_FLOAT) + .setOffset(offsetof(passes::LodVertex, p)) + .setElementStride(sizeof(passes::LodVertex)), + nvrhi::VertexAttributeDesc() + .setName("COLOR") + .setFormat(nvrhi::Format::RGBA8_UNORM) + .setOffset(offsetof(passes::LodVertex, color)) + .setElementStride(sizeof(passes::LodVertex)), + nvrhi::VertexAttributeDesc() + .setName("TEXCOORD") + .setFormat(nvrhi::Format::RG32_FLOAT) + .setOffset(offsetof(passes::LodVertex, tc0)) + .setElementStride(sizeof(passes::LodVertex)), + nvrhi::VertexAttributeDesc() + .setName("TEXCOORD") + .setArraySize(1) + .setFormat(nvrhi::Format::RG32_FLOAT) + .setOffset(offsetof(passes::LodVertex, tc1)) + .setElementStride(sizeof(passes::LodVertex)), + nvrhi::VertexAttributeDesc() + .setName("TEXCOORD") + .setArraySize(1) + .setFormat(nvrhi::Format::RGBA32_FLOAT) + .setOffset(offsetof(passes::LodVertex, af)) + .setElementStride(sizeof(passes::LodVertex)), + }; + attribs[2].setName("TEXCOORD0"); + attribs[3].setName("TEXCOORD1"); + attribs[4].setName("TEXCOORD2"); + m_lodInputLayout = cache.GetOrCreateInputLayout("FGEnv_Lod", attribs, 5, m_lodVS, m_device); + + nvrhi::RenderState renderState; + renderState.blendState.targets[0].setBlendEnable(false); + renderState.depthStencilState.setDepthTestEnable(true); + renderState.depthStencilState.setDepthWriteEnable(true); + renderState.depthStencilState.setDepthFunc(nvrhi::ComparisonFunc::GreaterOrEqual); + renderState.rasterState.setCullMode(nvrhi::RasterCullMode::None); + + nvrhi::GraphicsPipelineDesc pipelineDesc; + pipelineDesc.setVertexShader(m_lodVS); + pipelineDesc.setPixelShader(m_lodPS); + pipelineDesc.addBindingLayout(m_lodBindingLayout); + pipelineDesc.setInputLayout(m_lodInputLayout); + pipelineDesc.setRenderState(renderState); + pipelineDesc.setPrimType(nvrhi::PrimitiveType::TriangleList); + + nvrhi::FramebufferInfoEx fbInfo; + fbInfo.colorFormats.push_back(nvrhi::Format::RGBA16_FLOAT); + fbInfo.depthFormat = nvrhi::Format::D32; + m_lodPipeline = cache.GetOrCreatePipeline("FGEnv_Lod_v1", pipelineDesc, fbInfo, m_device); + m_lodInitialized = m_lodPipeline != nullptr; +} + +void FGEnvironmentRender::DrawLodImpostors( + nvrhi::ICommandList*, nvrhi::IFramebuffer*, u32, u32, + const xr_vector&) +{ +} } // namespace xray::render::fg diff --git a/src/Layers/xrRender/fgEnvironmentRender.h b/src/Layers/xrRender/fgEnvironmentRender.h index 30f6f2af033..03f0f4afd66 100644 --- a/src/Layers/xrRender/fgEnvironmentRender.h +++ b/src/Layers/xrRender/fgEnvironmentRender.h @@ -7,6 +7,7 @@ class CEnvironment; namespace xray::render::fg { +class dxRender_Visual; class FGEnvironmentRender; class FGEnvDescriptorRender : public IEnvDescriptorRender @@ -42,12 +43,18 @@ class FGEnvironmentRender : public IEnvironmentRender void lerp(CEnvDescriptorMixer& currentEnv, IEnvDescriptorRender* inA, IEnvDescriptorRender* inB) override; const particles_systems::library_interface& particles_systems_library() override; - void DrawSky(nvrhi::ICommandList* cmdList, nvrhi::IFramebuffer* framebuffer, CEnvironment* environment, u32 width, u32 height); + void DrawSky(nvrhi::ICommandList* cmdList, nvrhi::IFramebuffer* framebuffer, CEnvironment* environment, u32 width, u32 height, nvrhi::ITexture* depthTex = nullptr, bool composite = false); + void DrawClouds(nvrhi::ICommandList* cmdList, nvrhi::IFramebuffer* framebuffer, CEnvironment* environment, u32 width, u32 height); + void DrawPortals(nvrhi::ICommandList* cmdList, nvrhi::IFramebuffer* framebuffer, u32 width, u32 height); + void DrawLodImpostors(nvrhi::ICommandList* cmdList, nvrhi::IFramebuffer* framebuffer, u32 width, u32 height, const xr_vector& lods); void DrawSun(nvrhi::ICommandList* cmdList, nvrhi::IFramebuffer* framebuffer, CEnvironment* environment, u32 width, u32 height); private: void InitSkyResources(); void InitSunResources(); + void InitCloudResources(); + void InitPortalResources(); + void InitLodResources(); using RuntimeTextureList = xr_vector>; RuntimeTextureList sky_r_textures; @@ -69,6 +76,9 @@ class FGEnvironmentRender : public IEnvironmentRender nvrhi::BufferHandle m_skyIndexBuffer; nvrhi::BufferHandle m_skyConstantBuffer; nvrhi::TextureHandle m_skyPlaceholderCube; + nvrhi::TextureHandle m_skyExposureFallback; + nvrhi::TextureHandle m_skyDepthFallback; + nvrhi::BufferHandle m_skyPassCB; nvrhi::SamplerHandle m_skySampler; nvrhi::ShaderHandle m_skyVS; nvrhi::ShaderHandle m_skyPS; @@ -86,5 +96,39 @@ class FGEnvironmentRender : public IEnvironmentRender nvrhi::BindingLayoutHandle m_sunBindingLayout; nvrhi::GraphicsPipelineHandle m_sunPipeline; bool m_sunInitialized = false; + + nvrhi::BufferHandle m_cloudVertexBuffer; + nvrhi::BufferHandle m_cloudIndexBuffer; + nvrhi::TextureHandle m_cloudPlaceholder; + nvrhi::SamplerHandle m_cloudSampler; + nvrhi::ShaderHandle m_cloudVS; + nvrhi::ShaderHandle m_cloudPS; + nvrhi::InputLayoutHandle m_cloudInputLayout; + nvrhi::BindingLayoutHandle m_cloudBindingLayout; + nvrhi::GraphicsPipelineHandle m_cloudPipeline; + u32 m_cloudVBCapacity = 0; + u32 m_cloudIBCapacity = 0; + bool m_cloudInitialized = false; + + nvrhi::BufferHandle m_portalVertexBuffer; + nvrhi::ShaderHandle m_portalVS; + nvrhi::ShaderHandle m_portalPS; + nvrhi::InputLayoutHandle m_portalInputLayout; + nvrhi::BindingLayoutHandle m_portalBindingLayout; + nvrhi::GraphicsPipelineHandle m_portalPipeline; + u32 m_portalVBCapacity = 0; + bool m_portalInitialized = false; + + nvrhi::BufferHandle m_lodVertexBuffer; + nvrhi::BufferHandle m_lodIndexBuffer; + nvrhi::TextureHandle m_lodPlaceholder; + nvrhi::SamplerHandle m_lodSampler; + nvrhi::ShaderHandle m_lodVS; + nvrhi::ShaderHandle m_lodPS; + nvrhi::InputLayoutHandle m_lodInputLayout; + nvrhi::BindingLayoutHandle m_lodBindingLayout; + nvrhi::GraphicsPipelineHandle m_lodPipeline; + u32 m_lodVBCapacity = 0; + bool m_lodInitialized = false; }; } // namespace xray::render::fg diff --git a/src/Layers/xrRender/fgFlareRender.cpp b/src/Layers/xrRender/fgFlareRender.cpp index 9c361450232..42aedef7000 100644 --- a/src/Layers/xrRender/fgFlareRender.cpp +++ b/src/Layers/xrRender/fgFlareRender.cpp @@ -22,18 +22,32 @@ void FGFlareRender::CreateShader(LPCSTR sh_name, LPCSTR tex_name) if (!shaderLoader) return; - auto vsResult = shaderLoader->LoadVertexShader(sh_name, "main"); - auto psResult = shaderLoader->LoadPixelShader(sh_name, "main"); + const char* vsName = sh_name; + const char* psName = "sun_forward"; + if (sh_name) + { + if (strstr(sh_name, "flare")) + vsName = "effects_flare"; + else if (strstr(sh_name, "sun")) + vsName = "effects_sun"; + } + + auto vsResult = shaderLoader->LoadVertexShader(vsName, "main"); + auto psResult = shaderLoader->LoadPixelShader(psName, "main"); if (vsResult.handle && psResult.handle) { m_vsHandle = vsResult.handle; m_psHandle = psResult.handle; - Msg("* [FGFlareRender] Compiled flare shader: %s (tex: %s)", sh_name, tex_name); } - else + else if (psResult.handle) { - Msg("! [FGFlareRender] Failed to compile flare shader: %s", sh_name); + auto fallbackVs = shaderLoader->LoadVertexShader("sun_forward", "main"); + if (fallbackVs.handle) + { + m_vsHandle = fallbackVs.handle; + m_psHandle = psResult.handle; + } } } diff --git a/src/Layers/xrRender/fgFontRender.cpp b/src/Layers/xrRender/fgFontRender.cpp index 7147596e016..d0746abbad7 100644 --- a/src/Layers/xrRender/fgFontRender.cpp +++ b/src/Layers/xrRender/fgFontRender.cpp @@ -159,8 +159,12 @@ void FGFontRender::EnsureVertexCapacity(size_t vertexCount) void FGFontRender::EnsurePipeline(nvrhi::IFramebuffer* framebuffer) { - if (m_pipeline) + const auto& fbInfo = framebuffer->getFramebufferInfo(); + const nvrhi::Format fmt = fbInfo.colorFormats.empty() ? nvrhi::Format::UNKNOWN : fbInfo.colorFormats[0]; + if (m_pipeline && m_pipelineFormat == fmt) return; + m_pipeline = nullptr; + m_pipelineFormat = fmt; nvrhi::GraphicsPipelineDesc pipelineDesc; pipelineDesc.VS = m_vs; diff --git a/src/Layers/xrRender/fgFontRender.h b/src/Layers/xrRender/fgFontRender.h index 18452d2a9a0..0896eaab5b0 100644 --- a/src/Layers/xrRender/fgFontRender.h +++ b/src/Layers/xrRender/fgFontRender.h @@ -44,6 +44,7 @@ class FGFontRender : public IFontRender nvrhi::BufferHandle m_indexBuffer; size_t m_vertexCapacity = 0; nvrhi::GraphicsPipelineHandle m_pipeline; + nvrhi::Format m_pipelineFormat = nvrhi::Format::UNKNOWN; xr_vector m_vertices; Fvector2 m_textureSize{ 0.f, 0.f }; diff --git a/src/Layers/xrRender/fgLensFlareRender.cpp b/src/Layers/xrRender/fgLensFlareRender.cpp index 1194a97d659..e27c2eae2fe 100644 --- a/src/Layers/xrRender/fgLensFlareRender.cpp +++ b/src/Layers/xrRender/fgLensFlareRender.cpp @@ -57,6 +57,12 @@ void FGLensFlareRender::InitResources() R_ASSERT2(vsOverlayResult.handle, "FGLensFlareRender: failed to load effects_flare.vs"); m_vsOverlay = vsOverlayResult.handle; + auto vsSun = shaderLoader->LoadVertexShader("effects_sun_disc", "main"); + auto psSun = shaderLoader->LoadPixelShader("effects_sun_disc", "main"); + R_ASSERT2(vsSun.handle && psSun.handle, "FGLensFlareRender: failed to load effects_sun_disc"); + m_vsSunDisc = vsSun.handle; + m_psSunDisc = psSun.handle; + auto csResult = shaderLoader->LoadComputeShader("flare_visibility", "main"); R_ASSERT2(csResult.handle, "FGLensFlareRender: failed to load flare_visibility.cs"); m_visCS = csResult.handle; @@ -70,6 +76,8 @@ void FGLensFlareRender::InitResources() R_ASSERT2(m_inputLayout, "FGLensFlareRender: createInputLayout failed"); m_inputLayoutOverlay = m_device->createInputLayout(vertexAttrs, 3, m_vsOverlay); R_ASSERT2(m_inputLayoutOverlay, "FGLensFlareRender: createInputLayout (overlay) failed"); + m_inputLayoutSunDisc = m_device->createInputLayout(vertexAttrs, 3, m_vsSunDisc); + R_ASSERT2(m_inputLayoutSunDisc, "FGLensFlareRender: createInputLayout (sun disc) failed"); nvrhi::BufferDesc cbDesc; cbDesc.byteSize = sizeof(passes::DynamicTransforms); @@ -105,6 +113,37 @@ void FGLensFlareRender::InitResources() m_sampler = m_device->createSampler(samplerDesc); R_ASSERT2(m_sampler, "FGLensFlareRender: createSampler failed"); + { + constexpr u32 kDisc = 64; + nvrhi::TextureDesc discDesc; + discDesc.width = kDisc; + discDesc.height = kDisc; + discDesc.format = nvrhi::Format::RGBA8_UNORM; + discDesc.debugName = "FGLensFlare_SoftDisc"; + discDesc.initialState = nvrhi::ResourceStates::ShaderResource; + discDesc.keepInitialState = true; + m_softDisc = m_device->createTexture(discDesc); + xr_vector pixels(kDisc * kDisc); + for (u32 y = 0; y < kDisc; ++y) + { + for (u32 x = 0; x < kDisc; ++x) + { + const float u = (float(x) + 0.5f) / float(kDisc) * 2.f - 1.f; + const float v = (float(y) + 0.5f) / float(kDisc) * 2.f - 1.f; + const float r = _sqrt(u * u + v * v); + const float a = _max(0.f, 1.f - r); + const float s = a * a; + pixels[y * kDisc + x] = color_rgba( + u32(s * 255.f), u32(s * 255.f), u32(s * 255.f), u32(a * 255.f)); + } + } + nvrhi::CommandListHandle upload = m_device->createCommandList(); + upload->open(); + upload->writeTexture(m_softDisc, 0, 0, pixels.data(), kDisc * sizeof(u32)); + upload->close(); + m_device->executeCommandList(upload); + } + nvrhi::BindingLayoutDesc bindingLayoutDesc; bindingLayoutDesc.visibility = nvrhi::ShaderType::All; bindingLayoutDesc.bindings = { @@ -138,19 +177,18 @@ void FGLensFlareRender::InitResources() nvrhi::FramebufferInfo fbInfo; fbInfo.addColorFormat(nvrhi::Format::RGBA16_FLOAT); - fbInfo.setDepthFormat(nvrhi::Format::D32); fbInfo.setSampleCount(1); nvrhi::GraphicsPipelineDesc sourceDesc; - sourceDesc.VS = m_vs; + sourceDesc.VS = m_vsSunDisc; sourceDesc.PS = m_ps; - sourceDesc.inputLayout = m_inputLayout; + sourceDesc.inputLayout = m_inputLayoutSunDisc; sourceDesc.bindingLayouts = { m_bindingLayout }; sourceDesc.primType = nvrhi::PrimitiveType::TriangleList; sourceDesc.renderState.rasterState.cullMode = nvrhi::RasterCullMode::None; - sourceDesc.renderState.depthStencilState.depthTestEnable = true; + sourceDesc.renderState.depthStencilState.depthTestEnable = false; sourceDesc.renderState.depthStencilState.depthWriteEnable = false; - sourceDesc.renderState.depthStencilState.depthFunc = nvrhi::ComparisonFunc::GreaterOrEqual; + sourceDesc.renderState.depthStencilState.depthFunc = nvrhi::ComparisonFunc::Always; sourceDesc.renderState.blendState.targets[0] .setBlendEnable(true) .setSrcBlend(nvrhi::BlendFactor::SrcAlpha) @@ -165,12 +203,33 @@ void FGLensFlareRender::InitResources() overlayDesc.VS = m_vsOverlay; overlayDesc.inputLayout = m_inputLayoutOverlay; overlayDesc.bindingLayouts = { m_overlayBindingLayout }; - overlayDesc.renderState.depthStencilState.depthTestEnable = false; - overlayDesc.renderState.depthStencilState.depthFunc = nvrhi::ComparisonFunc::Always; m_pipelineOverlay = m_device->createGraphicsPipeline(overlayDesc, fbInfo); R_ASSERT2(m_pipelineOverlay, "FGLensFlareRender: createGraphicsPipeline (overlay) failed"); + nvrhi::BindingLayoutDesc sunDiscLayoutDesc; + sunDiscLayoutDesc.visibility = nvrhi::ShaderType::All; + sunDiscLayoutDesc.bindings = { + nvrhi::BindingLayoutItem::VolatileConstantBuffer(0), + }; + m_sunDiscBindingLayout = m_device->createBindingLayout(sunDiscLayoutDesc); + R_ASSERT2(m_sunDiscBindingLayout, "FGLensFlareRender: createBindingLayout (sun disc) failed"); + + nvrhi::GraphicsPipelineDesc sunDiscDesc = sourceDesc; + sunDiscDesc.VS = m_vsSunDisc; + sunDiscDesc.PS = m_psSunDisc; + sunDiscDesc.inputLayout = m_inputLayoutSunDisc; + sunDiscDesc.bindingLayouts = { m_sunDiscBindingLayout }; + m_pipelineSunDisc = m_device->createGraphicsPipeline(sunDiscDesc, fbInfo); + R_ASSERT2(m_pipelineSunDisc, "FGLensFlareRender: createGraphicsPipeline (sun disc) failed"); + + nvrhi::BindingSetDesc sunDiscSetDesc; + sunDiscSetDesc.bindings = { + nvrhi::BindingSetItem::ConstantBuffer(0, m_constantBuffer), + }; + m_sunDiscBindingSet = m_device->createBindingSet(sunDiscSetDesc, m_sunDiscBindingLayout); + R_ASSERT2(m_sunDiscBindingSet, "FGLensFlareRender: createBindingSet (sun disc) failed"); + nvrhi::ComputePipelineDesc visPipelineDesc; visPipelineDesc.CS = m_visCS; visPipelineDesc.bindingLayouts = { m_visBindingLayout }; @@ -207,9 +266,9 @@ nvrhi::ITexture* FGLensFlareRender::ResolveTexture(const shared_str& name) } void FGLensFlareRender::PushQuad(const Fvector& center, const Fvector& vecX, const Fvector& vecY, u32 color, - nvrhi::ITexture* tex, bool depthTested) + nvrhi::ITexture* tex, bool depthTested, bool procedural) { - if (!tex) + if (!tex && !procedural) return; const u32 base = static_cast(m_vertices.size()); @@ -261,6 +320,7 @@ void FGLensFlareRender::PushQuad(const Fvector& center, const Fvector& vecX, con b.indexCount = 6; b.texture = tex; b.depthTested = depthTested; + b.procedural = procedural; m_batches.push_back(b); } @@ -276,12 +336,10 @@ void FGLensFlareRender::Render(CLensFlare& owner, BOOL bSun, BOOL bFlares, BOOL if (clip.w > 0.f) { m_sunValid = true; - m_sunPosPx.set((clip.x * 0.5f + 0.5f) * float(Device.dwWidth), - (1.f - (clip.y * 0.5f + 0.5f)) * float(Device.dwHeight)); + m_sunPosPx.set(clip.x * 0.5f + 0.5f, 1.f - (clip.y * 0.5f + 0.5f)); const float radius = owner.m_Current->m_Flags.is(CLensFlareDescriptor::flSource) ? owner.m_Current->m_Source.fRadius : 0.15f; - m_sunRadiusPx = radius * 0.25f * float(Device.dwHeight) / tanf(deg2rad(Device.fFOV) * 0.5f); - clamp(m_sunRadiusPx, 4.f, 96.f); + m_sunRadiusPx = radius * 0.25f / tanf(deg2rad(Device.fFOV) * 0.5f); } Fcolor dwLight; @@ -305,7 +363,10 @@ void FGLensFlareRender::Render(CLensFlare& owner, BOOL bSun, BOOL bFlares, BOOL auto* flare = static_cast(&*owner.m_Current->m_Source.m_pRender); nvrhi::ITexture* tex = ResolveTexture(flare ? flare->m_textureName : shared_str{}); - PushQuad(owner.vecLight, vecSx, vecSy, color.get(), tex, true); + if (tex) + PushQuad(owner.vecLight, vecSx, vecSy, color.get(), tex, true, false); + else + PushQuad(owner.vecLight, vecSx, vecSy, color.get(), nullptr, false, true); } if (owner.fBlend >= EPS_L) @@ -337,15 +398,18 @@ void FGLensFlareRender::Render(CLensFlare& owner, BOOL bSun, BOOL bFlares, BOOL if (bGradient && owner.fGradientValue >= EPS_L && owner.m_Current->m_Flags.is(CLensFlareDescriptor::flGradient)) { - vecSx.mul(owner.vecX, owner.m_Current->m_Gradient.fRadius * owner.fGradientValue * fDistance); - vecSy.mul(owner.vecY, owner.m_Current->m_Gradient.fRadius * owner.fGradientValue * fDistance); + const float gradScale = 0.42f * owner.fGradientValue; + vecSx.mul(owner.vecX, owner.m_Current->m_Gradient.fRadius * gradScale * fDistance); + vecSy.mul(owner.vecY, owner.m_Current->m_Gradient.fRadius * gradScale * fDistance); Fcolor color; color.set(dwLight); - color.mul_rgba(owner.fGradientValue * owner.m_StateBlend); + color.mul_rgba(owner.fGradientValue * owner.m_StateBlend * 0.45f); auto* flare = static_cast(&*owner.m_Current->m_Gradient.m_pRender); nvrhi::ITexture* tex = ResolveTexture(flare ? flare->m_textureName : shared_str{}); + if (!tex) + tex = m_softDisc.Get(); PushQuad(owner.vecLight, vecSx, vecSy, color.get(), tex); } } @@ -362,10 +426,12 @@ void FGLensFlareRender::DispatchVisibility(nvrhi::ICommandList* cmdList, nvrhi:: m_visInitialized = true; } + const auto& depthDesc = depth->getDesc(); FlareVisParams cb{}; - cb.sunPosX = m_sunPosPx.x; - cb.sunPosY = m_sunPosPx.y; - cb.radiusPx = m_sunRadiusPx; + cb.sunPosX = m_sunPosPx.x * float(depthDesc.width); + cb.sunPosY = m_sunPosPx.y * float(depthDesc.height); + cb.radiusPx = m_sunRadiusPx * float(depthDesc.height); + clamp(cb.radiusPx, 4.f, 96.f); cb.emaAlpha = 1.f - expf(-8.f * Device.fTimeDelta); cb.valid = m_sunValid ? 1u : 0u; cmdList->writeBuffer(m_visConstantBuffer, &cb, sizeof(cb)); @@ -449,9 +515,32 @@ void FGLensFlareRender::Draw(nvrhi::ICommandList* cmdList, nvrhi::IFramebuffer* for (const Batch& b : m_batches) { - if (b.indexCount == 0 || !b.texture) + if (b.indexCount == 0 || (!b.texture && !b.procedural)) continue; + if (b.procedural) + { + if (!m_pipelineSunDisc || !m_sunDiscBindingSet) + continue; + nvrhi::GraphicsState state; + state.pipeline = m_pipelineSunDisc; + state.framebuffer = framebuffer; + state.bindings = { m_sunDiscBindingSet }; + state.vertexBuffers = { vertexBinding }; + state.indexBuffer.buffer = m_indexBuffer; + state.indexBuffer.format = nvrhi::Format::R16_UINT; + state.indexBuffer.offset = 0; + state.viewport = nvrhi::ViewportState().addViewportAndScissorRect( + nvrhi::Viewport(static_cast(fbInfo.width), static_cast(fbInfo.height))); + cmdList->setGraphicsState(state); + nvrhi::DrawArguments args; + args.vertexCount = b.indexCount; + args.instanceCount = 1; + args.startIndexLocation = b.indexOffset; + cmdList->drawIndexed(args); + continue; + } + auto& cache = b.depthTested ? m_sourceBindingSetCache : m_overlayBindingSetCache; auto it = cache.find(b.texture); if (it == cache.end()) diff --git a/src/Layers/xrRender/fgLensFlareRender.h b/src/Layers/xrRender/fgLensFlareRender.h index 87f59b84800..5e41a53c80a 100644 --- a/src/Layers/xrRender/fgLensFlareRender.h +++ b/src/Layers/xrRender/fgLensFlareRender.h @@ -22,6 +22,7 @@ class FGLensFlareRender : public ILensFlareRender u32 indexCount; nvrhi::ITexture* texture; bool depthTested; + bool procedural; }; FGLensFlareRender(); @@ -49,7 +50,7 @@ class FGLensFlareRender : public ILensFlareRender void EnsureGeometryCapacity(size_t vertexCount, size_t indexCount); nvrhi::ITexture* ResolveTexture(const shared_str& name); void PushQuad(const Fvector& center, const Fvector& vecX, const Fvector& vecY, u32 color, nvrhi::ITexture* tex, - bool depthTested = false); + bool depthTested = false, bool procedural = false); xr_vector m_vertices; xr_vector m_indices; @@ -79,7 +80,14 @@ class FGLensFlareRender : public ILensFlareRender size_t m_indexCapacity = 0; nvrhi::GraphicsPipelineHandle m_pipelineSource; nvrhi::GraphicsPipelineHandle m_pipelineOverlay; + nvrhi::GraphicsPipelineHandle m_pipelineSunDisc; + nvrhi::BindingLayoutHandle m_sunDiscBindingLayout; + nvrhi::BindingSetHandle m_sunDiscBindingSet; + nvrhi::ShaderHandle m_vsSunDisc; + nvrhi::ShaderHandle m_psSunDisc; + nvrhi::InputLayoutHandle m_inputLayoutSunDisc; nvrhi::ComputePipelineHandle m_visPipeline; + nvrhi::TextureHandle m_softDisc; bool m_visInitialized = false; bool m_sunValid = false; Fvector2 m_sunPosPx{}; diff --git a/src/Layers/xrRender/fgUIShader.cpp b/src/Layers/xrRender/fgUIShader.cpp index a0087f54af2..af103c29636 100644 --- a/src/Layers/xrRender/fgUIShader.cpp +++ b/src/Layers/xrRender/fgUIShader.cpp @@ -31,8 +31,14 @@ void fgUIShader::create(LPCSTR sh, LPCSTR tex) RImplementation.Resources->bDeferredLoad = prevDeferredLoad; } - auto vsResult = shaderLoader->LoadVertexShader(sh, "main"); - auto psResult = shaderLoader->LoadPixelShader(sh, "main"); + const bool movie = sh && (0 == xr_strcmp(sh, "hud_movie") || 0 == xr_strcmp(sh, "yuv2rgb")); + auto vsResult = shaderLoader->LoadVertexShader(movie ? "hud_movie" : sh, "main"); + auto psResult = shaderLoader->LoadPixelShader(movie ? "hud_movie" : sh, "main"); + if (movie && (!vsResult.handle || !psResult.handle)) + { + vsResult = shaderLoader->LoadVertexShader("stub_notransform_t", "main"); + psResult = shaderLoader->LoadPixelShader("yuv2rgb", "main"); + } if (!vsResult.handle || !psResult.handle) { diff --git a/src/Layers/xrRender/r4_rendertarget.cpp b/src/Layers/xrRender/r4_rendertarget.cpp index 0221e753dca..e93980a0320 100644 --- a/src/Layers/xrRender/r4_rendertarget.cpp +++ b/src/Layers/xrRender/r4_rendertarget.cpp @@ -1,5 +1,7 @@ #include "stdafx.h" #include "Layers/xrRender/ResourceManager.h" +#include "Layers/xrRender/r4_rendertarget.h" +#include "xrEngine/device.h" namespace xray::render::fg { @@ -69,11 +71,107 @@ Ivector vpack(const Fvector& src) CRenderTarget::CRenderTarget() { - return; + im_noise_time = 1.f; + im_noise_shift_w = 0; + im_noise_shift_h = 0; + param_blur = 0.f; + param_gray = 0.f; + param_duality_h = 0.f; + param_duality_v = 0.f; + param_noise = 0.f; + param_noise_scale = 1.f; + param_noise_fps = 25.f; + param_color_base = color_rgba(127, 127, 127, 0); + param_color_gray = color_rgba(85, 85, 85, 0); + param_color_add.set(0.f, 0.f, 0.f); + param_color_map_influence = 0.f; + param_color_map_interpolate = 0.f; + m_bHasActiveVolumetric = false; } CRenderTarget::~CRenderTarget() {} +void CRenderTarget::u_calc_tc_noise(Fvector2& p0, Fvector2& p1) +{ + u32 tw = iCeil(512.f * param_noise_scale + EPS_S); + u32 th = iCeil(512.f * param_noise_scale + EPS_S); + if (!tw) + tw = 1; + if (!th) + th = 1; + + im_noise_time -= Device.fTimeDelta; + if (im_noise_time < 0) + { + im_noise_shift_w = ::Random.randI(tw); + im_noise_shift_h = ::Random.randI(th); + float fps_time = 1.f / std::max(param_noise_fps, 1.f); + while (im_noise_time < 0) + im_noise_time += fps_time; + } + + float start_u = (float(im_noise_shift_w) + .5f) / float(tw); + float start_v = (float(im_noise_shift_h) + .5f) / float(th); + u32 cnt_w = Device.dwWidth / tw; + u32 cnt_h = Device.dwHeight / th; + p0.set(start_u, start_v); + p1.set(start_u + float(cnt_w) + 1.f, start_v + float(cnt_h) + 1.f); +} + +void CRenderTarget::u_calc_tc_duality_ss(Fvector2& r0, Fvector2& r1, Fvector2& l0, Fvector2& l1) +{ + float tw = float(Device.dwWidth); + float th = float(Device.dwHeight); + Fvector2 shift, p0, p1; + shift.set(.5f / tw, .5f / th); + shift.mul(param_blur); + p0.set(.5f / tw, .5f / th); + p0.add(shift); + p1.set((tw + .5f) / tw, (th + .5f) / th); + p1.add(shift); + + float shift_u = param_duality_h * .5f; + float shift_v = param_duality_v * .5f; + + r0.set(p0.x, p0.y); + r1.set(p1.x - shift_u, p1.y - shift_v); + l0.set(p0.x + shift_u, p0.y + shift_v); + l1.set(p1.x, p1.y); +} + +bool CRenderTarget::u_need_CM() +{ + return param_color_map_influence > 0.001f; +} + +bool CRenderTarget::u_need_PP() +{ + bool _blur = (param_blur > 0.001f); + bool _gray = (param_gray > 0.001f); + bool _noise = (param_noise > 0.001f); + bool _dual = (param_duality_h > 0.001f) || (param_duality_v > 0.001f); + + bool _cbase = false; + { + int _r = _abs(int(color_get_R(param_color_base)) - int(0x7f)); + int _g = _abs(int(color_get_G(param_color_base)) - int(0x7f)); + int _b = _abs(int(color_get_B(param_color_base)) - int(0x7f)); + if (_r > 2 || _g > 2 || _b > 2) + _cbase = true; + } + bool _cadd = false; + { + int _r = _abs((int)(param_color_add.x * 255)); + int _g = _abs((int)(param_color_add.y * 255)); + int _b = _abs((int)(param_color_add.z * 255)); + if (_r > 0 || _g > 0 || _b > 0) + _cadd = true; + } + return _blur || _gray || _noise || _dual || _cbase || _cadd || u_need_CM(); +} + +void CRenderTarget::phase_pp() {} + bool CRenderTarget::need_to_render_sunshafts() { if (!(RImplementation.o.advancedpp && ps_r_sun_shafts)) diff --git a/src/Layers/xrRender/r4_rendertarget.h b/src/Layers/xrRender/r4_rendertarget.h index c6a0fef48c9..b8e956cf626 100644 --- a/src/Layers/xrRender/r4_rendertarget.h +++ b/src/Layers/xrRender/r4_rendertarget.h @@ -260,6 +260,20 @@ class CRenderTarget color_map_manager.SetTextures(tex0, tex1); } + float get_blur() const { return param_blur; } + float get_gray() const { return param_gray; } + float get_duality_h() const { return param_duality_h; } + float get_duality_v() const { return param_duality_v; } + float get_noise() const { return param_noise; } + float get_noise_scale() const { return param_noise_scale; } + float get_noise_fps() const { return param_noise_fps; } + u32 get_color_base() const { return param_color_base; } + u32 get_color_gray() const { return param_color_gray; } + const Fvector& get_color_add() const { return param_color_add; } + float get_cm_influence() const { return param_color_map_influence; } + float get_cm_interpolate() const { return param_color_map_interpolate; } + nvrhi::ITexture* get_cm_texture(int i) const { return color_map_manager.GetTexture(i); } + #ifdef DEBUG void dbg_addline(const Fvector& P0, const Fvector& P1, u32 c) { diff --git a/src/Layers/xrRender/r_FrameGraphRenderer.cpp b/src/Layers/xrRender/r_FrameGraphRenderer.cpp index 40f97171414..f15b02d1cb1 100644 --- a/src/Layers/xrRender/r_FrameGraphRenderer.cpp +++ b/src/Layers/xrRender/r_FrameGraphRenderer.cpp @@ -36,6 +36,7 @@ #include "FrameGraphPasses/DetailCullPassSetup.h" // Detail culling (async compute) #include "FrameGraphPasses/DetailPassSetup.h" // Detail rendering pass #include "FrameGraphPasses/TransparentPassSetup.h" // Transparent alpha-blended geometry (after detail) +#include "FrameGraphPasses/PassCommon.h" // SM6 bindless: Textures registered directly with D3D12Backend via RegisterBindlessTexture() #include "Bindless/MaterialBuffer.h" // Bindless material buffer #include "Bindless/TerrainMaterialBuffer.h" // Terrain material buffer @@ -44,6 +45,7 @@ #include "FrameGraphPasses/SunPassSetup.h" // Sun disc rendering #include "FrameGraphPasses/SkinningPassSetup.h" #include "FrameGraphPasses/ParticlePassSetup.h" // Particle rendering (billboards/sprites) +#include "FrameGraphPasses/GlowPassSetup.h" #include "FrameGraphPasses/DistortionApplyPassSetup.h" // Distortion post-process #include "FrameGraphPasses/DecalPassSetup.h" // Screen-space box decals #include "Decals/DecalManager.h" // Decal manager @@ -51,13 +53,26 @@ #include "FrameGraphPasses/ExposurePassSetup.h" // Auto-exposure from histogram #include "FrameGraphPasses/UIPassSetup.h" #include "FrameGraphPasses/FontPassSetup.h" -#include "FrameGraphPasses/TonemapPassSetup.h" // Tonemap pass: HDR→LDR conversion +#include "FrameGraphPasses/TonemapPassSetup.h" +#include "FrameGraphPasses/PostProcessPassSetup.h" #include "FrameGraphPasses/SmokeTrailPassSetup.h" #include "FrameGraphPasses/ClusterLightPassSetup.h" #include "ClusteredLightManager.h" #include "light.h" #include "FrameGraphPasses/MotionVectorPassSetup.h" +#include "FrameGraphPasses/TAAPassSetup.h" #include "FrameGraphPasses/ReSTIRGIPassSetup.h" +#include "FrameGraphPasses/WetSurfacesPassSetup.h" +#include "FrameGraphPasses/RainShadowPassSetup.h" +#include "FrameGraphPasses/ShadowPassSetup.h" +#include "FrameGraphPasses/VolumetricFogPassSetup.h" +#include "Upscaling/UpscaleState.h" +#include "Upscaling/IUpscaleBackend.h" +#include "Upscaling/UpscalePassSetup.h" +#include "Upscaling/StreamlineDLSS.h" +#include "Upscaling/DlssFgPassSetup.h" +#include "Denoising/IDenoiseBackend.h" +#include "RayTracing/ReSTIRMemoryManager.h" #include "FrameGraphPasses/RibbonPassSetup.h" #include "FrameGraphPasses/TrailPassSetup.h" #include "Layers/xrRender/FrameGraph/Blackboard.h" @@ -175,11 +190,50 @@ extern ENGINE_API int ps_r_rt_gi; extern ENGINE_API float ps_r_rt_gi_intensity; extern ENGINE_API int ps_r_path_tracer; extern ENGINE_API int ps_r_path_tracer_bounces; +extern ENGINE_API int ps_r_taa; +extern ENGINE_API int ps_r_upscale; +extern ENGINE_API int ps_r_denoise; +extern ENGINE_API int ps_r_nrd_method; +extern ENGINE_API int ps_r_dlss; +extern ENGINE_API int ps_r_dlss_rr; +extern ENGINE_API int ps_r_dlss_fg; +extern ENGINE_API int ps_r_hdr_debug; namespace xray::render { using namespace fg; +static framegraph::VirtualResourceHandle CreateHdrCompose(framegraph::FrameGraph& fg, u32 w, u32 h) +{ + framegraph::ResourceDesc d; + d.type = framegraph::ResourceDesc::Type::Texture2D; + d.width = w; + d.height = h; + d.format = nvrhi::Format::RGBA16_FLOAT; + d.isRenderTarget = true; + d.debugName = "rt_HdrCompose"; + return fg.CreateTexture("rt_HdrCompose", d); +} + +static framegraph::VirtualResourceHandle CreateDisplayColor(framegraph::FrameGraph& fg, u32 w, u32 h) +{ + nvrhi::Format fmt = nvrhi::Format::RGBA8_UNORM; + if (GEnv.Backend && GEnv.Backend->GetBackBuffer()) + fmt = GEnv.Backend->GetBackBuffer()->getDesc().format; + framegraph::ResourceDesc d; + d.type = framegraph::ResourceDesc::Type::Texture2D; + d.width = w; + d.height = h; + d.format = fmt; + d.isRenderTarget = true; + d.debugName = "rt_FgDisplay"; + return fg.CreateTexture("rt_FgDisplay", d); +} + +static fg::UpscaleState g_upscaleState; +static xr_unique_ptr g_upscaleBackend; +static xr_unique_ptr g_denoiseBackend; + // Forward declaration and extern for accessing RImplementation namespace fg { extern xray::render::FrameGraphRenderer RImplementation; @@ -360,14 +414,38 @@ void FrameGraphRenderer::Shutdown() { fg::ClusteredLightManager::Instance().Shutdown(); + if (m_blackboard) { + if (auto* restir = m_blackboard->try_get()) + passes::ShutdownReSTIRGI(*restir); + if (auto* wet = m_blackboard->try_get()) + wet->initialized = false; + if (auto* rainSM = m_blackboard->try_get()) + passes::ShutdownRainShadowPass(m_device, *rainSM); + if (auto* grassSM = m_blackboard->try_get()) + passes::ShutdownGrassShadowPass(m_device, *grassSM); + if (auto* volFog = m_blackboard->try_get()) + passes::ShutdownVolumetricFog(*volFog); + } + passes::ShutdownPathTracer(); m_shaderPhaseCache = nullptr; m_framegraph = nullptr; + if (g_upscaleBackend) { + g_upscaleBackend->Shutdown(); + g_upscaleBackend.reset(); + } + if (g_denoiseBackend) { + g_denoiseBackend->Shutdown(); + g_denoiseBackend.reset(); + } + if (m_blackboard) { if (auto* tonemap = m_blackboard->try_get()) passes::ShutdownTonemapPass(*tonemap); + if (auto* taa = m_blackboard->try_get()) + passes::ShutdownTAAPass(*taa); m_blackboard.reset(); } @@ -490,14 +568,15 @@ void FrameGraphRenderer::Render() { auto& cache = framegraph::GetPassResourceCache(); auto* cmdList = m_renderContext->GetCommandList(); - auto staticGlobalsCB = cache.GetOrCreateVolatileCB("Frame", "StaticGlobals", sizeof(passes::StaticGlobals), m_device); + auto staticGlobalsCB = cache.GetOrCreateVolatileCB("Frame", "StaticGlobals", sizeof(passes::StaticGlobals), m_device, 512); auto staticGlobalsData = passes::BuildStaticGlobals(); auto& clm = fg::ClusteredLightManager::Instance(); if (clm.IsReady() && clm.GetLightCount() > 0) { float zNear = VIEWPORT_NEAR; float zFar = g_pGamePersistent->Environment().CurrentEnv.far_plane; - auto ccb = clm.BuildClusterCB(Device.dwWidth, Device.dwHeight, zNear, zFar); + auto ccb = clm.BuildClusterCB( + passes::GetRenderWidth(), passes::GetRenderHeight(), zNear, zFar); staticGlobalsData.cluster_params.set(ccb.gridDims.x, ccb.gridDims.y, ccb.gridDims.z, ccb.gridDims.w); staticGlobalsData.cluster_scales.set(ccb.depthParams.x, ccb.depthParams.y, ccb.depthParams.z, ccb.depthParams.w); } @@ -505,7 +584,7 @@ void FrameGraphRenderer::Render() { cmdList->writeBuffer(staticGlobalsCB, &staticGlobalsData, sizeof(staticGlobalsData)); auto dynamicTransformsCB = cache.GetOrCreateVolatileCB("Frame", "DynamicTransforms", - sizeof(passes::DynamicTransforms), m_device); + sizeof(passes::DynamicTransforms), m_device, 256); passes::DynamicTransforms dynamicTransformsData = {}; passes::FillDynamicTransforms(dynamicTransformsData); cmdList->writeBuffer(dynamicTransformsCB, &dynamicTransformsData, sizeof(dynamicTransformsData)); @@ -521,7 +600,8 @@ void FrameGraphRenderer::Render() { } m_hasPrevFrameData = true; - m_prevViewProj = Device.mFullTransform; + m_prevViewProj = passes::g_taa_unjittered_full_transform; + m_prevInvFullTransform = Device.mInvFullTransform; m_prevCameraPos = Device.vCameraPosition; m_pingPongIndex = 1 - m_pingPongIndex; @@ -587,15 +667,18 @@ void FrameGraphRenderer::RenderMenu() { backbufferHandle = m_framegraph->ImportTexture("Backbuffer", backbufferTexture, backbufferDesc); } + const bool hdr10 = GEnv.Backend && GEnv.Backend->IsHdr10(); + framegraph::ResourceDesc bgDesc; bgDesc.type = framegraph::ResourceDesc::Type::Texture2D; bgDesc.width = width; bgDesc.height = height; - bgDesc.format = nvrhi::Format::RGBA8_UNORM; + bgDesc.format = hdr10 ? nvrhi::Format::RGBA16_FLOAT : nvrhi::Format::RGBA8_UNORM; bgDesc.isRenderTarget = true; - bgDesc.debugName = "rt_MenuBackground"; + const char* bgName = hdr10 ? "rt_HdrCompose" : "rt_MenuBackground"; + bgDesc.debugName = bgName; - auto backgroundTarget = m_framegraph->CreateTexture("rt_MenuBackground", bgDesc); + auto backgroundTarget = m_framegraph->CreateTexture(bgName, bgDesc); framegraph::PassHandle clearPass = m_framegraph->AddPass("ClearBackground"); m_framegraph->PassWrite(clearPass, backgroundTarget, framegraph::ResourceState::RenderTarget); @@ -614,25 +697,33 @@ void FrameGraphRenderer::RenderMenu() { sceneWithUI = passes::setupCursorPass(*m_framegraph, sceneWithUI, width, height); sceneWithUI = passes::setupDebugDrawPass(*m_framegraph, sceneWithUI, width, height); - auto ldrOutput = passes::setupTonemapPass( - *m_framegraph, - m_device, - sceneWithUI, // HDR input (RGBA16_FLOAT) - framegraph::VirtualResourceHandle(), // No exposure for menu - backbufferHandle, // Output directly to imported backbuffer - width, - height, - m_blackboard->get_or_add() - ); + framegraph::VirtualResourceHandle ldrOutput = sceneWithUI; + if (!hdr10) + { + ldrOutput = passes::setupTonemapPass( + *m_framegraph, + m_device, + sceneWithUI, + framegraph::VirtualResourceHandle(), + backbufferHandle, + width, + height, + m_blackboard->get_or_add() + ); + } fg::ImGuiRendererNVRHI* imguiRenderer = GEnv.Render->GetImGuiRendererNVRHI(); auto finalOutput = passes::setupImGuiPass( *m_framegraph, - ldrOutput, // LDR input (RGBA8_UNORM) + ldrOutput, imguiRenderer, width, height ); + if (hdr10) + finalOutput = passes::setupHdr10EncodePass( + *m_framegraph, m_device, finalOutput, backbufferHandle, width, height, + m_blackboard->get_or_add()); m_finalOutput = finalOutput; @@ -649,6 +740,21 @@ void FrameGraphRenderer::RenderMenu() { void FrameGraphRenderer::RenderStatsOverlay() { + if (ps_r_hdr_debug) + { + const fg::passes::ExposurePassState* exp = nullptr; + if (m_blackboard) + { + if (auto* e = m_blackboard->try_get()) + { + if (m_device && m_device->GetNVRHIDevice()) + fg::passes::PollExposureHistogram(*e, m_device->GetNVRHIDevice()); + exp = e; + } + } + fg::passes::RenderHdrDebugUI(exp); + } + if (m_statsOverlay && psDeviceFlags.test(rsStatistic)) { xray::profiler::RenderStats stats; @@ -659,8 +765,6 @@ void FrameGraphRenderer::RenderStatsOverlay() const auto& batches = m_geometryCollector->GetBatches(); stats.totalBatches = static_cast(batches.size()); - xr_set uniqueSkeletons; - for (const auto& batch : batches) { u32 triangles = batch.indexCount / 3; @@ -670,26 +774,7 @@ void FrameGraphRenderer::RenderStatsOverlay() { stats.skinnedBatches++; stats.skinnedTriangles += triangles; - - if (batch.renderable) - { - IRenderVisual* rootVisual = batch.renderable->GetRenderData().visual; - if (rootVisual && uniqueSkeletons.find(rootVisual) == uniqueSkeletons.end()) - { - uniqueSkeletons.insert(rootVisual); - stats.skinnedMeshes++; - - // Get bone count from kinematics - IKinematics* K = rootVisual->dcast_PKinematics(); - if (K) - { - u32 boneCount = K->LL_BoneCount(); - stats.totalBones += boneCount; - if (boneCount > stats.maxBonesPerMesh) - stats.maxBonesPerMesh = boneCount; - } - } - } + stats.skinnedMeshes++; } else if (batch.isTerrain) { @@ -935,8 +1020,102 @@ framegraph::VirtualResourceHandle FrameGraphRenderer::CreateRT( } void FrameGraphRenderer::SetupFrameGraphPasses() { - const u32 width = Device.dwWidth; - const u32 height = Device.dwHeight; + passes::ApplyTAAJitter(); + + { + if (ps_r_dlss != 0 && ps_r_upscale == 0) + ps_r_upscale = 2; + const int reqUpscale = ps_r_upscale; + fg::UpscaleBackendType wantType = fg::UpscaleBackendType::None; + if (reqUpscale == 3) + wantType = fg::UpscaleBackendType::DLSS; + else if (reqUpscale == 2) + wantType = fg::UpscaleBackendType::DLSS; + else if (reqUpscale == 1) + wantType = fg::UpscaleBackendType::None; + const fg::UpscaleBackendType haveType = + g_upscaleBackend ? g_upscaleBackend->GetType() : fg::UpscaleBackendType::None; + const bool haveOk = g_upscaleBackend && g_upscaleBackend->IsAvailable(); + const bool needSwitch = + (wantType == fg::UpscaleBackendType::None && haveType != fg::UpscaleBackendType::None) || + (wantType != fg::UpscaleBackendType::None && (!haveOk || haveType != wantType)); + if (needSwitch) + { + if (GEnv.Backend) + GEnv.Backend->WaitForIdle(); + if (g_upscaleBackend) + { + if (haveType == fg::UpscaleBackendType::DLSS && + wantType != fg::UpscaleBackendType::DLSS) + fg::Streamline_ReleaseFeatures(); + g_upscaleBackend->Shutdown(); + g_upscaleBackend.reset(); + } + if (wantType != fg::UpscaleBackendType::None) + g_upscaleBackend.reset(fg::CreateUpscaleBackendAuto()); + m_hasPrevFrameData = false; + } + } + const bool upscaleBackendOk = g_upscaleBackend && g_upscaleBackend->IsAvailable(); + + { + static int s_denoiseCvar = -1; + static int s_nrdMethodCvar = -1; + static int s_dlssRrCvar = -1; + static int s_upscaleCvar = -1; + static int s_rrAvail = -1; + static u32 s_denoiseW = 0; + static u32 s_denoiseH = 0; + UpdateUpscaleState(g_upscaleState, Device.dwWidth, Device.dwHeight, upscaleBackendOk); + const u32 denoiseW = g_upscaleState.renderWidth ? g_upscaleState.renderWidth : Device.dwWidth; + const u32 denoiseH = g_upscaleState.renderHeight ? g_upscaleState.renderHeight : Device.dwHeight; + const int rrAvail = fg::Streamline_IsRRAvailable() ? 1 : 0; + const bool denoiseDirty = + s_denoiseCvar != ps_r_denoise || + s_nrdMethodCvar != ps_r_nrd_method || + s_dlssRrCvar != ps_r_dlss_rr || + s_upscaleCvar != ps_r_upscale || + s_rrAvail != rrAvail || + (g_denoiseBackend && (s_denoiseW != denoiseW || s_denoiseH != denoiseH)); + if (denoiseDirty && g_denoiseBackend) { + if (GEnv.Backend) + GEnv.Backend->WaitForIdle(); + g_denoiseBackend->Shutdown(); + g_denoiseBackend.reset(); + } + if (ps_r_denoise != 0 && !g_denoiseBackend) + g_denoiseBackend.reset(fg::CreateDenoiseBackendAuto(ps_r_dlss_rr != 0 && ps_r_upscale == 2)); + if (g_denoiseBackend && g_denoiseBackend->IsAvailable()) + g_denoiseBackend->Resize(denoiseW, denoiseH); + s_denoiseCvar = ps_r_denoise; + s_nrdMethodCvar = ps_r_nrd_method; + s_dlssRrCvar = ps_r_dlss_rr; + s_upscaleCvar = ps_r_upscale; + s_rrAvail = rrAvail; + s_denoiseW = denoiseW; + s_denoiseH = denoiseH; + } + + UpdateUpscaleState(g_upscaleState, Device.dwWidth, Device.dwHeight, upscaleBackendOk); + g_upscaleState.jitterX = passes::g_taa_jitter_px; + g_upscaleState.jitterY = passes::g_taa_jitter_py; + g_upscaleState.prevJitterX = passes::g_taa_jitter_prev_px; + g_upscaleState.prevJitterY = passes::g_taa_jitter_prev_py; + if (m_bFirstFrameAfterReset) + { + m_hasPrevFrameData = false; + m_bFirstFrameAfterReset = false; + fg::ReSTIRMemoryManager::Instance().RequestHistoryReset(); + } + g_upscaleState.resetHistory = !m_hasPrevFrameData || fg::Streamline_ConsumeFeatureReset(); + + const u32 displayWidth = g_upscaleState.displayWidth; + const u32 displayHeight = g_upscaleState.displayHeight; + const u32 width = g_upscaleState.renderWidth; + const u32 height = g_upscaleState.renderHeight; + passes::SetRenderResolution(width, height); + const bool upscaleResolved = g_upscaleState.upscaleActive && + g_upscaleBackend && g_upscaleBackend->IsAvailable(); nvrhi::ITexture* backbufferTexture = GEnv.Backend->GetBackBuffer(); framegraph::VirtualResourceHandle backbufferHandle; @@ -944,8 +1123,8 @@ void FrameGraphRenderer::SetupFrameGraphPasses() { if (backbufferTexture) { framegraph::ResourceDesc backbufferDesc; backbufferDesc.type = framegraph::ResourceDesc::Type::Texture2D; - backbufferDesc.width = width; - backbufferDesc.height = height; + backbufferDesc.width = displayWidth; + backbufferDesc.height = displayHeight; backbufferDesc.format = backbufferTexture->getDesc().format; backbufferDesc.isRenderTarget = true; backbufferDesc.isImported = true; @@ -1136,6 +1315,7 @@ void FrameGraphRenderer::SetupFrameGraphPasses() { colorDesc.format = nvrhi::Format::RGBA16_FLOAT; colorDesc.isRenderTarget = true; colorDesc.debugName = "rt_SceneColor"; + colorDesc.allowUAV = true; auto skyColorHandle = m_framegraph->CreateTexture("rt_SceneColor", colorDesc); @@ -1264,15 +1444,13 @@ void FrameGraphRenderer::SetupFrameGraphPasses() { ); } - // 2. Skinning Pass - Renders all skinned meshes (world + HUD) - // World skinned: NPCs, monsters with normal depth [0.0, 1.0] - // HUD skinned: First-person weapons/hands with depth [0.9, 1.0] - auto hudOutputs = passes::setupSkinningPass( + // 2. Skinning Pass - world skinned only + auto skinnedOutputs = passes::setupSkinningPass( *m_framegraph, m_device, forwardOutputs, m_geometryCollector.get(), - &m_hudBatches, + nullptr, m_materialCache.get(), width, height, @@ -1285,6 +1463,7 @@ void FrameGraphRenderer::SetupFrameGraphPasses() { // ═══════════════════════════════════════════════════════ // DETAIL CULL PASS (Async Compute) // ═══════════════════════════════════════════════════════ + auto& detailPassState = m_blackboard->get_or_add(); passes::setupDetailCullPass( *m_framegraph, m_device, @@ -1295,7 +1474,7 @@ void FrameGraphRenderer::SetupFrameGraphPasses() { hizOutput.mipLevels, m_hasPrevFrameData ? &m_prevViewProj : nullptr, m_gpuProfiler.get(), - &m_blackboard->get_or_add() + &detailPassState ); // ═══════════════════════════════════════════════════════ @@ -1328,12 +1507,26 @@ void FrameGraphRenderer::SetupFrameGraphPasses() { *m_framegraph, m_device, m_detailManager.get(), - hudOutputs, + skinnedOutputs, width, height, - m_gpuProfiler.get() + m_gpuProfiler.get(), + detailPassState.cullArgs ); + passes::GrassShadowOutputs grassShadowOut{}; + if (m_detailManager && g_pGamePersistent) + { + grassShadowOut = passes::setupGrassShadowPass( + *m_framegraph, + m_device, + m_detailManager.get(), + g_pGamePersistent->Environment().CurrentEnv.sun_dir, + m_blackboard->get_or_add(), + detailOutputs.albedo, + detailPassState.cullArgs); + } + // ═══════════════════════════════════════════════════════ // TRANSPARENT PASS (alpha-blended geometry) // ═══════════════════════════════════════════════════════ @@ -1347,9 +1540,12 @@ void FrameGraphRenderer::SetupFrameGraphPasses() { transparentConfig.compactMaterialIDBuffer = m_gpuCullingManager->GetTransparentCompactMaterialIDBuffer(); transparentConfig.compactCountBuffer = m_gpuCullingManager->GetTransparentCompactCountBuffer(); transparentConfig.objectCount = m_gpuCullingManager->GetTransparentObjectCount(); + passes::ResolveEnvSkyCubes(m_device, transparentConfig.envSky0, transparentConfig.envSky1); if (m_gpuCullingManager->IsVariantPartitionEnabled()) transparentConfig.variantPartition = m_gpuCullingManager->GetTransparentPartition().ToConfig(); + if ((ps_r_rt_gi != 0) && m_rtAccelMgr && m_rtAccelMgr->IsSupported() && m_rtAccelMgr->IsReady()) + transparentConfig.skipWmark = true; } auto transparentOutputs = passes::setupTransparentPass( @@ -1361,194 +1557,412 @@ void FrameGraphRenderer::SetupFrameGraphPasses() { m_blackboard->get_or_add() ); - // ═══════════════════════════════════════════════════════ - // DECAL PASS (screen-space box decals on surfaces) - // ═══════════════════════════════════════════════════════ - if (m_decalManager) { - m_decalManager->Update(Device.fTimeDelta, Device.fTimeGlobal); - if (m_decalManager->GetActiveCount() > 0) { - transparentOutputs = passes::setupDecalPass( - *m_framegraph, m_device, - transparentOutputs, m_decalManager.get(), - width, height, - m_blackboard->get_or_add() - ); - } + if (!m_hudBatches.empty()) { + framegraph::DefaultOutputLayout hudIn = transparentOutputs; + auto hudOut = passes::setupSkinningPass( + *m_framegraph, + m_device, + hudIn, + nullptr, + &m_hudBatches, + m_materialCache.get(), + width, + height, + m_gpuCullingManager.get(), + {}, + &m_blackboard->get_or_add(), + m_overlayManager.get()); + transparentOutputs.albedo = hudOut.albedo; + transparentOutputs.normal = hudOut.normal; + transparentOutputs.baseColor = hudOut.baseColor; + if (hudOut.worldPos.is_valid()) + transparentOutputs.worldPos = hudOut.worldPos; + transparentOutputs.depth = hudOut.depth; } - // ═══════════════════════════════════════════════════════ - // MOTION VECTOR PASS (Depth-based reprojection) - // ═══════════════════════════════════════════════════════ - passes::MotionVectorOutput motionOutput; - if (m_hasPrevFrameData) { - motionOutput = passes::setupMotionVectorPass( + if (m_decalManager) + m_decalManager->Update(Device.fTimeDelta, Device.fTimeGlobal); + + const bool restirWallmarks = transparentConfig.skipWmark; + if (restirWallmarks && transparentConfig.IsValid() && transparentOutputs.baseColor.is_valid()) { + framegraph::DefaultOutputLayout wmIn = transparentOutputs; + wmIn.albedo = transparentOutputs.baseColor; + auto wmOut = passes::setupWallmarkPass( + *m_framegraph, m_device, wmIn, transparentConfig, + width, height, + m_blackboard->get_or_add()); + transparentOutputs.baseColor = wmOut.albedo; + } + if (m_decalManager && m_decalManager->GetActiveCount() > 0) { + framegraph::DefaultOutputLayout decalIn = transparentOutputs; + if (restirWallmarks && transparentOutputs.baseColor.is_valid()) + decalIn.albedo = transparentOutputs.baseColor; + auto decalOut = passes::setupDecalPass( *m_framegraph, m_device, - transparentOutputs.depth, - Device.mInvFullTransform, m_prevViewProj, + decalIn, m_decalManager.get(), width, height, - m_blackboard->get_or_add() - ); + m_blackboard->get_or_add()); + if (restirWallmarks && transparentOutputs.baseColor.is_valid()) + transparentOutputs.baseColor = decalOut.albedo; + else + transparentOutputs.albedo = decalOut.albedo; } // ═══════════════════════════════════════════════════════ - // PARTICLE PASS (after all opaque + transparent geometry) - // ═══════════════════════════════════════════════════════ - auto particleOutputs = passes::setupParticlePass( - *m_framegraph, - m_device, - transparentOutputs, - &m_worldParticleBatches, - &m_hudParticleBatches, - m_materialCache.get(), - width, - height, - hizOutput.pyramid, - hizOutput.width, - hizOutput.height, - hizOutput.mipLevels, - m_hasPrevFrameData ? &m_prevViewProj : nullptr, - prevDepthHandle, - &m_blackboard->get_or_add() - ); - - // ═══════════════════════════════════════════════════════ - // RIBBON PASS (test quad, after particles) + // MOTION VECTOR PASS (Depth-based reprojection) // ═══════════════════════════════════════════════════════ - auto ribbonOutputs = passes::setupRibbonPass( - *m_framegraph, - m_device, - particleOutputs.layout, - width, - height, - &m_blackboard->get_or_add() + passes::MotionVectorOutput motionOutput = passes::setupMotionVectorPass( + *m_framegraph, m_device, + transparentOutputs.depth, + passes::g_taa_unjittered_full_transform, + m_prevViewProj, + Device.mInvFullTransform, + width, height, + m_blackboard->get_or_add() ); - // ═══════════════════════════════════════════════════════ - // TRAIL PASS (after ribbon, stored-direction width) - // ═══════════════════════════════════════════════════════ - auto trailOutputs = passes::setupTrailPass( - *m_framegraph, - m_device, - ribbonOutputs.layout, - width, - height, - &m_blackboard->get_or_add() - ); + auto sceneColor = transparentOutputs.albedo; + framegraph::VirtualResourceHandle pendingDistortRT = transparentOutputs.distortion; // ═══════════════════════════════════════════════════════ - // SMOKE TRAIL PASS (GPU-simulated weapon muzzle smoke) + // RT ACCEL + DYNAMIC BLAS (before ReSTIR / Path Tracer) // ═══════════════════════════════════════════════════════ - auto smokeOutputs = trailOutputs.layout; - if (m_smokeTrailManager && m_smokeTrailManager->IsReady()) { - smokeOutputs = passes::setupSmokeTrailPass( - *m_framegraph, - m_device, - trailOutputs.layout, - m_smokeTrailManager.get(), - width, - height, - m_blackboard->get_or_add(), - m_detailManager ? m_detailManager->perlin4dTexture.Get() : nullptr - ); + static int s_lastGi = -1; + if (s_lastGi != ps_r_rt_gi) { + s_lastGi = ps_r_rt_gi; + const bool supported = m_rtAccelMgr && m_rtAccelMgr->IsSupported(); + const bool ready = m_rtAccelMgr && m_rtAccelMgr->IsReady(); + Msg("* [ReSTIR] r_rt_gi=%d supported=%d ready=%d", ps_r_rt_gi, supported ? 1 : 0, ready ? 1 : 0); + } } - auto sceneColor = smokeOutputs.albedo; + bool needsRT = (ps_r_path_tracer || ps_r_rt_gi) && m_rtAccelMgr && m_rtAccelMgr->IsSupported(); + const bool levelLoading = g_pGamePersistent && g_pGamePersistent->IsLoadingScreenShown(); - if (particleOutputs.distortionRT.is_valid()) { - sceneColor = passes::setupDistortionApplyPass( - *m_framegraph, m_device, sceneColor, particleOutputs.distortionRT, - particleOutputs.layout.depth, width, height, - m_blackboard->get_or_add()); + if (needsRT && m_gpuCullingManager && !levelLoading) { + m_gpuCullingManager->Initialize(m_device); + m_gpuCullingManager->SetRTAccelStructManager(m_rtAccelMgr.get()); + + if (!m_rtAccelMgr->IsReady()) { + static bool s_rtWaitLogged = false; + if (!s_rtWaitLogged) { + s_rtWaitLogged = true; + Msg("* [RT] Waiting for mega geometry upload to build TLAS"); + } + } + + struct RTBuildData { + RTAccelStructManager* accelMgr; + GPUCullingManager* gpuCulling; + FGDetailManager* detailMgr; + const GeometryCollector* geometry; + const xr_vector* hudBatches; + const xr_vector* worldParticles; + bool buildDynamic; + }; + + const bool buildDynamic = m_rtAccelMgr->IsReady() && + ((ps_r_path_tracer && m_ptSampleIndex == 0) || (ps_r_rt_gi && !ps_r_path_tracer)); + + m_framegraph->addCallbackPass( + "RT Accel Build", + [&](framegraph::FrameGraph& builder, framegraph::PassHandle passHandle, RTBuildData& data) { + framegraph::RenderPassBuilder pb(builder, passHandle); + pb.sideEffects(); + if (detailPassState.cullArgs.is_valid()) + pb.read(detailPassState.cullArgs, framegraph::ResourceState::ShaderResource); + data.accelMgr = m_rtAccelMgr.get(); + data.gpuCulling = m_gpuCullingManager.get(); + data.detailMgr = m_detailManager.get(); + data.geometry = m_geometryCollector.get(); + data.hudBatches = &m_hudBatches; + data.worldParticles = &m_worldParticleBatches; + data.buildDynamic = buildDynamic; + }, + [](const RTBuildData& data, const framegraph::FrameGraph&, fg::RenderContext* ctx) { + nvrhi::ICommandList* cmdList = ctx->GetCommandList(); + data.accelMgr->BuildIfNeeded(cmdList, data.gpuCulling); + + if (data.accelMgr->IsReady()) { + if (!data.accelMgr->GetMaterialBuffer()) + data.accelMgr->SetMaterialBuffer(bindless::MaterialBuffer::Instance().GetBuffer()); + if (!data.accelMgr->GetTerrainMaterialBuffer()) + data.accelMgr->SetTerrainMaterialBuffer(bindless::TerrainMaterialBuffer::Instance().GetBuffer()); + } + + if (!data.buildDynamic || !data.accelMgr->IsReady()) + return; + + xr_vector worldSkinned; + for (const auto& b : data.geometry->GetBatches()) { + if (b.isSkinned && b.visual && b.indexCount > 0) + worldSkinned.push_back(b); + } + + xr_vector hudSkinned; + data.accelMgr->BuildSkinnedBLAS(cmdList, data.gpuCulling, worldSkinned, hudSkinned); + data.accelMgr->BuildGrassBLAS(cmdList, data.detailMgr); + if (data.worldParticles) + data.accelMgr->BuildParticleBLAS(cmdList, *data.worldParticles); + else + data.accelMgr->InvalidateParticles(); + data.accelMgr->RebuildDynamic(cmdList, data.gpuCulling); + } + ); } // ═══════════════════════════════════════════════════════ // ReSTIR GI (RT Shadows + Indirect Lighting) // ═══════════════════════════════════════════════════════ - if (ps_r_rt_gi && m_rtAccelMgr && m_rtAccelMgr->IsSupported() && m_rtAccelMgr->IsReady()) { + nvrhi::ITexture* restirNoisyDiffuse = nullptr; + nvrhi::ITexture* restirNoisySpecular = nullptr; + nvrhi::ITexture* restirHitDistance = nullptr; + + if (ps_r_rt_gi && m_rtAccelMgr && m_rtAccelMgr->IsSupported()) { + if (!m_rtAccelMgr->IsReady()) { + g_restirReplaceForward = false; + static u32 s_lastWaitFrame = 0; + if (Device.dwFrame - s_lastWaitFrame > 60) { + s_lastWaitFrame = Device.dwFrame; + Msg("! [ReSTIR] r_rt_gi=1 but TLAS not ready yet"); + } + } else { + auto& restirState = m_blackboard->get_or_add(); + { + auto& tpState = m_blackboard->get_or_add(); + restirState.waterUnderWorldPos = tpState.waterSceneWorldPos; + restirState.waterUnderColor = tpState.waterSsrColor; + } auto rtgiOutput = passes::setupReSTIRGIPass( *m_framegraph, m_device, m_rtAccelMgr.get(), transparentOutputs.depth, transparentOutputs.normal, transparentOutputs.baseColor, + transparentOutputs.worldPos, prevNormalsHandle, prevDepthHandle, motionOutput.motionVectors, sceneColor, - Device.mInvFullTransform, m_prevViewProj, + Device.mInvFullTransform, m_prevViewProj, m_prevInvFullTransform, Device.vCameraPosition, ps_r_rt_gi_intensity, width, height, - m_blackboard->get_or_add(), m_hasPrevFrameData + restirState, m_hasPrevFrameData, grassShadowOut ); sceneColor = rtgiOutput.sceneColor; - } - - // ═══════════════════════════════════════════════════════ - // DYNAMIC BLAS BUILD (shared by Path Tracer + ReSTIR GI) - // ═══════════════════════════════════════════════════════ - bool needsRT = (ps_r_path_tracer || ps_r_rt_gi) && m_rtAccelMgr && m_rtAccelMgr->IsSupported(); + restirNoisyDiffuse = rtgiOutput.noisyDiffuse; + restirNoisySpecular = rtgiOutput.noisySpecular; + restirHitDistance = rtgiOutput.hitDistance; - if (needsRT && m_rtAccelMgr->IsReady()) { - bool ptNeedsBLAS = ps_r_path_tracer && m_ptSampleIndex == 0; - bool giNeedsBLAS = ps_r_rt_gi && !ps_r_path_tracer; - - if (ptNeedsBLAS || giNeedsBLAS) { - struct DynamicBLASData { - RTAccelStructManager* accelMgr; - GPUCullingManager* gpuCulling; - FGDetailManager* detailMgr; - const GeometryCollector* geometry; - const xr_vector* hudBatches; + if (false && g_denoiseBackend && g_denoiseBackend->IsAvailable() && + ps_r_denoise != 0 && !(ps_r_dlss_rr != 0 && ps_r_upscale == 2)) + { + struct NrdPassData { + framegraph::VirtualResourceHandle depth; + framegraph::VirtualResourceHandle normal; + framegraph::VirtualResourceHandle baseColor; + framegraph::VirtualResourceHandle worldPos; + framegraph::VirtualResourceHandle motion; + framegraph::VirtualResourceHandle sceneColor; + nvrhi::ITexture* noisyDiffuse = nullptr; + nvrhi::ITexture* noisySpecular = nullptr; + nvrhi::ITexture* hitDistance = nullptr; + nvrhi::ITexture* directLighting = nullptr; + u32 width = 0; + u32 height = 0; }; - m_framegraph->addCallbackPass( - "Dynamic BLAS Build", - [&](framegraph::FrameGraph& builder, framegraph::PassHandle passHandle, DynamicBLASData& data) { + m_framegraph->addCallbackPass( + "ReSTIR NRD", + [&](framegraph::FrameGraph& builder, framegraph::PassHandle passHandle, NrdPassData& data) { framegraph::RenderPassBuilder pb(builder, passHandle); + data.depth = pb.read(transparentOutputs.depth, framegraph::ResourceState::ShaderResource); + data.normal = pb.read(transparentOutputs.normal, framegraph::ResourceState::ShaderResource); + data.baseColor = pb.read(transparentOutputs.baseColor, framegraph::ResourceState::ShaderResource); + if (transparentOutputs.worldPos.is_valid()) + data.worldPos = pb.read(transparentOutputs.worldPos, framegraph::ResourceState::ShaderResource); + if (motionOutput.motionVectors.is_valid()) + data.motion = pb.read(motionOutput.motionVectors, framegraph::ResourceState::ShaderResource); + data.sceneColor = pb.readWrite(sceneColor, framegraph::ResourceState::UnorderedAccess); pb.sideEffects(); - data.accelMgr = m_rtAccelMgr.get(); - data.gpuCulling = m_gpuCullingManager.get(); - data.detailMgr = m_detailManager.get(); - data.geometry = m_geometryCollector.get(); - data.hudBatches = &m_hudBatches; + data.noisyDiffuse = restirNoisyDiffuse; + data.noisySpecular = restirNoisySpecular; + data.hitDistance = restirHitDistance; + data.directLighting = fg::ReSTIRMemoryManager::Instance().GetDirectLighting(); + data.width = width; + data.height = height; }, - [](const DynamicBLASData& data, const framegraph::FrameGraph&, fg::RenderContext* ctx) { - nvrhi::ICommandList* cmdList = ctx->GetCommandList(); + [](const NrdPassData& data, const framegraph::FrameGraph& fgGraph, fg::RenderContext* ctx) { + if (!g_denoiseBackend || !g_denoiseBackend->IsAvailable() || !ctx) + return; + auto* depthTex = fgGraph.GetPhysicalTexture(data.depth); + auto* normalTex = fgGraph.GetPhysicalTexture(data.normal); + auto* baseTex = fgGraph.GetPhysicalTexture(data.baseColor); + auto* sceneTex = fgGraph.GetPhysicalTexture(data.sceneColor); + auto* worldPosTex = data.worldPos.is_valid() ? fgGraph.GetPhysicalTexture(data.worldPos) : nullptr; + auto* motionTex = data.motion.is_valid() ? fgGraph.GetPhysicalTexture(data.motion) : nullptr; + if (!depthTex || !normalTex || !baseTex || !sceneTex || !data.noisyDiffuse || !worldPosTex) + return; + + fg::DenoiseInputs in; + in.noisyDiffuse = data.noisyDiffuse; + in.noisySpecular = data.noisySpecular; + in.hitDistance = data.hitDistance; + in.normals = normalTex; + in.roughness = normalTex; + in.depth = depthTex; + in.baseColor = baseTex; + in.worldPos = worldPosTex; + in.motionVectors = motionTex; + in.directLighting = data.directLighting; + in.sceneColorIn = sceneTex; + in.outSceneColor = sceneTex; + in.outDiffuse = data.noisyDiffuse; + in.outSpecular = data.noisySpecular; + in.width = data.width; + in.height = data.height; + in.frameIndex = Device.dwFrame; + in.nearZ = 0.001f; + in.farZ = 500.f; + g_denoiseBackend->Evaluate(ctx->GetCommandList(), in); + } + ); + } + } + } else { + g_restirReplaceForward = false; + } - xr_vector worldSkinned; - for (const auto& b : data.geometry->GetBatches()) { - if (b.isSkinned && b.visual && b.indexCount > 0) - worldSkinned.push_back(b); - } + passes::RainShadowOutputs rainShadowOut{}; + { + const float rainDensity = g_pGamePersistent + ? g_pGamePersistent->Environment().CurrentEnv.rain_density + : 0.f; + const bool needRainSM = (rainDensity > 0.001f) || ps_r2_ls_flags.test(R3FLAG_DYN_WET_SURF); + if (needRainSM) + { + rainShadowOut = passes::setupRainShadowPass( + *m_framegraph, + m_device, + bindlessConfig, + m_blackboard->get_or_add()); + } + } - float fovScale = 1.0f / psHUD_FOV; - Fmatrix viewMatrix = Device.mView; - Fmatrix invView; - invView.invert(viewMatrix); - Fmatrix fovScaleMat; - fovScaleMat.identity(); - fovScaleMat._11 = fovScale; - fovScaleMat._22 = fovScale; - - xr_vector hudSkinned; - for (const auto& b : *data.hudBatches) { - if (b.isSkinned && b.visual && b.indexCount > 0) { - auto adjusted = b; - Fmatrix t1, t2; - t1.mul(viewMatrix, b.worldMatrix); - t2.mul(fovScaleMat, t1); - adjusted.worldMatrix.mul(invView, t2); - hudSkinned.push_back(adjusted); - } - } + { + passes::WetSurfacesExtras wetExtras{}; + if (rainShadowOut.rainSMTex) + { + wetExtras.rainSM = rainShadowOut.rainSM; + wetExtras.rainSMTex = rainShadowOut.rainSMTex; + wetExtras.rainSampleVP = rainShadowOut.sampleVP; + wetExtras.rainSMValid = rainShadowOut.valid; + } + auto wetIn = transparentOutputs; + wetIn.albedo = sceneColor; + if (ps_r_rt_gi && fg::ReSTIRMemoryManager::Instance().GetSkyOpen()) + wetExtras.skyOpenTex = fg::ReSTIRMemoryManager::Instance().GetSkyOpen(); + auto wetOut = passes::setupWetSurfacesPass( + *m_framegraph, + m_device, + wetIn, + width, + height, + m_blackboard->get_or_add(), + wetExtras); + sceneColor = wetOut.albedo; + } - data.accelMgr->BuildSkinnedBLAS(cmdList, data.gpuCulling, worldSkinned, hudSkinned); - data.accelMgr->BuildGrassBLAS(cmdList, data.detailMgr); - data.accelMgr->RebuildDynamic(cmdList, data.gpuCulling); - } + { + framegraph::DefaultOutputLayout fxIn = transparentOutputs; + fxIn.albedo = sceneColor; + auto particleOutputs = passes::setupParticlePass( + *m_framegraph, + m_device, + fxIn, + &m_worldParticleBatches, + &m_hudParticleBatches, + m_materialCache.get(), + width, + height, + hizOutput.pyramid, + hizOutput.width, + hizOutput.height, + hizOutput.mipLevels, + m_hasPrevFrameData ? &m_prevViewProj : nullptr, + prevDepthHandle, + &m_blackboard->get_or_add(), + pendingDistortRT + ); + auto ribbonOutputs = passes::setupRibbonPass( + *m_framegraph, + m_device, + particleOutputs.layout, + width, + height, + &m_blackboard->get_or_add() + ); + auto trailOutputs = passes::setupTrailPass( + *m_framegraph, + m_device, + ribbonOutputs.layout, + width, + height, + &m_blackboard->get_or_add() + ); + auto smokeOutputs = trailOutputs.layout; + if (m_smokeTrailManager && m_smokeTrailManager->IsReady()) + { + smokeOutputs = passes::setupSmokeTrailPass( + *m_framegraph, + m_device, + trailOutputs.layout, + m_smokeTrailManager.get(), + width, + height, + m_blackboard->get_or_add(), + m_detailManager ? m_detailManager->perlin4dTexture.Get() : nullptr ); } + sceneColor = smokeOutputs.albedo; + sceneColor = passes::setupGlowBillboardPass( + *m_framegraph, + m_device, + sceneColor, + transparentOutputs.depth.is_valid() ? transparentOutputs.depth : depthBuffer, + width, + height, + m_blackboard->get_or_add()); + if (particleOutputs.distortionRT.is_valid()) + pendingDistortRT = particleOutputs.distortionRT; + if (pendingDistortRT.is_valid()) { + auto& distortState = m_blackboard->get_or_add(); + { + auto& tpState = m_blackboard->get_or_add(); + distortState.waterUnderColor = tpState.waterSsrColor; + } + sceneColor = passes::setupDistortionApplyPass( + *m_framegraph, m_device, sceneColor, pendingDistortRT, + transparentOutputs.worldPos, transparentOutputs.baseColor, + transparentOutputs.depth, + width, height, + distortState); + } } + sceneColor = passes::setupVolumetricFogPass( + *m_framegraph, + m_device, + sceneColor, + transparentOutputs.depth, + transparentOutputs.worldPos, + Device.mInvFullTransform, + m_prevViewProj, + Device.vCameraPosition, + width, + height, + m_blackboard->get_or_add(), + m_rtAccelMgr.get()); + // ═══════════════════════════════════════════════════════ // PATH TRACER (Reference / Ground-Truth Mode) // ═══════════════════════════════════════════════════════ @@ -1645,6 +2059,22 @@ void FrameGraphRenderer::SetupFrameGraphPasses() { } } + if (ps_r_taa && ps_r_upscale == 0) + { + sceneColor = passes::setupTAAPass( + *m_framegraph, + m_device, + sceneColor, + transparentOutputs.depth, + motionOutput.motionVectors, + width, + height, + m_hasPrevFrameData, + m_blackboard->get_or_add(), + transparentOutputs.worldPos); + m_framegraph->GetRTRegistry().RegisterRT("rt_TAA", sceneColor); + } + // ═══════════════════════════════════════════════════════ // EXPOSURE PASS (Auto-Exposure / Eye Adaptation) // ═══════════════════════════════════════════════════════ @@ -1662,38 +2092,112 @@ void FrameGraphRenderer::SetupFrameGraphPasses() { m_exposureTexture = exposureOutput.exposureTexture; - auto sceneWithUI = passes::setupUIPass( + passes::UpscaleRRGuides rrGuides{}; + const passes::UpscaleRRGuides* rrGuidesPtr = nullptr; + if (restirNoisyDiffuse && restirNoisySpecular && transparentOutputs.normal.is_valid() && + transparentOutputs.baseColor.is_valid() && transparentOutputs.worldPos.is_valid()) + { + rrGuides.normals = transparentOutputs.normal; + rrGuides.baseColor = transparentOutputs.baseColor; + rrGuides.worldPos = transparentOutputs.worldPos; + rrGuides.depth = transparentOutputs.depth; + rrGuides.noisyDiffuse = restirNoisyDiffuse; + rrGuides.noisySpecular = restirNoisySpecular; + rrGuides.hitDistance = restirHitDistance; + rrGuidesPtr = &rrGuides; + } + + sceneColor = passes::setupUpscaleOrResolvePass( *m_framegraph, + m_device, sceneColor, + transparentOutputs.depth, + motionOutput.motionVectors, + exposureOutput.exposureTexture, + g_upscaleState, + g_upscaleBackend.get(), width, - height + height, + m_blackboard->get_or_add(), + rrGuidesPtr); + + const u32 postW = upscaleResolved ? displayWidth : width; + const u32 postH = upscaleResolved ? displayHeight : height; + + const bool hdr10 = GEnv.Backend && GEnv.Backend->IsHdr10(); + const bool useDlssFg = + g_upscaleBackend && + g_upscaleBackend->SupportsFG() && + g_upscaleBackend->GetType() == fg::UpscaleBackendType::DLSS && + ps_r_dlss_fg != 0; + + framegraph::VirtualResourceHandle tonemapTarget; + if (hdr10) + tonemapTarget = CreateHdrCompose(*m_framegraph, postW, postH); + else if (useDlssFg) + tonemapTarget = CreateDisplayColor(*m_framegraph, postW, postH); + else + tonemapTarget = backbufferHandle; + + bool needPP = false; + if (m_pTarget && m_pTarget->u_need_PP()) + needPP = true; + if (g_pGamePersistent && g_pGamePersistent->m_pGShaderConstants + && g_pGamePersistent->m_pGShaderConstants->m_blender_mode.x > 0.5f) + needPP = true; + + auto tonemapDest = tonemapTarget; + if (needPP) + tonemapDest = hdr10 ? CreateHdrCompose(*m_framegraph, postW, postH) + : CreateDisplayColor(*m_framegraph, postW, postH); + + auto ldrOutput = passes::setupTonemapPass( + *m_framegraph, + m_device, + sceneColor, + exposureOutput.exposureTexture, + tonemapDest, + postW, + postH, + m_blackboard->get_or_add(), + &m_blackboard->get_or_add(), + transparentOutputs.depth, + transparentOutputs.worldPos ); - sceneWithUI = passes::setupFontPass(*m_framegraph, sceneWithUI); + if (needPP) + { + ldrOutput = passes::setupPostProcessPass( + *m_framegraph, + m_device, + ldrOutput, + tonemapTarget, + postW, + postH, + m_pTarget, + m_blackboard->get_or_add()); + } - // 5. Cursor Pass - Renders cursor on top of UI+Text - sceneWithUI = passes::setupCursorPass( + auto sceneWithUI = passes::setupUIPass( *m_framegraph, - sceneWithUI, - width, - height + ldrOutput, + postW, + postH ); - sceneWithUI = passes::setupDebugDrawPass(*m_framegraph, sceneWithUI, width, height); + sceneWithUI = passes::setupFontPass(*m_framegraph, sceneWithUI); - // 6. Tonemap Pass - Convert HDR to LDR using ACES filmic tonemap - auto ldrOutput = passes::setupTonemapPass( + sceneWithUI = passes::setupCursorPass( *m_framegraph, - m_device, sceneWithUI, - exposureOutput.exposureTexture, - backbufferHandle, - width, - height, - m_blackboard->get_or_add(), - &m_blackboard->get_or_add() + postW, + postH ); + sceneWithUI = passes::setupDebugDrawPass(*m_framegraph, sceneWithUI, postW, postH); + + ldrOutput = sceneWithUI; + // ═══════════════════════════════════════════════════════ // DEBUG PREVIEW PASS (Render Inspector RT visualization) // ═══════════════════════════════════════════════════════ @@ -1832,11 +2336,38 @@ void FrameGraphRenderer::SetupFrameGraphPasses() { *m_framegraph, ldrOutput, imguiRenderer, - width, - height + postW, + postH ); + if (hdr10) + { + auto encodeDst = useDlssFg + ? CreateDisplayColor(*m_framegraph, postW, postH) + : backbufferHandle; + finalOutput = passes::setupHdr10EncodePass( + *m_framegraph, m_device, finalOutput, encodeDst, postW, postH, + m_blackboard->get_or_add()); + } + + if (useDlssFg) + { + const bool resetFg = !m_hasPrevFrameData || g_upscaleState.resetHistory; + passes::setupDlssFgPass( + *m_framegraph, + finalOutput, + framegraph::VirtualResourceHandle{}, + depthBuffer, + motionOutput.motionVectors, + Device.mProject, + m_prevViewProj, + Device.mFullTransform, + width, + height, + postW, + postH, + resetFg); + } - // Store final output for presentation (now points to backbuffer) m_finalOutput = finalOutput; // ═══════════════════════════════════════════════════════ @@ -1894,6 +2425,23 @@ void FrameGraphRenderer::SetupFrameGraphPasses() { m_prevFrameHeight = height; } +nvrhi::ITexture* FrameGraphRenderer::GetPersistentExposureTexture() const +{ + if (!m_blackboard) + return nullptr; + if (auto* exp = m_blackboard->try_get()) + { + if (exp->exposureTexture) + return exp->exposureTexture.Get(); + } + if (auto* tm = m_blackboard->try_get()) + { + if (tm->fallbackExposureTexture) + return tm->fallbackExposureTexture.Get(); + } + return nullptr; +} + void FrameGraphRenderer::PrintStats() const { Msg("═══════════════════════════════════════"); Msg(" FrameGraph Renderer Statistics"); @@ -2190,12 +2738,37 @@ bool FrameGraphRenderer::ProcessHudGeometry(dxRender_Visual* visual, const Fmatr return true; } -static u8 QueryParticleBlendMode(LPCSTR shaderName) +static bool ParticleNameLooksAdditive(LPCSTR shaderName, LPCSTR texName) +{ + auto has = [](LPCSTR s, const char* k) { return s && strstr(s, k); }; + if (has(shaderName, "smoke") || has(texName, "smoke") + || has(texName, "dust") || has(texName, "steam")) + return false; + return has(shaderName, "add") || has(shaderName, "glow") || has(shaderName, "flare") + || has(shaderName, "anomaly") || has(shaderName, "heat") + || has(texName, "anomaly") || has(texName, "heat") || has(texName, "zhar") + || has(texName, "fire") || has(texName, "flame") || has(texName, "glow") + || has(texName, "flash") || has(texName, "spark") || has(texName, "flare") + || has(texName, "explosion") || has(texName, "grenade") || has(texName, "blast") + || has(texName, "tracer"); +} + +static u8 QueryParticleBlendMode(LPCSTR shaderName, LPCSTR texName) { u32 id = 0; - if (!shader_info::GetParticleBlendIndex(shaderName, id)) - return passes::PARTICLE_BLEND_BLEND; - return (id < passes::PARTICLE_BLEND_COUNT) ? (u8)id : passes::PARTICLE_BLEND_BLEND; + if (shader_info::GetParticleBlendIndex(shaderName, id) + && id > 0 && id < passes::PARTICLE_BLEND_COUNT) + return (u8)id; + if (shaderName && (strstr(shaderName, "s-aadd") || strstr(shaderName, "alpha-add") + || strstr(shaderName, "alphaadd"))) + return passes::PARTICLE_BLEND_ALPHA_ADD; + if (ParticleNameLooksAdditive(shaderName, texName)) + return passes::PARTICLE_BLEND_ADD; + if (shaderName && (strstr(shaderName, "mul2x") || strstr(shaderName, "mul_2x"))) + return passes::PARTICLE_BLEND_MUL_2X; + if (shaderName && strstr(shaderName, "mul")) + return passes::PARTICLE_BLEND_MUL; + return passes::PARTICLE_BLEND_BLEND; } void FrameGraphRenderer::ProcessSingleParticleEffect( @@ -2225,7 +2798,9 @@ void FrameGraphRenderer::ProcessSingleParticleEffect( batch.renderable = renderable; batch.isHUDMode = isHUDParticle; batch.particleCount = particleCount; - batch.blendMode = QueryParticleBlendMode(pDef->m_ShaderName.c_str()); + batch.blendMode = QueryParticleBlendMode( + pDef->m_ShaderName.c_str(), + pDef->m_TextureName.size() ? pDef->m_TextureName.c_str() : ""); if (strstr(pDef->m_ShaderName.c_str(), "distort")) batch.shaderVariant = passes::ParticleShaderVariant::Distort; @@ -2297,6 +2872,7 @@ static void ForEachLeafVisual(dxRender_Visual* pVisual, F&& fn) { } case MT_LOD: { FLOD* pV = static_cast(pVisual); + fn(pVisual); for (auto& child : pV->children) { ForEachLeafVisual(child, fn); } @@ -2363,6 +2939,7 @@ void FrameGraphRenderer::CollectVisibleGeometry() { u32 submittedStatic = 0; if (!m_staticBatchesCached && !sectors.empty()) { + m_lodImpostors.clear(); Msg("* [GeomCache] Building static geometry cache from %zu sectors...", sectors.size()); xr_vector staticVisuals; @@ -2444,8 +3021,7 @@ void FrameGraphRenderer::CollectVisibleGeometry() { submittedDynamic++; } - if (!collectedLights.empty()) - fg::ClusteredLightManager::Instance().CollectLightsParallel(collectedLights); + fg::ClusteredLightManager::Instance().CollectLightsParallel(collectedLights); // ═══════════════════════════════════════════════════════ // HUD RENDERING (after dynamic objects) @@ -2499,23 +3075,7 @@ xr_set FrameGraphRenderer::ScanRequiredPhases() const { return phases; } -void FrameGraphRenderer::CreatePhasePass(framegraph::RenderPhase phase) { - PassEntry entry; - entry.phase = phase; - - switch (phase) { - case framegraph::RenderPhase::Geometry: { - return; - } - - case framegraph::RenderPhase::Lighting: - case framegraph::RenderPhase::PostProcess: - case framegraph::RenderPhase::Combine: - case framegraph::RenderPhase::Shadow: - case framegraph::RenderPhase::Custom: - default: - return; - } +void FrameGraphRenderer::CreatePhasePass(framegraph::RenderPhase /*phase*/) { } void FrameGraphRenderer::CreateAllRequiredPasses() { @@ -2618,15 +3178,71 @@ namespace class CGlow : public IRender_Glow { public: + light* m_light = nullptr; + Fvector m_pos{}; + Fvector m_dir{ 0.f, -1.f, 0.f }; + float m_radius = 1.f; + Fcolor m_color{ 1.f, 1.f, 1.f, 1.f }; + shared_str m_texture; bool bActive{ false }; + + explicit CGlow(light* L) : m_light(L) + { + fg::passes::GlowRegistry_Register(this); + if (m_light) + { + m_light->set_active(false); + xr_delete(m_light); + m_light = nullptr; + } + } + + ~CGlow() override + { + fg::passes::GlowRegistry_Unregister(this); + if (m_light) + { + m_light->set_active(false); + xr_delete(m_light); + m_light = nullptr; + } + } + + static bool TooCloseToCamera(const Fvector& pos, float radius) + { + const float minDist = _max(0.45f, radius * 2.f); + return Device.vCameraPosition.distance_to_sqr(pos) < minDist * minDist; + } + + static bool CollectBillboard(void* glow, fg::passes::GlowBillboard& out) + { + auto* g = static_cast(glow); + if (!g || !g->bActive) + return false; + if (TooCloseToCamera(g->m_pos, g->m_radius)) + return false; + out.pos = g->m_pos; + out.radius = g->m_radius; + out.color = g->m_color; + out.texture = g->m_texture; + return true; + } + void set_active(bool b) override { bActive = b; } bool get_active() override { return bActive; } - void set_position(const Fvector&) override {} - void set_direction(const Fvector&) override {} - void set_radius(float) override {} - void set_texture(LPCSTR) override {} - void set_color(const Fcolor&) override {} - void set_color(float, float, float) override {} + void set_position(const Fvector& P) override { m_pos = P; } + void set_direction(const Fvector& D) override + { + m_dir = D; + if (m_dir.magnitude() < 1e-4f) + m_dir.set(0.f, -1.f, 0.f); + else + m_dir.normalize(); + } + void set_radius(float R) override { m_radius = _max(R, 0.05f); } + void set_texture(LPCSTR name) override { m_texture = name; } + void set_color(const Fcolor& C) override { m_color = C; } + void set_color(float r, float g, float b) override { m_color.set(r, g, b, 1.f); } }; float EstimateSplatUVRadius(const fg::decals::MeshPickResult& pickResult, float worldRadius) @@ -2673,7 +3289,16 @@ IRender_ObjectSpecific* FrameGraphRenderer::ros_create(IRenderable*) { return xr void FrameGraphRenderer::ros_destroy(IRender_ObjectSpecific*& p) { xr_delete(p); } IRender_Light* FrameGraphRenderer::light_create() { return Lights.Create(); } -IRender_Glow* FrameGraphRenderer::glow_create() { return xr_new(); } +IRender_Glow* FrameGraphRenderer::glow_create() +{ + static bool s_glowCollectHooked = false; + if (!s_glowCollectHooked) + { + fg::passes::GlowRegistry_SetCollect(&CGlow::CollectBillboard); + s_glowCollectHooked = true; + } + return xr_new(nullptr); +} IRenderVisual* FrameGraphRenderer::model_Create(pcstr name, IReader* data) { return g_pModelPool->Create(name, data); } IRenderVisual* FrameGraphRenderer::model_CreateChild(pcstr name, IReader* data) { return g_pModelPool->CreateChild(name, data); } diff --git a/src/Layers/xrRender/r_FrameGraphRenderer.h b/src/Layers/xrRender/r_FrameGraphRenderer.h index 2331d7af14b..398511b9403 100644 --- a/src/Layers/xrRender/r_FrameGraphRenderer.h +++ b/src/Layers/xrRender/r_FrameGraphRenderer.h @@ -260,6 +260,7 @@ class FrameGraphRenderer: public xray::render::fg::FGRenderBase { xray::profiler::StatsOverlay* GetStatsOverlay() const { return m_statsOverlay.get(); } void ToggleStatsOverlay() { if (m_statsOverlay) m_statsOverlay->ToggleVisible(); } + nvrhi::ITexture* GetPersistentExposureTexture() const; fg::RenderDevice* GetRenderDevice() const override { return m_device; } framegraph::ShaderLoader* GetShaderLoader() const override { return m_shaderLoader; } fg::ImGuiRendererNVRHI* GetImGuiRendererNVRHI() const override { return m_imguiRendererNVRHI; } @@ -483,9 +484,10 @@ class FrameGraphRenderer: public xray::render::fg::FGRenderBase { nvrhi::TextureHandle m_normals[2]; u32 m_pingPongIndex = 0; - Fmatrix m_prevViewProj; // Previous frame's view-projection - Fvector m_prevCameraPos; // Previous frame's camera position - bool m_hasPrevFrameData = false; // Valid previous frame exists + Fmatrix m_prevViewProj; + Fmatrix m_prevInvFullTransform; + Fvector m_prevCameraPos; + bool m_hasPrevFrameData = false; u32 m_prevFrameWidth = 0; // Previous frame resolution u32 m_prevFrameHeight = 0; @@ -555,6 +557,7 @@ class FrameGraphRenderer: public xray::render::fg::FGRenderBase { // Only dynamic objects (from spatial DB) need per-frame collection xr_vector m_cachedStaticBatches; bool m_staticBatchesCached = false; + xr_vector m_lodImpostors; // RenderContext for execution xr_unique_ptr m_renderContext; diff --git a/src/Layers/xrRender/r_FrameGraphRenderer_Loader.cpp b/src/Layers/xrRender/r_FrameGraphRenderer_Loader.cpp index 2905504da01..f548f011f3e 100644 --- a/src/Layers/xrRender/r_FrameGraphRenderer_Loader.cpp +++ b/src/Layers/xrRender/r_FrameGraphRenderer_Loader.cpp @@ -195,137 +195,13 @@ void FrameGraphRenderer::CompileLevelShader(u32 shaderID, const char* shaderName auto& compiled = m_CompiledLevelShaders[shaderID]; compiled.shaderName = shaderName; compiled.textureName = textureName; - - // Use the CRender's ShaderLoader instance - if (!GEnv.Render->GetShaderLoader()) { - Msg("! [ERROR] ShaderLoader not available for shader: %s", shaderName); - return; - } - - // ═══════════════════════════════════════════════════ - // COMPILE VERTEX SHADER - // ═══════════════════════════════════════════════════ - auto vsResult = GEnv.Render->GetShaderLoader()->LoadVertexShader(shaderName, "main"); - - if (vsResult.handle) { - compiled.vsHandle = vsResult.handle; - // Move ownership of reflection data - compiled.vsReflection.reset(vsResult.reflection); - vsResult.reflection = nullptr; // Prevent double deletion - } else { - Msg("! [ERROR] Failed to compile VS for shader: %s", shaderName); - return; - } - - // ═══════════════════════════════════════════════════ - // COMPILE PIXEL SHADER - // ═══════════════════════════════════════════════════ - auto psResult = GEnv.Render->GetShaderLoader()->LoadPixelShader(shaderName, "main"); - - if (psResult.handle) { - compiled.psHandle = psResult.handle; - // Move ownership of reflection data - compiled.psReflection.reset(psResult.reflection); - psResult.reflection = nullptr; // Prevent double deletion - } else { - Msg("! [ERROR] Failed to compile PS for shader: %s", shaderName); - return; - } - - // ═══════════════════════════════════════════════════ - // GET MATERIAL INFO (from MaterialSystem) - // ═══════════════════════════════════════════════════ compiled.materialInfo = MaterialSystem::Instance().GetMaterialInfo(shaderName); - - Msg("* Compiled shader %u: %s (VS=%p, PS=%p, alphaTest=%d, transparent=%d)", - shaderID, shaderName, - compiled.vsHandle.Get(), - compiled.psHandle.Get(), - compiled.materialInfo.alphaTest, - compiled.materialInfo.transparent); } -// ═══════════════════════════════════════════════════ -// D3D12: Precompile PSOs for all level shaders -// ═══════════════════════════════════════════════════ void FrameGraphRenderer::PrecompileLevelPSOs() { ZoneScopedN("Precompile Level PSOs"); - - auto* materialCache = GetMaterialCache(); - if (!materialCache) { - Msg("! [ERROR] MaterialCache not available - skipping PSO precompilation"); - return; - } - - // ═══════════════════════════════════════════════════ - // HARDCODE FRAMEBUFFER FORMATS (matches ForwardColorPassSetup) - // ═══════════════════════════════════════════════════ - nvrhi::Format colorFormat = nvrhi::Format::RGBA16_FLOAT; // HDR - nvrhi::Format depthFormat = nvrhi::Format::D32; - - u32 totalPSOs = 0; - - for (u32 shaderID = 0; shaderID < m_CompiledLevelShaders.size(); ++shaderID) { - auto& compiled = m_CompiledLevelShaders[shaderID]; - - if (!compiled.vsHandle || !compiled.psHandle) - continue; // Skip failed compilations - - // Update progress - float progress = float(shaderID) / float(m_CompiledLevelShaders.size()); - g_pGamePersistent->LoadTitle("st_precompiling_pso", progress); - - // ═══════════════════════════════════════════════════ - // FIND COMPATIBLE VERTEX FORMATS - // ═══════════════════════════════════════════════════ - xr_vector compatibleFormats; - for (u32 dcl_id = 0; dcl_id < BufferPool.nDC.size(); ++dcl_id) { - if (IsVertexFormatCompatible(BufferPool.nDC[dcl_id], compiled.vsReflection.get())) { - compatibleFormats.push_back(dcl_id); - } - } - - if (compatibleFormats.empty()) { - Msg("! Shader %u (%s) has no compatible vertex formats!", - shaderID, compiled.shaderName.c_str()); - continue; - } - - // ═══════════════════════════════════════════════════ - // PRECOMPILE PSOs FOR EACH FORMAT + PASS TYPE - // ═══════════════════════════════════════════════════ - for (u32 dcl_id : compatibleFormats) { - // 1. Forward Color PSO (always needed) - if (CreatePrecompiledPSO( - shaderID, - dcl_id, - RenderPassType::ForwardColor, - colorFormat, - depthFormat, - materialCache - )) { - totalPSOs++; - } - - // 2. Depth Prepass PSO (for opaque + alpha-tested) - if (!compiled.materialInfo.transparent) { - if (CreatePrecompiledPSO( - shaderID, - dcl_id, - RenderPassType::DepthPrepass, - nvrhi::Format::UNKNOWN, // No color output - depthFormat, - materialCache - )) { - totalPSOs++; - } - } - } - } - - Msg("* Precompiled %u PSOs for %u shaders across %u vertex formats", - totalPSOs, m_CompiledLevelShaders.size(), BufferPool.nDC.size()); + Msg("* [FrameGraph] Skip level PSO precompile - bindless pass PSOs use unified DRAWINDEX layout"); } void FrameGraphRenderer::level_Unload() diff --git a/src/Layers/xrRender/r__sector.h b/src/Layers/xrRender/r__sector.h index a523db463fb..cd178918f95 100644 --- a/src/Layers/xrRender/r__sector.h +++ b/src/Layers/xrRender/r__sector.h @@ -39,6 +39,8 @@ class CPortal : public IRender_Portal void setup(const level_portal_data_t& data, const xr_vector& sectors); + const Poly& getPoly() const { return poly; } + CSector* getSectorFacing(const Fvector& V) { if (P.classify(V) > 0) diff --git a/src/Layers/xrRender/xrRender_console.cpp b/src/Layers/xrRender/xrRender_console.cpp index fe4fa10eb65..08075224865 100644 --- a/src/Layers/xrRender/xrRender_console.cpp +++ b/src/Layers/xrRender/xrRender_console.cpp @@ -32,6 +32,83 @@ // Detail manager debug extern ENGINE_API int dm_debug_trails; +namespace +{ +const xr_token qrt_quality_token[] = +{ + { "off", 0 }, + { "low", 1 }, + { "medium", 2 }, + { "high", 3 }, + { "ultra", 4 }, + { nullptr, 0 } +}; + +class CCC_RTQuality : public CCC_Token +{ +public: + CCC_RTQuality(LPCSTR N, u32* V, const xr_token* T) : CCC_Token(N, V, T) {} + + virtual void Execute(LPCSTR args) override + { + CCC_Token::Execute(args); + struct RTQualityPreset + { + int gi; + float intensity; + int bounces; + int spatial_samples; + float spatial_radius; + int m_max; + int local_samples; + int di_candidates; + int di_spatial_samples; + float di_spatial_radius; + int di_m_max; + int atrous_steps; + float ambient_scale; + int cache_size; + float cache_cell; + int vol_steps; + float detail_dist; + float lod_dist; + int sun_soft_samples; + int half; + }; + static const RTQualityPreset kPresets[] = + { + { 0, 1.0f, 1, 2, 24.0f, 4, 2, 6, 0, 16.0f, 10, 3, 0.00f, 131072, 1.00f, 8, 12.f, 30.f, 2, 1 }, + { 1, 1.0f, 1, 2, 24.0f, 4, 2, 6, 0, 16.0f, 10, 3, 0.00f, 131072, 1.00f, 8, 12.f, 30.f, 2, 1 }, + { 1, 1.0f, 1, 3, 40.0f, 8, 4, 8, 0, 32.0f, 20, 3, 0.00f, 262144, 0.75f, 16, 20.f, 40.f, 2, 0 }, + { 1, 1.0f, 2, 6, 48.0f, 12, 6, 12, 0, 40.0f, 24, 4, 0.00f, 393216, 0.60f, 24, 30.f, 50.f, 6, 0 }, + { 1, 1.0f, 2, 8, 56.0f, 16, 8, 16, 0, 48.0f, 30, 4, 0.00f, 524288, 0.50f, 32, 40.f, 60.f, 8, 0 }, + }; + const u32 idx = (*value <= 4u) ? *value : 2u; + const RTQualityPreset& p = kPresets[idx]; + ps_r_rt_gi = p.gi; + ps_r_rt_gi_half = p.half; + ps_r_rt_gi_intensity = p.intensity; + ps_r_rt_gi_bounces = p.bounces; + ps_r_rt_gi_spatial_samples = p.spatial_samples; + ps_r_rt_gi_spatial_radius = p.spatial_radius; + ps_r_rt_gi_m_max = p.m_max; + ps_r_rt_gi_local_samples = p.local_samples; + ps_r_rt_di_candidates = p.di_candidates; + ps_r_rt_di_spatial_samples = p.di_spatial_samples; + ps_r_rt_di_spatial_radius = p.di_spatial_radius; + ps_r_rt_di_m_max = p.di_m_max; + ps_r_rt_gi_atrous_steps = p.atrous_steps; + ps_r_rt_gi_ambient_scale = p.ambient_scale; + ps_r_rt_gi_cache_size = p.cache_size; + ps_r_rt_gi_cache_cell = p.cache_cell; + ps_r_rt_vol_steps = p.vol_steps; + ps_r_rt_detail_dist = p.detail_dist; + ps_r_rt_gi_lod_dist = p.lod_dist; + ps_r_rt_sun_soft_samples = p.sun_soft_samples; + } +}; +} + namespace xray::render::fg { u32 ps_Preset = 2; @@ -191,7 +268,7 @@ Flags32 ps_r2_ls_flags = {R2FLAG_SUN //| R3FLAG_MSAA //| R3FLAG_MSAA_OPT | R3FLAG_GBUFFER_OPT | R2FLAG_DETAIL_BUMP | R2FLAG_DOF | R2FLAG_SOFT_PARTICLES | R2FLAG_SOFT_WATER | - R2FLAG_STEEP_PARALLAX | R2FLAG_SUN_FOCUS | R2FLAG_SUN_TSM | R2FLAG_TONEMAP | R2FLAG_VOLUMETRIC_LIGHTS}; // r2-only + R2FLAG_STEEP_PARALLAX | R2FLAG_SUN_FOCUS | R2FLAG_SUN_TSM | R2FLAG_SUN_DETAILS | R2FLAG_TONEMAP | R2FLAG_VOLUMETRIC_LIGHTS}; Flags32 ps_r2_ls_flags_ext = { /*R2FLAGEXT_SSAO_OPT_DATA |*/ R2FLAGEXT_SSAO_HALF_DATA | R2FLAGEXT_ENABLE_TESSELLATION | R3FLAGEXT_SSR_HALF_DEPTH | @@ -956,8 +1033,79 @@ void xrRender_initconsole() CMD4(CCC_Integer, "r4_debug_gpu_culling", &ps_r4_debug_gpu_culling, 0, 1); CMD4(CCC_Integer, "r_path_tracer", &ps_r_path_tracer, 0, 1); CMD4(CCC_Integer, "r_path_tracer_bounces", &ps_r_path_tracer_bounces, 1, 16); - CMD4(CCC_Integer, "r_rt_gi", &ps_r_rt_gi, 0, 1); + CMD3(CCC_RTQuality, "r_rt_quality", &ps_r_rt_quality, qrt_quality_token); + CMD4(CCC_Integer, "r_rt_gi", &ps_r_rt_gi, 0, 2); + CMD4(CCC_Integer, "r_rt_gi_half", &ps_r_rt_gi_half, 0, 1); CMD4(CCC_Float, "r_rt_gi_intensity", &ps_r_rt_gi_intensity, 0.0f, 4.0f); + CMD4(CCC_Integer, "r_rt_gi_spatial_samples", &ps_r_rt_gi_spatial_samples, 0, 16); + CMD4(CCC_Float, "r_rt_gi_spatial_radius", &ps_r_rt_gi_spatial_radius, 1.0f, 128.0f); + CMD4(CCC_Integer, "r_rt_gi_m_max", &ps_r_rt_gi_m_max, 1, 100); + CMD4(CCC_Integer, "r_rt_gi_local_samples", &ps_r_rt_gi_local_samples, 0, 16); + CMD4(CCC_Integer, "r_rt_di_candidates", &ps_r_rt_di_candidates, 1, 64); + CMD4(CCC_Integer, "r_rt_di_spatial_samples", &ps_r_rt_di_spatial_samples, 0, 16); + CMD4(CCC_Float, "r_rt_di_spatial_radius", &ps_r_rt_di_spatial_radius, 1.0f, 128.0f); + CMD4(CCC_Integer, "r_rt_di_m_max", &ps_r_rt_di_m_max, 1, 100); + CMD4(CCC_Integer, "r_rt_gi_atrous_steps", &ps_r_rt_gi_atrous_steps, 1, 6); + CMD4(CCC_Float, "r_rt_gi_temporal_alpha", &ps_r_rt_gi_temporal_alpha, 0.0f, 0.98f); + CMD4(CCC_Float, "r_rt_gi_ambient_scale", &ps_r_rt_gi_ambient_scale, 0.0f, 1.0f); + CMD4(CCC_Integer, "r_rt_gi_bounces", &ps_r_rt_gi_bounces, 1, 2); + CMD4(CCC_Integer, "r_rt_gi_cache_size", &ps_r_rt_gi_cache_size, 0, 1048576); + CMD4(CCC_Float, "r_rt_gi_cache_cell", &ps_r_rt_gi_cache_cell, 0.05f, 8.0f); + CMD4(CCC_Integer, "r_rt_vol_steps", &ps_r_rt_vol_steps, 0, 64); + CMD4(CCC_Integer, "r_rt_vol_light_samples", &ps_r_rt_vol_light_samples, 0, 8); + CMD4(CCC_Float, "r_rt_detail_dist", &ps_r_rt_detail_dist, 2.f, 64.f); + CMD4(CCC_Float, "r_rt_gi_lod_dist", &ps_r_rt_gi_lod_dist, 10.f, 120.f); + CMD4(CCC_Integer, "r_rt_sun_soft_samples", &ps_r_rt_sun_soft_samples, 1, 8); + CMD4(CCC_Float, "r_rt_sun_angular", &ps_r_rt_sun_angular, 0.001f, 0.05f); + CMD4(CCC_Integer, "r_vol_fog", &ps_r_vol_fog, 0, 1); + CMD4(CCC_Float, "r_vol_fog_density", &ps_r_vol_fog_density, 0.0f, 2.0f); + CMD4(CCC_Float, "r_vol_fog_height", &ps_r_vol_fog_height, -200.0f, 200.0f); + CMD4(CCC_Float, "r_vol_fog_falloff", &ps_r_vol_fog_falloff, 0.0f, 1.0f); + CMD4(CCC_Float, "r_vol_fog_g", &ps_r_vol_fog_g, -0.99f, 0.99f); + CMD4(CCC_Float, "r_vol_fog_noise", &ps_r_vol_fog_noise, 0.0f, 2.0f); + CMD4(CCC_Integer, "r_vol_fog_gi", &ps_r_vol_fog_gi, 0, 1); + CMD4(CCC_Integer, "r_vol_fog_sun", &ps_r_vol_fog_sun, 0, 1); + CMD4(CCC_Integer, "r_rt_refl", &ps_r_rt_refl, 0, 2); + CMD4(CCC_Integer, "r_vol_fog_rt", &ps_r_vol_fog_rt, 0, 1); + CMD4(CCC_Integer, "r_vol_fog_lights", &ps_r_vol_fog_lights, 0, 8); + CMD4(CCC_Integer, "r_vol_fog_temporal", &ps_r_vol_fog_temporal, 0, 1); + CMD4(CCC_Integer, "r_vol_fog_spot", &ps_r_vol_fog_spot, 0, 2); + CMD4(CCC_Integer, "r_atmosphere", &ps_r_atmosphere, 0, 1); + CMD4(CCC_Float, "r_atmosphere_strength", &ps_r_atmosphere_strength, 0.0f, 4.0f); + CMD4(CCC_Integer, "r_rt_pt_bounces", &ps_r_rt_pt_bounces, 1, 8); + CMD4(CCC_Integer, "r_rt_pt_ccap", &ps_r_rt_pt_ccap, 1, 32); + CMD4(CCC_Integer, "r_rt_pt_decorrelate", &ps_r_rt_pt_decorrelate, 0, 1); + + CMD4(CCC_Integer, "r_taa", &ps_r_taa, 0, 1); + CMD4(CCC_Float, "r_taa_sharpness", &ps_r_taa_sharpness, 0.0f, 1.0f); + CMD4(CCC_Integer, "r_taa_jitter", &ps_r_taa_jitter, 0, 1); + CMD4(CCC_Float, "r_render_scale", &ps_r_render_scale, 0.25f, 1.0f); + CMD4(CCC_Integer, "r_upscale", &ps_r_upscale, 0, 3); + CMD4(CCC_Integer, "r_upscale_quality", &ps_r_upscale_quality, 0, 5); + CMD4(CCC_Integer, "r_dlss", &ps_r_dlss, 0, 1); + CMD4(CCC_Integer, "r_dlss_quality", &ps_r_dlss_quality, 0, 5); + CMD4(CCC_Integer, "r_dlss_fg", &ps_r_dlss_fg, 0, 1); + CMD4(CCC_Integer, "r_dlss_rr", &ps_r_dlss_rr, 0, 1); + CMD4(CCC_Float, "r_dlss_sharpness", &ps_r_dlss_sharpness, 0.0f, 1.0f); + CMD4(CCC_Integer, "r_dlss_auto_exposure", &ps_r_dlss_auto_exposure, 0, 1); + CMD4(CCC_Integer, "r_denoise", &ps_r_denoise, 0, 1); + CMD4(CCC_Integer, "r_nrd_method", &ps_r_nrd_method, 0, 1); + CMD4(CCC_Integer, "r_nrd_apply", &ps_r_nrd_apply, 0, 1); + CMD4(CCC_Integer, "r_hdr10", &ps_r_hdr10, 0, 1); + CMD4(CCC_Float, "r_hdr10_hud", &ps_r_hdr10_hud, 80.f, 1000.f); + CMD4(CCC_Float, "r_hdr10_paper_white", &ps_r_hdr10_paper_white, 80.f, 1000.f); + CMD4(CCC_Float, "r_hdr10_peak", &ps_r_hdr10_peak, 200.f, 10000.f); + CMD4(CCC_Integer, "r_hdr_debug", &ps_r_hdr_debug, 0, 1); + CMD4(CCC_Float, "r_hdr_exposure_bias", &ps_r_hdr_exposure_bias, -4.f, 4.f); + CMD4(CCC_Float, "r_hdr_contrast", &ps_r_hdr_contrast, 0.2f, 3.f); + CMD4(CCC_Float, "r_hdr_saturation", &ps_r_hdr_saturation, 0.f, 3.f); + CMD4(CCC_Float, "r_hdr_white", &ps_r_hdr_white, 0.4f, 8.f); + CMD4(CCC_Float, "r_hdr_lift", &ps_r_hdr_lift, -0.5f, 0.5f); + CMD4(CCC_Float, "r_hdr_gamma", &ps_r_hdr_gamma, 0.3f, 2.6f); + CMD4(CCC_Float, "r_hdr_gain", &ps_r_hdr_gain, 0.2f, 3.f); + CMD4(CCC_Float, "r_hdr_temp", &ps_r_hdr_temp, -1.f, 1.f); + CMD4(CCC_Float, "r_hdr_tint", &ps_r_hdr_tint, -1.f, 1.f); + CMD4(CCC_Float, "r_hdr_bloom", &ps_r_hdr_bloom, 0.f, 4.f); // Smoke Trail (weapon muzzle smoke) CMD4(CCC_Integer, "r_smoke_trail", &ps_r_smoke_trail_enabled, 0, 1); diff --git a/src/xrCore/CMakeLists.txt b/src/xrCore/CMakeLists.txt index aadc4604b93..c10ea6ced70 100644 --- a/src/xrCore/CMakeLists.txt +++ b/src/xrCore/CMakeLists.txt @@ -518,6 +518,18 @@ set_target_properties(xrCore PROPERTIES PREFIX "" ) +if (XRAY_ENABLE_TRACY) + target_sources(xrCore PRIVATE + "${CMAKE_SOURCE_DIR}/Externals/tracy/public/TracyClient.cpp" + ) + set_source_files_properties( + "${CMAKE_SOURCE_DIR}/Externals/tracy/public/TracyClient.cpp" + PROPERTIES + SKIP_PRECOMPILE_HEADERS ON + SKIP_UNITY_BUILD_INCLUSION ON + ) +endif() + target_precompile_headers(xrCore PRIVATE stdafx.h diff --git a/src/xrCore/xrDebug.cpp b/src/xrCore/xrDebug.cpp index ae27b46039e..eaa3a48500a 100644 --- a/src/xrCore/xrDebug.cpp +++ b/src/xrCore/xrDebug.cpp @@ -83,7 +83,7 @@ AssertionResult xrDebug::ShowMessage(pcstr title, pcstr message, bool simpleMode { SDL_MESSAGEBOX_ERROR, windowHandler ? windowHandler->GetApplicationWindow() : nullptr, - title, message, SDL_arraysize(buttons), buttons, nullptr + title, message, (int)(sizeof(buttons) / sizeof(buttons[0])), buttons, nullptr }; int button = -1; @@ -289,7 +289,7 @@ AssertionResult xrDebug::Fail(bool& ignoreAlways, const ErrorLocation& loc, cons // we must hide the window if (windowHandler && !DebuggerIsPresent()) windowHandler->OnFatalError(); - DEBUG_BREAK; + DEBUG_BREAK; } // switch (result) } diff --git a/src/xrEngine/Device_Initialize.cpp b/src/xrEngine/Device_Initialize.cpp index f5fa201c452..ace317050ab 100644 --- a/src/xrEngine/Device_Initialize.cpp +++ b/src/xrEngine/Device_Initialize.cpp @@ -102,6 +102,8 @@ void CRenderDevice::DumpStatistics(IGameFont& font, IPerformanceAlert* alert) { font.OutNext("*** ENGINE: %2.2fms", stats.EngineTotal.result); font.OutNext("FPS/RFPS: %3.1f/%3.1f", stats.fFPS, stats.fRFPS); + if (stats.fFPS_FG > 1.f) + font.OutNext("FPS after FG: %3.1f", stats.fFPS_FG); font.OutNext("TPS: %2.2f M", stats.fTPS); if (alert && stats.fFPS < 30) alert->Print(font, "FPS < 30: %3.1f", stats.fFPS); diff --git a/src/xrEngine/Device_imgui.cpp b/src/xrEngine/Device_imgui.cpp index d1b7a920ac0..9f8e37705c7 100644 --- a/src/xrEngine/Device_imgui.cpp +++ b/src/xrEngine/Device_imgui.cpp @@ -29,7 +29,7 @@ void CRenderDevice::InitializeImGui() ImGuiConfigFlags_NavEnableGamepad | ImGuiConfigFlags_DockingEnable; - io.ConfigNavMoveSetMousePos = true; + io.ConfigNavMoveSetMousePos = false; string_path fName; FS.update_path(fName, "$app_data_root$", io.IniFilename); diff --git a/src/xrEngine/Device_mode.cpp b/src/xrEngine/Device_mode.cpp index 669e661f139..c8ab3022d23 100644 --- a/src/xrEngine/Device_mode.cpp +++ b/src/xrEngine/Device_mode.cpp @@ -222,7 +222,9 @@ void CRenderDevice::UpdateWindowState() if (!m_sdlWnd) return; + int winW = 0, winH = 0; int pxW = 0, pxH = 0; + SDL_GetWindowSize(m_sdlWnd, &winW, &winH); SDL_GetWindowSizeInPixels(m_sdlWnd, &pxW, &pxH); m_windowVisible = (pxW > 0 && pxH > 0); if (!m_windowVisible) @@ -232,16 +234,18 @@ void CRenderDevice::UpdateWindowState() { if (psDeviceMode.WindowStyle == rsWindowed) { - psDeviceMode.Width = static_cast(pxW); - psDeviceMode.Height = static_cast(pxH); + psDeviceMode.Width = static_cast(winW); + psDeviceMode.Height = static_cast(winH); } Reset(); return; } ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = { static_cast(dwWidth), static_cast(dwHeight) }; - io.DisplayFramebufferScale = ImVec2{ 1.0f, 1.0f }; + io.DisplaySize = { static_cast(winW), static_cast(winH) }; + io.DisplayFramebufferScale = ImVec2{ + (winW > 0) ? static_cast(pxW) / static_cast(winW) : 1.0f, + (winH > 0) ? static_cast(pxH) / static_cast(winH) : 1.0f }; } SDL_Window* CRenderDevice::GetApplicationWindow() diff --git a/src/xrEngine/Environment.cpp b/src/xrEngine/Environment.cpp index c38f60c9109..9830524442a 100644 --- a/src/xrEngine/Environment.cpp +++ b/src/xrEngine/Environment.cpp @@ -399,7 +399,6 @@ void CEnvironment::lerp() // final lerp const float current_weight = TimeWeight(fGameTime, Current[0]->exec_time, Current[1]->exec_time); CurrentEnv.lerp(*this, *Current[0], *Current[1], current_weight, EM, mpower); - // FrameGraph handles sky/environment rendering } void CEnvironment::OnFrame() diff --git a/src/xrEngine/IRenderBackend.h b/src/xrEngine/IRenderBackend.h index c53dfb35adc..b599b432779 100644 --- a/src/xrEngine/IRenderBackend.h +++ b/src/xrEngine/IRenderBackend.h @@ -80,8 +80,15 @@ class ENGINE_API IRenderBackend virtual u32 GetCurrentBackBufferIndex() const { return 0; } virtual u32 GetBackBufferCount() const { return 1; } virtual void Present(bool vsync) = 0; + virtual bool PresentFrameGeneration(nvrhi::ITexture* interpolated, nvrhi::ITexture* real) + { + (void)interpolated; + (void)real; + return false; + } virtual std::pair GetBackBufferSize() const = 0; virtual void ResizeSwapChain(u32 width, u32 height) {} + virtual bool IsHdr10() const { return false; } // ═══════ Frame Sync ═══════ virtual void BeginFrame() = 0; diff --git a/src/xrEngine/Stats.cpp b/src/xrEngine/Stats.cpp index 5eefd0d54c9..754556bc771 100644 --- a/src/xrEngine/Stats.cpp +++ b/src/xrEngine/Stats.cpp @@ -153,8 +153,12 @@ void CStats::Show() if (psDeviceFlags.test(rsShowFPS)) { - const auto fps = u32(Device.GetStats().fFPS); - fpsFont->Out(static_cast(Device.dwWidth - 40), 5, "%3d", fps); + const auto fps = Device.GetStats().fFPS; + const auto fpsFg = Device.GetStats().fFPS_FG; + if (fpsFg > fps + 0.5f) + fpsFont->Out(static_cast(Device.dwWidth - 120), 5, "%3.0f/%3.0f", fps, fpsFg); + else + fpsFont->Out(static_cast(Device.dwWidth - 40), 5, "%3.0f", fps); fpsFont->OnRender(); } if (psDeviceFlags.test(rsShowFPSGraph)) diff --git a/src/xrEngine/device.h b/src/xrEngine/device.h index ceace58acb2..43efb9f519a 100644 --- a/src/xrEngine/device.h +++ b/src/xrEngine/device.h @@ -102,13 +102,14 @@ class ENGINE_API CRenderDevice : public IWindowHandler { CStatTimer RenderTotal; // pureRender CStatTimer EngineTotal; // pureFrame - float fFPS, fRFPS, fTPS; // FPS, RenderFPS, TPS + float fFPS, fRFPS, fTPS, fFPS_FG; RenderDeviceStatistics() { fFPS = 30.f; fRFPS = 30.f; fTPS = 0; + fFPS_FG = 0.f; } }; @@ -208,6 +209,7 @@ class ENGINE_API CRenderDevice : public IWindowHandler void CleanupVideoModes(); const RenderDeviceStatistics& GetStats() const { return stats; } + void SetPresentedFps(float fpsFg) { stats.fFPS_FG = fpsFg; } void DumpStatistics(class IGameFont& font, class IPerformanceAlert* alert); void* GetApplicationWindowHandle() const override; diff --git a/src/xrEngine/editor_base_input.cpp b/src/xrEngine/editor_base_input.cpp index 16e559ce674..5746340041f 100644 --- a/src/xrEngine/editor_base_input.cpp +++ b/src/xrEngine/editor_base_input.cpp @@ -141,21 +141,18 @@ void ide::UpdateMouseData() auto& bd = m_imgui_backend; const bool anyMouseButtonPressed = pInput->iAnyMouseButtonDown(); - if (bd.mouse_last_leave_frame && bd.mouse_last_leave_frame >= ImGui::GetFrameCount() && anyMouseButtonPressed) + if (bd.mouse_last_leave_frame && bd.mouse_last_leave_frame >= ImGui::GetFrameCount() && !anyMouseButtonPressed) { bd.mouse_window_id = 0; bd.mouse_last_leave_frame = 0; io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); } - // Our io.AddMouseViewportEvent() calls will only be valid when not capturing. - // Technically speaking testing for 'anyMouseButtonPressed' would be more rygorous, but testing for payload reduces noise and potential side-effects. if (bd.mouse_can_report_hovered_viewport && ImGui::GetDragDropPayload() == nullptr) io.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport; else io.BackendFlags &= ~ImGuiBackendFlags_HasMouseHoveredViewport; - // We forward mouse input when hovered or captured (via SDL_EVENT_MOUSE_MOTION) or when focused (below) #if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE && defined(IMGUI_ENABLE_VIEWPORTS) SDL_CaptureMouse(anyMouseButtonPressed ? true : false); SDL_Window* focused_window = SDL_GetKeyboardFocus(); @@ -170,6 +167,15 @@ void ide::UpdateMouseData() { pInput->iSetMousePos({ (int)io.MousePos.x, (int)io.MousePos.y }, io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable); } + else if (bd.mouse_last_leave_frame == 0) + { + float mx = 0.0f, my = 0.0f; + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + SDL_GetGlobalMouseState(&mx, &my); + else + SDL_GetMouseState(&mx, &my); + io.AddMousePosEvent(mx, my); + } } if (io.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport) @@ -285,6 +291,12 @@ void ide::IR_OnDeactivate() void ide::IR_OnMousePress(int key) { ImGuiIO& io = ImGui::GetIO(); + float mx = 0.0f, my = 0.0f; + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + SDL_GetGlobalMouseState(&mx, &my); + else + SDL_GetMouseState(&mx, &my); + io.AddMousePosEvent(mx, my); const int imkey = key - (MOUSE_INVALID + 1); io.AddMouseButtonEvent(imkey, true); } @@ -298,7 +310,6 @@ void ide::IR_OnMouseRelease(int key) void ide::IR_OnMouseHold(int /*key*/) { - // ImGui handles hold state on its own } void ide::IR_OnMouseWheel(float x, float y) @@ -309,13 +320,13 @@ void ide::IR_OnMouseWheel(float x, float y) void ide::IR_OnMouseMove(int /*x*/, int /*y*/) { - // x and y are relative to previous mouse position - // ImGui accepts absolute coordinates (that are relative to window or monitor) - Ivector2 p; - pInput->iGetAsyncMousePos(p, ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable); - ImGuiIO& io = ImGui::GetIO(); - io.AddMousePosEvent(static_cast(p.x), static_cast(p.y)); + float mx = 0.0f, my = 0.0f; + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + SDL_GetGlobalMouseState(&mx, &my); + else + SDL_GetMouseState(&mx, &my); + io.AddMousePosEvent(mx, my); } void ide::IR_OnKeyboardPress(int key) diff --git a/src/xrEngine/xr_input.cpp b/src/xrEngine/xr_input.cpp index fa72e17f4e0..14963bad4e4 100644 --- a/src/xrEngine/xr_input.cpp +++ b/src/xrEngine/xr_input.cpp @@ -187,6 +187,7 @@ void CInput::MouseUpdate() static_assert(std::size(IdxToKey) == COUNT_MOUSE_BUTTONS); bool mouseMoved = false; + bool mouseMotion = false; int offs[2]{}; float scroll[2]{}; const auto mousePrev = mouseState; @@ -206,6 +207,7 @@ void CInput::MouseUpdate() { case SDL_EVENT_MOUSE_MOTION: mouseMoved = true; + mouseMotion = true; offs[0] += static_cast(event.motion.xrel); offs[1] += static_cast(event.motion.yrel); mouseAxisState[0] = static_cast(event.motion.x); @@ -244,7 +246,7 @@ void CInput::MouseUpdate() if (mouseMoved) { - if (offs[0] || offs[1]) + if (mouseMotion || offs[0] || offs[1]) cbStack.back()->IR_OnMouseMove(offs[0], offs[1]); if (!fis_zero(scroll[0]) || !fis_zero(scroll[1])) @@ -541,14 +543,8 @@ bool KbdKeyToButtonName(const int dik, xr_string& result) return false; } -bool OtherDevicesKeyToButtonName(const int btn, xr_string& /*result*/) +bool OtherDevicesKeyToButtonName(const int /*btn*/, xr_string& /*result*/) { - if (btn > CInput::COUNT_KB_BUTTONS) - { - // XXX: Not implemented - return false; - } - return false; } diff --git a/src/xrEngine/xr_ioc_cmd.cpp b/src/xrEngine/xr_ioc_cmd.cpp index f8ef549a4bf..2616037bb36 100644 --- a/src/xrEngine/xr_ioc_cmd.cpp +++ b/src/xrEngine/xr_ioc_cmd.cpp @@ -25,7 +25,77 @@ ENGINE_API int ps_fg_pbr_diffuse_mode = 0; ENGINE_API int ps_r_path_tracer = 0; ENGINE_API int ps_r_path_tracer_bounces = 8; ENGINE_API int ps_r_rt_gi = 0; +ENGINE_API int ps_r_rt_gi_half = 0; ENGINE_API float ps_r_rt_gi_intensity = 1.0f; +ENGINE_API int ps_r_rt_gi_spatial_samples = 3; +ENGINE_API float ps_r_rt_gi_spatial_radius = 40.0f; +ENGINE_API int ps_r_rt_gi_m_max = 8; +ENGINE_API int ps_r_rt_gi_local_samples = 16; +ENGINE_API int ps_r_rt_di_candidates = 12; +ENGINE_API int ps_r_rt_di_spatial_samples = 0; +ENGINE_API float ps_r_rt_di_spatial_radius = 32.0f; +ENGINE_API int ps_r_rt_di_m_max = 20; +ENGINE_API int ps_r_rt_gi_atrous_steps = 3; +ENGINE_API float ps_r_rt_gi_temporal_alpha = 0.96f; +ENGINE_API float ps_r_rt_gi_ambient_scale = 0.0f; +ENGINE_API int ps_r_rt_gi_bounces = 2; +ENGINE_API int ps_r_rt_gi_cache_size = 262144; +ENGINE_API float ps_r_rt_gi_cache_cell = 0.75f; +ENGINE_API int ps_r_rt_vol_steps = 16; +ENGINE_API int ps_r_rt_vol_light_samples = 2; +ENGINE_API float ps_r_rt_detail_dist = 20.f; +ENGINE_API float ps_r_rt_gi_lod_dist = 40.f; +ENGINE_API int ps_r_rt_sun_soft_samples = 2; +ENGINE_API float ps_r_rt_sun_angular = 0.0047f; +ENGINE_API u32 ps_r_rt_quality = 2; +ENGINE_API int ps_r_vol_fog = 0; +ENGINE_API float ps_r_vol_fog_density = 0.01f; +ENGINE_API float ps_r_vol_fog_height = 0.0f; +ENGINE_API float ps_r_vol_fog_falloff = 0.12f; +ENGINE_API float ps_r_vol_fog_g = 0.55f; +ENGINE_API float ps_r_vol_fog_noise = 0.65f; +ENGINE_API int ps_r_vol_fog_gi = 1; +ENGINE_API int ps_r_vol_fog_sun = 1; +ENGINE_API int ps_r_rt_refl = 1; +ENGINE_API int ps_r_vol_fog_rt = 1; +ENGINE_API int ps_r_vol_fog_lights = 4; +ENGINE_API int ps_r_vol_fog_temporal = 1; +ENGINE_API int ps_r_vol_fog_spot = 2; +ENGINE_API int ps_r_atmosphere = 1; +ENGINE_API float ps_r_atmosphere_strength = 0.22f; +ENGINE_API int ps_r_rt_pt_bounces = 4; +ENGINE_API int ps_r_rt_pt_ccap = 20; +ENGINE_API int ps_r_rt_pt_decorrelate = 1; +ENGINE_API int ps_r_taa = 1; +ENGINE_API float ps_r_taa_sharpness = 0.65f; +ENGINE_API int ps_r_taa_jitter = 1; +ENGINE_API float ps_r_render_scale = 1.0f; +ENGINE_API int ps_r_upscale = 0; +ENGINE_API int ps_r_upscale_quality = 3; +ENGINE_API int ps_r_dlss = 0; +ENGINE_API int ps_r_dlss_quality = 3; +ENGINE_API int ps_r_dlss_fg = 0; +ENGINE_API int ps_r_dlss_rr = 0; +ENGINE_API float ps_r_dlss_sharpness = 0.0f; +ENGINE_API int ps_r_dlss_auto_exposure = 1; +ENGINE_API int ps_r_denoise = 1; +ENGINE_API int ps_r_nrd_method = 0; +ENGINE_API int ps_r_nrd_apply = 1; +ENGINE_API int ps_r_hdr10 = 0; +ENGINE_API float ps_r_hdr10_hud = 200.f; +ENGINE_API float ps_r_hdr10_paper_white = 320.f; +ENGINE_API float ps_r_hdr10_peak = 1000.f; +ENGINE_API int ps_r_hdr_debug = 0; +ENGINE_API float ps_r_hdr_exposure_bias = 0.f; +ENGINE_API float ps_r_hdr_contrast = 1.f; +ENGINE_API float ps_r_hdr_saturation = 1.f; +ENGINE_API float ps_r_hdr_white = 1.7f; +ENGINE_API float ps_r_hdr_lift = 0.f; +ENGINE_API float ps_r_hdr_gamma = 1.f; +ENGINE_API float ps_r_hdr_gain = 1.f; +ENGINE_API float ps_r_hdr_temp = 0.f; +ENGINE_API float ps_r_hdr_tint = 0.f; +ENGINE_API float ps_r_hdr_bloom = 1.f; ENGINE_API Fvector4 ps_dev_param_1 = {0, 0, 0, 0}; ENGINE_API Fvector4 ps_dev_param_2 = {0, 0, 0, 0}; ENGINE_API Fvector4 ps_dev_param_3 = {0, 0, 0, 0}; diff --git a/src/xrEngine/xr_ioc_cmd.h b/src/xrEngine/xr_ioc_cmd.h index db35c9c6b68..d06707bcea5 100644 --- a/src/xrEngine/xr_ioc_cmd.h +++ b/src/xrEngine/xr_ioc_cmd.h @@ -34,7 +34,77 @@ extern ENGINE_API int ps_fg_pbr_diffuse_mode; extern ENGINE_API int ps_r_path_tracer; extern ENGINE_API int ps_r_path_tracer_bounces; extern ENGINE_API int ps_r_rt_gi; +extern ENGINE_API int ps_r_rt_gi_half; extern ENGINE_API float ps_r_rt_gi_intensity; +extern ENGINE_API int ps_r_rt_gi_spatial_samples; +extern ENGINE_API float ps_r_rt_gi_spatial_radius; +extern ENGINE_API int ps_r_rt_gi_m_max; +extern ENGINE_API int ps_r_rt_gi_local_samples; +extern ENGINE_API int ps_r_rt_di_candidates; +extern ENGINE_API int ps_r_rt_di_spatial_samples; +extern ENGINE_API float ps_r_rt_di_spatial_radius; +extern ENGINE_API int ps_r_rt_di_m_max; +extern ENGINE_API int ps_r_rt_gi_atrous_steps; +extern ENGINE_API float ps_r_rt_gi_temporal_alpha; +extern ENGINE_API float ps_r_rt_gi_ambient_scale; +extern ENGINE_API int ps_r_rt_gi_bounces; +extern ENGINE_API int ps_r_rt_gi_cache_size; +extern ENGINE_API float ps_r_rt_gi_cache_cell; +extern ENGINE_API int ps_r_rt_vol_steps; +extern ENGINE_API int ps_r_rt_vol_light_samples; +extern ENGINE_API float ps_r_rt_detail_dist; +extern ENGINE_API float ps_r_rt_gi_lod_dist; +extern ENGINE_API int ps_r_rt_sun_soft_samples; +extern ENGINE_API float ps_r_rt_sun_angular; +extern ENGINE_API u32 ps_r_rt_quality; +extern ENGINE_API int ps_r_vol_fog; +extern ENGINE_API float ps_r_vol_fog_density; +extern ENGINE_API float ps_r_vol_fog_height; +extern ENGINE_API float ps_r_vol_fog_falloff; +extern ENGINE_API float ps_r_vol_fog_g; +extern ENGINE_API float ps_r_vol_fog_noise; +extern ENGINE_API int ps_r_vol_fog_gi; +extern ENGINE_API int ps_r_vol_fog_sun; +extern ENGINE_API int ps_r_rt_refl; +extern ENGINE_API int ps_r_vol_fog_rt; +extern ENGINE_API int ps_r_vol_fog_lights; +extern ENGINE_API int ps_r_vol_fog_temporal; +extern ENGINE_API int ps_r_vol_fog_spot; +extern ENGINE_API int ps_r_atmosphere; +extern ENGINE_API float ps_r_atmosphere_strength; +extern ENGINE_API int ps_r_rt_pt_bounces; +extern ENGINE_API int ps_r_rt_pt_ccap; +extern ENGINE_API int ps_r_rt_pt_decorrelate; +extern ENGINE_API int ps_r_taa; +extern ENGINE_API float ps_r_taa_sharpness; +extern ENGINE_API int ps_r_taa_jitter; +extern ENGINE_API float ps_r_render_scale; +extern ENGINE_API int ps_r_upscale; +extern ENGINE_API int ps_r_upscale_quality; +extern ENGINE_API int ps_r_dlss; +extern ENGINE_API int ps_r_dlss_quality; +extern ENGINE_API int ps_r_dlss_fg; +extern ENGINE_API int ps_r_dlss_rr; +extern ENGINE_API float ps_r_dlss_sharpness; +extern ENGINE_API int ps_r_dlss_auto_exposure; +extern ENGINE_API int ps_r_denoise; +extern ENGINE_API int ps_r_nrd_method; +extern ENGINE_API int ps_r_nrd_apply; +extern ENGINE_API int ps_r_hdr10; +extern ENGINE_API float ps_r_hdr10_hud; +extern ENGINE_API float ps_r_hdr10_paper_white; +extern ENGINE_API float ps_r_hdr10_peak; +extern ENGINE_API int ps_r_hdr_debug; +extern ENGINE_API float ps_r_hdr_exposure_bias; +extern ENGINE_API float ps_r_hdr_contrast; +extern ENGINE_API float ps_r_hdr_saturation; +extern ENGINE_API float ps_r_hdr_white; +extern ENGINE_API float ps_r_hdr_lift; +extern ENGINE_API float ps_r_hdr_gamma; +extern ENGINE_API float ps_r_hdr_gain; +extern ENGINE_API float ps_r_hdr_temp; +extern ENGINE_API float ps_r_hdr_tint; +extern ENGINE_API float ps_r_hdr_bloom; extern ENGINE_API Fvector4 ps_dev_param_1; extern ENGINE_API Fvector4 ps_dev_param_2; extern ENGINE_API Fvector4 ps_dev_param_3; diff --git a/src/xrGame/CharacterPhysicsSupport.cpp b/src/xrGame/CharacterPhysicsSupport.cpp index 6bcabcd5701..879c5aa939c 100644 --- a/src/xrGame/CharacterPhysicsSupport.cpp +++ b/src/xrGame/CharacterPhysicsSupport.cpp @@ -1165,18 +1165,14 @@ void CCharacterPhysicsSupport::EndActivateFreeShell( // actualize m_pPhysicsShell->GetGlobalTransformDynamic(&mXFORM); m_pPhysicsShell->mXFORM.set(mXFORM); + if (!_valid(mXFORM)) + { + mXFORM.identity(); + mXFORM.c.set(m_EntityAlife.Position()); + m_pPhysicsShell->mXFORM.set(mXFORM); + m_pPhysicsShell->SetGlTransformDynamic(mXFORM); + } - // if( false && anim_mov_ctrl && anim_mov_blend && anim_mov_blend->blend != CBlend::eFREE_SLOT && - // anim_mov_blend->timeCurrent + Device.fTimeDelta*anim_mov_blend->speed < - // anim_mov_blend->timeTotal-SAMPLE_SPF-EPS)//. - //{ - // const Fmatrix sv_xform = mXFORM; - // mXFORM.set( start_xform ); - // //anim_mov_blend->blendPower = 1; - // anim_mov_blend->timeCurrent += Device.fTimeDelta * anim_mov_blend->speed; - // m_pPhysicsShell->AnimToVelocityState( Device.fTimeDelta, 2 * default_l_limit, 10.f * default_w_limit ); - // mXFORM.set( sv_xform ); - //} IKinematics* K = smart_cast(m_EntityAlife.Visual()); // u16 root =K->LL_GetBoneRoot(); // if( root!=0 ) diff --git a/src/xrGame/ShootingObject.cpp b/src/xrGame/ShootingObject.cpp index 91a8927a1f0..90e6ed0da06 100644 --- a/src/xrGame/ShootingObject.cpp +++ b/src/xrGame/ShootingObject.cpp @@ -391,6 +391,7 @@ void CShootingObject::UpdateLight() { if (light_render && light_time > 0) { + Light_Render(get_CurrentFirePoint()); light_time -= Device.fTimeDelta; if (light_time <= 0) StopLight(); diff --git a/src/xr_3da/CMakeLists.txt b/src/xr_3da/CMakeLists.txt index 8ca0e2abbd9..57553b8db52 100644 --- a/src/xr_3da/CMakeLists.txt +++ b/src/xr_3da/CMakeLists.txt @@ -44,3 +44,18 @@ set_target_properties(xr_3da PROPERTIES install(TARGETS xr_3da RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" ) + +if(XRAY_USE_DLSS AND XRAY_DLSS_RUNTIME_DIR AND EXISTS "${XRAY_DLSS_RUNTIME_DIR}") + add_custom_command(TARGET xr_3da POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${XRAY_DLSS_RUNTIME_DIR}" + "$" + COMMAND ${CMAKE_COMMAND} -E chdir "$" + ${CMAKE_COMMAND} -E create_symlink libnvidia-ngx-dlss.so.310.5.3 libnvidia-ngx-dlss.so + COMMAND ${CMAKE_COMMAND} -E chdir "$" + ${CMAKE_COMMAND} -E create_symlink libnvidia-ngx-dlssd.so.310.5.3 libnvidia-ngx-dlssd.so + COMMAND ${CMAKE_COMMAND} -E chdir "$" + ${CMAKE_COMMAND} -E create_symlink libnvidia-ngx-dlssg.so.310.5.3 libnvidia-ngx-dlssg.so + COMMENT "Copy DLSS runtime libraries next to xr_3da") +endif() +