From e8c6f824a85a8bc6a3ef0a0c8d9e31cdce5d3c90 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Sat, 2 May 2026 10:11:02 -0700 Subject: [PATCH] Fixed and sped up secondary bounce. --- neo/opengl/gl_d3d12raylight.cpp | 633 ++++++++++++++++++++++++++++++-- neo/opengl/gl_d3d12shim.cpp | 2 +- 2 files changed, 605 insertions(+), 30 deletions(-) diff --git a/neo/opengl/gl_d3d12raylight.cpp b/neo/opengl/gl_d3d12raylight.cpp index 61f0d790..42fdcce5 100644 --- a/neo/opengl/gl_d3d12raylight.cpp +++ b/neo/opengl/gl_d3d12raylight.cpp @@ -72,6 +72,90 @@ static T glRaytracingClamp(T v, T lo, T hi) return (v < lo) ? lo : ((v > hi) ? hi : v); } +static void glRaytracingSetIdentity4x4(float* m) +{ + if (!m) + return; + + memset(m, 0, sizeof(float) * 16); + m[0] = 1.0f; + m[5] = 1.0f; + m[10] = 1.0f; + m[15] = 1.0f; +} + +static int glRaytracingInvertMatrix4x4(const float* m, float* out) +{ + if (!m || !out) + return 0; + + float a[4][8]; + + for (int r = 0; r < 4; ++r) + { + for (int c = 0; c < 4; ++c) + a[r][c] = m[r * 4 + c]; + + for (int c = 0; c < 4; ++c) + a[r][4 + c] = (r == c) ? 1.0f : 0.0f; + } + + for (int col = 0; col < 4; ++col) + { + int pivot = col; + float best = a[col][col] < 0.0f ? -a[col][col] : a[col][col]; + + for (int r = col + 1; r < 4; ++r) + { + const float v = a[r][col] < 0.0f ? -a[r][col] : a[r][col]; + if (v > best) + { + best = v; + pivot = r; + } + } + + if (best <= 1.0e-8f) + return 0; + + if (pivot != col) + { + for (int c = 0; c < 8; ++c) + { + const float tmp = a[col][c]; + a[col][c] = a[pivot][c]; + a[pivot][c] = tmp; + } + } + + const float invPivot = 1.0f / a[col][col]; + for (int c = 0; c < 8; ++c) + a[col][c] *= invPivot; + + for (int r = 0; r < 4; ++r) + { + if (r == col) + continue; + + const float f = a[r][col]; + if (f == 0.0f) + continue; + + for (int c = 0; c < 8; ++c) + a[r][c] -= f * a[col][c]; + } + } + + for (int r = 0; r < 4; ++r) + { + for (int c = 0; c < 4; ++c) + out[r * 4 + c] = a[r][4 + c]; + } + + return 1; +} + + static DXGI_FORMAT glRaytracingGetSrvFormatForDepth(DXGI_FORMAT fmt) { switch (fmt) @@ -1987,6 +2071,7 @@ struct glRaytracingLightingConstants_t { float invViewProj[16]; float invViewMatrix[16]; + float viewProj[16]; float cameraPos[4]; float ambientColor[4]; float screenSize[4]; @@ -2127,10 +2212,19 @@ struct ShadowPayload uint hit; }; +struct BouncePayload +{ + uint hit; + float hitT; + uint materialFlags; + uint pad0; +}; + cbuffer LightingCB : register(b0) { float4x4 gInvViewProj; float4x4 gInvViewMatrix; + float4x4 gViewProj; float4 gCameraPos; float4 gAmbientColor; float4 gScreenSize; @@ -2256,6 +2350,47 @@ void ShadowClosestHit(inout ShadowPayload payload, in BuiltInTriangleIntersectio payload.hit = 1; } +[shader("miss")] +void BounceMiss(inout BouncePayload payload) +{ + payload.hit = 0; + payload.hitT = 0.0; + payload.materialFlags = 0; + payload.pad0 = 0; +} + +[shader("anyhit")] +void BounceAnyHit(inout BouncePayload payload, in BuiltInTriangleIntersectionAttributes attr) +{ + // Secondary diffuse rays should see through the same glass that shadow rays + // see through. This keeps a glass pane from killing all bounced light behind it. + if (CurrentRayHitIsGlass()) + { + IgnoreHit(); + return; + } +} + +[shader("closesthit")] +void BounceClosestHit(inout BouncePayload payload, in BuiltInTriangleIntersectionAttributes attr) +{ + // Fallback for glass geometry that was accidentally built opaque. Correctly + // tagged glass is ignored in BounceAnyHit() above. + if (CurrentRayHitIsGlass()) + { + payload.hit = 0; + payload.hitT = 0.0; + payload.materialFlags = DecodeInstanceMaterialFlags(); + payload.pad0 = 0; + return; + } + + payload.hit = 1; + payload.hitT = RayTCurrent(); + payload.materialFlags = DecodeInstanceMaterialFlags(); + payload.pad0 = 0; +} + float TraceShadow(float3 origin, float3 dir, float maxT) { RayDesc ray; @@ -2280,6 +2415,35 @@ 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) +{ + RayDesc ray; + ray.Origin = origin; + ray.Direction = dir; + ray.TMin = 0.001; + ray.TMax = maxT; + + BouncePayload payload; + payload.hit = 0; + payload.hitT = 0.0; + payload.materialFlags = 0; + payload.pad0 = 0; + + TraceRay( + gSceneBVH, + RAY_FLAG_NONE, + 0xFF, + 1, + 0, + 1, + ray, + payload); + + hitT = payload.hitT; + materialFlags = payload.materialFlags; + return payload.hit != 0; +} + float Hash12(float2 p) { float3 p3 = frac(float3(p.xyx) * 0.1031); @@ -2821,6 +2985,132 @@ float TraceVisibilityBiased(float3 worldPos, float3 N, float3 dir, float maxT) return TraceShadow(origin, dir, max(maxT - gShadowBias * 0.5, 0.001)); } +float3 SafeNormalizeOr(float3 v, float3 fallback) +{ + float lenSq = dot(v, v); + if (lenSq <= 1e-8) + return fallback; + return v * rsqrt(lenSq); +} + +bool ProjectClipToGBufferCandidate( + float4 clipPos, + bool flipY, + float3 rayHitPos, + inout float bestDistSq, + inout uint2 bestPixel, + inout float3 bestPos, + inout float3 bestNormal, + inout float3 bestAlbedo, + inout uint bestGeoFlag) +{ + if (abs(clipPos.w) <= 1e-6) + return false; + + float3 ndc = clipPos.xyz / clipPos.w; + + if (ndc.x < -1.0 || ndc.x > 1.0 || + ndc.y < -1.0 || ndc.y > 1.0 || + ndc.z < 0.0 || ndc.z > 1.0) + { + return false; + } + + float2 uv; + uv.x = ndc.x * 0.5 + 0.5; + uv.y = flipY ? (0.5 - ndc.y * 0.5) : (ndc.y * 0.5 + 0.5); + + if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) + return false; + + int2 ip = int2(uv * gScreenSize.xy); + ip = clamp(ip, int2(0, 0), int2((int)gScreenSize.x - 1, (int)gScreenSize.y - 1)); + + float depth = gDepthTex.Load(int3(ip, 0)); + if (depth <= 0.0 || depth >= 1.0) + return false; + + float4 posSample = gPositionTex.Load(int3(ip, 0)); + float3 gbufPos = posSample.xyz; + float3 delta = gbufPos - rayHitPos; + float distSq = dot(delta, delta); + + if (distSq >= bestDistSq) + return false; + + float4 normalSample = LoadSceneNormal(uint2(ip)); + float3 gbufNormal = SafeNormalizeOr(normalSample.xyz, float3(0.0, 0.0, 1.0)); + float3 gbufAlbedo = saturate(gAlbedoTex.Load(int3(ip, 0)).rgb); + + bestDistSq = distSq; + bestPixel = uint2(ip); + bestPos = gbufPos; + bestNormal = gbufNormal; + bestAlbedo = gbufAlbedo; + bestGeoFlag = DecodeGeometryFlag(posSample.w); + return true; +} + +bool TryFetchGBufferAtRayHit( + float3 rayHitPos, + out uint2 hitPixel, + out float3 hitPos, + out float3 hitNormal, + out float3 hitAlbedo, + out uint hitGeoFlag) +{ + float bestDistSq = 1.0e30; + uint2 bestPixel = uint2(0, 0); + float3 bestPos = rayHitPos; + float3 bestNormal = float3(0.0, 0.0, 1.0); + float3 bestAlbedo = float3(0.5, 0.5, 0.5); + uint bestGeoFlag = GEOMETRY_FLAG_NONE; + + // CPU computes gViewProj from the inverse view-projection matrix. Try both + // matrix-vector orders and both Y conventions so this remains tolerant of + // row/column-major engine uploads and render-target origin conventions. + float4 wpos = float4(rayHitPos, 1.0); + float4 clipA = mul(wpos, gViewProj); + float4 clipB = mul(gViewProj, wpos); + + bool found = false; + found = ProjectClipToGBufferCandidate(clipA, true, rayHitPos, bestDistSq, bestPixel, bestPos, bestNormal, bestAlbedo, bestGeoFlag) || found; + found = ProjectClipToGBufferCandidate(clipA, false, rayHitPos, bestDistSq, bestPixel, bestPos, bestNormal, bestAlbedo, bestGeoFlag) || found; + found = ProjectClipToGBufferCandidate(clipB, true, rayHitPos, bestDistSq, bestPixel, bestPos, bestNormal, bestAlbedo, bestGeoFlag) || found; + found = ProjectClipToGBufferCandidate(clipB, false, rayHitPos, bestDistSq, bestPixel, bestPos, bestNormal, bestAlbedo, bestGeoFlag) || found; + + if (!found) + { + hitPixel = uint2(0, 0); + hitPos = rayHitPos; + hitNormal = bestNormal; + hitAlbedo = bestAlbedo; + hitGeoFlag = GEOMETRY_FLAG_NONE; + return false; + } + + // The TLAS hit is exact, but the material data comes from the camera G-buffer. + // Reject projections that land on an unrelated visible surface. + float viewDist = length(rayHitPos - gCameraPos.xyz); + float maxPositionError = max(24.0, viewDist * 0.035); + if (bestDistSq > maxPositionError * maxPositionError) + { + hitPixel = bestPixel; + hitPos = bestPos; + hitNormal = bestNormal; + hitAlbedo = bestAlbedo; + hitGeoFlag = bestGeoFlag; + return false; + } + + hitPixel = bestPixel; + hitPos = bestPos; + hitNormal = bestNormal; + hitAlbedo = bestAlbedo; + hitGeoFlag = bestGeoFlag; + return true; +} + float3 EstimatePathTracedSky(float3 worldPos, float3 N, inout uint rng) { const float SKY_TMAX = 1000000.0; @@ -2968,6 +3258,256 @@ float3 PathTraceDirectRectLight(float3 worldPos, float3 N, float3 V, float3 base return clamp(Lgt.color * (Lgt.intensity * NdotL * faceTerm * atten * shadow), 0.0, 4.0); } +float3 EstimateFastBounceLight(float3 hitPos, float3 hitN, Light Lgt) +{ + // Secondary-bounce lighting needs to be cheap. The primary pass already casts + // detailed visibility rays. Here we evaluate every active light analytically + // without extra shadow rays so all lights still contribute to GI, but the + // indirect path no longer explodes into many TraceRay() calls per pixel. + if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT) + { + float3 toLight = Lgt.position - hitPos; + float dist = length(toLight); + if (dist <= 0.01) + return 0.0; + + float3 L = toLight / dist; + float atten = ComputePointLightAttenuation(hitPos, Lgt); + if (atten <= 0.0) + return 0.0; + + float wrap = 0.35; + float nDotL = saturate((dot(hitN, L) + wrap) / (1.0 + wrap)); + return Lgt.color * (Lgt.intensity * atten * nDotL); + } + else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT) + { + float3 toLight = Lgt.position - hitPos; + float dist = length(toLight); + if (dist <= 0.01) + return 0.0; + + float3 L = toLight / dist; + float atten = ComputeSpotLightAttenuation(hitPos, Lgt); + if (atten <= 0.0) + return 0.0; + + float wrap = 0.35; + float nDotL = saturate((dot(hitN, L) + wrap) / (1.0 + wrap)); + return Lgt.color * (Lgt.intensity * atten * nDotL); + } + else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT) + { + // Use the rect center for the bounce estimate. It is stable and avoids + // spending another random sample plus visibility ray on secondary hits. + float3 toCenter = Lgt.position - hitPos; + float centerDist = length(toCenter); + if (centerDist <= 0.01) + return 0.0; + + float attenRadius = max(Lgt.radius, 1e-4); + float atten = saturate((attenRadius - centerDist) / attenRadius); + atten = atten * atten; + if (atten <= 0.0) + return 0.0; + + float3 L = toCenter / centerDist; + float nDotL = saturate(dot(hitN, L)); + if (nDotL <= 0.0) + return 0.0; + + float faceTerm = (Lgt.twoSided != 0) + ? abs(dot(-L, Lgt.normal)) + : saturate(dot(-L, Lgt.normal)); + + if (faceTerm <= 0.0) + return 0.0; + + return clamp(Lgt.color * (Lgt.intensity * nDotL * faceTerm * atten), 0.0, 4.0); + } + + return 0.0; +} + +float3 EstimateDirectLightingForBounceHit(uint2 hitPixel, float3 hitPos, float3 hitN, float3 hitV, float3 hitAlbedo, uint hitGeoFlag, inout uint rng) +{ + bool hitIsSkeletal = (hitGeoFlag & GEOMETRY_FLAG_SKELETAL) != 0u; + bool hitIsUnlit = (hitGeoFlag & GEOMETRY_FLAG_UNLIT) != 0u; + + // Treat unlit G-buffer surfaces as emissive-ish for bounce purposes. This is + // useful for light cards / bright UI-like surfaces that deliberately bypass + // the regular lighting pass. + if (hitIsUnlit) + return hitAlbedo; + + // Cheap environment term. The expensive sky visibility probes stay in the + // primary lighting path; doing them again for every secondary hit was a major + // source of the framerate drop. + float upness = saturate(hitN.z * 0.5 + 0.5); + float3 lighting = gAmbientColor.rgb * (gAmbientColor.a * 0.025); + lighting += GetSkyRadiance(hitN) * (0.06 + 0.10 * upness); + + // Still walk the full light list, but do not cast secondary-hit shadow rays. + // That preserves the "all lights bounce" behavior while making the cost + // roughly one extra bounce TraceRay() per indirect path instead of one bounce + // TraceRay() plus many more visibility TraceRay() calls. + [loop] + for (uint i = 0; i < gLightCount; ++i) + { + lighting += EstimateFastBounceLight(hitPos, hitN, gLights[i]); + } + + if (hitIsSkeletal) + lighting *= 1.10; + + // Return outgoing diffuse radiance from the bounce surface. The caller adds + // this as incoming indirect light at the primary surface; primary albedo is + // applied later in RayGen just like direct lighting. + return max(hitAlbedo * max(lighting, 0.0), 0.0); +} +)" +R"( +float3 TraceOneIndirectBouncePath(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, inout uint rng) +{ + const float BOUNCE_TMAX = 1000000.0; + + uint maxIndirectDepth = (gMaxBounces > 1u) ? 1u : 0u; + + // Extra diffuse depths are very expensive because each depth launches another + // DXR ray. Keep the common 1-2 SPP mode to one indirect hit. Higher SPP can + // opt into one additional diffuse depth, capped at two total secondary hits. + if (gSamplesPerPixel >= 4u) + maxIndirectDepth = min(gMaxBounces - 1u, 2u); + + float3 accum = 0.0; + float3 throughput = 1.0; + + float3 pathPos = worldPos; + float3 pathNormal = N; + float3 pathView = V; + uint2 pathPixel = pixel; + + [loop] + for (uint depth = 0; depth < maxIndirectDepth; ++depth) + { + float3 bounceDir = SampleCosineWorld(pathNormal, rng); + float NoD = saturate(dot(pathNormal, bounceDir)); + + float normalBias = lerp(gShadowBias * 3.0, gShadowBias * 0.75, NoD); + float3 bounceOrigin = + pathPos + + pathNormal * normalBias + + bounceDir * (gShadowBias * 0.5); + + float hitT = 0.0; + uint materialFlags = 0; + bool hit = TraceBounce(bounceOrigin, bounceDir, BOUNCE_TMAX, hitT, materialFlags); + + if (!hit) + { + // A miss is ordinary environment lighting for the path. Keep this + // modest because the main pass already has stable direct sky terms. + accum += throughput * GetSkyRadiance(bounceDir) * 0.18; + break; + } + + float3 rayHitPos = bounceOrigin + bounceDir * hitT; + + uint2 hitPixel = pathPixel; + float3 hitPos = rayHitPos; + float3 hitNormal = SafeNormalizeOr(-bounceDir, pathNormal); + float3 hitAlbedo = float3(0.55, 0.55, 0.55); + uint hitGeoFlag = GEOMETRY_FLAG_NONE; + + bool hasGBufferMaterial = TryFetchGBufferAtRayHit( + rayHitPos, + hitPixel, + hitPos, + hitNormal, + hitAlbedo, + hitGeoFlag); + + // 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 + // the ray actually reached. + if (dot(hitNormal, -bounceDir) < 0.0) + hitNormal = -hitNormal; + + float3 hitV = SafeNormalizeOr(-bounceDir, pathView); + float3 bouncedRadiance = EstimateDirectLightingForBounceHit( + hitPixel, + hitPos, + hitNormal, + hitV, + hitAlbedo, + hitGeoFlag, + rng); + + if (!hasGBufferMaterial) + { + // IMPORTANT: this is the path that made some lights look like they + // were not bouncing. The TLAS can hit an off-screen or camera-hidden + // surface, and the old code returned only a tiny sky fallback because + // it could not fetch albedo/normal from the G-buffer. Still shade the + // real ray hit against every active light using a neutral diffuse + // material so point, spot, and rect lights all contribute to bounce. + float3 skyTint = GetSkyRadiance(SafeNormalizeOr(reflect(bounceDir, pathNormal), float3(0.0, 0.0, 1.0))); + bouncedRadiance += skyTint * 0.035; + } + + accum += throughput * bouncedRadiance; + + if ((hitGeoFlag & GEOMETRY_FLAG_UNLIT) != 0u) + break; + + // Cosine-weighted diffuse sampling cancels the Lambertian cosine/pdf term, + // so the path throughput is the surface albedo plus a conservative energy + // scale to keep multiple G-buffer-assisted bounces stable. + throughput *= saturate(hitAlbedo) * 0.68; + + if (max(max(throughput.x, throughput.y), throughput.z) < 0.02) + break; + + pathPos = hitPos; + pathNormal = hitNormal; + pathView = hitV; + pathPixel = hitPixel; + } + + return accum; +} + +float3 EstimatePathTracedIndirectBounce(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, inout uint rng) +{ + if (gMaxBounces <= 1u) + return 0.0; + + // The previous version forced up to four secondary rays per lighting sample, + // which made the pass scale badly with light count and SPP. One indirect ray + // is enough in the common realtime mode because each secondary hit is now + // shaded against every active light. Higher SPP can buy a little more GI + // coverage without tanking the default framerate. + uint indirectRayCount = 1u; + if (gSamplesPerPixel >= 6u && gLightCount > 2u) + indirectRayCount = 2u; + if (gSamplesPerPixel >= 8u && gLightCount > 5u) + indirectRayCount = 3u; + indirectRayCount = min(indirectRayCount, 3u); + + float3 accum = 0.0; + + [loop] + for (uint r = 0; r < indirectRayCount; ++r) + { + accum += TraceOneIndirectBouncePath(pixel, worldPos, N, V, baseAlbedo, rng); + } + + accum /= (float)indirectRayCount; + + const float INDIRECT_STRENGTH = 0.65; + return accum * INDIRECT_STRENGTH; +} + float3 PathTraceLightingSample(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, bool isSkeletal, inout uint rng, out float3 specularAccum) { specularAccum = 0.0; @@ -2993,9 +3533,9 @@ float3 PathTraceLightingSample(uint2 pixel, float3 worldPos, float3 N, float3 V, lightingAccum += skyColor * (0.70 * skyVis); lightingAccum += ambientSkyVis * (skyColorRGB * 0.15); - if (gMaxBounces > 1u && gSamplesPerPixel > 1u) + if (gMaxBounces > 1u) { - lightingAccum += EstimatePathTracedSky(worldPos, N, rng) * 0.20; + lightingAccum += EstimatePathTracedIndirectBounce(pixel, worldPos, N, V, baseAlbedo, rng); } if (isSkeletal) @@ -3109,6 +3649,7 @@ cbuffer LightingCB : register(b0) { float4x4 gInvViewProj; float4x4 gInvViewMatrix; + float4x4 gViewProj; float4 gCameraPos; float4 gAmbientColor; float4 gScreenSize; @@ -3664,11 +4205,14 @@ static int glRaytracingLightingCreateStateObject(void) if (!dxil) return 0; - D3D12_EXPORT_DESC exports[4] = {}; + D3D12_EXPORT_DESC exports[7] = {}; exports[0].Name = L"RayGen"; exports[1].Name = L"ShadowMiss"; exports[2].Name = L"ShadowAnyHit"; exports[3].Name = L"ShadowClosestHit"; + exports[4].Name = L"BounceMiss"; + exports[5].Name = L"BounceAnyHit"; + exports[6].Name = L"BounceClosestHit"; D3D12_DXIL_LIBRARY_DESC libDesc = {}; D3D12_SHADER_BYTECODE libBytecode = {}; @@ -3678,14 +4222,19 @@ static int glRaytracingLightingCreateStateObject(void) libDesc.NumExports = _countof(exports); libDesc.pExports = exports; - D3D12_HIT_GROUP_DESC hitGroup = {}; - hitGroup.HitGroupExport = L"ShadowHitGroup"; - hitGroup.AnyHitShaderImport = L"ShadowAnyHit"; - hitGroup.ClosestHitShaderImport = L"ShadowClosestHit"; - hitGroup.Type = D3D12_HIT_GROUP_TYPE_TRIANGLES; + D3D12_HIT_GROUP_DESC hitGroups[2] = {}; + hitGroups[0].HitGroupExport = L"ShadowHitGroup"; + hitGroups[0].AnyHitShaderImport = L"ShadowAnyHit"; + hitGroups[0].ClosestHitShaderImport = L"ShadowClosestHit"; + hitGroups[0].Type = D3D12_HIT_GROUP_TYPE_TRIANGLES; + + hitGroups[1].HitGroupExport = L"BounceHitGroup"; + hitGroups[1].AnyHitShaderImport = L"BounceAnyHit"; + hitGroups[1].ClosestHitShaderImport = L"BounceClosestHit"; + hitGroups[1].Type = D3D12_HIT_GROUP_TYPE_TRIANGLES; D3D12_RAYTRACING_SHADER_CONFIG shaderConfig = {}; - shaderConfig.MaxPayloadSizeInBytes = sizeof(uint32_t); + shaderConfig.MaxPayloadSizeInBytes = 16; // BouncePayload: uint + float + uint + uint. shaderConfig.MaxAttributeSizeInBytes = 8; D3D12_GLOBAL_ROOT_SIGNATURE globalRS = {}; @@ -3702,7 +4251,11 @@ static int glRaytracingLightingCreateStateObject(void) ++sub; subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP; - subobjects[sub].pDesc = &hitGroup; + subobjects[sub].pDesc = &hitGroups[0]; + ++sub; + + subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP; + subobjects[sub].pDesc = &hitGroups[1]; ++sub; subobjects[sub].Type = D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_SHADER_CONFIG; @@ -3717,9 +4270,17 @@ static int glRaytracingLightingCreateStateObject(void) subobjects[sub].pDesc = &localRS; ++sub; - LPCWSTR localExports[] = { L"RayGen", L"ShadowMiss", L"ShadowHitGroup" }; + LPCWSTR localExports[] = + { + L"RayGen", + L"ShadowMiss", + L"ShadowHitGroup", + L"BounceMiss", + L"BounceHitGroup" + }; + D3D12_SUBOBJECT_TO_EXPORTS_ASSOCIATION assoc = {}; - assoc.pSubobjectToAssociate = &subobjects[4]; + assoc.pSubobjectToAssociate = &subobjects[5]; assoc.NumExports = _countof(localExports); assoc.pExports = localExports; @@ -3747,10 +4308,12 @@ static int glRaytracingLightingCreateStateObject(void) static int glRaytracingLightingCreateShaderTables(void) { void* raygenId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"RayGen"); - void* missId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"ShadowMiss"); - void* hitId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"ShadowHitGroup"); + void* shadowMissId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"ShadowMiss"); + void* bounceMissId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"BounceMiss"); + void* shadowHitId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"ShadowHitGroup"); + void* bounceHitId = g_glRaytracingLighting.rtStateProps->GetShaderIdentifier(L"BounceHitGroup"); - if (!raygenId || !missId || !hitId) + if (!raygenId || !shadowMissId || !bounceMissId || !shadowHitId || !bounceHitId) { glRaytracingFatal("Failed to fetch shader identifiers"); return 0; @@ -3758,6 +4321,8 @@ static int glRaytracingLightingCreateShaderTables(void) const UINT shaderIdSize = D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES; const UINT recordSize = (UINT)glRaytracingAlignUp(shaderIdSize, D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT); + const UINT missTableSize = recordSize * 2u; + const UINT hitTableSize = recordSize * 2u; g_glRaytracingLighting.raygenTable = glRaytracingCreateBuffer( g_glRaytracingCmd.device.Get(), @@ -3768,14 +4333,14 @@ static int glRaytracingLightingCreateShaderTables(void) g_glRaytracingLighting.missTable = glRaytracingCreateBuffer( g_glRaytracingCmd.device.Get(), - recordSize, + missTableSize, D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_FLAG_NONE); g_glRaytracingLighting.hitTable = glRaytracingCreateBuffer( g_glRaytracingCmd.device.Get(), - recordSize, + hitTableSize, D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_FLAG_NONE); @@ -3787,19 +4352,22 @@ static int glRaytracingLightingCreateShaderTables(void) return 0; } - uint8_t temp[256] = {}; + std::vector temp; + temp.resize((size_t)max(recordSize, max(missTableSize, hitTableSize)), 0); - memset(temp, 0, sizeof(temp)); - memcpy(temp, raygenId, shaderIdSize); - glRaytracingMapCopy(g_glRaytracingLighting.raygenTable.resource.Get(), temp, recordSize); + memset(temp.data(), 0, temp.size()); + memcpy(temp.data(), raygenId, shaderIdSize); + glRaytracingMapCopy(g_glRaytracingLighting.raygenTable.resource.Get(), temp.data(), recordSize); - memset(temp, 0, sizeof(temp)); - memcpy(temp, missId, shaderIdSize); - glRaytracingMapCopy(g_glRaytracingLighting.missTable.resource.Get(), temp, recordSize); + memset(temp.data(), 0, temp.size()); + memcpy(temp.data(), shadowMissId, shaderIdSize); + memcpy(temp.data() + recordSize, bounceMissId, shaderIdSize); + glRaytracingMapCopy(g_glRaytracingLighting.missTable.resource.Get(), temp.data(), missTableSize); - memset(temp, 0, sizeof(temp)); - memcpy(temp, hitId, shaderIdSize); - glRaytracingMapCopy(g_glRaytracingLighting.hitTable.resource.Get(), temp, recordSize); + memset(temp.data(), 0, temp.size()); + memcpy(temp.data(), shadowHitId, shaderIdSize); + memcpy(temp.data() + recordSize, bounceHitId, shaderIdSize); + glRaytracingMapCopy(g_glRaytracingLighting.hitTable.resource.Get(), temp.data(), hitTableSize); return 1; } @@ -4053,10 +4621,10 @@ static bool glRaytracingLightingExecuteInternal( rays.RayGenerationShaderRecord.StartAddress = g_glRaytracingLighting.raygenTable.gpuVA; rays.RayGenerationShaderRecord.SizeInBytes = shaderRecordSize; rays.MissShaderTable.StartAddress = g_glRaytracingLighting.missTable.gpuVA; - rays.MissShaderTable.SizeInBytes = shaderRecordSize; + rays.MissShaderTable.SizeInBytes = shaderRecordSize * 2u; rays.MissShaderTable.StrideInBytes = shaderRecordSize; rays.HitGroupTable.StartAddress = g_glRaytracingLighting.hitTable.gpuVA; - rays.HitGroupTable.SizeInBytes = shaderRecordSize; + rays.HitGroupTable.SizeInBytes = shaderRecordSize * 2u; rays.HitGroupTable.StrideInBytes = shaderRecordSize; rays.Width = pass->width; rays.Height = pass->height; @@ -4201,6 +4769,9 @@ bool glRaytracingLightingInit(void) return false; memset(&g_glRaytracingLighting.constants, 0, sizeof(g_glRaytracingLighting.constants)); + glRaytracingSetIdentity4x4(g_glRaytracingLighting.constants.invViewProj); + glRaytracingSetIdentity4x4(g_glRaytracingLighting.constants.invViewMatrix); + glRaytracingSetIdentity4x4(g_glRaytracingLighting.constants.viewProj); g_glRaytracingLighting.constants.ambientColor[0] = 0.08f; g_glRaytracingLighting.constants.ambientColor[1] = 0.08f; g_glRaytracingLighting.constants.ambientColor[2] = 0.09f; @@ -4344,6 +4915,10 @@ void glRaytracingLightingSetInvViewProjMatrix(const float* m16) glRaytracingLightingResetDenoiseHistory(); memcpy(g_glRaytracingLighting.constants.invViewProj, m16, sizeof(float) * 16); + + if (!glRaytracingInvertMatrix4x4(m16, g_glRaytracingLighting.constants.viewProj)) + glRaytracingSetIdentity4x4(g_glRaytracingLighting.constants.viewProj); + glRaytracingLightingUpdateConstants(); } diff --git a/neo/opengl/gl_d3d12shim.cpp b/neo/opengl/gl_d3d12shim.cpp index a68cc331..348218fe 100644 --- a/neo/opengl/gl_d3d12shim.cpp +++ b/neo/opengl/gl_d3d12shim.cpp @@ -943,7 +943,7 @@ struct GLState bool motionHistoryReset = true; QD3D12UpscalerBackend upscalerBackend = QD3D12_UPSCALER_DLSS; - QD3D12UpscalerQuality upscalerQuality = QD3D12_QUALITY_BALANCED; + QD3D12UpscalerQuality upscalerQuality = QD3D12_QUALITY_QUALITY; bool enableRayAIDenoise = false; bool enableDLSSRayReconstruction = true; bool enableFSRRayRegeneration = false;