diff --git a/base/maps/game/alphalabs1/textures/objective1.tga b/base/maps/game/alphalabs1/textures/objective1.tga index af2a8a88..950dc41c 100644 Binary files a/base/maps/game/alphalabs1/textures/objective1.tga and b/base/maps/game/alphalabs1/textures/objective1.tga differ diff --git a/base/maps/game/mars_city1/textures/objective1.tga b/base/maps/game/mars_city1/textures/objective1.tga index efd48d37..b607d917 100644 Binary files a/base/maps/game/mars_city1/textures/objective1.tga and b/base/maps/game/mars_city1/textures/objective1.tga differ diff --git a/base/maps/game/mars_city1/textures/objective2.tga b/base/maps/game/mars_city1/textures/objective2.tga index 5b69f1a5..1aafc86f 100644 Binary files a/base/maps/game/mars_city1/textures/objective2.tga and b/base/maps/game/mars_city1/textures/objective2.tga differ diff --git a/base/maps/game/mars_city2/textures/objective3.tga b/base/maps/game/mars_city2/textures/objective3.tga index b80b8393..c8b75488 100644 Binary files a/base/maps/game/mars_city2/textures/objective3.tga and b/base/maps/game/mars_city2/textures/objective3.tga differ diff --git a/neo/doom3/game/gamesys/TypeInfoGen.h b/neo/doom3/game/gamesys/TypeInfoGen.h index 32814474..799e19ad 100644 --- a/neo/doom3/game/gamesys/TypeInfoGen.h +++ b/neo/doom3/game/gamesys/TypeInfoGen.h @@ -4099,7 +4099,8 @@ static classVariableInfo_t glRaytracingLight_t_typeInfo[] = { { "float", "pointRadiusPad", (intptr_t)(&((glRaytracingLight_t *)0)->pointRadiusPad), sizeof( ((glRaytracingLight_t *)0)->pointRadiusPad ) }, { "uint32_t", "iesTextureId", (intptr_t)(&((glRaytracingLight_t *)0)->iesTextureId), sizeof( ((glRaytracingLight_t *)0)->iesTextureId ) }, { "float", "iesStrength", (intptr_t)(&((glRaytracingLight_t *)0)->iesStrength), sizeof( ((glRaytracingLight_t *)0)->iesStrength ) }, - { "float[2]", "iesPad", (intptr_t)(&((glRaytracingLight_t *)0)->iesPad), sizeof( ((glRaytracingLight_t *)0)->iesPad ) }, + { "int32_t", "areaNum", (intptr_t)(&((glRaytracingLight_t *)0)->areaNum), sizeof( ((glRaytracingLight_t *)0)->areaNum ) }, + { "float", "iesPad", (intptr_t)(&((glRaytracingLight_t *)0)->iesPad), sizeof( ((glRaytracingLight_t *)0)->iesPad ) }, { NULL, 0 } }; @@ -4112,6 +4113,8 @@ static classVariableInfo_t glRaytracingLightingPassDesc_t_typeInfo[] = { { "DXGI_FORMAT", "normalFormat", (intptr_t)(&((glRaytracingLightingPassDesc_t *)0)->normalFormat), sizeof( ((glRaytracingLightingPassDesc_t *)0)->normalFormat ) }, { "ID3D12Resource *", "positionTexture", (intptr_t)(&((glRaytracingLightingPassDesc_t *)0)->positionTexture), sizeof( ((glRaytracingLightingPassDesc_t *)0)->positionTexture ) }, { "DXGI_FORMAT", "positionFormat", (intptr_t)(&((glRaytracingLightingPassDesc_t *)0)->positionFormat), sizeof( ((glRaytracingLightingPassDesc_t *)0)->positionFormat ) }, + { "ID3D12Resource *", "velocityTexture", (intptr_t)(&((glRaytracingLightingPassDesc_t *)0)->velocityTexture), sizeof( ((glRaytracingLightingPassDesc_t *)0)->velocityTexture ) }, + { "DXGI_FORMAT", "velocityFormat", (intptr_t)(&((glRaytracingLightingPassDesc_t *)0)->velocityFormat), sizeof( ((glRaytracingLightingPassDesc_t *)0)->velocityFormat ) }, { "ID3D12Resource *", "outputTexture", (intptr_t)(&((glRaytracingLightingPassDesc_t *)0)->outputTexture), sizeof( ((glRaytracingLightingPassDesc_t *)0)->outputTexture ) }, { "DXGI_FORMAT", "outputFormat", (intptr_t)(&((glRaytracingLightingPassDesc_t *)0)->outputFormat), sizeof( ((glRaytracingLightingPassDesc_t *)0)->outputFormat ) }, { "uint32_t", "width", (intptr_t)(&((glRaytracingLightingPassDesc_t *)0)->width), sizeof( ((glRaytracingLightingPassDesc_t *)0)->width ) }, diff --git a/neo/engine/opengl/gl_d3d12raylight.cpp b/neo/engine/opengl/gl_d3d12raylight.cpp index efe68ed1..ba859425 100644 --- a/neo/engine/opengl/gl_d3d12raylight.cpp +++ b/neo/engine/opengl/gl_d3d12raylight.cpp @@ -42,6 +42,7 @@ If you have questions concerning this license or the applicable additional terms #include #include #include +#include #include #include @@ -86,6 +87,57 @@ static void glRaytracingFatal(const char* fmt, ...) #define GLR_CHECKV(x) \ do { HRESULT _hr = (x); if (FAILED(_hr)) { glRaytracingFatal("HRESULT 0x%08X failed at %s:%d", (unsigned)_hr, __FILE__, __LINE__); return; } } while (0) +static void glRaytracingBeginPixEvent(ID3D12GraphicsCommandList* cl, const wchar_t* name) +{ + if (!cl || !name) + return; + + cl->BeginEvent(0, name, (UINT)((wcslen(name) + 1) * sizeof(wchar_t))); +} + +static void glRaytracingEndPixEvent(ID3D12GraphicsCommandList* cl) +{ + if (cl) + cl->EndEvent(); +} + +static void glRaytracingSetPixMarker(ID3D12GraphicsCommandList* cl, const wchar_t* name) +{ + if (!cl || !name) + return; + + cl->SetMarker(0, name, (UINT)((wcslen(name) + 1) * sizeof(wchar_t))); +} + +static void glRaytracingSetObjectName(ID3D12Object* object, const wchar_t* name) +{ + if (object && name) + object->SetName(name); +} + +class glRaytracingScopedPixEvent +{ +public: + glRaytracingScopedPixEvent(ID3D12GraphicsCommandList* commandList, const wchar_t* name) + : cl(commandList) + { + glRaytracingBeginPixEvent(cl, name); + } + + ~glRaytracingScopedPixEvent() + { + glRaytracingEndPixEvent(cl); + } + +private: + ID3D12GraphicsCommandList* cl; +}; + +#define GLR_CONCAT_INNER(a, b) a##b +#define GLR_CONCAT(a, b) GLR_CONCAT_INNER(a, b) +#define GLR_PIX_EVENT(cl, name) glRaytracingScopedPixEvent GLR_CONCAT(glrPixEvent_, __LINE__)(cl, name) +#define GLR_PIX_MARKER(cl, name) glRaytracingSetPixMarker(cl, name) + // ============================================================ // Helpers // ============================================================ @@ -797,7 +849,14 @@ struct glRaytracingMeshRecord_t UINT64 blasBuildFenceValue; int currentBlasIndex; uint32_t materialFlags; + uint32_t procFlags; + int32_t areaNum; + uint32_t materialVertexOffset; + uint32_t materialIndexOffset; + uint32_t materialVertexCount; + uint32_t materialIndexCount; float averageColor[4]; + float emissiveColor[4]; glRaytracingMeshRecord_t() { @@ -811,7 +870,14 @@ struct glRaytracingMeshRecord_t blasBuildFenceValue = 0; currentBlasIndex = 0; materialFlags = 0; + procFlags = 0; + areaNum = -1; + materialVertexOffset = 0; + materialIndexOffset = 0; + materialVertexCount = 0; + materialIndexCount = 0; averageColor[0] = averageColor[1] = averageColor[2] = averageColor[3] = 1.0f; + emissiveColor[0] = emissiveColor[1] = emissiveColor[2] = emissiveColor[3] = 0.0f; } }; @@ -1615,15 +1681,20 @@ static int glRaytracingBuildDirtyMeshesInternal(void) if (!glRaytracingBeginBlasCmd()) return 0; + glRaytracingBeginPixEvent(g_glRaytracingCmd.blasCmdList.Get(), L"GLR Build Dirty Mesh BLAS Batch"); for (size_t i = 0; i < builds.size(); ++i) { - g_glRaytracingCmd.blasCmdList->BuildRaytracingAccelerationStructure(&builds[i].buildDesc, 0, nullptr); + { + GLR_PIX_EVENT(g_glRaytracingCmd.blasCmdList.Get(), L"GLR Build BLAS"); + g_glRaytracingCmd.blasCmdList->BuildRaytracingAccelerationStructure(&builds[i].buildDesc, 0, nullptr); + } D3D12_RESOURCE_BARRIER uav = {}; uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; uav.UAV.pResource = builds[i].barrierResource; g_glRaytracingCmd.blasCmdList->ResourceBarrier(1, &uav); } + glRaytracingEndPixEvent(g_glRaytracingCmd.blasCmdList.Get()); const UINT64 blasFenceValue = glRaytracingEndBlasCmd(); if (!blasFenceValue) @@ -1844,6 +1915,7 @@ static int glRaytracingBuildSceneInternal(glRaytracingRenderWorld_t* world) // queue, so GPU ordering is sufficient and a CPU wait would stall the frame. if (!glRaytracingBeginTlasCmd()) return 0; + glRaytracingBeginPixEvent(g_glRaytracingCmd.tlasCmdList.Get(), needsRebuild ? L"GLR Build TLAS" : L"GLR Update TLAS"); D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC buildDesc = {}; buildDesc.Inputs = inputs; @@ -1863,6 +1935,7 @@ static int glRaytracingBuildSceneInternal(glRaytracingRenderWorld_t* world) uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; uav.UAV.pResource = dstTLAS->resource.Get(); g_glRaytracingCmd.tlasCmdList->ResourceBarrier(1, &uav); + glRaytracingEndPixEvent(g_glRaytracingCmd.tlasCmdList.Get()); const UINT64 tlasFenceValue = glRaytracingEndTlasCmd(); if (!tlasFenceValue) @@ -2212,14 +2285,34 @@ void glRaytracingSetMeshAverageColor(glRaytracingMeshHandle_t meshHandle, float glRaytracingLightingResetDenoiseHistory(); } -void glRaytracingSetMeshProcMetadata(glRaytracingMeshHandle_t meshHandle, uint32_t procFlags, float emissiveR, float emissiveG, float emissiveB, float emissivePower) +void glRaytracingSetMeshProcMetadata(glRaytracingMeshHandle_t meshHandle, uint32_t procFlags, int areaNum, float emissiveR, float emissiveG, float emissiveB, float emissivePower) { - (void)meshHandle; - (void)procFlags; - (void)emissiveR; - (void)emissiveG; - (void)emissiveB; - (void)emissivePower; + std::lock_guard lock(g_glRaytracingMutex); + + glRaytracingMeshRecord_t* mesh = glRaytracingFindMesh(meshHandle); + if (!mesh) + return; + + const float emissiveColor[4] = + { + glRaytracingClamp(emissiveR, 0.0f, 64.0f), + glRaytracingClamp(emissiveG, 0.0f, 64.0f), + glRaytracingClamp(emissiveB, 0.0f, 64.0f), + glRaytracingClamp(emissivePower, 0.0f, 64.0f) + }; + + const int32_t clampedAreaNum = (areaNum >= 0) ? (int32_t)areaNum : -1; + if (mesh->procFlags == procFlags && + mesh->areaNum == clampedAreaNum && + memcmp(mesh->emissiveColor, emissiveColor, sizeof(emissiveColor)) == 0) + { + return; + } + + mesh->procFlags = procFlags; + mesh->areaNum = clampedAreaNum; + memcpy(mesh->emissiveColor, emissiveColor, sizeof(mesh->emissiveColor)); + glRaytracingLightingResetDenoiseHistory(); } void glRaytracingDeleteMesh(glRaytracingMeshHandle_t meshHandle) @@ -2472,11 +2565,24 @@ struct glRaytracingLightingConstants_t float denoisePhiNormal; float denoisePhiPosition; float bumpStrength; + + int32_t secondaryLightBudget; + uint32_t secondaryCorrectionBudget; + float secondaryLightCompensation; + uint32_t historyReset; }; struct glRaytracingMeshMetadata_t { float averageColor[4]; + float emissiveColor[4]; + uint32_t procFlags; + int32_t areaNum; + uint32_t vertexOffset; + uint32_t indexOffset; + uint32_t vertexCount; + uint32_t indexCount; + uint32_t pad[2]; }; static const uint32_t GL_RAYTRACING_MAX_IES_TEXTURES = 8; @@ -2494,12 +2600,20 @@ struct glRaytracingLightingState_t glRaytracingBuffer_t constantBuffer; glRaytracingBuffer_t lightBuffer; glRaytracingBuffer_t meshMetadataBuffer; + glRaytracingBuffer_t materialVertexBuffer; + glRaytracingBuffer_t materialIndexBuffer; void* constantBufferMapped; void* lightBufferMapped; void* meshMetadataBufferMapped; glRaytracingBuffer_t constantBufferRing[GL_RAYTRACING_CMD_RING_SIZE]; glRaytracingBuffer_t lightBufferRing[GL_RAYTRACING_CMD_RING_SIZE]; glRaytracingBuffer_t meshMetadataBufferRing[GL_RAYTRACING_CMD_RING_SIZE]; + glRaytracingBuffer_t materialVertexBufferRing[GL_RAYTRACING_CMD_RING_SIZE]; + glRaytracingBuffer_t materialIndexBufferRing[GL_RAYTRACING_CMD_RING_SIZE]; + uint32_t materialVertexCapacityRing[GL_RAYTRACING_CMD_RING_SIZE]; + uint32_t materialIndexCapacityRing[GL_RAYTRACING_CMD_RING_SIZE]; + uint32_t materialVertexCount; + uint32_t materialIndexCount; void* constantBufferMappedRing[GL_RAYTRACING_CMD_RING_SIZE]; void* lightBufferMappedRing[GL_RAYTRACING_CMD_RING_SIZE]; void* meshMetadataBufferMappedRing[GL_RAYTRACING_CMD_RING_SIZE]; @@ -2557,9 +2671,13 @@ struct glRaytracingLightingState_t constantBufferMappedRing[frame] = nullptr; lightBufferMappedRing[frame] = nullptr; meshMetadataBufferMappedRing[frame] = nullptr; + materialVertexCapacityRing[frame] = 0; + materialIndexCapacityRing[frame] = 0; for (int i = 0; i < 3; ++i) denoiseConstantBufferMappedRing[frame][i] = nullptr; } + materialVertexCount = 0; + materialIndexCount = 0; for (int i = 0; i < 3; ++i) denoiseConstantBufferMapped[i] = nullptr; denoiseWidth = 0; @@ -2608,14 +2726,17 @@ enum glRaytracingLightingDescriptorIndex_t GLR_DESC_SPECULAR_SRV = 12, GLR_DESC_MESH_METADATA_SRV = 13, GLR_DESC_IES_TEXTURES_SRV = 14, - GLR_DESC_PATHTRACE_UAV = GLR_DESC_IES_TEXTURES_SRV + GL_RAYTRACING_MAX_IES_TEXTURES, + GLR_DESC_VELOCITY_SRV = GLR_DESC_IES_TEXTURES_SRV + GL_RAYTRACING_MAX_IES_TEXTURES, + GLR_DESC_MATERIAL_VERTICES_SRV = GLR_DESC_VELOCITY_SRV + 1, + GLR_DESC_MATERIAL_INDICES_SRV = GLR_DESC_VELOCITY_SRV + 2, + GLR_DESC_PATHTRACE_UAV = GLR_DESC_VELOCITY_SRV + 3, GLR_DESC_DENOISE_A_UAV = GLR_DESC_PATHTRACE_UAV + 1, GLR_DESC_DENOISE_B_UAV = GLR_DESC_PATHTRACE_UAV + 2, GLR_DESC_OUTPUT_UAV = GLR_DESC_PATHTRACE_UAV + 3, GLR_DESC_TEMPORAL_UAV = GLR_DESC_PATHTRACE_UAV + 4, GLR_DESC_HISTORY_UAV = GLR_DESC_PATHTRACE_UAV + 5, GLR_DESC_COUNT = GLR_DESC_PATHTRACE_UAV + 6, - GLR_DESC_SRV_COUNT = 14 + GL_RAYTRACING_MAX_IES_TEXTURES, + GLR_DESC_SRV_COUNT = GLR_DESC_PATHTRACE_UAV, GLR_DESC_UAV_COUNT = 6 }; @@ -2665,12 +2786,28 @@ struct Light uint iesTextureIndex; // 0 = none, 1..8 = gIesTextures index + 1 float iesStrength; - float2 iesPad; + int areaNum; + float iesPad; }; struct MeshMetadata { float4 averageColor; + float4 emissiveColor; + uint procFlags; + int areaNum; + uint vertexOffset; + uint indexOffset; + uint vertexCount; + uint indexCount; + uint2 pad; +}; + +struct RayMaterialVertex +{ + float3 position; + float3 normal; + float2 texCoord; }; struct ShadowPayload @@ -2684,6 +2821,8 @@ struct BouncePayload float hitT; uint materialFlags; uint meshIndex; + float3 hitNormal; + float2 hitTexCoord; }; cbuffer LightingCB : register(b0) @@ -2710,6 +2849,10 @@ cbuffer LightingCB : register(b0) float gDenoisePhiNormal; float gDenoisePhiPosition; float gBumpStrength; + int gSecondaryLightBudget; + uint gSecondaryCorrectionBudget; + float gSecondaryLightCompensation; + uint gHistoryReset; }; StructuredBuffer gLights : register(t0); @@ -2722,6 +2865,9 @@ Texture2D gEmissiveTex : register(t11); Texture2D gSpecularTex : register(t12); StructuredBuffer gMeshMetadata : register(t13); Texture2D gIesTextures[8] : register(t14); +Texture2D gVelocityTex : register(t22); +StructuredBuffer gMaterialVertices : register(t23); +StructuredBuffer gMaterialIndices : register(t24); RWTexture2D gOutputTex : register(u0); static const uint GL_RAYTRACING_LIGHT_TYPE_POINT = 0; @@ -2733,6 +2879,12 @@ static const uint GEOMETRY_FLAG_SKELETAL = 1; static const uint GEOMETRY_FLAG_UNLIT = 2; static const uint GEOMETRY_FLAG_GLASS = 4; +static const uint GL_RAYTRACING_PROC_SURFACE_EMISSIVE = 0x00000001u; +static const uint GL_RAYTRACING_PROC_SURFACE_UNLIT = 0x00000002u; +static const uint GL_RAYTRACING_PROC_SURFACE_SKY = 0x00000004u; +static const uint GL_RAYTRACING_PROC_SURFACE_ALPHA_TESTED = 0x00000008u; +static const uint GL_RAYTRACING_PROC_SURFACE_TWO_SIDED = 0x00000010u; + // The shim encodes per-instance material flags into the upper bits of // D3D12_RAYTRACING_INSTANCE_DESC::InstanceID so any-hit shaders can make // visibility decisions without binding a separate material table. @@ -2758,6 +2910,34 @@ float3 GetMeshAverageColor(uint meshIndex) return max(saturate(c), float3(0.04, 0.04, 0.04)); } +MeshMetadata GetMeshMetadataSafe(uint meshIndex) +{ + return gMeshMetadata[meshIndex]; +} + +uint MeshMetadataGeometryFlags(MeshMetadata meta, uint materialFlags) +{ + uint geoFlags = GEOMETRY_FLAG_NONE; + if ((meta.procFlags & (GL_RAYTRACING_PROC_SURFACE_UNLIT | GL_RAYTRACING_PROC_SURFACE_SKY)) != 0u) + geoFlags |= GEOMETRY_FLAG_UNLIT; + if ((materialFlags & GL_RAYTRACING_MATERIAL_FLAG_GLASS) != 0u) + geoFlags |= GEOMETRY_FLAG_GLASS; + return geoFlags; +} + +float3 GetMeshEmissiveRadiance(MeshMetadata meta) +{ + if ((meta.procFlags & GL_RAYTRACING_PROC_SURFACE_EMISSIVE) == 0u) + return 0.0; + + float power = max(meta.emissiveColor.a, 1.0); + float3 e = max(meta.emissiveColor.rgb * power, 0.0); + float peak = max(max(e.r, e.g), e.b); + if (peak > 8.0 && peak > 1e-5) + e *= 8.0 / peak; + return e; +} + uint DecodeGeometryFlag(float geoFlag) { // position.w comes from a render target / buffer path, so do not require exact @@ -2927,6 +3107,42 @@ float3 StabilizeSkeletalNormal(uint2 pixel, float3 worldPos, float3 N) return SafeNormalizeLocal(lerp(N, smoothed, 0.70), N); } +float3 TransformObjectNormalToWorld(float3 n) +{ + float3x4 objectToWorld = ObjectToWorld3x4(); + float3 worldN = float3( + dot(objectToWorld[0].xyz, n), + dot(objectToWorld[1].xyz, n), + dot(objectToWorld[2].xyz, n)); + return SafeNormalizeLocal(worldN, n); +} + +void DecodeRayMaterialAtHit(uint meshIndex, BuiltInTriangleIntersectionAttributes attr, out float3 hitNormal, out float2 hitTexCoord) +{ + MeshMetadata meta = GetMeshMetadataSafe(meshIndex); + uint localTriangleIndex = PrimitiveIndex() * 3u; + + hitNormal = float3(0.0, 0.0, 0.0); + hitTexCoord = float2(0.0, 0.0); + + if (meta.indexCount < localTriangleIndex + 3u || meta.vertexCount == 0u) + return; + + uint i0 = gMaterialIndices[meta.indexOffset + localTriangleIndex + 0u]; + uint i1 = gMaterialIndices[meta.indexOffset + localTriangleIndex + 1u]; + uint i2 = gMaterialIndices[meta.indexOffset + localTriangleIndex + 2u]; + + RayMaterialVertex v0 = gMaterialVertices[i0]; + RayMaterialVertex v1 = gMaterialVertices[i1]; + RayMaterialVertex v2 = gMaterialVertices[i2]; + + float3 bary = float3(1.0 - attr.barycentrics.x - attr.barycentrics.y, attr.barycentrics.x, attr.barycentrics.y); + float3 localN = SafeNormalizeLocal(v0.normal * bary.x + v1.normal * bary.y + v2.normal * bary.z, float3(0.0, 0.0, 1.0)); + + hitNormal = TransformObjectNormalToWorld(localN); + hitTexCoord = v0.texCoord * bary.x + v1.texCoord * bary.y + v2.texCoord * bary.z; +} + [shader("miss")] void ShadowMiss(inout ShadowPayload payload) { @@ -2968,6 +3184,8 @@ void BounceMiss(inout BouncePayload payload) payload.hitT = 0.0; payload.materialFlags = 0; payload.meshIndex = 0; + payload.hitNormal = 0.0; + payload.hitTexCoord = 0.0; } [shader("anyhit")] @@ -2994,6 +3212,8 @@ void BounceClosestHit(inout BouncePayload payload, in BuiltInTriangleIntersectio payload.hitT = 0.0; payload.materialFlags = DecodeInstanceMaterialFlags(); payload.meshIndex = InstanceID() & GL_RAYTRACING_INSTANCE_USER_ID_MASK; + payload.hitNormal = 0.0; + payload.hitTexCoord = 0.0; return; } @@ -3001,6 +3221,7 @@ void BounceClosestHit(inout BouncePayload payload, in BuiltInTriangleIntersectio payload.hitT = RayTCurrent(); payload.materialFlags = DecodeInstanceMaterialFlags(); payload.meshIndex = InstanceID() & GL_RAYTRACING_INSTANCE_USER_ID_MASK; + DecodeRayMaterialAtHit(payload.meshIndex, attr, payload.hitNormal, payload.hitTexCoord); } [shader("closesthit")] @@ -3013,8 +3234,10 @@ void ReflectionClosestHit(inout BouncePayload payload, in BuiltInTriangleInterse payload.hitT = RayTCurrent(); payload.materialFlags = DecodeInstanceMaterialFlags(); payload.meshIndex = InstanceID() & GL_RAYTRACING_INSTANCE_USER_ID_MASK; + DecodeRayMaterialAtHit(payload.meshIndex, attr, payload.hitNormal, payload.hitTexCoord); } - +)" +R"( float TraceShadow(float3 origin, float3 dir, float maxT) { RayDesc ray; @@ -3039,7 +3262,7 @@ float TraceShadow(float3 origin, float3 dir, float maxT) return (payload.hit != 0) ? 0.0 : 1.0; } -bool TraceBounce(float3 origin, float3 dir, float maxT, out float hitT, out uint materialFlags, out uint meshIndex) +bool TraceBounce(float3 origin, float3 dir, float maxT, out float hitT, out uint materialFlags, out uint meshIndex, out float3 hitNormal, out float2 hitTexCoord) { RayDesc ray; ray.Origin = origin; @@ -3052,6 +3275,8 @@ bool TraceBounce(float3 origin, float3 dir, float maxT, out float hitT, out uint payload.hitT = 0.0; payload.materialFlags = 0; payload.meshIndex = 0; + payload.hitNormal = 0.0; + payload.hitTexCoord = 0.0; TraceRay( gSceneBVH, @@ -3066,10 +3291,12 @@ bool TraceBounce(float3 origin, float3 dir, float maxT, out float hitT, out uint hitT = payload.hitT; materialFlags = payload.materialFlags; meshIndex = payload.meshIndex; + hitNormal = payload.hitNormal; + hitTexCoord = payload.hitTexCoord; return payload.hit != 0; } -bool TraceSpecularReflection(float3 origin, float3 dir, float maxT, out float hitT, out uint materialFlags, out uint meshIndex) +bool TraceSpecularReflection(float3 origin, float3 dir, float maxT, out float hitT, out uint materialFlags, out uint meshIndex, out float3 hitNormal, out float2 hitTexCoord) { RayDesc ray; ray.Origin = origin; @@ -3082,6 +3309,8 @@ bool TraceSpecularReflection(float3 origin, float3 dir, float maxT, out float hi payload.hitT = 0.0; payload.materialFlags = 0; payload.meshIndex = 0; + payload.hitNormal = 0.0; + payload.hitTexCoord = 0.0; TraceRay( gSceneBVH, @@ -3096,9 +3325,12 @@ bool TraceSpecularReflection(float3 origin, float3 dir, float maxT, out float hi hitT = payload.hitT; materialFlags = payload.materialFlags; meshIndex = payload.meshIndex; + hitNormal = payload.hitNormal; + hitTexCoord = payload.hitTexCoord; return payload.hit != 0; } - +)" +R"( float Hash12(float2 p) { float3 p3 = frac(float3(p.xyx) * 0.1031); @@ -3704,21 +3936,83 @@ bool LooksLikeAuthoredSpecularSample(float4 specSample, float3 baseAlbedo) return rgbPeak > 0.025 && rgbDiff > 0.035; } -float3 Doom3PseudoSpecularMask(float3 baseAlbedo) +float EstimateSpecularNormalVariance(uint2 pixel, float3 centerN) { - // Doom 3 normally uses a dedicated specular map. - // This fallback is only used for pixels whose specular G-buffer says no - // specular map was written by the raster pass. - float lum = dot(saturate(baseAlbedo), float3(0.299, 0.587, 0.114)); + centerN = SafeNormalizeLocal(centerN, float3(0.0, 0.0, 1.0)); - float specStrength = lerp(0.22, 0.72, saturate(lum * 1.35)); + int2 p = int2(pixel); + int2 maxP = int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1); - // Slight warm/colored contribution from the diffuse texture, but mostly neutral - // like a missing/default specular map. - float3 neutralSpec = float3(specStrength, specStrength, specStrength); - float3 tintedSpec = saturate(baseAlbedo) * 0.35 + neutralSpec * 0.65; + int2 pxp = clamp(p + int2( 1, 0), int2(0, 0), maxP); + int2 pyp = clamp(p + int2( 0, 1), int2(0, 0), maxP); - return max(tintedSpec, float3(0.18, 0.18, 0.18)); + float3 nR = SafeNormalizeLocal(gNormalTex.Load(int3(pxp, 0)).xyz, centerN); + float3 nD = SafeNormalizeLocal(gNormalTex.Load(int3(pyp, 0)).xyz, centerN); + + float v = + (1.0 - saturate(dot(centerN, nR))) + + (1.0 - saturate(dot(centerN, nD))); + + return saturate(v * 1.70); +} + +float3 Doom3SyntheticSpecularMask(uint2 pixel, float3 baseAlbedo) +{ + // Missing Doom 3 specular maps used to get a bright Phong-era fallback. Build + // a small dielectric F0 instead, then damp it with normal-map roughness/cavity + // so cracks, noisy concrete, and damaged surfaces do not become glossy plastic. + float3 albedo = saturate(baseAlbedo); + float lum = dot(albedo, float3(0.299, 0.587, 0.114)); + float albedoMax = max(max(albedo.r, albedo.g), albedo.b); + float albedoMin = min(min(albedo.r, albedo.g), albedo.b); + float saturation = (albedoMax > 1.0e-4) ? ((albedoMax - albedoMin) / albedoMax) : 0.0; + + float3 centerN = SafeNormalizeLocal(gNormalTex.Load(int3(pixel, 0)).xyz, float3(0.0, 0.0, 1.0)); + float normalVariance = EstimateSpecularNormalVariance(pixel, centerN); + + float cleanDiffuse = lerp(0.62, 1.08, smoothstep(0.05, 0.62, lum)); + cleanDiffuse *= lerp(1.0, 0.78, saturate(saturation * 1.25)); + + float cavityDamping = lerp(1.0, 0.48, smoothstep(0.03, 0.32, normalVariance)); + float roughSurfaceDamping = lerp(1.0, 0.58, smoothstep(0.08, 0.45, normalVariance)); + + float f0 = 0.04 * cleanDiffuse * cavityDamping * roughSurfaceDamping; + f0 = clamp(f0, 0.010, 0.055); + + float tintAmount = 0.10 * saturate(saturation); + float3 neutralSpec = float3(f0, f0, f0); + float3 tintedSpec = lerp(neutralSpec, albedo * f0, tintAmount); + + return max(tintedSpec, float3(0.006, 0.006, 0.006)); +} + +float3 ShapeAuthoredDoom3Specular(uint2 pixel, float3 baseAlbedo, float3 authoredSpecular) +{ + float3 authored = saturate(authoredSpecular); + float authoredPeak = SpecularPeak3(authored); + + // Authored black still means "no specular" for Doom materials. + if (authoredPeak <= 0.002) + return 0.0; + + float3 synthetic = Doom3SyntheticSpecularMask(pixel, baseAlbedo); + + // Doom/idTech spec maps were painted for Phong, often as broad intensity masks. + // Treat them as strength/tint hints over a physical dielectric baseline instead + // of feeding their RGB straight into F0. + float authoredStrength = smoothstep(0.025, 0.72, authoredPeak); + float strengthScale = lerp(0.65, 2.85, authoredStrength); + float3 shaped = synthetic * strengthScale; + + float3 authoredTint = authored / max(authoredPeak, 1.0e-4); + float tintTrust = saturate((authoredPeak - 0.08) * 0.55); + shaped = lerp(shaped, authoredTint * SpecularPeak3(shaped), tintTrust); + + float peak = SpecularPeak3(shaped); + if (peak > 0.16 && peak > 1.0e-5) + shaped *= 0.16 / peak; + + return max(shaped, float3(0.004, 0.004, 0.004)); } float3 LoadSceneSpecularAlbedo(uint2 pixel, float3 baseAlbedo) @@ -3731,9 +4025,9 @@ float3 LoadSceneSpecularAlbedo(uint2 pixel, float3 baseAlbedo) // albedo descriptor, which fixes materials whose specular buffer forgot to // set the alpha-valid bit. if (LooksLikeAuthoredSpecularSample(specSample, baseAlbedo)) - return saturate(specSample.rgb); + return ShapeAuthoredDoom3Specular(pixel, baseAlbedo, specSample.rgb); - return Doom3PseudoSpecularMask(baseAlbedo); + return Doom3SyntheticSpecularMask(pixel, baseAlbedo); } @@ -3763,7 +4057,8 @@ float EstimateSpecularRoughness(float3 specularAlbedo) // get tighter highlights, but keep a floor high enough that POM/normal-map // detail cannot collapse into one-pixel GGX fireflies. float peak = SpecularPeak3(saturate(specularAlbedo)); - return clamp(lerp(0.66, 0.40, peak), 0.36, 0.72); + float strength = smoothstep(0.010, 0.145, peak); + return clamp(lerp(0.70, 0.42, strength), 0.38, 0.76); } float3 LimitSpecularPeak(float3 c, float peakLimit) @@ -3969,7 +4264,8 @@ float ComputeSubsurfaceShape(float3 N, float3 V, float3 L) return saturate(wrap * 0.42 + back * (0.55 + forward * 0.85) + rim * wrap * 0.18); } - +)" +R"( float3 ComputeSkeletalSubsurfaceRadiance( float3 N, float3 V, @@ -4126,6 +4422,21 @@ float3 CompressEmissiveRadiance(float3 e, float peakLimit) return e; } +float3 LimitStochasticRadiance(float3 c, float lumaLimit, float peakLimit) +{ + c = max(c, 0.0); + + float luma = dot(c, float3(0.2126, 0.7152, 0.0722)); + if (luma > lumaLimit && luma > 1.0e-5) + c *= lumaLimit / luma; + + float peak = max(max(c.r, c.g), c.b); + if (peak > peakLimit && peak > 1.0e-5) + c *= peakLimit / peak; + + return c; +} + float3 LoadEmissiveRadianceClamped(int2 p) { int2 maxPixel = int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1); @@ -4805,30 +5116,75 @@ float3 EstimateFastBounceLight(float3 hitPos, float3 hitN, Light Lgt) return 0.0; } -uint GetSecondaryBounceFastLightBudget() +bool SecondaryLightMatchesArea(Light Lgt, int hitAreaNum) { - if (gLightCount > 16u) - return 8u; - if (gLightCount > 10u) - return 7u; - if (gLightCount > 6u) - return 6u; - return gLightCount; + // Unknown areas stay permissive so dynamic entities, particles, and legacy + // proc files do not disappear from GI. Known world/static hits prefer lights + // tagged to the same portal area, which reduces room-to-room bounce leaks. + return hitAreaNum < 0 || Lgt.areaNum < 0 || Lgt.areaNum == hitAreaNum; } -float GetSecondaryBounceFastLightScale(uint fastBudget) +uint CountSecondaryBounceAreaLights(int hitAreaNum) { - if (fastBudget == 0u || fastBudget >= gLightCount) + uint count = 0u; + [loop] + for (uint i = 0u; i < gLightCount; ++i) + { + if (SecondaryLightMatchesArea(gLights[i], hitAreaNum)) + ++count; + } + return count; +} + +uint GetSecondaryBounceFastLightBudget(uint candidateCount) +{ + if (candidateCount == 0u) + return 0u; + + if (gSecondaryLightBudget < 0) + return candidateCount; + + if (gSecondaryLightBudget > 0) + return min(candidateCount, (uint)gSecondaryLightBudget); + + if (candidateCount > 16u) + return 8u; + if (candidateCount > 10u) + return 7u; + if (candidateCount > 6u) + return 6u; + return candidateCount; +} + +uint GetSecondaryBounceCorrectionBudget(uint candidateCount) +{ + if (candidateCount == 0u) + return 0u; + + if (gSecondaryCorrectionBudget > 0u) + return min(candidateCount, gSecondaryCorrectionBudget); + + uint correctionBudget = (candidateCount <= 2u) ? 1u : 0u; + if (gSamplesPerPixel >= 4u) + correctionBudget = min(candidateCount, max(correctionBudget, 1u)); + if (gSamplesPerPixel >= 6u && candidateCount <= 4u) + correctionBudget = min(candidateCount, correctionBudget + 1u); + return correctionBudget; +} + +float GetSecondaryBounceFastLightScale(uint fastBudget, uint candidateCount) +{ + if (fastBudget == 0u || fastBudget >= candidateCount) return 1.0; // The uploaded light list is CPU-sorted by broad importance. Use a small // energy compensation for skipped low-importance lights without pretending // the top few lights represent the entire room perfectly. - float fullScale = (float)gLightCount / (float)fastBudget; - return lerp(1.0, fullScale, 0.18); + float fullScale = (float)candidateCount / (float)fastBudget; + return lerp(1.0, fullScale, saturate(gSecondaryLightCompensation)); } -float3 EstimateUnresolvedBounceRadiance(float3 hitPos, float3 hitN, float3 albedo) +float3 EstimateUnresolvedBounceRadiance(float3 hitPos, float3 hitN, float3 albedo, int hitAreaNum) { hitN = SafeNormalizeOr(hitN, float3(0.0, 0.0, 1.0)); albedo = max(saturate(albedo), float3(0.03, 0.03, 0.03)); @@ -4838,15 +5194,22 @@ float3 EstimateUnresolvedBounceRadiance(float3 hitPos, float3 hitN, float3 albed lighting += GetSkyRadiance(hitN) * (0.035 + 0.035 * upness); float3 fastAllLights = 0.0; - uint fastBudget = GetSecondaryBounceFastLightBudget(); + uint candidateCount = CountSecondaryBounceAreaLights(hitAreaNum); + uint fastBudget = GetSecondaryBounceFastLightBudget(candidateCount); + uint accepted = 0u; [loop] - for (uint i = 0; i < fastBudget; ++i) + for (uint i = 0; i < gLightCount; ++i) { + if (accepted >= fastBudget) + break; + if (!SecondaryLightMatchesArea(gLights[i], hitAreaNum)) + continue; fastAllLights += EstimateFastBounceLight(hitPos, hitN, gLights[i]); + ++accepted; } - lighting += fastAllLights * (0.35 * GetSecondaryBounceFastLightScale(fastBudget)); + lighting += fastAllLights * (0.85 * GetSecondaryBounceFastLightScale(accepted, candidateCount)); - return clamp(albedo * max(lighting, 0.0), 0.0, 6.0); + return clamp(albedo * max(lighting, 0.0), 0.0, 8.0); } float3 EstimateShadowedBounceLight(uint2 hitPixel, float3 hitPos, float3 hitN, float3 hitV, float3 hitAlbedo, bool hitIsSkeletal, Light Lgt, inout uint rng) @@ -4876,6 +5239,108 @@ float3 EstimateShadowedBounceLight(uint2 hitPixel, float3 hitPos, float3 hitN, f return clamp(diffuse + sss * 0.35, 0.0, 12.0); } +struct LightReservoir +{ + uint lightIndex; + float weightSum; + float selectedWeight; + uint candidateCount; +}; + +float EstimateBounceLightReservoirWeight(float3 hitPos, float3 hitN, Light Lgt) +{ + float3 fast = EstimateFastBounceLight(hitPos, hitN, Lgt); + float lum = dot(max(fast, 0.0), float3(0.2126, 0.7152, 0.0722)); + float peak = max(max(fast.r, fast.g), fast.b); + return max(lum + peak * 0.35, 1.0e-5); +} + +void UpdateLightReservoir(inout LightReservoir reservoir, uint lightIndex, float weight, inout uint rng) +{ + if (weight <= 0.0) + return; + + reservoir.weightSum += weight; + reservoir.candidateCount += 1u; + + if (Rand(rng) * reservoir.weightSum <= weight) + { + reservoir.lightIndex = lightIndex; + reservoir.selectedWeight = weight; + } +} + +LightReservoir BuildBounceLightReservoir(float3 hitPos, float3 hitN, int hitAreaNum, uint seed, inout uint rng) +{ + LightReservoir reservoir; + reservoir.lightIndex = 0u; + reservoir.weightSum = 0.0; + reservoir.selectedWeight = 0.0; + reservoir.candidateCount = 0u; + + uint localRng = PcgHash(rng ^ seed ^ 0xA511E9B3u); + [loop] + for (uint i = 0u; i < gLightCount; ++i) + { + Light Lgt = gLights[i]; + if (!SecondaryLightMatchesArea(Lgt, hitAreaNum)) + continue; + + float weight = EstimateBounceLightReservoirWeight(hitPos, hitN, Lgt); + UpdateLightReservoir(reservoir, i, weight, localRng); + } + + rng = PcgHash(localRng + reservoir.lightIndex + reservoir.candidateCount * 17u); + return reservoir; +} + +float3 EstimateReservoirShadowCorrection( + uint2 hitPixel, + float3 hitPos, + float3 hitN, + float3 hitV, + float3 hitAlbedo, + bool hitIsSkeletal, + int hitAreaNum, + float fastLightScale, + uint fastBudget, + uint sampleIndex, + inout uint rng) +{ + LightReservoir reservoir = BuildBounceLightReservoir( + hitPos, + hitN, + hitAreaNum, + PcgHash(sampleIndex * 0x9E3779B9u + 0xD1B54A35u), + rng); + + if (reservoir.candidateCount == 0u || reservoir.selectedWeight <= 0.0) + return 0.0; + + Light selected = gLights[reservoir.lightIndex]; + float3 fast = 0.0; + uint fastRank = 0u; + + [loop] + for (uint rankIndex = 0u; rankIndex <= reservoir.lightIndex && rankIndex < gLightCount; ++rankIndex) + { + if (SecondaryLightMatchesArea(gLights[rankIndex], hitAreaNum)) + ++fastRank; + } + + if (fastRank > 0u && fastRank <= fastBudget) + fast = EstimateFastBounceLight(hitPos, hitN, selected) * fastLightScale; + + float3 shadowed = EstimateShadowedBounceLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, hitIsSkeletal, selected, rng); + + // Convert the selected reservoir light into a gentle many-light correction. + // The fast baseline still carries the full cheap estimate; the reservoir only + // spends visibility rays on the lights most likely to matter. + float selectionPdf = reservoir.selectedWeight / max(reservoir.weightSum, 1.0e-5); + float correctionScale = min(1.0 / max(selectionPdf, 1.0e-3), 2.0); + return (shadowed - fast) * correctionScale; +} + float3 EstimateBounceSkyLighting(float3 hitPos, float3 hitN, inout uint rng) { const float SKY_TMAX = 1000000.0; @@ -4901,7 +5366,7 @@ float3 EstimateBounceSkyLighting(float3 hitPos, float3 hitN, inout uint rng) return accum / (float)skySamples; } -float3 EstimateDirectLightingForBounceHit(uint2 hitPixel, float3 hitPos, float3 hitN, float3 hitV, float3 hitAlbedo, uint hitGeoFlag, inout uint rng) +float3 EstimateDirectLightingForBounceHit(uint2 hitPixel, float3 hitPos, float3 hitN, float3 hitV, float3 hitAlbedo, uint hitGeoFlag, int hitAreaNum, inout uint rng) { bool hitIsSkeletal = (hitGeoFlag & GEOMETRY_FLAG_SKELETAL) != 0u; bool hitIsUnlit = (hitGeoFlag & GEOMETRY_FLAG_UNLIT) != 0u; @@ -4923,54 +5388,54 @@ float3 EstimateDirectLightingForBounceHit(uint2 hitPixel, float3 hitPos, float3 // Real occluded sky contribution at the secondary hit. The previous GI path // used a fixed sky term, so corners/cavities received too much indirect light. - lighting += EstimateBounceSkyLighting(hitPos, hitN, lightingRng) * (0.14 + 0.10 * upness); - - // Glow-map emissive no longer participates in secondary GI. It remains a - // direct visible/bloom-only effect until real material-space emissive lighting - // is implemented. + lighting += EstimateBounceSkyLighting(hitPos, hitN, lightingRng) * (0.30 + 0.18 * upness); // Baseline: secondary GI uses a capped important-light set. Primary direct // lighting still evaluates every light, but bounce hits were multiplying the // full light list by every stochastic path depth. float3 fastAllLights = 0.0; - uint fastBudget = GetSecondaryBounceFastLightBudget(); + uint candidateCount = CountSecondaryBounceAreaLights(hitAreaNum); + uint fastBudget = GetSecondaryBounceFastLightBudget(candidateCount); + uint accepted = 0u; [loop] - for (uint i = 0; i < fastBudget; ++i) + for (uint i = 0; i < gLightCount; ++i) { + if (accepted >= fastBudget) + break; + if (!SecondaryLightMatchesArea(gLights[i], hitAreaNum)) + continue; fastAllLights += EstimateFastBounceLight(hitPos, hitN, gLights[i]); + ++accepted; } - float fastLightScale = GetSecondaryBounceFastLightScale(fastBudget); + float fastLightScale = GetSecondaryBounceFastLightScale(accepted, candidateCount); lighting += fastAllLights * fastLightScale; // Visibility correction: replace a small stable subset of the unshadowed // baseline with real shadowed direct lighting. Keying this to the hit // position avoids frame-to-frame sparkle while keeping the all-lights bounce. - uint correctionBudget = 0u; - if (gLightCount > 0u) - { - correctionBudget = (gLightCount <= 3u) ? gLightCount : 2u; - if (gSamplesPerPixel >= 4u) - correctionBudget = min(gLightCount, correctionBudget + 1u); - } + uint correctionBudget = GetSecondaryBounceCorrectionBudget(candidateCount); if (correctionBudget > 0u) { - uint start = PcgHash(lightingRng ^ 0x9E3779B9u) % gLightCount; - lightingRng = PcgHash(lightingRng + 0xBB67AE85u); - uint stride = (gLightCount > 1u) ? (1u + (PcgHash(lightingRng ^ 0x3C6EF372u) % (gLightCount - 1u))) : 1u; - lightingRng = PcgHash(lightingRng + 0xA54FF53Au); - + float3 reservoirCorrection = 0.0; [loop] for (uint c = 0u; c < correctionBudget; ++c) { - uint lightIndex = (start + c * stride) % gLightCount; - Light Lgt = gLights[lightIndex]; - float3 fast = (lightIndex < fastBudget) - ? EstimateFastBounceLight(hitPos, hitN, Lgt) * fastLightScale - : 0.0; - float3 shadowed = EstimateShadowedBounceLight(hitPixel, hitPos, hitN, hitV, hitAlbedo, hitIsSkeletal, Lgt, lightingRng); - lighting += shadowed - fast; + reservoirCorrection += EstimateReservoirShadowCorrection( + hitPixel, + hitPos, + hitN, + hitV, + hitAlbedo, + hitIsSkeletal, + hitAreaNum, + fastLightScale, + fastBudget, + c, + lightingRng); } + + lighting += reservoirCorrection / (float)correctionBudget; } //if (hitIsSkeletal) @@ -5018,7 +5483,7 @@ float3 EstimateReactiveScreenSpaceFinalGather(uint2 pixel, float3 worldPos, floa // Doom/idTech-like unit scale: large enough to catch wall/floor color bleed, // small enough to avoid room-to-room light leaking from unrelated screen hits. - const float MAX_GATHER_DISTANCE = 176.0; + const float MAX_GATHER_DISTANCE = 224.0; float2 jitter = float2( Hash12((float2)pixel + float2(13.7, 91.1)), @@ -5097,6 +5562,7 @@ float3 EstimateReactiveScreenSpaceFinalGather(uint2 pixel, float3 worldPos, floa sampleView, sampleAlbedo, sampleGeoFlag, + -1, rng); accum += outgoingRadiance * formWeight; @@ -5110,9 +5576,9 @@ float3 EstimateReactiveScreenSpaceFinalGather(uint2 pixel, float3 worldPos, floa // Coverage keeps one bright tap from flooding a pixel while still letting // nearby high-confidence samples respond strongly to dynamic lights. - float coverage = saturate(weightSum * 1.65); + float coverage = saturate(weightSum * 2.15); - const float FINAL_GATHER_STRENGTH = 0.38; + const float FINAL_GATHER_STRENGTH = 1.05; return gatheredRadiance * coverage * FINAL_GATHER_STRENGTH; } )" @@ -5165,7 +5631,9 @@ float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3 float hitT = 0.0; uint materialFlags = 0; uint meshIndex = 0; - bool hit = TraceBounce(bounceOrigin, bounceDir, BOUNCE_TMAX, hitT, materialFlags, meshIndex); + float3 rayHitNormal = 0.0; + float2 rayHitTexCoord = 0.0; + bool hit = TraceBounce(bounceOrigin, bounceDir, BOUNCE_TMAX, hitT, materialFlags, meshIndex, rayHitNormal, rayHitTexCoord); if (!hit) { @@ -5180,11 +5648,14 @@ float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3 uint2 hitPixel = pathPixel; float3 hitPos = rayHitPos; - float3 hitNormal = SafeNormalizeOr(-bounceDir, pathNormal); + float3 hitNormal = SafeNormalizeOr(rayHitNormal, SafeNormalizeOr(-bounceDir, pathNormal)); + MeshMetadata meshMeta = GetMeshMetadataSafe(meshIndex); float3 meshAverageColor = GetMeshAverageColor(meshIndex); float3 hitAlbedo = meshAverageColor; uint hitGeoFlag = GEOMETRY_FLAG_NONE; + int hitAreaNum = meshMeta.areaNum; + float3 materialNormal = hitNormal; bool hasGBufferMaterial = TryFetchGBufferAtRayHit( rayHitPos, hitPixel, @@ -5192,6 +5663,8 @@ float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3 hitNormal, hitAlbedo, hitGeoFlag); + hitNormal = materialNormal; + hitGeoFlag |= MeshMetadataGeometryFlags(meshMeta, materialFlags); // If the G-buffer normal points away from the incoming bounce ray, flip it // so direct lighting at the secondary hit is evaluated on the side that @@ -5210,9 +5683,10 @@ float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3 hitV, hitAlbedo, hitGeoFlag, + hitAreaNum, rng); - bouncedRadiance *= meshAverageColor * 1.12; + bouncedRadiance *= 1.28; } else { @@ -5221,10 +5695,16 @@ float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3 // Use mesh-derived color instead of neutral grey/screen material. hitPos = rayHitPos; hitAlbedo = meshAverageColor; - bouncedRadiance = EstimateUnresolvedBounceRadiance(hitPos, hitNormal, hitAlbedo); + bouncedRadiance = EstimateUnresolvedBounceRadiance(hitPos, hitNormal, hitAlbedo, hitAreaNum); } - bouncedRadiance = CompressEmissiveRadiance(bouncedRadiance, depth == 0u ? 8.0 : 5.0); + bouncedRadiance += GetMeshEmissiveRadiance(meshMeta); + + bouncedRadiance = CompressEmissiveRadiance(bouncedRadiance, depth == 0u ? 6.0 : 3.5); + bouncedRadiance = LimitStochasticRadiance( + bouncedRadiance, + depth == 0u ? 2.20 : 1.35, + depth == 0u ? 3.80 : 2.20); accum += throughput * bouncedRadiance; if ((hitGeoFlag & GEOMETRY_FLAG_UNLIT) != 0u) @@ -5265,8 +5745,8 @@ float3 EstimatePathTracedIndirectBounce(uint2 pixel, float3 worldPos, float3 N, // several diffuse depths, while the temporal pass below handles convergence. float3 accum = TraceOneIndirectBouncePath(pixel, worldPos, N, V, baseAlbedo, sampleIndex, rng); - const float INDIRECT_STRENGTH = 0.72; - return accum * INDIRECT_STRENGTH; + const float INDIRECT_STRENGTH = 0.95; + return LimitStochasticRadiance(accum * INDIRECT_STRENGTH, 2.50, 4.00); } float3 ApplyPrimaryDiffusePost(float3 lightingAccum, float ao, float microShadow, bool isSkeletal) @@ -5381,7 +5861,16 @@ float3 PathTraceDeterministicLighting( } -float3 EstimateFallbackReflectionHitRadiance(float3 hitPos, float3 hitNormal, float3 incomingViewDir) +uint GetSpecularReflectionFastLightBudget() +{ + if (gLightCount > 12u) + return 3u; + if (gLightCount > 6u) + return 2u; + return min(gLightCount, 2u); +} + +float3 EstimateSpecularReflectionFastHitRadiance(float3 hitPos, float3 hitNormal, float3 incomingViewDir, float3 hitAlbedo, int hitAreaNum) { // Reflection rays can hit off-screen or camera-hidden TLAS geometry. In that // case this pass has no per-triangle material/normal table, so use a neutral @@ -5398,65 +5887,40 @@ float3 EstimateFallbackReflectionHitRadiance(float3 hitPos, float3 hitNormal, fl lighting += GetSkyRadiance(hitNormal) * (0.12 + 0.08 * upness); lighting += GetSkyRadiance(SafeNormalizeOr(reflect(-incomingViewDir, hitNormal), hitNormal)) * 0.055; + uint lightBudget = GetSpecularReflectionFastLightBudget(); + uint accepted = 0u; [loop] for (uint i = 0; i < gLightCount; ++i) { - lighting += EstimateFastBounceLight(hitPos, hitNormal, gLights[i]) * 0.78; - } + if (accepted >= lightBudget) + break; - const float3 NEUTRAL_UNKNOWN_ALBEDO = float3(0.58, 0.58, 0.58); - return clamp(NEUTRAL_UNKNOWN_ALBEDO * max(lighting, 0.0), 0.0, 10.0); -} - - -float ComputeSpecularReflectionLocalEnergy(float3 worldPos, float3 N) -{ - float energy = 0.0; - - [loop] - for (uint i = 0; i < gLightCount; ++i) - { Light Lgt = gLights[i]; - - float atten = 0.0; - - if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT) - { - atten = ComputePointLightAttenuation(worldPos, Lgt); - } - else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT) - { - atten = ComputeSpotLightAttenuation(worldPos, Lgt); - } - else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT) - { - float dist = length(Lgt.position - worldPos); - float range = max(Lgt.radius, 1.0); - atten = saturate((range - dist) / range); - atten = atten * atten; - } - - if (atten <= 0.0) + if (!SecondaryLightMatchesArea(Lgt, hitAreaNum)) continue; - float3 toLight = SafeNormalizeOr(Lgt.position - worldPos, N); - float facing = saturate((dot(N, toLight) + 0.18) / 1.18); - float lightPeak = max(max(Lgt.color.r, Lgt.color.g), Lgt.color.b); - - energy += atten * facing * Lgt.intensity * lightPeak; + lighting += EstimateFastBounceLight(hitPos, hitNormal, Lgt) * 0.72; + ++accepted; } - // Keep reflections tied to nearby light contribution instead of becoming - // a global mirror pass. - return saturate(energy * 0.12); + return clamp(max(saturate(hitAlbedo), float3(0.08, 0.08, 0.08)) * max(lighting, 0.0), 0.0, 8.0); } -float ComputeSpecularReflectionMaxDistance(float3 worldPos) +float3 EstimateFallbackReflectionHitRadiance(float3 hitPos, float3 hitNormal, float3 incomingViewDir) { - float maxDistance = 0.0; + const float3 NEUTRAL_UNKNOWN_ALBEDO = float3(0.58, 0.58, 0.58); + return EstimateSpecularReflectionFastHitRadiance(hitPos, hitNormal, incomingViewDir, NEUTRAL_UNKNOWN_ALBEDO, -1); +} + +void ComputeSpecularReflectionLocalInfluence(float3 worldPos, float3 N, out float energy, out float maxDistance) +{ + energy = 0.0; + maxDistance = 0.0; + + uint lightBudget = GetSpecularReflectionFastLightBudget(); [loop] - for (uint i = 0; i < gLightCount; ++i) + for (uint i = 0; i < lightBudget; ++i) { Light Lgt = gLights[i]; @@ -5484,12 +5948,18 @@ float ComputeSpecularReflectionMaxDistance(float3 worldPos) if (atten <= 0.0) continue; - // Reflections should not reach the full light range like a mirror. - // This makes the reflection proportional to local light influence. + float3 toLight = SafeNormalizeOr(Lgt.position - worldPos, N); + float facing = saturate((dot(N, toLight) + 0.18) / 1.18); + float lightPeak = max(max(Lgt.color.r, Lgt.color.g), Lgt.color.b); + + energy += atten * facing * Lgt.intensity * lightPeak; maxDistance = max(maxDistance, range * lerp(0.18, 0.55, saturate(atten))); } - return clamp(maxDistance, 24.0, 768.0); + // Keep reflections tied to nearby light contribution instead of becoming + // a global mirror pass. + energy = saturate(energy * 0.12); + maxDistance = clamp(maxDistance, 24.0, 768.0); } float ComputeSpecularReflectionDistanceFade(float hitT, float maxT) @@ -5525,16 +5995,16 @@ float3 EstimateRayTracedSpecularReflection( N = SafeNormalizeOr(N, float3(0.0, 0.0, 1.0)); V = SafeNormalizeOr(V, -N); + float normalVariance = EstimateSpecularNormalVariance(pixel, N); - // Prefer authored specular maps, but do not hard-disable reflections on - // missing/RGB-only specular inputs. Missing spec maps get a muted fallback - // reflection from the pseudo-spec mask; authored black spec maps still return - // zero because LooksLikeAuthoredSpecularSample() preserves them. + // Prefer authored specular maps, but let missing maps use the synthesized + // dielectric fallback. The visibility threshold below keeps fallback rays + // rare unless the surface is grazing and locally lit. float4 rawSpecular = gSpecularTex.Load(int3(pixel, 0)); bool hasSpecularMap = LooksLikeAuthoredSpecularSample(rawSpecular, baseAlbedo); float specPeak = SpecularPeak3(specularAlbedo); - if (specPeak <= 0.006) + if (specPeak <= (hasSpecularMap ? 0.035 : 0.012)) return 0.0; float NoV = saturate(dot(N, V)); @@ -5550,12 +6020,50 @@ float3 EstimateRayTracedSpecularReflection( ? saturate(specularAlbedo) : saturate(lerp(float3(0.02, 0.02, 0.02), specularAlbedo, 0.38)); float3 fresnel = F0 + (1.0 - F0) * pow(1.0 - NoV, 5.0); + float roughness = EstimateSpecularRoughness(specularAlbedo); + roughness = clamp(lerp(roughness, 0.78, smoothstep(0.12, 0.46, normalVariance)), 0.38, 0.82); - float localReflectionEnergy = ComputeSpecularReflectionLocalEnergy(worldPos, N); + // Do not multiply by specPeak again here. specularAlbedo is already F0, and + // double-penalizing it made normal dielectric floors fall below the ray gate. + float reflectionVisibility = SpecularPeak3(fresnel) * saturate(1.0 - roughness * 0.72); + float visibilityThreshold = hasSpecularMap ? 0.035 : 0.030; + if (reflectionVisibility <= visibilityThreshold) + return 0.0; + + float localReflectionEnergy = 0.0; + float reflectionMaxT = 0.0; + ComputeSpecularReflectionLocalInfluence(worldPos, N, localReflectionEnergy, reflectionMaxT); if (localReflectionEnergy <= 0.001) return 0.0; - float reflectionMaxT = ComputeSpecularReflectionMaxDistance(worldPos); + float cavityFade = lerp(0.42, 1.0, saturate(cavity)); + float reflectionStrength = hasSpecularMap + ? saturate(specPeak * 1.35) + : saturate(specPeak * 4.0); + float reflectionScale = hasSpecularMap ? 0.72 : 0.34; + + if (reflectionStrength <= 0.001) + return 0.0; + + float rayVisibilityThreshold = hasSpecularMap ? 0.13 : 0.22; + if (reflectionVisibility < rayVisibilityThreshold) + { + float confidence = saturate(reflectionVisibility / max(rayVisibilityThreshold, 1.0e-4)); + float3 stableGlossRadiance = + GetSkyRadiance(R) * 0.065 + + max(baseAlbedo, float3(0.05, 0.05, 0.05)) * (0.06 + localReflectionEnergy * 0.42); + + return clamp( + stableGlossRadiance * + fresnel * + reflectionStrength * + cavityFade * + reflectionScale * + localReflectionEnergy * + lerp(0.35, 0.82, confidence), + 0.0, + 0.45); + } float normalBias = lerp(gShadowBias * 3.0, gShadowBias * 0.75, NoR); float3 reflectionOrigin = worldPos + N * normalBias + R * (gShadowBias * 0.5); @@ -5563,7 +6071,9 @@ float3 EstimateRayTracedSpecularReflection( float hitT = 0.0; uint materialFlags = 0u; uint meshIndex = 0u; - bool hit = TraceSpecularReflection(reflectionOrigin, R, reflectionMaxT, hitT, materialFlags, meshIndex); + float3 rayHitNormal = 0.0; + float2 rayHitTexCoord = 0.0; + bool hit = TraceSpecularReflection(reflectionOrigin, R, reflectionMaxT, hitT, materialFlags, meshIndex, rayHitNormal, rayHitTexCoord); float3 reflectedRadiance = 0.0; float distanceFade = 1.0; @@ -5580,74 +6090,38 @@ float3 EstimateRayTracedSpecularReflection( { distanceFade = ComputeSpecularReflectionDistanceFade(hitT, reflectionMaxT); float3 rayHitPos = reflectionOrigin + R * hitT; + MeshMetadata reflectedMeshMeta = GetMeshMetadataSafe(meshIndex); reflectedSurfaceAverageColor = GetMeshAverageColor(meshIndex); - - uint2 hitPixel = pixel; + int reflectedAreaNum = reflectedMeshMeta.areaNum; float3 hitPos = rayHitPos; - float3 hitNormal = SafeNormalizeOr(-R, N); - float3 hitAlbedo = float3(0.55, 0.55, 0.55); - uint hitGeoFlag = GEOMETRY_FLAG_NONE; - - bool hasGBufferMaterial = TryFetchGBufferAtRayHit( - rayHitPos, - hitPixel, - hitPos, - hitNormal, - hitAlbedo, - hitGeoFlag); + float3 hitNormal = SafeNormalizeOr(rayHitNormal, SafeNormalizeOr(-R, N)); + float3 hitAlbedo = reflectedSurfaceAverageColor; if (dot(hitNormal, -R) < 0.0) hitNormal = -hitNormal; - if (hasGBufferMaterial) - { - float3 hitV = SafeNormalizeOr(-R, V); - uint stableHitRng = InitRng(hitPixel, 0u, 0x5EECu); - reflectedRadiance = EstimateDirectLightingForBounceHit( - hitPixel, - hitPos, - hitNormal, - hitV, - hitAlbedo, - hitGeoFlag, - stableHitRng); - - // Reflected glow maps should be visible in the reflection, but still - // remain direct radiance; this does not turn emissive into GI. - float3 hitEmissive = CompressEmissiveRadiance(gEmissiveTex.Load(int3(hitPixel, 0)).rgb, 6.50); - reflectedRadiance += hitEmissive; - } - else - { - // Off-screen/hidden TLAS hits lack material data in this G-buffer-only - // material path. Use a neutral lit fallback instead of a dim sky-only - // tint, otherwise many real reflected objects vanish completely. - reflectedRadiance = EstimateFallbackReflectionHitRadiance(rayHitPos, hitNormal, -R); - } + reflectedRadiance = EstimateSpecularReflectionFastHitRadiance( + hitPos, + hitNormal, + SafeNormalizeOr(-R, V), + hitAlbedo, + reflectedAreaNum); + reflectedRadiance += GetMeshEmissiveRadiance(reflectedMeshMeta); } reflectedRadiance *= reflectedSurfaceAverageColor; - reflectedRadiance = CompressEmissiveRadiance(reflectedRadiance, hasSpecularMap ? 3.5 : 2.5); + reflectedRadiance = CompressEmissiveRadiance(reflectedRadiance, hasSpecularMap ? 2.4 : 1.6); - float cavityFade = lerp(0.42, 1.0, saturate(cavity)); - float reflectionStrength = hasSpecularMap - ? saturate(specPeak * 1.30) - : saturate((specPeak - 0.08) * 0.70); - float reflectionScale = hasSpecularMap ? 0.85 : 0.52; - - if (reflectionStrength <= 0.001) - return 0.0; - - return clamp( + float3 reflectionResult = reflectedRadiance * fresnel * reflectionStrength * cavityFade * reflectionScale * localReflectionEnergy * - distanceFade, - 0.0, - 2.6); + distanceFade; + + return LimitStochasticRadiance(reflectionResult, hasSpecularMap ? 0.72 : 0.38, hasSpecularMap ? 1.15 : 0.62); } )" R"( @@ -5673,6 +6147,26 @@ float3 ApplyRealisticOutputCurve(float3 color) return max(color, 0.0); } +float3 PreparePathTraceOutputColor(float3 color) +{ + color.r = ((color.r == color.r) && abs(color.r) < 65504.0) ? color.r : 0.0; + color.g = ((color.g == color.g) && abs(color.g) < 65504.0) ? color.g : 0.0; + color.b = ((color.b == color.b) && abs(color.b) < 65504.0) ? color.b : 0.0; + + // External/hardware denoisers such as DLSS Ray Reconstruction need the noisy + // HDR lighting/GI signal. The display shoulder is applied after reconstruction. + if (gEnableDenoiser == 0u) + return max(color, 0.0); + + return ApplyRealisticOutputCurve(color); +} + +float PreparePathTraceHitDistance(float3 worldPos) +{ + float d = length(gCameraPos.xyz - worldPos); + return ((d == d) && d >= 0.0 && d < 65504.0) ? d : 0.0; +} + [shader("raygeneration")] void RayGen() { @@ -5690,7 +6184,7 @@ void RayGen() if (depthSample <= 0.0 || depthSample >= 1.0) { - gOutputTex[pixel] = float4(ApplyRealisticOutputCurve(albedoSample.rgb + emissiveSurface + emissiveBloom), albedoSample.a); + gOutputTex[pixel] = float4(PreparePathTraceOutputColor(albedoSample.rgb + emissiveSurface + emissiveBloom), 0.0); return; } @@ -5698,6 +6192,7 @@ void RayGen() float3 specularAlbedo = LoadSceneSpecularAlbedo(pixel, baseAlbedo); float4 positionSample = gPositionTex.Load(int3(pixel, 0)); float3 worldPos = positionSample.xyz; + float primaryHitDistance = PreparePathTraceHitDistance(worldPos); uint geoFlag = DecodeGeometryFlag(positionSample.w); bool isSkeletal = (geoFlag & GEOMETRY_FLAG_SKELETAL) != 0u; bool isUnlit = (geoFlag & GEOMETRY_FLAG_UNLIT) != 0u; @@ -5708,7 +6203,7 @@ void RayGen() if (isUnlit) { - gOutputTex[pixel] = float4(ApplyRealisticOutputCurve(baseAlbedo + emissiveSurface + emissiveBloom), albedoSample.a); + gOutputTex[pixel] = float4(PreparePathTraceOutputColor(baseAlbedo + emissiveSurface + emissiveBloom), primaryHitDistance); return; } @@ -5826,7 +6321,7 @@ void RayGen() // the eventual swap-chain/backbuffer is LDR. finalColor += emissiveSurface + emissiveBloom; - gOutputTex[pixel] = float4(ApplyRealisticOutputCurve(finalColor), albedoSample.a); + gOutputTex[pixel] = float4(PreparePathTraceOutputColor(finalColor), primaryHitDistance); } )"; @@ -5855,6 +6350,10 @@ cbuffer LightingCB : register(b0) float gDenoisePhiNormal; float gDenoisePhiPosition; float gBumpStrength; + int gSecondaryLightBudget; + uint gSecondaryCorrectionBudget; + float gSecondaryLightCompensation; + uint gHistoryReset; }; Texture2D gAlbedoTex : register(t1); @@ -6137,6 +6636,10 @@ cbuffer LightingCB : register(b0) float gDenoisePhiNormal; float gDenoisePhiPosition; float gBumpStrength; + int gSecondaryLightBudget; + uint gSecondaryCorrectionBudget; + float gSecondaryLightCompensation; + uint gHistoryReset; }; Texture2D gAlbedoTex : register(t1); @@ -6145,6 +6648,7 @@ Texture2D gNormalTex : register(t3); Texture2D gPositionTex : register(t4); Texture2D gPathTraceTex : register(t6); Texture2D gHistoryTex : register(t9); +Texture2D gVelocityTex : register(t22); RWTexture2D gTemporalOutTex : register(u4); RWTexture2D gHistoryOutTex : register(u5); @@ -6173,6 +6677,29 @@ float3 ClampHistoryToCurrent(float3 history, float3 current) return clamp(history, current - radius, current + radius); } +float4 LoadHistoryClamped(int2 p) +{ + int2 maxPixel = int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1); + p = clamp(p, int2(0, 0), maxPixel); + return gHistoryTex.Load(int3(p, 0)); +} + +float4 SampleHistoryBilinear(float2 uv) +{ + float2 pixelF = uv * gScreenSize.xy - 0.5; + int2 p0 = int2(floor(pixelF)); + float2 f = frac(pixelF); + + float4 h00 = LoadHistoryClamped(p0 + int2(0, 0)); + float4 h10 = LoadHistoryClamped(p0 + int2(1, 0)); + float4 h01 = LoadHistoryClamped(p0 + int2(0, 1)); + float4 h11 = LoadHistoryClamped(p0 + int2(1, 1)); + + float4 hx0 = lerp(h00, h10, f.x); + float4 hx1 = lerp(h01, h11, f.x); + return lerp(hx0, hx1, f.y); +} + [numthreads(8, 8, 1)] void TemporalAccumCS(uint3 dispatchThreadId : SV_DispatchThreadID) { @@ -6194,20 +6721,18 @@ void TemporalAccumCS(uint3 dispatchThreadId : SV_DispatchThreadID) return; } - float4 history = gHistoryTex.Load(int3(pixel, 0)); - float historyCount = (gFrameIndex == 0u) ? 0.0 : clamp(history.a, 0.0, 63.0); + float2 uv = ((float2)pixel + 0.5) / max(gScreenSize.xy, float2(1.0, 1.0)); + float2 velocity = gVelocityTex.Load(int3(pixel, 0)).xy; // previousUV - currentUV + float2 prevUv = uv + velocity; + bool validHistoryUv = + prevUv.x >= 0.0 && prevUv.x <= 1.0 && + prevUv.y >= 0.0 && prevUv.y <= 1.0 && + all(abs(velocity) < 1.0); - // This temporal accumulator has no reprojection/object history validation. - // Animated characters can inherit the previous frame's wall/background color - // at the same screen pixel, which shows up as grey path-trace-only dirt even - // though the current G-buffer is clean. Keep skeletal pixels current-frame - // and let the following geometry-aware spatial pass do the filtering. - if (isSkeletal) - { - gTemporalOutTex[pixel] = raw; - gHistoryOutTex[pixel] = float4(raw.rgb, 0.0); - return; - } + float4 history = validHistoryUv ? SampleHistoryBilinear(prevUv) : float4(raw.rgb, 0.0); + float historyCount = (gFrameIndex == 0u || gHistoryReset != 0u || !validHistoryUv) + ? 0.0 + : clamp(history.a, 0.0, 63.0); if (historyCount <= 0.0) { @@ -6221,11 +6746,15 @@ void TemporalAccumCS(uint3 dispatchThreadId : SV_DispatchThreadID) float rawLum = LuminanceTemporal(max(raw.rgb, 0.0)); float histLum = LuminanceTemporal(historyColor); float relChange = abs(rawLum - histLum) / max(max(rawLum, histLum), 0.08); + float velocityPixels = length(velocity * gScreenSize.xy); // Base accumulation approaches 64 frames, but large lighting changes raise // current-frame weight so the accumulator does not leave obvious trails. - float currentWeight = max(1.0 / (historyCount + 1.0), 0.035); - currentWeight = max(currentWeight, saturate(relChange * 0.24)); + float currentWeight = max(1.0 / (historyCount + 1.0), 0.024); + currentWeight = max(currentWeight, saturate(relChange * 0.18)); + currentWeight = max(currentWeight, saturate(velocityPixels * 0.016)); + if (isSkeletal) + currentWeight = max(currentWeight, 0.18); currentWeight = saturate(currentWeight); float3 resolved = lerp(historyColor, max(raw.rgb, 0.0), currentWeight); @@ -6491,6 +7020,8 @@ static void glRaytracingLightingSelectFrameResources(UINT frameSlot) g_glRaytracingLighting.constantBuffer = g_glRaytracingLighting.constantBufferRing[frameSlot]; g_glRaytracingLighting.lightBuffer = g_glRaytracingLighting.lightBufferRing[frameSlot]; g_glRaytracingLighting.meshMetadataBuffer = g_glRaytracingLighting.meshMetadataBufferRing[frameSlot]; + g_glRaytracingLighting.materialVertexBuffer = g_glRaytracingLighting.materialVertexBufferRing[frameSlot]; + g_glRaytracingLighting.materialIndexBuffer = g_glRaytracingLighting.materialIndexBufferRing[frameSlot]; g_glRaytracingLighting.constantBufferMapped = g_glRaytracingLighting.constantBufferMappedRing[frameSlot]; g_glRaytracingLighting.lightBufferMapped = g_glRaytracingLighting.lightBufferMappedRing[frameSlot]; g_glRaytracingLighting.meshMetadataBufferMapped = g_glRaytracingLighting.meshMetadataBufferMappedRing[frameSlot]; @@ -6749,6 +7280,114 @@ static void glRaytracingLightingUpdateLights(void) bytes); } +static bool glRaytracingLightingEnsureMaterialGeometryBuffers(uint32_t vertexCount, uint32_t indexCount) +{ + const UINT frameSlot = g_glRaytracingCmd.cmdCurrentSlot % GL_RAYTRACING_CMD_RING_SIZE; + const UINT64 vertexBytes = glRaytracingAlignUp((UINT64)std::max(vertexCount, 1u) * sizeof(glRaytracingVertex_t), 256); + const UINT64 indexBytes = glRaytracingAlignUp((UINT64)std::max(indexCount, 1u) * sizeof(uint32_t), 256); + + if (!g_glRaytracingLighting.materialVertexBufferRing[frameSlot].resource || + g_glRaytracingLighting.materialVertexCapacityRing[frameSlot] < vertexCount) + { + g_glRaytracingLighting.materialVertexBufferRing[frameSlot] = glRaytracingCreateBuffer( + g_glRaytracingCmd.device.Get(), + vertexBytes, + D3D12_HEAP_TYPE_UPLOAD, + D3D12_RESOURCE_STATE_GENERIC_READ, + D3D12_RESOURCE_FLAG_NONE); + if (!g_glRaytracingLighting.materialVertexBufferRing[frameSlot].resource) + return false; + g_glRaytracingLighting.materialVertexCapacityRing[frameSlot] = vertexCount; + } + + if (!g_glRaytracingLighting.materialIndexBufferRing[frameSlot].resource || + g_glRaytracingLighting.materialIndexCapacityRing[frameSlot] < indexCount) + { + g_glRaytracingLighting.materialIndexBufferRing[frameSlot] = glRaytracingCreateBuffer( + g_glRaytracingCmd.device.Get(), + indexBytes, + D3D12_HEAP_TYPE_UPLOAD, + D3D12_RESOURCE_STATE_GENERIC_READ, + D3D12_RESOURCE_FLAG_NONE); + if (!g_glRaytracingLighting.materialIndexBufferRing[frameSlot].resource) + return false; + g_glRaytracingLighting.materialIndexCapacityRing[frameSlot] = indexCount; + } + + g_glRaytracingLighting.materialVertexBuffer = g_glRaytracingLighting.materialVertexBufferRing[frameSlot]; + g_glRaytracingLighting.materialIndexBuffer = g_glRaytracingLighting.materialIndexBufferRing[frameSlot]; + return true; +} + +static bool glRaytracingLightingUpdateMaterialGeometryBuffers(void) +{ + if (!g_glRaytracingLighting.uploadToCurrentFrameResource) + return true; + + uint64_t totalVertices = 0; + uint64_t totalIndices = 0; + for (size_t i = 0; i < g_glRaytracingScene.meshes.size(); ++i) + { + const glRaytracingMeshRecord_t& mesh = g_glRaytracingScene.meshes[i]; + if (!mesh.alive) + continue; + totalVertices += mesh.verticesCpu.size(); + totalIndices += mesh.indicesCpu.size(); + } + + if (totalVertices > 0xFFFFFFFFull || totalIndices > 0xFFFFFFFFull) + return false; + + const uint32_t vertexCount = (uint32_t)totalVertices; + const uint32_t indexCount = (uint32_t)totalIndices; + if (!glRaytracingLightingEnsureMaterialGeometryBuffers(vertexCount, indexCount)) + return false; + + std::vector packedVertices; + std::vector packedIndices; + packedVertices.reserve(vertexCount); + packedIndices.reserve(indexCount); + + for (size_t i = 0; i < g_glRaytracingScene.meshes.size(); ++i) + { + glRaytracingMeshRecord_t& mesh = g_glRaytracingScene.meshes[i]; + if (!mesh.alive) + continue; + + const uint32_t vertexOffset = (uint32_t)packedVertices.size(); + const uint32_t indexOffset = (uint32_t)packedIndices.size(); + + mesh.materialVertexOffset = vertexOffset; + mesh.materialIndexOffset = indexOffset; + mesh.materialVertexCount = (uint32_t)mesh.verticesCpu.size(); + mesh.materialIndexCount = (uint32_t)mesh.indicesCpu.size(); + + packedVertices.insert(packedVertices.end(), mesh.verticesCpu.begin(), mesh.verticesCpu.end()); + for (size_t index = 0; index < mesh.indicesCpu.size(); ++index) + packedIndices.push_back(vertexOffset + mesh.indicesCpu[index]); + } + + g_glRaytracingLighting.materialVertexCount = vertexCount; + g_glRaytracingLighting.materialIndexCount = indexCount; + + if (vertexCount > 0) + { + glRaytracingMapCopy( + g_glRaytracingLighting.materialVertexBuffer.resource.Get(), + packedVertices.data(), + (size_t)vertexCount * sizeof(glRaytracingVertex_t)); + } + if (indexCount > 0) + { + glRaytracingMapCopy( + g_glRaytracingLighting.materialIndexBuffer.resource.Get(), + packedIndices.data(), + (size_t)indexCount * sizeof(uint32_t)); + } + + return true; +} + static void glRaytracingLightingUpdateMeshMetadata(void) { if (!g_glRaytracingLighting.uploadToCurrentFrameResource) @@ -6759,6 +7398,7 @@ static void glRaytracingLightingUpdateMeshMetadata(void) glRaytracingMeshMetadata_t fallback = {}; fallback.averageColor[0] = fallback.averageColor[1] = fallback.averageColor[2] = fallback.averageColor[3] = 1.0f; + fallback.areaNum = -1; void* mapped = g_glRaytracingLighting.meshMetadataBufferMapped; if (!mapped) @@ -6781,6 +7421,13 @@ static void glRaytracingLightingUpdateMeshMetadata(void) const uint32_t metadataIndex = mesh.handle & GL_RAYTRACING_INSTANCE_USER_ID_MASK; memcpy(metadata[metadataIndex].averageColor, mesh.averageColor, sizeof(metadata[metadataIndex].averageColor)); + memcpy(metadata[metadataIndex].emissiveColor, mesh.emissiveColor, sizeof(metadata[metadataIndex].emissiveColor)); + metadata[metadataIndex].procFlags = mesh.procFlags; + metadata[metadataIndex].areaNum = mesh.areaNum; + metadata[metadataIndex].vertexOffset = mesh.materialVertexOffset; + metadata[metadataIndex].indexOffset = mesh.materialIndexOffset; + metadata[metadataIndex].vertexCount = mesh.materialVertexCount; + metadata[metadataIndex].indexCount = mesh.materialIndexCount; } } @@ -6903,7 +7550,7 @@ static int glRaytracingLightingCreateStateObject(void) hitGroups[2].Type = D3D12_HIT_GROUP_TYPE_TRIANGLES; D3D12_RAYTRACING_SHADER_CONFIG shaderConfig = {}; - shaderConfig.MaxPayloadSizeInBytes = 16; // BouncePayload: uint + float + uint + uint. + shaderConfig.MaxPayloadSizeInBytes = 48; // BouncePayload now carries hit normal/UV for secondary material shading. shaderConfig.MaxAttributeSizeInBytes = 8; D3D12_GLOBAL_ROOT_SIGNATURE globalRS = {}; @@ -7132,6 +7779,8 @@ static int glRaytracingLightingEnsureDenoiseResources(UINT width, UINT height) GL_RAYTRACING_DENOISE_FORMAT, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS); + glRaytracingSetObjectName(g_glRaytracingLighting.pathTraceTexture[i].resource.Get(), + i == 0 ? L"GLR Raw Path Trace HDR 0" : L"GLR Raw Path Trace HDR 1"); } g_glRaytracingLighting.temporalTexture = glRaytracingCreateTexture2D( @@ -7141,6 +7790,7 @@ static int glRaytracingLightingEnsureDenoiseResources(UINT width, UINT height) GL_RAYTRACING_DENOISE_FORMAT, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS); + glRaytracingSetObjectName(g_glRaytracingLighting.temporalTexture.resource.Get(), L"GLR Temporal GI Accumulation"); for (int i = 0; i < 2; ++i) { @@ -7151,6 +7801,8 @@ static int glRaytracingLightingEnsureDenoiseResources(UINT width, UINT height) GL_RAYTRACING_DENOISE_FORMAT, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS); + glRaytracingSetObjectName(g_glRaytracingLighting.historyTexture[i].resource.Get(), + i == 0 ? L"GLR GI History 0" : L"GLR GI History 1"); } for (int i = 0; i < 2; ++i) @@ -7162,6 +7814,8 @@ static int glRaytracingLightingEnsureDenoiseResources(UINT width, UINT height) GL_RAYTRACING_DENOISE_FORMAT, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS); + glRaytracingSetObjectName(g_glRaytracingLighting.denoiseTemp[i].resource.Get(), + i == 0 ? L"GLR A-Trous Temp A" : L"GLR A-Trous Temp B"); } if (!g_glRaytracingLighting.pathTraceTexture[0].resource || @@ -7300,6 +7954,42 @@ static void glRaytracingLightingCreatePerPassDescriptors( glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_IES_TEXTURES_SRV + i)); } + D3D12_SHADER_RESOURCE_VIEW_DESC velocitySrv = {}; + velocitySrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + velocitySrv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + velocitySrv.Format = pass->velocityTexture ? pass->velocityFormat : pass->positionFormat; + velocitySrv.Texture2D.MipLevels = 1; + g_glRaytracingCmd.device->CreateShaderResourceView( + pass->velocityTexture ? pass->velocityTexture : pass->positionTexture, + &velocitySrv, + glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_VELOCITY_SRV)); + + D3D12_SHADER_RESOURCE_VIEW_DESC materialVertexSrv = {}; + materialVertexSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + materialVertexSrv.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; + materialVertexSrv.Format = DXGI_FORMAT_UNKNOWN; + materialVertexSrv.Buffer.FirstElement = 0; + materialVertexSrv.Buffer.NumElements = std::max(g_glRaytracingLighting.materialVertexCount, 1u); + materialVertexSrv.Buffer.StructureByteStride = sizeof(glRaytracingVertex_t); + materialVertexSrv.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE; + g_glRaytracingCmd.device->CreateShaderResourceView( + g_glRaytracingLighting.materialVertexBuffer.resource.Get(), + &materialVertexSrv, + glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_MATERIAL_VERTICES_SRV)); + + D3D12_SHADER_RESOURCE_VIEW_DESC materialIndexSrv = {}; + materialIndexSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + materialIndexSrv.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; + materialIndexSrv.Format = DXGI_FORMAT_UNKNOWN; + materialIndexSrv.Buffer.FirstElement = 0; + materialIndexSrv.Buffer.NumElements = std::max(g_glRaytracingLighting.materialIndexCount, 1u); + materialIndexSrv.Buffer.StructureByteStride = sizeof(uint32_t); + materialIndexSrv.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE; + g_glRaytracingCmd.device->CreateShaderResourceView( + g_glRaytracingLighting.materialIndexBuffer.resource.Get(), + &materialIndexSrv, + glRaytracingOffsetCpu(base, g_glRaytracingLighting.descriptorStride, GLR_DESC_MATERIAL_INDICES_SRV)); + D3D12_UNORDERED_ACCESS_VIEW_DESC rayOutputUav = {}; rayOutputUav.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; rayOutputUav.Format = GL_RAYTRACING_DENOISE_FORMAT; @@ -7374,6 +8064,13 @@ static bool glRaytracingLightingExecuteInternal( g_glRaytracingLighting.uploadToCurrentFrameResource = true; glRaytracingLightingUpdateLights(); + if (!glRaytracingLightingUpdateMaterialGeometryBuffers()) + { + g_glRaytracingLighting.uploadToCurrentFrameResource = false; + if (g_glRaytracingCmd.cmdList) + g_glRaytracingCmd.cmdList->Close(); + return false; + } glRaytracingLightingUpdateMeshMetadata(); glRaytracingLightingUpdateConstants(); @@ -7386,24 +8083,32 @@ static bool glRaytracingLightingExecuteInternal( } g_glRaytracingLighting.uploadToCurrentFrameResource = false; + glRaytracingBeginPixEvent(g_glRaytracingCmd.cmdList.Get(), useInternalDenoiser + ? L"GLR Path Traced Lighting With Internal Denoiser" + : L"GLR Path Traced Lighting Raw For External Denoiser"); + const uint32_t historyReadIndex = g_glRaytracingLighting.currentHistoryIndex & 1u; const uint32_t historyWriteIndex = (g_glRaytracingLighting.currentHistoryIndex ^ 1u) & 1u; const uint32_t pathTraceIndex = g_glRaytracingLighting.currentPathTraceIndex & 1u; glRaytracingTexture_t& pathTraceTexture = g_glRaytracingLighting.pathTraceTexture[pathTraceIndex]; - glRaytracingLightingCreatePerPassDescriptors( - pass, - topLevelAS, - rayOutputTexture, - pathTraceTexture.resource.Get(), - g_glRaytracingLighting.denoiseTemp[0].resource.Get(), - g_glRaytracingLighting.denoiseTemp[1].resource.Get(), - g_glRaytracingLighting.historyTexture[historyReadIndex].resource.Get(), - g_glRaytracingLighting.historyTexture[historyWriteIndex].resource.Get(), - g_glRaytracingLighting.temporalTexture.resource.Get()); + { + GLR_PIX_EVENT(g_glRaytracingCmd.cmdList.Get(), L"GLR Create Per-Pass Descriptors"); + glRaytracingLightingCreatePerPassDescriptors( + pass, + topLevelAS, + rayOutputTexture, + pathTraceTexture.resource.Get(), + g_glRaytracingLighting.denoiseTemp[0].resource.Get(), + g_glRaytracingLighting.denoiseTemp[1].resource.Get(), + g_glRaytracingLighting.historyTexture[historyReadIndex].resource.Get(), + g_glRaytracingLighting.historyTexture[historyWriteIndex].resource.Get(), + g_glRaytracingLighting.temporalTexture.resource.Get()); + } if (useInternalDenoiser) { + GLR_PIX_MARKER(g_glRaytracingCmd.cmdList.Get(), L"GLR transition raw path-trace target to UAV"); glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(), pathTraceTexture.resource.Get(), pathTraceTexture.state, @@ -7412,6 +8117,7 @@ static bool glRaytracingLightingExecuteInternal( } else { + GLR_PIX_MARKER(g_glRaytracingCmd.cmdList.Get(), L"GLR transition external denoiser output to UAV"); glRaytracingTransition(g_glRaytracingCmd.cmdList.Get(), pass->outputTexture, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, @@ -7458,15 +8164,20 @@ static bool glRaytracingLightingExecuteInternal( rays.Width = pass->width; rays.Height = pass->height; rays.Depth = 1; - g_glRaytracingCmd.cmdList->DispatchRays(&rays); + { + GLR_PIX_EVENT(g_glRaytracingCmd.cmdList.Get(), L"GLR DispatchRays RayGen GI"); + g_glRaytracingCmd.cmdList->DispatchRays(&rays); + } D3D12_RESOURCE_BARRIER rawUav = {}; rawUav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; rawUav.UAV.pResource = rayOutputTexture; + GLR_PIX_MARKER(g_glRaytracingCmd.cmdList.Get(), L"GLR raw ray output UAV barrier"); g_glRaytracingCmd.cmdList->ResourceBarrier(1, &rawUav); if (useInternalDenoiser) { + GLR_PIX_EVENT(g_glRaytracingCmd.cmdList.Get(), L"GLR Internal Temporal + A-Trous Denoise"); const UINT groupsX = (pass->width + 7u) / 8u; const UINT groupsY = (pass->height + 7u) / 8u; @@ -7501,7 +8212,10 @@ static bool glRaytracingLightingExecuteInternal( glRaytracingOffsetGpu(gpuBase, g_glRaytracingLighting.descriptorStride, GLR_DESC_PATHTRACE_UAV)); g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView(2, g_glRaytracingLighting.constantBuffer.gpuVA); g_glRaytracingCmd.cmdList->SetPipelineState(g_glRaytracingLighting.temporalPSO.Get()); - g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1); + { + GLR_PIX_EVENT(g_glRaytracingCmd.cmdList.Get(), L"GLR Temporal Accumulation CS"); + g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1); + } D3D12_RESOURCE_BARRIER temporalUavs[2] = {}; temporalUavs[0].Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; @@ -7545,7 +8259,10 @@ static bool glRaytracingLightingExecuteInternal( // Pass 0: temporally accumulated path trace -> temp A. g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView(2, g_glRaytracingLighting.denoiseConstantBuffer[0].gpuVA); - g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1); + { + GLR_PIX_EVENT(g_glRaytracingCmd.cmdList.Get(), L"GLR A-Trous Denoise Pass 0"); + g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1); + } D3D12_RESOURCE_BARRIER uavA = {}; uavA.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; @@ -7560,7 +8277,10 @@ static bool glRaytracingLightingExecuteInternal( // Pass 1: temp A -> temp B. g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView(2, g_glRaytracingLighting.denoiseConstantBuffer[1].gpuVA); - g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1); + { + GLR_PIX_EVENT(g_glRaytracingCmd.cmdList.Get(), L"GLR A-Trous Denoise Pass 1"); + g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1); + } D3D12_RESOURCE_BARRIER uavB = {}; uavB.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; @@ -7575,7 +8295,10 @@ static bool glRaytracingLightingExecuteInternal( // Pass 2: temp B -> final output. g_glRaytracingCmd.cmdList->SetComputeRootConstantBufferView(2, g_glRaytracingLighting.denoiseConstantBuffer[2].gpuVA); - g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1); + { + GLR_PIX_EVENT(g_glRaytracingCmd.cmdList.Get(), L"GLR A-Trous Denoise Pass 2"); + g_glRaytracingCmd.cmdList->Dispatch(groupsX, groupsY, 1); + } D3D12_RESOURCE_BARRIER outputUav = {}; outputUav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; @@ -7590,6 +8313,7 @@ static bool glRaytracingLightingExecuteInternal( if (writeTimestamp) { + GLR_PIX_MARKER(g_glRaytracingCmd.cmdList.Get(), L"GLR resolve path tracing timestamp"); g_glRaytracingCmd.cmdList->EndQuery( g_glRaytracingCmd.pathTracingTimestampHeap.Get(), D3D12_QUERY_TYPE_TIMESTAMP, @@ -7603,6 +8327,8 @@ static bool glRaytracingLightingExecuteInternal( sizeof(UINT64) * timestampBase); } + glRaytracingEndPixEvent(g_glRaytracingCmd.cmdList.Get()); + if (!glRaytracingEndCmd()) return false; @@ -7694,6 +8420,10 @@ bool glRaytracingLightingInit(void) g_glRaytracingLighting.constants.denoisePhiNormal = 64.0f; g_glRaytracingLighting.constants.denoisePhiPosition = 0.045f; g_glRaytracingLighting.constants.bumpStrength = 2.35f; + g_glRaytracingLighting.constants.secondaryLightBudget = 0; + g_glRaytracingLighting.constants.secondaryCorrectionBudget = 0; + g_glRaytracingLighting.constants.secondaryLightCompensation = 0.62f; + g_glRaytracingLighting.constants.historyReset = 1u; glRaytracingLightingResetDenoiseHistory(); glRaytracingLightingUpdateConstants(); @@ -7797,13 +8527,6 @@ void glRaytracingLightingSetCameraPosition(float x, float y, float z) { std::lock_guard lock(g_glRaytracingMutex); - if (g_glRaytracingLighting.constants.cameraPos[0] != x || - g_glRaytracingLighting.constants.cameraPos[1] != y || - g_glRaytracingLighting.constants.cameraPos[2] != z) - { - glRaytracingLightingResetDenoiseHistory(); - } - g_glRaytracingLighting.constants.cameraPos[0] = x; g_glRaytracingLighting.constants.cameraPos[1] = y; g_glRaytracingLighting.constants.cameraPos[2] = z; @@ -7818,9 +8541,6 @@ void glRaytracingLightingSetInvViewProjMatrix(const float* m16) if (!m16) return; - if (memcmp(g_glRaytracingLighting.constants.invViewProj, m16, sizeof(float) * 16) != 0) - glRaytracingLightingResetDenoiseHistory(); - memcpy(g_glRaytracingLighting.constants.invViewProj, m16, sizeof(float) * 16); if (!glRaytracingInvertMatrix4x4(m16, g_glRaytracingLighting.constants.viewProj)) @@ -7836,10 +8556,18 @@ void glRaytracingLightingSetInvViewMatrix(const float* m16) if (!m16) return; - if (memcmp(g_glRaytracingLighting.constants.invViewMatrix, m16, sizeof(float) * 16) != 0) + memcpy(g_glRaytracingLighting.constants.invViewMatrix, m16, sizeof(float) * 16); + glRaytracingLightingUpdateConstants(); +} + +void glRaytracingLightingSetHistoryReset(int reset) +{ + std::lock_guard lock(g_glRaytracingMutex); + + g_glRaytracingLighting.constants.historyReset = reset ? 1u : 0u; + if (reset) glRaytracingLightingResetDenoiseHistory(); - memcpy(g_glRaytracingLighting.constants.invViewMatrix, m16, sizeof(float) * 16); glRaytracingLightingUpdateConstants(); } @@ -7927,8 +8655,25 @@ void glRaytracingLightingSetPathTracingOptions(uint32_t samplesPerPixel, uint32_ void glRaytracingLightingSetSecondaryLightBudget(int budget, float compensation) { - (void)budget; - (void)compensation; + std::lock_guard lock(g_glRaytracingMutex); + + if (budget < -1) + budget = -1; + if (budget > (int)GL_RAYTRACING_MAX_LIGHTS) + budget = (int)GL_RAYTRACING_MAX_LIGHTS; + + compensation = glRaytracingClamp(compensation, 0.0f, 1.0f); + + if (g_glRaytracingLighting.constants.secondaryLightBudget != budget || + g_glRaytracingLighting.constants.secondaryLightCompensation != compensation) + { + glRaytracingLightingResetDenoiseHistory(); + } + + g_glRaytracingLighting.constants.secondaryLightBudget = budget; + g_glRaytracingLighting.constants.secondaryCorrectionBudget = 0; + g_glRaytracingLighting.constants.secondaryLightCompensation = compensation; + glRaytracingLightingUpdateConstants(); } void glRaytracingLightingSetDenoiseTuning(float phiColor, float phiNormal, float phiPosition) @@ -8080,6 +8825,7 @@ glRaytracingLight_t glRaytracingLightingMakePointLight( l.twoSided = 0; l.persistant = 0.0f; l.pad1 = 0.0f; // volumetric scattering disabled by default. + l.areaNum = -1; return l; } @@ -8193,6 +8939,7 @@ glRaytracingLight_t glRaytracingLightingMakeSpotLight( l.twoSided = 0; l.persistant = 0.0f; l.pad1 = 0.0f; // volumetric scattering disabled by default. + l.areaNum = -1; return l; } @@ -8257,6 +9004,7 @@ glRaytracingLight_t glRaytracingLightingMakeRectLight( l.twoSided = twoSided ? 1u : 0u; l.persistant = 0.0f; l.pad1 = 0.0f; // volumetric scattering disabled by default. + l.areaNum = -1; return l; } @@ -8326,18 +9074,38 @@ bool glRaytracingLightingGetProfileCounters(glRaytracingProfileCounters_t* count uint32_t correctionBudget = 0; if (lightCount > 0u) { - correctionBudget = (lightCount <= 3u) ? lightCount : 2u; - if (samplesPerPixel >= 4u) - correctionBudget = glRaytracingClamp(correctionBudget + 1u, 0u, lightCount); + if (constants.secondaryCorrectionBudget > 0u) + { + correctionBudget = glRaytracingClamp(constants.secondaryCorrectionBudget, 0u, lightCount); + } + else + { + correctionBudget = (lightCount <= 2u) ? 1u : 0u; + if (samplesPerPixel >= 4u) + correctionBudget = glRaytracingClamp(std::max(correctionBudget, 1u), 0u, lightCount); + if (samplesPerPixel >= 6u && lightCount <= 4u) + correctionBudget = glRaytracingClamp(correctionBudget + 1u, 0u, lightCount); + } } uint32_t fastBounceLightBudget = lightCount; - if (lightCount > 16u) - fastBounceLightBudget = 8u; - else if (lightCount > 10u) - fastBounceLightBudget = 7u; - else if (lightCount > 6u) - fastBounceLightBudget = 6u; + if (constants.secondaryLightBudget < 0) + { + fastBounceLightBudget = lightCount; + } + else if (constants.secondaryLightBudget > 0) + { + fastBounceLightBudget = glRaytracingClamp((uint32_t)constants.secondaryLightBudget, 0u, lightCount); + } + else + { + if (lightCount > 16u) + fastBounceLightBudget = 8u; + else if (lightCount > 10u) + fastBounceLightBudget = 7u; + else if (lightCount > 6u) + fastBounceLightBudget = 6u; + } uint32_t gatherSamples = 0; if (maxBounces > 1u) diff --git a/neo/engine/opengl/gl_d3d12shim.cpp b/neo/engine/opengl/gl_d3d12shim.cpp index 41f04dbd..ddc023ac 100644 --- a/neo/engine/opengl/gl_d3d12shim.cpp +++ b/neo/engine/opengl/gl_d3d12shim.cpp @@ -42,6 +42,7 @@ If you have questions concerning this license or the applicable additional terms #include #include #include +#include #include #include #include @@ -74,6 +75,7 @@ using Microsoft::WRL::ComPtr; #include #include #include +#include #include #endif @@ -296,6 +298,10 @@ void APIENTRY glRaytracingMaterialFlagQD3D12(GLuint flag, GLboolean enable); // whenever an external denoiser such as DLSS Ray Reconstruction will consume it. void glRaytracingLightingSetExternalDenoiser(int enable); void glRaytracingLightingSetPathTracingOptions(uint32_t samplesPerPixel, uint32_t maxBounces, int enableDenoiser, float denoiseStrength); +void glRaytracingLightingSetCameraPosition(float x, float y, float z); +void glRaytracingLightingSetInvViewProjMatrix(const float* m16); +void glRaytracingLightingSetInvViewMatrix(const float* m16); +void glRaytracingLightingSetHistoryReset(int reset); void glRaytracingLightingSetVolumetricScattering(glRaytracingLight_t* light, float strength); void glRaytracingLightingSetEmissiveInput(ID3D12Resource* texture, DXGI_FORMAT format); void glRaytracingLightingSetSpecularInput(ID3D12Resource* texture, DXGI_FORMAT format); @@ -432,6 +438,71 @@ struct VertexArena #define QD3D12_CHECK(x) do { HRESULT _hr = (x); if (FAILED(_hr)) { QD3D12_Fatal("HRESULT failed 0x%08X at %s:%d", (unsigned)_hr, __FILE__, __LINE__); } } while(0) +static void QD3D12_BeginPixEvent(ID3D12GraphicsCommandList* cl, const wchar_t* name) +{ + if (!cl || !name) + return; + + cl->BeginEvent(0, name, (UINT)((wcslen(name) + 1) * sizeof(wchar_t))); +} + +static void QD3D12_EndPixEvent(ID3D12GraphicsCommandList* cl) +{ + if (cl) + cl->EndEvent(); +} + +static void QD3D12_SetPixMarker(ID3D12GraphicsCommandList* cl, const wchar_t* name) +{ + if (!cl || !name) + return; + + cl->SetMarker(0, name, (UINT)((wcslen(name) + 1) * sizeof(wchar_t))); +} + +static void QD3D12_SetPixMarkerFormat(ID3D12GraphicsCommandList* cl, const wchar_t* fmt, ...) +{ + if (!cl || !fmt) + return; + + wchar_t buffer[512]; + va_list args; + va_start(args, fmt); + vswprintf_s(buffer, _countof(buffer), fmt, args); + va_end(args); + QD3D12_SetPixMarker(cl, buffer); +} + +static void QD3D12_SetObjectName(ID3D12Object* object, const wchar_t* name) +{ + if (object && name) + object->SetName(name); +} + +class QD3D12ScopedPixEvent +{ +public: + QD3D12ScopedPixEvent(ID3D12GraphicsCommandList* commandList, const wchar_t* name) + : cl(commandList) + { + QD3D12_BeginPixEvent(cl, name); + } + + ~QD3D12ScopedPixEvent() + { + QD3D12_EndPixEvent(cl); + } + +private: + ID3D12GraphicsCommandList* cl; +}; + +#define QD3D12_CONCAT_INNER(a, b) a##b +#define QD3D12_CONCAT(a, b) QD3D12_CONCAT_INNER(a, b) +#define QD3D12_PIX_EVENT(cl, name) QD3D12ScopedPixEvent QD3D12_CONCAT(qd3d12PixEvent_, __LINE__)(cl, name) +#define QD3D12_PIX_MARKER(cl, name) QD3D12_SetPixMarker(cl, name) +#define QD3D12_PIX_MARKERF(cl, fmt, ...) QD3D12_SetPixMarkerFormat(cl, fmt, __VA_ARGS__) + struct GLOcclusionQuery { GLuint id = 0; @@ -1516,18 +1587,20 @@ struct GLState QD3D12UpscalerQuality upscalerQuality = QD3D12_QUALITY_QUALITY; bool enableInternalTAA = false; bool enableRayAIDenoise = false; - bool enableDLSSRayReconstruction = false; + bool enableDLSSRayReconstruction = true; bool enableFSRRayRegeneration = false; bool blockLightingUpscaleInputThisFrame = false; - uint32_t frameGenerationMultiplier = 0; - uint32_t pathTracingSamplesPerPixel = 1; - uint32_t pathTracingFallbackSamplesPerPixel = 2; + uint32_t frameGenerationMultiplier = 4; + uint32_t pathTracingSamplesPerPixel = 2; + uint32_t pathTracingFallbackSamplesPerPixel = 1; uint32_t pathTracingMaxBounces = 3; glScreenSpaceDecal_t screenSpaceDecals[QD3D12_MaxScreenSpaceDecals] = {}; uint32_t screenSpaceDecalCount = 0; std::vector screenSpaceParticleBatches; glRaytracingLight_t screenSpaceParticleLights[QD3D12_MaxScreenSpaceParticleLights] = {}; uint32_t screenSpaceParticleLightCount = 0; + bool deferScreenSpaceParticlesToEndFrame = false; + ComPtr deferredScreenSpaceParticleTlas; ComPtr screenSpaceDecalBuffer; void* screenSpaceDecalBufferMapped = nullptr; UINT screenSpaceDecalSrvIndex = UINT_MAX; @@ -2202,6 +2275,32 @@ static bool QD3D12_UpdateCameraInfoFromCurrentMatrices() return true; } +static bool QD3D12_UpdateRaytracingLightingCameraFromCameraState() +{ + if (!g_gl.cameraState.valid) + return false; + + float clipToWorld[16]; + QD3D12_MatrixMultiplyCM( + g_gl.cameraState.viewToWorld.m, + g_gl.cameraState.clipToView.m, + clipToWorld); + + if (!QD3D12_MatrixFinite(clipToWorld) || + !QD3D12_MatrixFinite(g_gl.cameraState.viewToWorld.m)) + { + return false; + } + + glRaytracingLightingSetCameraPosition( + g_gl.cameraState.cameraPos[0], + g_gl.cameraState.cameraPos[1], + g_gl.cameraState.cameraPos[2]); + glRaytracingLightingSetInvViewMatrix(g_gl.cameraState.viewToWorld.m); + glRaytracingLightingSetInvViewProjMatrix(clipToWorld); + return true; +} + static inline uint32_t QD3D12_ClampRayMaterialFlags(uint32_t flags) { @@ -4699,6 +4798,7 @@ cbuffer PostCB : register(b0) #define gScreenDecalCount gPostFogCameraWorldPos.w #define gScreenParticleEmissiveScale gPostParticleParams.x #define gScreenParticleOutputMode gPostParticleParams.y +#define gScreenParticleDepthScale gPostParticleParams.zw #define QD3D12_TONEMAP_TILE_DIM 64u static const uint GL_RAYTRACING_LIGHT_TYPE_POINT = 0u; @@ -4983,18 +5083,28 @@ float4 PSScreenParticles(ParticleVSOut i) : SV_Target0 discard; uint2 pixel = uint2(i.pos.xy); - float sceneDepth = gFogDepth.Load(int3(pixel, 0)); + uint depthW, depthH; + gFogDepth.GetDimensions(depthW, depthH); + if (depthW == 0u || depthH == 0u) + discard; + + uint2 gbufferPixel = min( + uint2(floor(float2(pixel) * max(gScreenParticleDepthScale, float2(0.0, 0.0)))), + uint2(depthW - 1u, depthH - 1u)); + + float sceneDepth = gFogDepth.Load(int3(gbufferPixel, 0)); float particleDepth = saturate(i.pos.z); + bool nativeComposite = (gScreenParticleOutputMode < -0.5); if (sceneDepth < 0.99999) { - if (particleDepth > sceneDepth + 0.00005) + if (particleDepth > sceneDepth + (nativeComposite ? 0.00025 : 0.00005)) { discard; } - if (gPostFogCameraValid > 0.5) + if (!nativeComposite && gPostFogCameraValid > 0.5) { - float4 positionSample = gScreenDecalPosition.Load(int3(pixel, 0)); + float4 positionSample = gScreenDecalPosition.Load(int3(gbufferPixel, 0)); uint geometryBits = (uint)round(max(positionSample.w, 0.0)); if ((geometryBits & 1u) == 0u) { @@ -5024,7 +5134,8 @@ float4 PSCopy(VSOut i) : SV_Target0 { return QD3D12_ApplyPostFog(gTex0.Sample(gSamp0, i.uv), i.uv); } - +)HLSL" +R"HLSL( float4 PSSharpen(VSOut i) : SV_Target0 { float4 center = gTex0.Sample(gSamp0, i.uv); @@ -7374,6 +7485,8 @@ struct QD3D12StreamlineState bool deviceBound = false; bool dlssSupported = false; bool dlssRrSupported = false; + bool dlssRrOperational = false; + bool dlssRrTagsLogged = false; bool dlssGSupported = false; bool dlssGFeatureLoaded = false; bool reflexSupported = false; @@ -7392,6 +7505,43 @@ struct QD3D12StreamlineState }; static QD3D12StreamlineState g_qd3d12Sl; +static const char* QD3D12_StreamlineResourceLifecycleName(sl::ResourceLifecycle lifecycle) +{ + switch (lifecycle) + { + case sl::ResourceLifecycle::eOnlyValidNow: + return "eOnlyValidNow"; + case sl::ResourceLifecycle::eValidUntilPresent: + return "eValidUntilPresent"; + case sl::ResourceLifecycle::eValidUntilEvaluate: + return "eValidUntilEvaluate"; + default: + return "unknown"; + } +} + +static void QD3D12_LogStreamlineResourceTags(const char* name, const sl::ResourceTag* tags, UINT tagCount) +{ + if (!tags) + return; + + for (UINT i = 0; i < tagCount; ++i) + { + const sl::ResourceTag& tag = tags[i]; + const sl::Resource* resource = tag.resource; + QD3D12_Log("%s tag[%u]=%s(%u) lifecycle=%s state=0x%08x extent=%ux%u native=%p.", + name, + i, + sl::getBufferTypeAsStr(tag.type), + uint32_t(tag.type), + QD3D12_StreamlineResourceLifecycleName(tag.lifecycle), + resource ? uint32_t(resource->state) : 0xffffffffu, + tag.extent.width, + tag.extent.height, + resource ? resource->native : nullptr); + } +} + typedef HRESULT(WINAPI* QD3D12_PFN_CreateDXGIFactory1)(REFIID riid, void** ppFactory); typedef HRESULT(WINAPI* QD3D12_PFN_D3D12CreateDevice)(IUnknown* pAdapter, D3D_FEATURE_LEVEL minimumFeatureLevel, REFIID riid, void** ppDevice); @@ -7577,13 +7727,13 @@ static void QD3D12_InitStreamlineEarly() sl::Preferences pref{}; pref.showConsole = false; - pref.applicationId = 231313132; // Replace with your NVIDIA-assigned application ID for NGX-backed features like DLSS. + pref.applicationId = 0; // Use an NVIDIA-assigned id here if one is issued for the project. + pref.projectId = "d3d2cf72-6f48-4de8-8a2e-091c2a871c0d"; + pref.renderAPI = sl::RenderAPI::eD3D12; pref.engine = sl::EngineType::eCustom; pref.engineVersion = "IceBridge 1.0"; pref.flags |= sl::PreferenceFlags::eUseFrameBasedResourceTagging; pref.flags |= sl::PreferenceFlags::eUseDXGIFactoryProxy; - pref.flags &= ~sl::PreferenceFlags::eAllowOTA; - pref.flags &= ~sl::PreferenceFlags::eLoadDownloadedPlugins; static wchar_t pluginPath[MAX_PATH] = {}; static const wchar_t* pluginPaths[] = { pluginPath }; @@ -7616,12 +7766,79 @@ static void QD3D12_InitStreamlineEarly() g_qd3d12Sl.initialized = true; g_qd3d12Sl.dlssGFeatureLoaded = true; + QD3D12_Log("Streamline initialized for D3D12 with NGX project id %s; OTA/downloaded plugin loading is enabled.", pref.projectId); +} + +static void QD3D12_LogStreamlineFeatureDiagnostics(sl::Feature feature, const char* name) +{ + sl::FeatureVersion version{}; + const sl::Result versionResult = slGetFeatureVersion(feature, version); + if (versionResult == sl::Result::eOk) + { + QD3D12_Log("%s version: SL=%u.%u.%u NGX=%u.%u.%u.", + name, + version.versionSL.major, + version.versionSL.minor, + version.versionSL.build, + version.versionNGX.major, + version.versionNGX.minor, + version.versionNGX.build); + } + else + { + QD3D12_Log("slGetFeatureVersion(%s) failed (%d).", name, int(versionResult)); + } + + sl::FeatureRequirements requirements{}; + const sl::Result requirementsResult = slGetFeatureRequirements(feature, requirements); + if (requirementsResult == sl::Result::eOk) + { + QD3D12_Log("%s requirements: flags=0x%08x requiredTags=%u maxViewports=%u driverDetected=%u.%u.%u driverRequired=%u.%u.%u osDetected=%u.%u.%u osRequired=%u.%u.%u.", + name, + uint32_t(requirements.flags), + requirements.numRequiredTags, + requirements.maxNumViewports, + requirements.driverVersionDetected.major, + requirements.driverVersionDetected.minor, + requirements.driverVersionDetected.build, + requirements.driverVersionRequired.major, + requirements.driverVersionRequired.minor, + requirements.driverVersionRequired.build, + requirements.osVersionDetected.major, + requirements.osVersionDetected.minor, + requirements.osVersionDetected.build, + requirements.osVersionRequired.major, + requirements.osVersionRequired.minor, + requirements.osVersionRequired.build); + + for (uint32_t i = 0; i < requirements.numRequiredTags; ++i) + { + if (!requirements.requiredTags) + { + QD3D12_Log("%s required tag[%u]=.", name, i); + continue; + } + + const sl::BufferType tag = requirements.requiredTags[i]; + QD3D12_Log("%s required tag[%u]=%s(%u).", + name, + i, + sl::getBufferTypeAsStr(tag), + uint32_t(tag)); + } + } + else + { + QD3D12_Log("slGetFeatureRequirements(%s) failed (%d).", name, int(requirementsResult)); + } } static void QD3D12_CheckStreamlineFeatureSupport() { g_qd3d12Sl.dlssSupported = false; g_qd3d12Sl.dlssRrSupported = false; + g_qd3d12Sl.dlssRrOperational = false; + g_qd3d12Sl.dlssRrTagsLogged = false; g_qd3d12Sl.dlssGSupported = false; g_qd3d12Sl.dlssGFeatureLoaded = false; g_qd3d12Sl.reflexSupported = false; @@ -7634,17 +7851,27 @@ static void QD3D12_CheckStreamlineFeatureSupport() adapterInfo.deviceLUID = reinterpret_cast(&luid); adapterInfo.deviceLUIDSizeInBytes = sizeof(luid); + QD3D12_LogStreamlineFeatureDiagnostics(sl::kFeatureDLSS_RR, "DLSS_RR"); + const sl::Result dlssSupport = slIsFeatureSupported(sl::kFeatureDLSS, adapterInfo); g_qd3d12Sl.dlssSupported = (dlssSupport == sl::Result::eOk); if (!g_qd3d12Sl.dlssSupported) QD3D12_Log("slIsFeatureSupported(DLSS) failed (%d).", int(dlssSupport)); const sl::Result rrSupport = slIsFeatureSupported(sl::kFeatureDLSS_RR, adapterInfo); - g_qd3d12Sl.dlssRrSupported = (rrSupport == sl::Result::eOk); + bool dlssRrLoaded = false; + const sl::Result rrLoadedResult = slIsFeatureLoaded(sl::kFeatureDLSS_RR, dlssRrLoaded); + g_qd3d12Sl.dlssRrSupported = + (rrSupport == sl::Result::eOk) && + (rrLoadedResult == sl::Result::eOk) && + dlssRrLoaded; if (!g_qd3d12Sl.dlssRrSupported) { - QD3D12_Log("slIsFeatureSupported(DLSS_RR) failed (%d); disabling Ray Reconstruction.", int(rrSupport)); - g_gl.enableDLSSRayReconstruction = false; + QD3D12_Log("DLSS_RR unavailable: support=%d loadedResult=%d loaded=%d; Ray Reconstruction request remains enabled but unavailable.", + int(rrSupport), + int(rrLoadedResult), + dlssRrLoaded ? 1 : 0); + g_qd3d12Sl.dlssRrOperational = false; } const sl::Result fgSupport = slIsFeatureSupported(sl::kFeatureDLSS_G, adapterInfo); @@ -7688,17 +7915,73 @@ static void QD3D12_StreamlineOnDeviceCreated() static bool QD3D12_WantsDLSSRayReconstruction() { - return false; + return g_gl.enableDLSSRayReconstruction && + g_qd3d12Sl.deviceBound && + g_qd3d12Sl.dlssSupported && + g_qd3d12Sl.dlssRrSupported; } static bool QD3D12_CanUseDLSSRayReconstructionForLighting(const QD3D12Window& w) { return !w.isPbuffer && QD3D12_AllowsGamePresentationFeatures(w) && + g_gl.upscalerBackend == QD3D12_UPSCALER_DLSS && g_gl.cameraState.valid && + w.slOutputBuffers[w.frameIndex] && + w.depthBuffer && + w.velocityBuffers[w.frameIndex] && + w.normalBuffers[w.frameIndex] && + w.specularBuffers[w.frameIndex] && QD3D12_WantsDLSSRayReconstruction(); } +static uint32_t QD3D12_GetDLSSRayReconstructionEligibilityMask(const QD3D12Window& w) +{ + uint32_t mask = 0; + if (w.isPbuffer) + mask |= 1u << 0; + if (!QD3D12_AllowsGamePresentationFeatures(w)) + mask |= 1u << 1; + if (g_gl.upscalerBackend != QD3D12_UPSCALER_DLSS) + mask |= 1u << 2; + if (!g_gl.cameraState.valid) + mask |= 1u << 3; + if (!g_qd3d12Sl.deviceBound) + mask |= 1u << 4; + if (!g_qd3d12Sl.dlssSupported) + mask |= 1u << 5; + if (!g_qd3d12Sl.dlssRrSupported) + mask |= 1u << 6; + if (!w.slOutputBuffers[w.frameIndex]) + mask |= 1u << 7; + if (!w.depthBuffer) + mask |= 1u << 8; + if (!w.velocityBuffers[w.frameIndex]) + mask |= 1u << 9; + if (!w.normalBuffers[w.frameIndex]) + mask |= 1u << 10; + if (!w.specularBuffers[w.frameIndex]) + mask |= 1u << 11; + if (!g_gl.enableDLSSRayReconstruction) + mask |= 1u << 12; + if (g_gl.blockLightingUpscaleInputThisFrame) + mask |= 1u << 13; + if (!g_gl.raytracedLightingReadyThisFrame) + mask |= 1u << 14; + + TextureResource* readLightingTex = g_lightingTexture[g_lightingReadIndex]; + if (!readLightingTex || !readLightingTex->texture) + mask |= 1u << 15; + + return mask; +} + +static bool QD3D12_DLSSRayReconstructionOperational() +{ + return QD3D12_WantsDLSSRayReconstruction() && + g_qd3d12Sl.dlssRrOperational; +} + static bool QD3D12_UseLightingTextureAsUpscaleInput(const QD3D12Window& w) { return QD3D12_CanUseDLSSRayReconstructionForLighting(w) && @@ -7719,9 +8002,9 @@ static sl::Result QD3D12_SetStreamlineCommonConstants(sl::FrameToken& frameToken memcpy(&consts.clipToPrevClip, g_gl.cameraState.clipToPrevClip.m, sizeof(float) * 16); memcpy(&consts.prevClipToClip, g_gl.cameraState.prevClipToClip.m, sizeof(float) * 16); consts.jitterOffset = { g_gl.jitterX, g_gl.jitterY }; - const float motionScaleX = g_currentWindow ? (float)g_currentWindow->renderWidth : 1.0f; - const float motionScaleY = g_currentWindow ? (float)g_currentWindow->renderHeight : 1.0f; - consts.mvecScale = { motionScaleX, motionScaleY }; // motion vectors are written in PreviousUV - CurrentUV + // The G-buffer stores motion as previous UV minus current UV after removing + // projection jitter, so it is already in normalized screen space. + consts.mvecScale = { 1.0f, 1.0f }; consts.cameraPinholeOffset = { 0.0f, 0.0f }; memcpy(&consts.cameraPos, g_gl.cameraState.cameraPos, sizeof(float) * 3); memcpy(&consts.cameraRight, g_gl.cameraState.cameraRight, sizeof(float) * 3); @@ -8085,6 +8368,7 @@ static void QD3D12_InitStreamlineEarly() {} static void QD3D12_StreamlineOnDeviceCreated() {} static bool QD3D12_WantsDLSSRayReconstruction() { return false; } static bool QD3D12_CanUseDLSSRayReconstructionForLighting(const QD3D12Window&) { return false; } +static bool QD3D12_DLSSRayReconstructionOperational() { return false; } static bool QD3D12_UseLightingTextureAsUpscaleInput(const QD3D12Window&) { return false; } static void QD3D12_ConfigureDLSSFrameGeneration(QD3D12Window&, void*) {} static void QD3D12_LogDLSSFrameGenerationPresentResult() {} @@ -8245,7 +8529,9 @@ static void QD3D12_SetPostConstants( float screenParticleSoftDepth = 16.0f, float screenParticleLightCount = 0.0f, float screenParticleEmissiveScale = 0.0f, - float screenParticleOutputMode = 0.0f) + float screenParticleOutputMode = 0.0f, + float screenParticleDepthScaleX = 1.0f, + float screenParticleDepthScaleY = 1.0f) { if (!cl) return; @@ -8286,6 +8572,8 @@ static void QD3D12_SetPostConstants( constants[23] = screenDecalCount; constants[24] = screenParticleEmissiveScale; constants[25] = screenParticleOutputMode; + constants[26] = screenParticleDepthScaleX; + constants[27] = screenParticleDepthScaleY; cl->SetGraphicsRoot32BitConstants(QD3D12_PostRootConstants, QD3D12_PostConstantDwords, constants, 0); } @@ -8731,7 +9019,7 @@ void APIENTRY glDrawScreenSpaceDecalsQD3D12(void) } } -static void QD3D12_DrawScreenSpaceParticleBatch(const QD3D12ScreenSpaceParticleBatch& batch, ID3D12Resource* tlas, bool emissiveOnly) +static void QD3D12_DrawScreenSpaceParticleBatch(const QD3D12ScreenSpaceParticleBatch& batch, ID3D12Resource* tlas, bool emissiveOnly, bool nativeOutput) { const glScreenSpaceParticleVertex_t* verts = batch.vertices.empty() ? nullptr : batch.vertices.data(); const uint32_t vertexCount = (uint32_t)batch.vertices.size(); @@ -8739,6 +9027,7 @@ static void QD3D12_DrawScreenSpaceParticleBatch(const QD3D12ScreenSpaceParticleB const uint32_t blendMode = batch.blendMode; const float softDepth = batch.softDepth; const float emissiveScale = batch.emissiveScale; + const bool drawToNativeOutput = nativeOutput && !emissiveOnly; if (!verts || vertexCount == 0) return; @@ -8750,6 +9039,8 @@ static void QD3D12_DrawScreenSpaceParticleBatch(const QD3D12ScreenSpaceParticleB return; if (!window->sceneColorBuffers[window->frameIndex] || !window->positionBuffers[window->frameIndex] || !window->depthBuffer) return; + if (drawToNativeOutput && !window->backBuffers[window->frameIndex]) + return; if (emissiveOnly && !window->emissiveBuffers[window->frameIndex]) return; if (!QD3D12_EnsureScreenSpaceParticleLightBuffer() || !QD3D12_UpdateScreenSpaceParticleTlasSrv(tlas)) @@ -8766,6 +9057,10 @@ static void QD3D12_DrawScreenSpaceParticleBatch(const QD3D12ScreenSpaceParticleB { QD3D12_TransitionResource(cl, window->emissiveBuffers[frame].Get(), window->emissiveBufferState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET); } + else if (drawToNativeOutput) + { + QD3D12_TransitionResource(cl, window->backBuffers[frame].Get(), window->backBufferState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET); + } else { QD3D12_TransitionResource(cl, window->sceneColorBuffers[frame].Get(), window->sceneColorState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET); @@ -8785,16 +9080,16 @@ static void QD3D12_DrawScreenSpaceParticleBatch(const QD3D12ScreenSpaceParticleB D3D12_VIEWPORT viewport{}; viewport.TopLeftX = 0.0f; viewport.TopLeftY = 0.0f; - viewport.Width = (float)window->renderWidth; - viewport.Height = (float)window->renderHeight; + viewport.Width = (float)(drawToNativeOutput ? window->width : window->renderWidth); + viewport.Height = (float)(drawToNativeOutput ? window->height : window->renderHeight); viewport.MinDepth = 0.0f; viewport.MaxDepth = 1.0f; D3D12_RECT scissor{}; scissor.left = 0; scissor.top = 0; - scissor.right = (LONG)window->renderWidth; - scissor.bottom = (LONG)window->renderHeight; + scissor.right = (LONG)(drawToNativeOutput ? window->width : window->renderWidth); + scissor.bottom = (LONG)(drawToNativeOutput ? window->height : window->renderHeight); D3D12_GPU_DESCRIPTOR_HANDLE particleTextureSrv = g_gl.whiteTexture.srvGpu; TextureResource* particleTexture = textureId ? QD3D12_FindTextureResource(textureId) : nullptr; @@ -8820,9 +9115,12 @@ static void QD3D12_DrawScreenSpaceParticleBatch(const QD3D12ScreenSpaceParticleB ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() }; cl->SetDescriptorHeaps(_countof(heaps), heaps); cl->SetGraphicsRootSignature(g_gl.postRootSig.Get()); - QD3D12_SetPostConstants(cl, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, false, 0.0f, 0.0f, true, softDepth, (float)g_gl.screenSpaceParticleLightCount, emissiveScale, emissiveOnly ? 1.0f : 0.0f); + const float depthScaleX = drawToNativeOutput && window->width > 0 ? (float)window->renderWidth / (float)window->width : 1.0f; + const float depthScaleY = drawToNativeOutput && window->height > 0 ? (float)window->renderHeight / (float)window->height : 1.0f; + const float particleOutputMode = emissiveOnly ? 1.0f : (drawToNativeOutput ? -1.0f : 0.0f); + QD3D12_SetPostConstants(cl, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, false, 0.0f, 0.0f, true, softDepth, (float)g_gl.screenSpaceParticleLightCount, emissiveScale, particleOutputMode, depthScaleX, depthScaleY); cl->SetPipelineState(particlePSO); - D3D12_CPU_DESCRIPTOR_HANDLE particleRtv = emissiveOnly ? CurrentResolvedEmissiveRTV() : CurrentResolvedSceneColorRTV(); + D3D12_CPU_DESCRIPTOR_HANDLE particleRtv = emissiveOnly ? CurrentResolvedEmissiveRTV() : (drawToNativeOutput ? CurrentBackBufferRTV() : CurrentResolvedSceneColorRTV()); cl->OMSetRenderTargets(1, &particleRtv, FALSE, nullptr); cl->RSSetViewports(1, &viewport); cl->RSSetScissorRects(1, &scissor); @@ -8863,14 +9161,26 @@ void APIENTRY glSetScreenSpaceParticleLightsQD3D12(const glRaytracingLight_t* li g_gl.screenSpaceParticleLightCount = count; } -static void QD3D12_DrawQueuedScreenSpaceParticles(ID3D12Resource* tlas) +static void QD3D12_DrawQueuedScreenSpaceParticles(ID3D12Resource* tlas, bool nativeOutput = false) { if (g_gl.screenSpaceParticleBatches.empty()) return; + ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); + QD3D12ScopedPixEvent particlePixEvent( + cl, + nativeOutput + ? L"QD3D12 Draw Screen-Space Particles To BackBuffer" + : L"QD3D12 Draw Screen-Space Particles To Scene Color"); + QD3D12_PIX_MARKERF( + cl, + L"QD3D12 screen-space particle batches=%u nativeOutput=%d", + (uint32_t)g_gl.screenSpaceParticleBatches.size(), + nativeOutput ? 1 : 0); + for (const QD3D12ScreenSpaceParticleBatch& batch : g_gl.screenSpaceParticleBatches) { - QD3D12_DrawScreenSpaceParticleBatch(batch, tlas, false); + QD3D12_DrawScreenSpaceParticleBatch(batch, tlas, false, nativeOutput); } g_gl.screenSpaceParticleBatches.clear(); } @@ -8882,7 +9192,7 @@ static void QD3D12_DrawQueuedScreenSpaceParticleEmissive(ID3D12Resource* tlas) for (const QD3D12ScreenSpaceParticleBatch& batch : g_gl.screenSpaceParticleBatches) { - QD3D12_DrawScreenSpaceParticleBatch(batch, tlas, true); + QD3D12_DrawScreenSpaceParticleBatch(batch, tlas, true, false); } } @@ -8891,6 +9201,7 @@ static ID3D12Resource* QD3D12_PrepareStreamlineOutputForWrite(QD3D12Window& w) ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); if (!cl) return nullptr; + QD3D12_PIX_EVENT(cl, L"QD3D12 Prepare Streamline Output UAV"); ID3D12Resource* output = w.slOutputBuffers[w.frameIndex].Get(); if (!output) @@ -8917,6 +9228,7 @@ static bool QD3D12_CopyStreamlineOutputToBackBuffer( ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); if (!cl || !w.slOutputBuffers[w.frameIndex] || !g_gl.postCopyPSO) return false; + QD3D12_PIX_EVENT(cl, L"QD3D12 Copy Streamline Output To BackBuffer"); QD3D12_TransitionResource( cl, @@ -8971,6 +9283,7 @@ static bool QD3D12_FinalBlitToBackBuffer( ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); if (!cl || !g_gl.postCopyPSO) return false; + QD3D12_PIX_EVENT(cl, L"QD3D12 Final Blit To BackBuffer"); QD3D12_TransitionResource( cl, @@ -8983,6 +9296,7 @@ static bool QD3D12_FinalBlitToBackBuffer( if (toneMapInput) { + QD3D12_PIX_MARKER(cl, L"QD3D12 Final Blit HDR tone-map attempt"); // HDR inputs get reduced to a brightest-pixel white point, then tone mapped. // If that path is unavailable, fall through to the old copy/sharpen path. if (QD3D12_ToneMapFullscreenPass(w, inputSrv, CurrentBackBufferRTV(), outputViewport, outputScissor)) @@ -9021,6 +9335,7 @@ static bool QD3D12_RunInternalTAA(QD3D12Window& w, D3D12_GPU_DESCRIPTOR_HANDLE i if (!QD3D12_ShouldRunInternalTAA(w)) return false; + QD3D12_PIX_EVENT(cl, L"QD3D12 Internal TAA"); QD3D12_TransitionResource(cl, w.taaBuffers[frame].Get(), w.taaBufferState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET); QD3D12_TransitionResource(cl, w.velocityBuffers[frame].Get(), w.velocityBufferState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); @@ -9134,6 +9449,7 @@ static void QD3D12_RunRayAIDenoiseIfEnabled(ID3D12GraphicsCommandList* cl, QD3D1 if (!g_gl.enableRayAIDenoise) return; + QD3D12_PIX_MARKER(cl, L"QD3D12 RayAI denoise requested"); // DLSS Ray Reconstruction is evaluated in QD3D12_RunUpscalerOrBlit(), because // Streamline wants the noisy lighting input, depth, motion vectors, albedo, and @@ -9148,6 +9464,7 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); if (!cl) return; + QD3D12_PIX_EVENT(cl, L"QD3D12 Upscale Or Final Blit"); QD3D12_UpdateCameraInfoFromCurrentMatrices(); @@ -9157,12 +9474,14 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) ID3D12Resource* upscaleInputResource = w.sceneColorBuffers[w.frameIndex].Get(); D3D12_GPU_DESCRIPTOR_HANDLE upscaleInputSrv = w.sceneColorSrvGpu[w.frameIndex]; + D3D12_RESOURCE_STATES upscaleInputState = w.sceneColorState[w.frameIndex]; TextureResource* readLightingTex = g_lightingTexture[g_lightingReadIndex]; if (useLightingUpscaleInput && readLightingTex && readLightingTex->texture) { upscaleInputResource = readLightingTex->texture.Get(); upscaleInputSrv = readLightingTex->srvGpu; + upscaleInputState = g_lightingTextureState[g_lightingReadIndex]; } D3D12_VIEWPORT outputViewport{}; @@ -9179,9 +9498,23 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) outputScissor.right = (LONG)w.width; outputScissor.bottom = (LONG)w.height; + const uint32_t rrEligibilityMask = QD3D12_GetDLSSRayReconstructionEligibilityMask(w); + QD3D12_PIX_MARKERF( + cl, + L"QD3D12 DLSS_RR eligibility mask=0x%08X backend=%d rrWanted=%d rrSupported=%d rrOperational=%d useLightingInput=%d lightingReady=%d blocked=%d", + rrEligibilityMask, + g_gl.upscalerBackend, + QD3D12_WantsDLSSRayReconstruction() ? 1 : 0, + g_qd3d12Sl.dlssRrSupported ? 1 : 0, + g_qd3d12Sl.dlssRrOperational ? 1 : 0, + useLightingUpscaleInput ? 1 : 0, + g_gl.raytracedLightingReadyThisFrame ? 1 : 0, + g_gl.blockLightingUpscaleInputThisFrame ? 1 : 0); + #if defined(QD3D12_ENABLE_STREAMLINE) if (haveTemporalCameraInputs && useLightingUpscaleInput && g_gl.upscalerBackend == QD3D12_UPSCALER_DLSS && g_qd3d12Sl.deviceBound && w.slOutputBuffers[w.frameIndex]) { + QD3D12_PIX_EVENT(cl, L"QD3D12 Streamline DLSS Ray Reconstruction"); sl::FrameToken* frameToken = nullptr; const sl::Result frameResult = QD3D12_GetStreamlineFrameToken(frameToken); if (frameResult == sl::Result::eOk && frameToken) @@ -9200,14 +9533,14 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) ID3D12Resource* streamlineOutputResource = QD3D12_PrepareStreamlineOutputForWrite(w); - sl::Resource colorIn = { sl::ResourceType::eTex2d, upscaleInputResource, nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; - sl::Resource colorOut = { sl::ResourceType::eTex2d, streamlineOutputResource, nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_UNORDERED_ACCESS) }; - sl::Resource depth = { sl::ResourceType::eTex2d, w.depthBuffer.Get(), nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; - sl::Resource mvec = { sl::ResourceType::eTex2d, w.velocityBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; - sl::Resource specularMvec = { sl::ResourceType::eTex2d, w.velocityBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; - sl::Resource diffuseAlbedo = { sl::ResourceType::eTex2d, w.sceneColorBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; - sl::Resource specularAlbedo = { sl::ResourceType::eTex2d, w.specularBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; - sl::Resource normalRoughness = { sl::ResourceType::eTex2d, w.normalBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) }; + sl::Resource colorIn = { sl::ResourceType::eTex2d, upscaleInputResource, nullptr, nullptr, uint32_t(upscaleInputState) }; + sl::Resource colorOut = { sl::ResourceType::eTex2d, streamlineOutputResource, nullptr, nullptr, uint32_t(w.slOutputState[w.frameIndex]) }; + sl::Resource depth = { sl::ResourceType::eTex2d, w.depthBuffer.Get(), nullptr, nullptr, uint32_t(w.depthState) }; + sl::Resource mvec = { sl::ResourceType::eTex2d, w.velocityBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(w.velocityBufferState[w.frameIndex]) }; + sl::Resource diffuseAlbedo = { sl::ResourceType::eTex2d, w.sceneColorBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(w.sceneColorState[w.frameIndex]) }; + sl::Resource specularMvec = { sl::ResourceType::eTex2d, w.velocityBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(w.velocityBufferState[w.frameIndex]) }; + sl::Resource specularAlbedo = { sl::ResourceType::eTex2d, w.specularBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(w.specularBufferState[w.frameIndex]) }; + sl::Resource normalRoughness = { sl::ResourceType::eTex2d, w.normalBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(w.normalBufferState[w.frameIndex]) }; sl::ResourceTag tags[] = { @@ -9215,12 +9548,19 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) sl::ResourceTag{ &colorOut, sl::kBufferTypeScalingOutputColor, sl::ResourceLifecycle::eOnlyValidNow, &outputExtent }, sl::ResourceTag{ &depth, sl::kBufferTypeDepth, sl::ResourceLifecycle::eValidUntilPresent, &renderExtent }, sl::ResourceTag{ &mvec, sl::kBufferTypeMotionVectors, sl::ResourceLifecycle::eOnlyValidNow, &renderExtent }, - sl::ResourceTag{ &specularMvec, sl::kBufferTypeSpecularMotionVectors, sl::ResourceLifecycle::eOnlyValidNow, &renderExtent }, sl::ResourceTag{ &diffuseAlbedo, sl::kBufferTypeAlbedo, sl::ResourceLifecycle::eOnlyValidNow, &renderExtent }, + sl::ResourceTag{ &specularMvec, sl::kBufferTypeSpecularMotionVectors, sl::ResourceLifecycle::eOnlyValidNow, &renderExtent }, sl::ResourceTag{ &specularAlbedo, sl::kBufferTypeSpecularAlbedo, sl::ResourceLifecycle::eOnlyValidNow, &renderExtent }, sl::ResourceTag{ &normalRoughness, sl::kBufferTypeNormalRoughness, sl::ResourceLifecycle::eOnlyValidNow, &renderExtent } }; + if (!g_qd3d12Sl.dlssRrTagsLogged) + { + QD3D12_LogStreamlineResourceTags("DLSS_RR submitted", tags, _countof(tags)); + g_qd3d12Sl.dlssRrTagsLogged = true; + } + + QD3D12_PIX_MARKER(cl, L"QD3D12 DLSS_RR set resource tags"); slSetTagForFrame(*frameToken, g_qd3d12Sl.viewport, tags, _countof(tags), cl); sl::DLSSOptions dlssOptions{}; @@ -9235,6 +9575,7 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) const sl::Result dlssOptionsResult = slDLSSSetOptions(g_qd3d12Sl.viewport, dlssOptions); if (dlssOptionsResult != sl::Result::eOk) { + g_qd3d12Sl.dlssRrOperational = false; QD3D12_Log("slDLSSSetOptions for DLSS_RR failed (%d), falling back.", int(dlssOptionsResult)); } else @@ -9256,28 +9597,37 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) const sl::Result rrOptionsResult = slDLSSDSetOptions(g_qd3d12Sl.viewport, rrOptions); if (rrOptionsResult != sl::Result::eOk) { + g_qd3d12Sl.dlssRrOperational = false; QD3D12_Log("slDLSSDSetOptions failed (%d), falling back.", int(rrOptionsResult)); } else { + QD3D12_PIX_MARKER(cl, L"QD3D12 DLSS_RR set constants"); const sl::Result constResult = QD3D12_SetStreamlineCommonConstants(*frameToken); if (constResult != sl::Result::eOk) { + g_qd3d12Sl.dlssRrOperational = false; QD3D12_Log("slSetConstants for DLSS_RR failed (%d), falling back.", int(constResult)); } else { const sl::BaseStructure* inputs[] = { &g_qd3d12Sl.viewport }; + QD3D12_PIX_MARKER(cl, L"QD3D12 DLSS_RR evaluate feature"); const sl::Result evalResult = slEvaluateFeature(sl::kFeatureDLSS_RR, *frameToken, inputs, _countof(inputs), cl); if (evalResult == sl::Result::eOk) { if (QD3D12_CopyStreamlineOutputToBackBuffer(w, outputViewport, outputScissor, true)) + { + g_qd3d12Sl.dlssRrOperational = true; return; + } + g_qd3d12Sl.dlssRrOperational = false; QD3D12_Log("DLSS_RR succeeded but copying Streamline output to backbuffer failed; falling back."); } else { + g_qd3d12Sl.dlssRrOperational = false; QD3D12_Log("slEvaluateFeature(DLSS_RR) failed (%d), falling back.", int(evalResult)); } } @@ -9286,12 +9636,19 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) } else if (frameResult != sl::Result::eOk) { + g_qd3d12Sl.dlssRrOperational = false; QD3D12_Log("slGetNewFrameToken for DLSS_RR failed (%d), falling back.", int(frameResult)); } + else + { + g_qd3d12Sl.dlssRrOperational = false; + QD3D12_Log("slGetNewFrameToken for DLSS_RR returned no frame token, falling back."); + } } if (haveTemporalCameraInputs && g_gl.upscalerBackend == QD3D12_UPSCALER_DLSS && g_qd3d12Sl.deviceBound && w.slOutputBuffers[w.frameIndex]) { + QD3D12_PIX_EVENT(cl, L"QD3D12 Streamline DLSS Super Resolution"); sl::FrameToken* frameToken = nullptr; const sl::Result frameResult = QD3D12_GetStreamlineFrameToken(frameToken); if (frameResult == sl::Result::eOk && frameToken) @@ -9323,6 +9680,7 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) sl::ResourceTag{ &mvec, sl::kBufferTypeMotionVectors, sl::ResourceLifecycle::eOnlyValidNow, &renderExtent } }; + QD3D12_PIX_MARKER(cl, L"QD3D12 DLSS set resource tags"); slSetTagForFrame(*frameToken, g_qd3d12Sl.viewport, tags, _countof(tags), cl); sl::DLSSOptions options{}; @@ -9343,6 +9701,7 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) else { const sl::BaseStructure* inputs[] = { &g_qd3d12Sl.viewport }; + QD3D12_PIX_MARKER(cl, L"QD3D12 DLSS evaluate feature"); const sl::Result evalResult = slEvaluateFeature(sl::kFeatureDLSS, *frameToken, inputs, _countof(inputs), cl); if (evalResult == sl::Result::eOk) { @@ -9365,6 +9724,7 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) g_gl.upscalerBackend == QD3D12_UPSCALER_FSR && !QD3D12_IsDLAAQuality(g_gl.upscalerQuality)) { + QD3D12_PIX_EVENT(cl, L"QD3D12 FSR Upscale"); QD3D12_EnsureFfxUpscaleContext(w); if (g_qd3d12Ffx.upscaleContextValid) @@ -9407,6 +9767,7 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) #endif D3D12_GPU_DESCRIPTOR_HANDLE finalInputSrv = upscaleInputSrv; + QD3D12_PIX_MARKER(cl, L"QD3D12 fallback internal TAA/final blit path"); QD3D12_RunInternalTAA(w, upscaleInputSrv, finalInputSrv); QD3D12_FinalBlitToBackBuffer(w, finalInputSrv, outputViewport, outputScissor, useLightingUpscaleInput); } @@ -9850,6 +10211,37 @@ static void QD3D12_CreateRTVsForWindow(QD3D12Window& w) true); } + auto NameFrameResource = [&](ID3D12Resource* res, const wchar_t* baseName, UINT frame) + { + if (!res || !baseName) + return; + + wchar_t name[128]; + swprintf_s(name, L"%ls frame %u", baseName, frame); + QD3D12_SetObjectName(res, name); + }; + + for (UINT i = 0; i < QD3D12_FrameCount; ++i) + { + NameFrameResource(w.sceneColorBuffers[i].Get(), L"QD3D12 Scene Color", i); + NameFrameResource(w.sceneColorMsaaBuffers[i].Get(), L"QD3D12 Scene Color MSAA", i); + NameFrameResource(w.normalBuffers[i].Get(), L"QD3D12 Normal Roughness", i); + NameFrameResource(w.normalMsaaBuffers[i].Get(), L"QD3D12 Normal Roughness MSAA", i); + NameFrameResource(w.positionBuffers[i].Get(), L"QD3D12 Position", i); + NameFrameResource(w.positionMsaaBuffers[i].Get(), L"QD3D12 Position MSAA", i); + NameFrameResource(w.velocityBuffers[i].Get(), L"QD3D12 Motion Vectors", i); + NameFrameResource(w.velocityMsaaBuffers[i].Get(), L"QD3D12 Motion Vectors MSAA", i); + NameFrameResource(w.emissiveBuffers[i].Get(), L"QD3D12 Emissive", i); + NameFrameResource(w.emissiveMsaaBuffers[i].Get(), L"QD3D12 Emissive MSAA", i); + NameFrameResource(w.specularBuffers[i].Get(), L"QD3D12 Specular Albedo", i); + NameFrameResource(w.specularMsaaBuffers[i].Get(), L"QD3D12 Specular Albedo MSAA", i); + NameFrameResource(w.backBuffers[i].Get(), L"QD3D12 Back Buffer", i); + NameFrameResource(w.slOutputBuffers[i].Get(), L"QD3D12 Streamline Output", i); + NameFrameResource(w.taaBuffers[i].Get(), L"QD3D12 Internal TAA History", i); + NameFrameResource(w.toneMapTileMaxBuffers[i].Get(), L"QD3D12 ToneMap Tile Max", i); + NameFrameResource(w.toneMapSceneMaxBuffers[i].Get(), L"QD3D12 ToneMap Scene Max", i); + } + auto CreateTextureSrv = [&](ID3D12Resource* res, DXGI_FORMAT format, UINT& srvIndex, D3D12_CPU_DESCRIPTOR_HANDLE& cpu, D3D12_GPU_DESCRIPTOR_HANDLE& gpu) { @@ -9988,6 +10380,8 @@ void QD3D12_CreateDSVForWindow(QD3D12Window& w) CreateDepthTexture(w.renderWidth, w.renderHeight, 1, w.depthBuffer); CreateDepthTexture(w.width, w.height, 1, w.nativeDepthBuffer); + QD3D12_SetObjectName(w.depthBuffer.Get(), L"QD3D12 Scene Depth"); + QD3D12_SetObjectName(w.nativeDepthBuffer.Get(), L"QD3D12 Native Output Depth"); D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc{}; dsvDesc.Format = QD3D12_DepthDsvFormat; @@ -10000,6 +10394,7 @@ void QD3D12_CreateDSVForWindow(QD3D12Window& w) if (QD3D12_GBufferMsaaEnabled()) { CreateDepthTexture(w.renderWidth, w.renderHeight, QD3D12_GBufferSampleCount(), w.depthMsaaBuffer); + QD3D12_SetObjectName(w.depthMsaaBuffer.Get(), L"QD3D12 Scene Depth MSAA"); D3D12_DEPTH_STENCIL_VIEW_DESC msaaDsvDesc{}; msaaDsvDesc.Format = QD3D12_DepthDsvFormat; @@ -11225,6 +11620,8 @@ void QD3D12_BeginFrame() g_gl.blockLightingUpscaleInputThisFrame = false; g_gl.screenSpaceParticleBatches.clear(); g_gl.screenSpaceParticleLightCount = 0; + g_gl.deferScreenSpaceParticlesToEndFrame = false; + g_gl.deferredScreenSpaceParticleTlas.Reset(); QD3D12_UpdateViewportState(); FrameResources& fr = w.frames[w.frameIndex]; @@ -11289,6 +11686,22 @@ void QD3D12_EndFrame() QD3D12_RunUpscalerOrBlit(w); } + if (g_gl.deferScreenSpaceParticlesToEndFrame && !g_gl.screenSpaceParticleBatches.empty()) + { + ID3D12Resource* particleTlas = g_gl.deferredScreenSpaceParticleTlas.Get(); + if (particleTlas) + { + QD3D12_DrawQueuedScreenSpaceParticles(particleTlas, true); + } + else + { + QD3D12_PIX_MARKER(g_gl.cmdList.Get(), L"QD3D12 deferred screen-space particles skipped: missing TLAS"); + g_gl.screenSpaceParticleBatches.clear(); + } + } + g_gl.deferScreenSpaceParticlesToEndFrame = false; + g_gl.deferredScreenSpaceParticleTlas.Reset(); + // Do the nonblocking mipchain handoff after the frame's draws/blit are recorded. // This keeps first-use texture uploads to mip 0 and spreads completed mip uploads. if (allowGamePresentationFeatures) @@ -11638,6 +12051,9 @@ static TextureResource* QD3D12_EnsureLightingTexture(int width, int height, UINT D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, nullptr, IID_PPV_ARGS(&lightingTex->texture))); + wchar_t lightingName[96]; + swprintf_s(lightingName, L"QD3D12 Path Traced Lighting HDR slot %u", slot); + QD3D12_SetObjectName(lightingTex->texture.Get(), lightingName); D3D12_SHADER_RESOURCE_VIEW_DESC srv{}; srv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; @@ -18003,6 +18419,7 @@ static void QD3D12_ResolveSceneToOutputAndEnterNativePhase(QD3D12Window& w) ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); if (!cl) return; + QD3D12_PIX_EVENT(cl, L"QD3D12 Resolve Scene To Output"); QD3D12_ResolveGBufferForCurrentFrame(w); @@ -18065,6 +18482,7 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) return; QD3D12_EnsureFrameOpen(); + QD3D12_PIX_MARKER(g_gl.cmdList.Get(), L"QD3D12 glLightScene begin"); const UINT writeLightingIndex = g_lightingWriteIndex % QD3D12_LightingTextureCount; const UINT previousLightingIndex = writeLightingIndex ^ 1u; @@ -18079,10 +18497,12 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) // Scene/TLAS ownership is now per render world. Querying the scene TLAS here // also builds dirty shared BLAS resources and this scene's TLAS before we // touch the window G-buffer state. + QD3D12_PIX_MARKER(g_gl.cmdList.Get(), L"QD3D12 glLightScene resolve scene TLAS"); ID3D12Resource* tlas = glRaytracingGetTopLevelASForScene(sceneHandle); if (!tlas) return; + QD3D12_PIX_MARKER(g_gl.cmdList.Get(), L"QD3D12 glLightScene flush and resolve GBuffer"); QD3D12_FlushQueuedBatches(); QD3D12_ResolveGBufferForCurrentFrame(*window); QD3D12_DrawQueuedScreenSpaceParticleEmissive(tlas); @@ -18091,84 +18511,108 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) ID3D12Resource* sceneColor = window->sceneColorBuffers[frameIndex].Get(); ID3D12Resource* sceneNormal = window->normalBuffers[frameIndex].Get(); ID3D12Resource* scenePosition = window->positionBuffers[frameIndex].Get(); + ID3D12Resource* sceneVelocity = window->velocityBuffers[frameIndex].Get(); ID3D12Resource* sceneEmissive = window->emissiveBuffers[frameIndex].Get(); ID3D12Resource* sceneSpecular = window->specularBuffers[frameIndex].Get(); ID3D12Resource* sceneDepth = window->depthBuffer.Get(); - if (!sceneColor || !sceneNormal || !scenePosition || !sceneEmissive || !sceneSpecular || !sceneDepth) + if (!sceneColor || !sceneNormal || !scenePosition || !sceneVelocity || !sceneEmissive || !sceneSpecular || !sceneDepth) return; - QD3D12_TransitionResource( - cl, - sceneColor, - window->sceneColorState[frameIndex], - D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); - - QD3D12_TransitionResource( - cl, - sceneNormal, - window->normalBufferState[frameIndex], - D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); - - QD3D12_TransitionResource( - cl, - scenePosition, - window->positionBufferState[frameIndex], - D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); - - QD3D12_TransitionResource( - cl, - sceneEmissive, - window->emissiveBufferState[frameIndex], - D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); - - QD3D12_TransitionResource( - cl, - sceneSpecular, - window->specularBufferState[frameIndex], - D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); - - QD3D12_TransitionResource( - cl, - sceneDepth, - window->depthState, - D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); - - // The external DXR module records and executes its own command list. Because - // of that, the scene input transitions must be submitted on the main command - // list before we call into glRaytracingLightingExecuteForScene. - if (g_lightingTextureState[writeLightingIndex] != D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) { + QD3D12_PIX_EVENT(cl, L"QD3D12 glLightScene GBuffer SRV transitions"); QD3D12_TransitionResource( cl, - writeLightingTex->texture.Get(), - g_lightingTextureState[writeLightingIndex], - D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + sceneColor, + window->sceneColorState[frameIndex], + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); - g_lightingTextureState[writeLightingIndex] = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + QD3D12_TransitionResource( + cl, + sceneNormal, + window->normalBufferState[frameIndex], + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + + QD3D12_TransitionResource( + cl, + scenePosition, + window->positionBufferState[frameIndex], + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + + QD3D12_TransitionResource( + cl, + sceneVelocity, + window->velocityBufferState[frameIndex], + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + + QD3D12_TransitionResource( + cl, + sceneEmissive, + window->emissiveBufferState[frameIndex], + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + + QD3D12_TransitionResource( + cl, + sceneSpecular, + window->specularBufferState[frameIndex], + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + + QD3D12_TransitionResource( + cl, + sceneDepth, + window->depthState, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + + // The external DXR module records and executes its own command list. Because + // of that, the scene input transitions must be submitted on the main command + // list before we call into glRaytracingLightingExecuteForScene. + if (g_lightingTextureState[writeLightingIndex] != D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) + { + QD3D12_TransitionResource( + cl, + writeLightingTex->texture.Get(), + g_lightingTextureState[writeLightingIndex], + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + + g_lightingTextureState[writeLightingIndex] = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + } } + QD3D12_PIX_MARKER(cl, L"QD3D12 glLightScene submit raster work before DXR"); QD3D12_ExecuteMainCommandListForRaytracingHandoff(*window); cl = g_gl.cmdList.Get(); + QD3D12_PIX_MARKER(cl, L"QD3D12 glLightScene setup lighting constants and pass desc"); QD3D12_UpdateCameraInfoFromCurrentMatrices(); + QD3D12_UpdateRaytracingLightingCameraFromCameraState(); const bool useDLSSRayReconstruction = QD3D12_CanUseDLSSRayReconstructionForLighting(*window); + const bool useExternalRayReconstruction = + useDLSSRayReconstruction && + !g_gl.blockLightingUpscaleInputThisFrame; + QD3D12_PIX_MARKERF( + cl, + L"QD3D12 glLightScene DLSS_RR mask=0x%08X canUse=%d operational=%d externalThisFrame=%d particles=%u", + QD3D12_GetDLSSRayReconstructionEligibilityMask(*window), + useDLSSRayReconstruction ? 1 : 0, + QD3D12_DLSSRayReconstructionOperational() ? 1 : 0, + useExternalRayReconstruction ? 1 : 0, + (uint32_t)g_gl.screenSpaceParticleBatches.size()); const uint32_t activeMaxBounces = std::max(1u, g_gl.pathTracingMaxBounces); - const uint32_t raySpp = useDLSSRayReconstruction + const uint32_t raySpp = useExternalRayReconstruction ? std::max(1u, g_gl.pathTracingSamplesPerPixel) : std::max(1u, g_gl.pathTracingFallbackSamplesPerPixel); - // Keep Streamline/DLSS RR on the raw lighting path. The noise fix is now in - // the DXR sampling itself: stable GI seeds, deterministic area-light samples, - // and non-jittered volume slices. Feeding RR a prefiltered image can make the - // temporal reconstruction amplify blur/speckles. - glRaytracingLightingSetExternalDenoiser(useDLSSRayReconstruction ? 1 : 0); + // Feed raw HDR GI only after DLSS_RR has successfully evaluated. Until then, + // keep the internal temporal/a-trous pass as a fallback so ordinary DLSS/final + // blits never see the unfiltered 1-SPP GI buffer. + glRaytracingLightingSetExternalDenoiser(useExternalRayReconstruction ? 1 : 0); + glRaytracingLightingSetHistoryReset(g_gl.motionHistoryReset ? 1 : 0); glRaytracingLightingSetPathTracingOptions( raySpp, activeMaxBounces, - useDLSSRayReconstruction ? 0 : 1, - useDLSSRayReconstruction ? 0.0f : 0.75f); + useExternalRayReconstruction ? 0 : 1, + useExternalRayReconstruction ? 0.0f : 0.75f); glRaytracingLightingSetEmissiveInput(sceneEmissive, QD3D12_EmissiveFormat); glRaytracingLightingSetSpecularInput(sceneSpecular, QD3D12_SpecularAlbedoFormat); @@ -18182,6 +18626,9 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) pass.positionTexture = scenePosition; pass.positionFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; + pass.velocityTexture = sceneVelocity; + pass.velocityFormat = QD3D12_VelocityFormat; + pass.depthTexture = sceneDepth; pass.depthFormat = QD3D12_DepthSrvFormat; @@ -18191,6 +18638,9 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) pass.width = (uint32_t)width; pass.height = (uint32_t)height; + QD3D12_PIX_MARKER(cl, useExternalRayReconstruction + ? L"QD3D12 glLightScene dispatch raw lighting for DLSS_RR" + : L"QD3D12 glLightScene dispatch lighting with internal denoiser"); if (!glRaytracingLightingExecuteForScene(&pass, sceneHandle)) { if (QD3D12_GBufferMsaaEnabled()) @@ -18223,6 +18673,12 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) window->positionBufferState[frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET); + QD3D12_TransitionResource( + cl, + sceneVelocity, + window->velocityBufferState[frameIndex], + D3D12_RESOURCE_STATE_RENDER_TARGET); + QD3D12_TransitionResource( cl, sceneEmissive, @@ -18254,13 +18710,17 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) QD3D12_RunRayAIDenoiseIfEnabled(cl, *window, writeLightingTex->texture.Get()); - if (!g_gl.screenSpaceParticleBatches.empty()) + if (!g_gl.screenSpaceParticleBatches.empty() && !useDLSSRayReconstruction) { // The particle pass is a blended graphics pass, so it needs a render-target // scene color surface. Keep particle frames on the composited scene-color // path instead of feeding the raw lighting texture straight to RR/upscale. g_gl.blockLightingUpscaleInputThisFrame = true; } + else if (!g_gl.screenSpaceParticleBatches.empty()) + { + QD3D12_PIX_MARKER(cl, L"QD3D12 screen-space particles present but DLSS_RR lighting input remains enabled"); + } // If the denoiser changes the written texture state, restore it to a // shader-readable state before this slot becomes the next read target. @@ -18278,8 +18738,10 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) g_gl.raytracedLightingReadyThisFrame = true; - if (!QD3D12_UseLightingTextureAsUpscaleInput(*window)) + const bool useLightingUpscaleInputForResolve = QD3D12_UseLightingTextureAsUpscaleInput(*window); + if (!useLightingUpscaleInputForResolve) { + QD3D12_PIX_EVENT(cl, L"QD3D12 glLightScene composite lighting into scene color"); QD3D12_TransitionResource( cl, sceneColor, @@ -18330,7 +18792,16 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) // guide instead of overwriting it here. } - QD3D12_DrawQueuedScreenSpaceParticles(tlas); + if (!useLightingUpscaleInputForResolve) + { + QD3D12_DrawQueuedScreenSpaceParticles(tlas); + } + else + { + g_gl.deferScreenSpaceParticlesToEndFrame = true; + g_gl.deferredScreenSpaceParticleTlas = tlas; + QD3D12_PIX_MARKER(cl, L"QD3D12 defer screen-space particles until EndFrame"); + } QD3D12_ResolveSceneToOutputAndEnterNativePhase(*window); } @@ -18624,13 +19095,31 @@ void QD3D12_EnableRayAIDenoise(int enabled) void QD3D12_EnableDLSSRayReconstruction(int enabled) { - (void)enabled; - const bool newValue = false; + bool newValue = enabled ? true : false; +#if defined(QD3D12_ENABLE_STREAMLINE) + if (newValue && g_qd3d12Sl.deviceBound && (!g_qd3d12Sl.dlssSupported || !g_qd3d12Sl.dlssRrSupported)) + { + QD3D12_Log("DLSS Ray Reconstruction requested but Streamline reports DLSS=%d DLSS_RR=%d; keeping request enabled and waiting for support.", + g_qd3d12Sl.dlssSupported ? 1 : 0, + g_qd3d12Sl.dlssRrSupported ? 1 : 0); + } +#else + newValue = false; +#endif if (g_gl.enableDLSSRayReconstruction == newValue) return; g_gl.enableDLSSRayReconstruction = newValue; +#if defined(QD3D12_ENABLE_STREAMLINE) + g_qd3d12Sl.dlssRrOperational = false; +#endif g_gl.enableRayAIDenoise = false; + if (newValue) + { + g_gl.upscalerBackend = QD3D12_UPSCALER_DLSS; + if (g_gl.upscalerQuality == QD3D12_QUALITY_NATIVE) + g_gl.upscalerQuality = QD3D12_QUALITY_QUALITY; + } g_gl.motionHistoryReset = true; QD3D12_ReconfigureCurrentWindowForUpscalerChange(); } diff --git a/neo/engine/opengl/opengl.h b/neo/engine/opengl/opengl.h index 208b619f..b69fd367 100644 --- a/neo/engine/opengl/opengl.h +++ b/neo/engine/opengl/opengl.h @@ -1714,7 +1714,8 @@ typedef struct glRaytracingLight_s uint32_t iesTextureId; // optional GL texture containing a lat-long IES profile float iesStrength; // 0 disables, 1 uses texture as authored - float iesPad[2]; + int32_t areaNum; // proc/render portal area, -1 if unknown + float iesPad; } glRaytracingLight_t; typedef struct glRaytracingLightingPassDesc_s @@ -1731,6 +1732,9 @@ typedef struct glRaytracingLightingPassDesc_s ID3D12Resource* positionTexture; DXGI_FORMAT positionFormat; + ID3D12Resource* velocityTexture; + DXGI_FORMAT velocityFormat; + ID3D12Resource* outputTexture; DXGI_FORMAT outputFormat; @@ -1841,6 +1845,7 @@ void glRaytracingLightingSetAmbient(float r, float g, floa void glRaytracingLightingSetCameraPosition(float x, float y, float z); void glRaytracingLightingSetInvViewProjMatrix(const float* m16); void glRaytracingLightingSetInvViewMatrix(const float* m16); +void glRaytracingLightingSetHistoryReset(int reset); void glRaytracingLightingSetNormalReconstructSign(float signValue); void glRaytracingLightingEnableSpecular(int enable); void glRaytracingLightingEnableHalfLambert(int enable); @@ -2345,7 +2350,7 @@ extern "C" { void glRaytracingSetMeshMaterialFlags(glRaytracingMeshHandle_t meshHandle, uint32_t materialFlags); void glRaytracingSetMeshGlass(glRaytracingMeshHandle_t meshHandle, int isGlass); void glRaytracingSetMeshAverageColor(glRaytracingMeshHandle_t meshHandle, float r, float g, float b, float a); - void glRaytracingSetMeshProcMetadata(glRaytracingMeshHandle_t meshHandle, uint32_t procFlags, float emissiveR, float emissiveG, float emissiveB, float emissivePower); + void glRaytracingSetMeshProcMetadata(glRaytracingMeshHandle_t meshHandle, uint32_t procFlags, int areaNum, float emissiveR, float emissiveG, float emissiveB, float emissivePower); // For callers that create raytracing instances directly instead of using the // shim's glUpdateTopLevelAceelStructure() helper. Glass meshes must also use diff --git a/neo/engine/renderer/RenderSystem_init.cpp b/neo/engine/renderer/RenderSystem_init.cpp index 656d9bdd..1e8f8a03 100644 --- a/neo/engine/renderer/RenderSystem_init.cpp +++ b/neo/engine/renderer/RenderSystem_init.cpp @@ -190,7 +190,7 @@ idCVar r_showInteractionScissors( "r_showInteractionScissors", "0", CVAR_RENDERE idCVar r_showLightCount( "r_showLightCount", "0", CVAR_RENDERER | CVAR_INTEGER, "1 = colors surfaces based on light count, 2 = also count everything through walls, 3 = also print overdraw", 0, 3, idCmdSystem::ArgCompletion_Integer<0,3> ); idCVar r_showPathTraceProfile( "r_showPathTraceProfile", "0", CVAR_RENDERER | CVAR_INTEGER, "report path tracer GPU and estimated ray workload, 1 = once per second, 2 = every frame", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); idCVar r_pathTraceSecondaryLightBudget( "r_pathTraceSecondaryLightBudget", "0", CVAR_RENDERER | CVAR_INTEGER, "secondary GI light budget, 0 = automatic, -1 = full light list" ); -idCVar r_pathTraceSecondaryLightCompensation( "r_pathTraceSecondaryLightCompensation", "0.18", CVAR_RENDERER | CVAR_FLOAT, "energy compensation for capped secondary GI lights, 0 = none, 1 = full scale" ); +idCVar r_pathTraceSecondaryLightCompensation( "r_pathTraceSecondaryLightCompensation", "0.62", CVAR_RENDERER | CVAR_FLOAT, "energy compensation for capped secondary GI lights, 0 = none, 1 = full scale" ); idCVar r_pathTracePortalWorldCulling( "r_pathTracePortalWorldCulling", "0", CVAR_RENDERER | CVAR_BOOL, "use portal flood to hide/show static world ray tracing instances" ); idCVar r_pathTraceAreaLightCulling( "r_pathTraceAreaLightCulling", "1", CVAR_RENDERER | CVAR_BOOL, "use proc area graph to reduce uploaded path tracing lights" ); idCVar r_pathTraceAreaLightDepth( "r_pathTraceAreaLightDepth", "5", CVAR_RENDERER | CVAR_INTEGER, "proc area neighbor depth for path tracing lights", 0, 8, idCmdSystem::ArgCompletion_Integer<0,8> ); @@ -530,8 +530,8 @@ void R_InitOpenGL( void ) { // Reset our gamma R_SetColorMappings(); - glRaytracingLightingSetExternalDenoiser(1); - glRaytracingLightingSetPathTracingOptions(256, 2, 1, 1.0f); + glRaytracingLightingSetExternalDenoiser(0); + glRaytracingLightingSetPathTracingOptions(1, 2, 1, 1.0f); // Create the DXR worlds. for (int i = 0; i < DXR_WORLD_NUM; i++) diff --git a/neo/engine/renderer/RenderWorld_load.cpp b/neo/engine/renderer/RenderWorld_load.cpp index 1e611118..c6e44bf9 100644 --- a/neo/engine/renderer/RenderWorld_load.cpp +++ b/neo/engine/renderer/RenderWorld_load.cpp @@ -519,7 +519,10 @@ idRenderModel *idRenderWorldLocal::ParseModel( idLexer *src ) { // add the completed surface to the model model->AddSurface( surf ); - if (!surf.shader->IsSky() && surf.shader->IsLitMaterial() && surf.shader->SurfaceCastsShadow()) + const bool raytraceMetadataSurface = + raytraceMeta.valid && + ( raytraceMeta.flags & ( GL_RAYTRACING_PROC_SURFACE_EMISSIVE | GL_RAYTRACING_PROC_SURFACE_UNLIT ) ) != 0; + if ( !surf.shader->IsSky() && ( ( surf.shader->IsLitMaterial() && surf.shader->SurfaceCastsShadow() ) || raytraceMetadataSurface ) ) { idRenderModelStatic* modelStatic = (idRenderModelStatic*)model; dxrWorldModel_t dxrModel; @@ -535,6 +538,7 @@ idRenderModel *idRenderWorldLocal::ParseModel( idLexer *src ) { glRaytracingSetMeshProcMetadata( dxrModel.dxrBottomAcel, raytraceMeta.flags, + raytraceMeta.areaNum, raytraceMeta.emissiveColor[0], raytraceMeta.emissiveColor[1], raytraceMeta.emissiveColor[2], diff --git a/neo/engine/renderer/draw_dx.cpp b/neo/engine/renderer/draw_dx.cpp index d493b854..24a763c1 100644 --- a/neo/engine/renderer/draw_dx.cpp +++ b/neo/engine/renderer/draw_dx.cpp @@ -203,6 +203,32 @@ static void RB_DXRAddUniquePathTraceArea(idList& areas, int areaNum) areas.Append(areaNum); } +static int RB_DXRSingleReferencedLightArea(const idRenderLightLocal* light) +{ + if (!light) + return -1; + + int singleArea = -1; + for (const areaReference_t* ref = light->references; ref; ref = ref->ownerNext) + { + if (!ref->area) + continue; + + const int areaNum = ref->area->areaNum; + if (singleArea < 0) + { + singleArea = areaNum; + continue; + } + if (singleArea != areaNum) + { + return -1; + } + } + + return singleArea; +} + static bool RB_DXRBuildPathTraceLightAreaSet(idRenderWorldLocal* world, idList& areas) { areas.Clear(); @@ -325,6 +351,7 @@ void RB_DXDrawInteractions(void) light.samples = srcLight.noShadows ? 0u : 1u; light.pointRadiusPad = srcLight.noSpecular ? 1.0f : 0.0f; + light.areaNum = RB_DXRSingleReferencedLightArea(vLight->lightDef); const idVec3 axisU = RB_DXRNormalizeVec3Safe(RB_DXRTransformLightVector(srcLight.axis, RB_DXRMakeVec3(1.0f, 0.0f, 0.0f)), RB_DXRMakeVec3(1.0f, 0.0f, 0.0f)); const idVec3 axisV = RB_DXRNormalizeVec3Safe(RB_DXRTransformLightVector(srcLight.axis, RB_DXRMakeVec3(0.0f, 1.0f, 0.0f)), RB_DXRMakeVec3(0.0f, 1.0f, 0.0f)); @@ -346,6 +373,7 @@ void RB_DXDrawInteractions(void) vLight->globalLightOrigin, r, g, b, intensity); + light.areaNum = RB_DXRSingleReferencedLightArea(vLight->lightDef); } else {