diff --git a/NvLowLatencyVk.dll b/NvLowLatencyVk.dll deleted file mode 100644 index 043c2d45..00000000 Binary files a/NvLowLatencyVk.dll and /dev/null differ diff --git a/OpenAL32.dll b/OpenAL32.dll deleted file mode 100644 index d772963a..00000000 Binary files a/OpenAL32.dll and /dev/null differ diff --git a/neo/opengl/gl_d3d12raylight.cpp b/neo/opengl/gl_d3d12raylight.cpp index 13588985..c9997aba 100644 --- a/neo/opengl/gl_d3d12raylight.cpp +++ b/neo/opengl/gl_d3d12raylight.cpp @@ -2197,7 +2197,12 @@ struct Light uint samples; uint twoSided; float persistant; - float pad1; + + // Reuses the old pad1 slot in glRaytracingLight_t. Keeping this in the + // same 16-byte lane preserves the CPU StructuredBuffer stride while giving + // point/spot lights an explicit volumetric scattering control. + // <= 0 disables the effect. Values around 0.25-1.0 are useful in Doom 3 units. + float volumetricScattering; // For point lights, this is the axis-aligned XYZ attenuation radius. // For spot lights, pointRadius.x stores the near clip plane. @@ -3342,7 +3347,7 @@ float3 EstimatePathTracedSky(float3 worldPos, float3 N, inout uint rng) return accum * 0.55; } -float3 PathTraceDirectPointLight(float3 worldPos, float3 N, float3 V, float3 baseAlbedo, Light Lgt, inout uint rng, out float3 specularOut) +float3 PathTraceDirectPointLight(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, Light Lgt, inout uint rng, out float3 specularOut) { specularOut = 0.0; @@ -3355,29 +3360,53 @@ float3 PathTraceDirectPointLight(float3 worldPos, float3 N, float3 V, float3 bas float3 tangent, bitangent; BuildOrthonormalBasis(centerDir, tangent, bitangent); - float areaRadius = (Lgt.samples != 0u) ? max(GetPointLightMaxRadius(Lgt) * 0.03, 0.12) : 0.0; - float2 disk = ConcentricSampleDisk(Rand2(rng)) * areaRadius; - - float3 sampleLightPos = Lgt.position + tangent * disk.x + bitangent * disk.y; - float3 toLight = sampleLightPos - worldPos; - float dist = length(toLight); - if (dist <= 0.01) + float atten = ComputePointLightAttenuation(worldPos, Lgt); + if (atten <= 0.0) return 0.0; - float3 L = toLight / dist; - float atten = ComputePointLightAttenuation(worldPos, Lgt); + uint sampleCount = max(Lgt.samples, 1u); + sampleCount = min(sampleCount, 4u); - float wrap = 0.28; - float NdotLWrap = saturate((dot(N, L) + wrap) / (1.0 + wrap)); + // One random area-light sample per frame was one of the visible noise sources. + // Use a deterministic low-discrepancy pattern instead. With a single sample, + // use the light center so default point lights are hard-shadowed and stable. + float areaRadius = (Lgt.samples > 1u) ? max(GetPointLightMaxRadius(Lgt) * 0.03, 0.12) : 0.0; + float rand = Hash12((float2)pixel + worldPos.xy + float2(worldPos.z, centerDist)); - float shadow = 1.0; - if (Lgt.samples != 0u && NdotLWrap > 0.0001 && atten > 0.0) - shadow = TraceVisibilityBiased(worldPos, N, L, dist); + float3 diffuseAccum = 0.0; + float3 specAccum = 0.0; - if (Lgt.pointRadiusPad <= 0.5) - specularOut = ComputeSpecular(N, V, L, Lgt.color, Lgt.intensity, atten, shadow, baseAlbedo); + [loop] + for (uint s = 0u; s < sampleCount; ++s) + { + float2 disk = float2(0.0, 0.0); + if (areaRadius > 0.0) + disk = ConcentricSampleDisk(Hammersley2D(s, sampleCount, rand)) * areaRadius; - return Lgt.color * (Lgt.intensity * atten * NdotLWrap * shadow); + float3 sampleLightPos = Lgt.position + tangent * disk.x + bitangent * disk.y; + float3 toLight = sampleLightPos - worldPos; + float dist = length(toLight); + if (dist <= 0.01) + continue; + + float3 L = toLight / dist; + + float wrap = 0.28; + float NdotLWrap = saturate((dot(N, L) + wrap) / (1.0 + wrap)); + + float shadow = 1.0; + if (Lgt.samples != 0u && NdotLWrap > 0.0001) + shadow = TraceVisibilityBiased(worldPos, N, L, dist); + + if (Lgt.pointRadiusPad <= 0.5) + specAccum += ComputeSpecular(N, V, L, Lgt.color, Lgt.intensity, atten, shadow, baseAlbedo); + + diffuseAccum += Lgt.color * (Lgt.intensity * atten * NdotLWrap * shadow); + } + + float invSamples = 1.0 / (float)sampleCount; + specularOut = specAccum * invSamples; + return diffuseAccum * invSamples; } float3 PathTraceDirectSpotLight(float3 worldPos, float3 N, float3 V, float3 baseAlbedo, Light Lgt, inout uint rng, out float3 specularOut) @@ -3405,7 +3434,7 @@ float3 PathTraceDirectSpotLight(float3 worldPos, float3 N, float3 V, float3 base return Lgt.color * (Lgt.intensity * atten * NdotLWrap * shadow); } -float3 PathTraceDirectRectLight(float3 worldPos, float3 N, float3 V, float3 baseAlbedo, Light Lgt, inout uint rng, out float3 specularOut) +float3 PathTraceDirectRectLight(uint2 pixel, float3 worldPos, float3 N, float3 V, float3 baseAlbedo, Light Lgt, inout uint rng, out float3 specularOut) { specularOut = 0.0; @@ -3421,49 +3450,180 @@ float3 PathTraceDirectRectLight(float3 worldPos, float3 N, float3 V, float3 base if (atten <= 0.0) return 0.0; - float2 uv = Rand2(rng) * 2.0 - 1.0; - float3 sampleLightPos = - Lgt.position + - Lgt.axisU * (uv.x * Lgt.halfWidth) + - Lgt.axisV * (uv.y * Lgt.halfHeight); + uint sampleCount = max(Lgt.samples, 1u); + sampleCount = min(sampleCount, 8u); + float rand = Hash12((float2)pixel + worldPos.xy + float2(centerDist, worldPos.z)); - float3 sampleVec = sampleLightPos - worldPos; - float sampleDist = length(sampleVec); - if (sampleDist <= 0.01) - return 0.0; + float3 diffuseAccum = 0.0; + float3 specAccum = 0.0; - float3 L = sampleVec / sampleDist; - float NdotL = saturate(dot(N, 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; - - float shadow = 1.0; - if (Lgt.samples != 0u) - shadow = TraceVisibilityBiased(worldPos, N, L, sampleDist); - - if (Lgt.pointRadiusPad <= 0.5) + [loop] + for (uint s = 0u; s < sampleCount; ++s) { - specularOut = ComputeSpecular( - N, - V, - L, - Lgt.color, - Lgt.intensity * faceTerm, - 1.0, - shadow, - baseAlbedo) * atten; + float2 uv = (sampleCount == 1u) + ? float2(0.0, 0.0) + : (Hammersley2D(s, sampleCount, rand) * 2.0 - 1.0); + + float3 sampleLightPos = + Lgt.position + + Lgt.axisU * (uv.x * Lgt.halfWidth) + + Lgt.axisV * (uv.y * Lgt.halfHeight); + + float3 sampleVec = sampleLightPos - worldPos; + float sampleDist = length(sampleVec); + if (sampleDist <= 0.01) + continue; + + float3 L = sampleVec / sampleDist; + float NdotL = saturate(dot(N, L)); + if (NdotL <= 0.0) + continue; + + float faceTerm = (Lgt.twoSided != 0) + ? abs(dot(-L, Lgt.normal)) + : saturate(dot(-L, Lgt.normal)); + + if (faceTerm <= 0.0) + continue; + + float shadow = 1.0; + if (Lgt.samples != 0u) + shadow = TraceVisibilityBiased(worldPos, N, L, sampleDist); + + if (Lgt.pointRadiusPad <= 0.5) + { + specAccum += ComputeSpecular( + N, + V, + L, + Lgt.color, + Lgt.intensity * faceTerm, + 1.0, + shadow, + baseAlbedo) * atten; + } + + diffuseAccum += clamp(Lgt.color * (Lgt.intensity * NdotL * faceTerm * atten * shadow), 0.0, 4.0); } - return clamp(Lgt.color * (Lgt.intensity * NdotL * faceTerm * atten * shadow), 0.0, 4.0); + float invSamples = 1.0 / (float)sampleCount; + specularOut = specAccum * invSamples; + return diffuseAccum * invSamples; } +float HenyeyGreensteinPhase(float cosTheta, float g) +{ + g = clamp(g, -0.85, 0.85); + float g2 = g * g; + float denom = max(1.0 + g2 - 2.0 * g * cosTheta, 1e-3); + return (1.0 - g2) / max(4.0 * 3.14159265 * pow(denom, 1.5), 1e-3); +} + +float ComputeLightVolumeAttenuation(float3 samplePos, Light Lgt) +{ + if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT) + return ComputePointLightAttenuation(samplePos, Lgt); + + if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT) + return ComputeSpotLightAttenuation(samplePos, Lgt); + + return 0.0; +} + +float EstimateVolumeDensityFromLight(Light Lgt) +{ + // Doom 3 world units are large. Tie the default participating-medium density + // to light range so the caller only needs one artist-facing attribute. + float range = (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT) + ? GetPointLightMaxRadius(Lgt) + : max(Lgt.radius, 1.0); + + return clamp(2.25 / max(range, 32.0), 0.0015, 0.035); +} + +float3 EstimateSingleLightVolumetricScattering(uint2 pixel, float3 cameraPos, float3 worldPos, Light Lgt, inout uint rng) +{ + if (Lgt.volumetricScattering <= 0.0) + return 0.0; + + if (Lgt.type != GL_RAYTRACING_LIGHT_TYPE_POINT && Lgt.type != GL_RAYTRACING_LIGHT_TYPE_SPOT) + return 0.0; + + float3 cameraToSurface = worldPos - cameraPos; + float viewDist = length(cameraToSurface); + if (viewDist <= 0.01) + return 0.0; + + float3 viewDir = cameraToSurface / viewDist; + + uint stepCount = min(max(Lgt.samples, 4u), 12u); + float stepLen = viewDist / (float)stepCount; + + // Do not frame-jitter the march. This renderer has no temporal GI history, + // so varying the volume sample positions every frame creates visible sparkle. + // A centered deterministic slice is stable and the spatial denoiser can smooth it. + float jitter = 0.5; + + float density = EstimateVolumeDensityFromLight(Lgt); + float anisotropy = (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT) ? 0.55 : 0.35; + + float3 accum = 0.0; + + [loop] + for (uint s = 0u; s < stepCount; ++s) + { + float t = ((float)s + jitter) * stepLen; + t = min(t, viewDist - 0.001); + + float3 samplePos = cameraPos + viewDir * t; + float atten = ComputeLightVolumeAttenuation(samplePos, Lgt); + if (atten <= 0.0) + continue; + + float3 toLight = Lgt.position - samplePos; + float lightDist = length(toLight); + if (lightDist <= 0.01) + continue; + + float3 L = toLight / lightDist; + + float3 shadowOrigin = samplePos + L * (gShadowBias * 0.75) + viewDir * (gShadowBias * 0.15); + float visibility = TraceShadow(shadowOrigin, L, max(lightDist - gShadowBias, 0.001)); + if (visibility <= 0.0) + continue; + + float phase = HenyeyGreensteinPhase(dot(L, -viewDir), anisotropy); + float transmittance = exp(-density * t); + float slice = density * stepLen; + + accum += Lgt.color * (Lgt.intensity * atten * visibility * phase * transmittance * slice); + } + + // Scale from normalized phase-function energy into a game-facing glow term. + // The user-facing light attribute still controls the final strength. + const float DOOM3_VOLUME_SCALE = 7.5; + return clamp(accum * max(Lgt.volumetricScattering, 0.0) * DOOM3_VOLUME_SCALE, 0.0, 12.0); +} + +float3 EstimatePathTracedVolumetricScattering(uint2 pixel, float3 worldPos, inout uint rng) +{ + float3 volume = 0.0; + + [loop] + for (uint i = 0; i < gLightCount; ++i) + { + Light Lgt = gLights[i]; + if (Lgt.volumetricScattering <= 0.0) + continue; + + if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT || Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT) + volume += EstimateSingleLightVolumetricScattering(pixel, gCameraPos.xyz, worldPos, Lgt, rng); + } + + return volume; +} +)" +R"( float3 EstimateFastBounceLight(float3 hitPos, float3 hitN, Light Lgt) { // Secondary-bounce lighting needs to be cheap. The primary pass already casts @@ -3868,7 +4028,7 @@ float3 PathTraceLightingSample(uint2 pixel, float3 worldPos, float3 N, float3 V, if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_POINT) { - diffuse = PathTraceDirectPointLight(worldPos, N, V, baseAlbedo, Lgt, rng, spec); + diffuse = PathTraceDirectPointLight(pixel, worldPos, N, V, baseAlbedo, Lgt, rng, spec); } else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_SPOT) { @@ -3876,7 +4036,7 @@ float3 PathTraceLightingSample(uint2 pixel, float3 worldPos, float3 N, float3 V, } else if (Lgt.type == GL_RAYTRACING_LIGHT_TYPE_RECT) { - diffuse = PathTraceDirectRectLight(worldPos, N, V, baseAlbedo, Lgt, rng, spec); + diffuse = PathTraceDirectRectLight(pixel, worldPos, N, V, baseAlbedo, Lgt, rng, spec); } lightingAccum += diffuse; @@ -3954,7 +4114,10 @@ void RayGen() [loop] for (uint s = 0; s < spp; ++s) { - uint rng = InitRng(pixel, gFrameIndex, s); + // The current denoiser is spatial, not temporal. Do not use gFrameIndex + // for the primary GI seed or the same pixel flickers forever instead of + // presenting a stable signal for the a-trous pass. + uint rng = InitRng(pixel, 0u, s); float3 specularAccum = 0.0; float3 lightingAccum = PathTraceLightingSample( @@ -3982,6 +4145,13 @@ void RayGen() finalColor += baseAlbedo * reactiveFinalGather; } + // Volumetric light scattering is radiance in the camera ray, not surface + // reflectance, so add it after surface albedo/specular composition. Because + // it is written into the same path-trace target, the internal a-trous pass + // denoises the stochastic volume/GI signal together with the rest of the ray result. + uint volumeRng = InitRng(pixel, 0u, 0x51u); + finalColor += EstimatePathTracedVolumetricScattering(pixel, worldPos, volumeRng); + gOutputTex[pixel] = float4(max(finalColor, 0.0), albedoSample.a); } )"; @@ -4121,6 +4291,10 @@ float GeometryAwareWeight( float sampleLum = Luminance(sampleLighting); float illumDiff = abs(sampleLum - centerLum); float relativeIllumDiff = illumDiff / max(max(abs(centerLum), abs(sampleLum)), 0.05); + + // Keep this gate conservative. The noise fix is to stabilize and stratify the + // ray samples; over-loosening this filter smears direct lighting and makes the + // scene look noisier/blotchier. float illuminationWeight = exp(-relativeIllumDiff * max(gDenoisePhiColor * 0.035, 0.10)); float normalWeight = pow(saturate(dot(centerNormal, sampleNormal)), max(gDenoisePhiNormal, 1.0)); @@ -4235,6 +4409,8 @@ void DenoiseCS(uint3 dispatchThreadId : SV_DispatchThreadID) } } + // Keep the clamp tight. A wide clamp lets bright stochastic GI/volume + // outliers survive and was the main reason the previous patch looked worse. filtered = clamp(filtered, minRaw - 0.15, maxRaw + 0.15); } @@ -5382,6 +5558,17 @@ void glRaytracingLightingSetDenoiseTuning(float phiColor, float phiNormal, float glRaytracingLightingUpdateConstants(); } +void glRaytracingLightingSetVolumetricScattering(glRaytracingLight_t* light, float strength) +{ + if (!light) + return; + + // This uses glRaytracingLight_t::pad1, which is renamed to + // Light::volumetricScattering in HLSL. Keeping the existing pad slot avoids + // changing the StructuredBuffer stride for already-integrated callers. + light->pad1 = glRaytracingClamp(strength, 0.0f, 16.0f); +} + void glRaytracingLightingSetExternalDenoiser(int enabled) { std::lock_guard lock(g_glRaytracingMutex); @@ -5463,7 +5650,7 @@ glRaytracingLight_t glRaytracingLightingMakePointLight( l.samples = 1; l.twoSided = 0; l.persistant = 0.0f; - l.pad1 = 0.0f; + l.pad1 = 0.0f; // volumetric scattering disabled by default. return l; } @@ -5576,7 +5763,7 @@ glRaytracingLight_t glRaytracingLightingMakeSpotLight( l.samples = samples ? samples : 1u; l.twoSided = 0; l.persistant = 0.0f; - l.pad1 = 0.0f; + l.pad1 = 0.0f; // volumetric scattering disabled by default. return l; } @@ -5640,7 +5827,7 @@ glRaytracingLight_t glRaytracingLightingMakeRectLight( l.samples = samples ? samples : 4u; l.twoSided = twoSided ? 1u : 0u; l.persistant = 0.0f; - l.pad1 = 0.0f; + l.pad1 = 0.0f; // volumetric scattering disabled by default. return l; } diff --git a/neo/opengl/gl_d3d12shim.cpp b/neo/opengl/gl_d3d12shim.cpp index 69bdb864..705f6ede 100644 --- a/neo/opengl/gl_d3d12shim.cpp +++ b/neo/opengl/gl_d3d12shim.cpp @@ -190,11 +190,25 @@ 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 glRaytracingLightingSetVolumetricScattering(glRaytracingLight_t* light, float strength); void glRaytracingSetMeshMaterialFlags(glRaytracingMeshHandle_t meshHandle, uint32_t materialFlags); void glRaytracingSetMeshGlass(glRaytracingMeshHandle_t meshHandle, int isGlass); uint32_t glRaytracingGetMeshMaterialFlags(glRaytracingMeshHandle_t meshHandle); void QD3D12_SetPathTracingQuality(uint32_t samplesPerPixel, uint32_t maxBounces); void QD3D12_SetPathTracingFallbackSamples(uint32_t samplesPerPixel); +void QD3D12_SetCameraInfo( + const float* viewToClip, + const float* clipToView, + const float* clipToPrevClip, + const float* prevClipToClip, + const float* cameraPos, + const float* cameraRight, + const float* cameraUp, + const float* cameraForward, + float nearPlane, + float farPlane, + float verticalFovRadians, + float aspectRatio); static void QD3D12_CreateUploadRingForWindow(struct QD3D12Window& w); static void QD3D12_DestroyUploadRingForWindow(struct QD3D12Window& w); @@ -303,6 +317,7 @@ enum QD3D12RTVSlotGroup }; static const DXGI_FORMAT QD3D12_SceneColorFormat = DXGI_FORMAT_R8G8B8A8_UNORM; +static const DXGI_FORMAT QD3D12_StreamlineOutputFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; static const DXGI_FORMAT QD3D12_VelocityFormat = DXGI_FORMAT_R16G16B16A16_FLOAT; static const DXGI_FORMAT QD3D12_DepthFormat = DXGI_FORMAT_D32_FLOAT_S8X24_UINT; static const DXGI_FORMAT QD3D12_DepthResourceFormat = DXGI_FORMAT_R32G8X24_TYPELESS; @@ -487,7 +502,7 @@ struct GLBufferObject const char* vendor = "Justin Marshall"; const char* renderer = "Quake D3D12 Wrapper"; const char* version = "1.1-quake-d3d12"; -const char* extensions = "GL_SGIS_multitexture GL_ARB_multitexture GL_EXT_texture_env_add GL_ARB_texture_env_combine GL_ARB_texture_compression GL_EXT_texture_compression_s3tc GL_ARB_vertex_program GL_ARB_fragment_program GL_EXT_texture_cube_map GL_EXT_depth_bounds_test GL_EXT_stencil_two_side GL_ATI_separate_stencil GL_QD3D12_normal_map GL_QD3D12_glass_material"; +const char* extensions = "GL_SGIS_multitexture GL_ARB_multitexture GL_EXT_texture_env_add GL_ARB_texture_env_combine GL_ARB_texture_compression GL_EXT_texture_compression_s3tc GL_ARB_vertex_program GL_ARB_fragment_program GL_EXT_texture_cube_map GL_EXT_depth_bounds_test GL_EXT_stencil_two_side GL_ATI_separate_stencil GL_QD3D12_normal_map GL_QD3D12_glass_material GL_QD3D12_volumetric_light"; enum TexEnvModeShader { @@ -758,6 +773,12 @@ struct QD3D12Window std::array, QD3D12_FrameCount> backBuffers; D3D12_RESOURCE_STATES backBufferState[QD3D12_FrameCount] = {}; + // Streamline/DLSS/DLSS-RR writes through UAV, so do not tag the swap-chain + // backbuffer as kBufferTypeScalingOutputColor. Streamline writes here first; + // the shim then samples this texture and copies into the swap-chain RTV. + std::array, QD3D12_FrameCount> slOutputBuffers; + D3D12_RESOURCE_STATES slOutputState[QD3D12_FrameCount] = {}; + ComPtr dsvHeap; ComPtr depthBuffer; D3D12_RESOURCE_STATES depthState = D3D12_RESOURCE_STATE_DEPTH_WRITE; @@ -782,6 +803,10 @@ struct QD3D12Window D3D12_CPU_DESCRIPTOR_HANDLE velocitySrvCpu[QD3D12_FrameCount]{}; D3D12_GPU_DESCRIPTOR_HANDLE velocitySrvGpu[QD3D12_FrameCount]{}; + UINT slOutputSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; + D3D12_CPU_DESCRIPTOR_HANDLE slOutputSrvCpu[QD3D12_FrameCount]{}; + D3D12_GPU_DESCRIPTOR_HANDLE slOutputSrvGpu[QD3D12_FrameCount]{}; + UINT depthSrvIndex = UINT_MAX; D3D12_CPU_DESCRIPTOR_HANDLE depthSrvCpu{}; D3D12_GPU_DESCRIPTOR_HANDLE depthSrvGpu{}; @@ -1159,6 +1184,354 @@ QD3D12Window* g_currentWindow = nullptr; static std::unordered_map g_qd3d12RaytracingMeshMaterialFlags; +struct QD3D12AutoCameraHistory +{ + bool haveLastCamera = false; + bool haveFramePrevious = false; + uint64_t frameSerial = UINT64_MAX; + float lastViewToClip[16] = {}; + float lastWorldToView[16] = {}; + float framePreviousViewToClip[16] = {}; + float framePreviousWorldToView[16] = {}; +}; + +static QD3D12AutoCameraHistory g_qd3d12AutoCamera; + +static void QD3D12_MatrixIdentity(float* m) +{ + memset(m, 0, sizeof(float) * 16); + m[0] = 1.0f; + m[5] = 1.0f; + m[10] = 1.0f; + m[15] = 1.0f; +} + +static bool QD3D12_MatrixFinite(const float* m) +{ + if (!m) + return false; + + for (int i = 0; i < 16; ++i) + { + if (!std::isfinite(m[i])) + return false; + } + + return true; +} + +static void QD3D12_MatrixCopy(float* dst, const float* src) +{ + memcpy(dst, src, sizeof(float) * 16); +} + +static void QD3D12_MatrixMultiplyCM(const float* a, const float* b, float* out) +{ + float r[16]; + + for (int col = 0; col < 4; ++col) + { + for (int row = 0; row < 4; ++row) + { + float v = 0.0f; + for (int k = 0; k < 4; ++k) + v += a[k * 4 + row] * b[col * 4 + k]; + r[col * 4 + row] = v; + } + } + + memcpy(out, r, sizeof(r)); +} + +static bool QD3D12_MatrixInvertCM(const float* m, float* out) +{ + if (!QD3D12_MatrixFinite(m) || !out) + return false; + + float a[4][8]; + + for (int r = 0; r < 4; ++r) + { + for (int c = 0; c < 4; ++c) + a[r][c] = m[c * 4 + r]; + + 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 = fabsf(a[col][col]); + + for (int r = col + 1; r < 4; ++r) + { + const float v = fabsf(a[r][col]); + if (v > best) + { + best = v; + pivot = r; + } + } + + if (best <= 1.0e-8f) + return false; + + 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[c * 4 + r] = a[r][4 + c]; + } + + return QD3D12_MatrixFinite(out); +} + +static bool QD3D12_IsPerspectiveProjectionCM(const float* projection) +{ + if (!QD3D12_MatrixFinite(projection)) + return false; + + // OpenGL perspective matrices have m[11] = -1 and m[15] = 0 in column-major + // storage. Orthographic/UI passes must not replace the real game camera used + // by DLSS Ray Reconstruction. + return fabsf(projection[11]) > 0.5f && fabsf(projection[15]) < 1.0e-4f; +} + +static void QD3D12_ConvertGLProjectionToD3DClipCM(const float* glProjection, float* d3dProjection) +{ + QD3D12_MatrixCopy(d3dProjection, glProjection); + + // The raster shader does: clip.z = 0.5 * (clip.z + clip.w). Bake that same + // GL [-w,+w] to D3D [0,+w] depth remap into the matrix given to Streamline. + for (int col = 0; col < 4; ++col) + { + const int z = col * 4 + 2; + const int w = col * 4 + 3; + d3dProjection[z] = 0.5f * (glProjection[z] + glProjection[w]); + } +} + +static void QD3D12_Normalize3(float* v, const float* fallback) +{ + float lenSq = v[0] * v[0] + v[1] * v[1] + v[2] * v[2]; + if (lenSq <= 1.0e-8f || !std::isfinite(lenSq)) + { + v[0] = fallback[0]; + v[1] = fallback[1]; + v[2] = fallback[2]; + return; + } + + const float invLen = 1.0f / sqrtf(lenSq); + v[0] *= invLen; + v[1] *= invLen; + v[2] *= invLen; +} + +static void QD3D12_DeriveProjectionScalars( + const float* glProjection, + float fallbackAspect, + float* outNearPlane, + float* outFarPlane, + float* outVerticalFovRadians, + float* outAspectRatio) +{ + float nearPlane = 0.01f; + float farPlane = 4096.0f; + float verticalFov = 1.0471975512f; + float aspectRatio = (fallbackAspect > 0.0f) ? fallbackAspect : 1.0f; + + const float xScale = fabsf(glProjection[0]); + const float yScale = fabsf(glProjection[5]); + if (yScale > 1.0e-6f) + verticalFov = 2.0f * atanf(1.0f / yScale); + if (xScale > 1.0e-6f && yScale > 1.0e-6f) + aspectRatio = yScale / xScale; + + const float A = glProjection[10]; + const float B = glProjection[14]; + const float nDenom = A - 1.0f; + const float fDenom = A + 1.0f; + if (fabsf(nDenom) > 1.0e-6f && fabsf(fDenom) > 1.0e-6f) + { + const float n = B / nDenom; + const float f = B / fDenom; + if (std::isfinite(n) && std::isfinite(f) && n > 0.0f && f > n) + { + nearPlane = n; + farPlane = f; + } + } + + *outNearPlane = nearPlane; + *outFarPlane = farPlane; + *outVerticalFovRadians = verticalFov; + *outAspectRatio = aspectRatio; +} + +static void QD3D12_ResetAutoCameraHistory() +{ + g_qd3d12AutoCamera = QD3D12AutoCameraHistory{}; + g_gl.cameraState.valid = false; +} + +static bool QD3D12_UpdateCameraInfoFromCurrentMatrices() +{ + if (g_gl.projStack.empty() || g_gl.modelStack.empty()) + return false; + + const float* glProjection = g_gl.projStack.back().m; + const float* modelView = g_gl.modelStack.back().m; + const float* modelToWorld = g_gl.modelMatrix.m; + + if (!QD3D12_IsPerspectiveProjectionCM(glProjection) || + !QD3D12_MatrixFinite(modelView) || + !QD3D12_MatrixFinite(modelToWorld)) + { + return false; + } + + float worldToModel[16]; + if (!QD3D12_MatrixInvertCM(modelToWorld, worldToModel)) + return false; + + float worldToView[16]; + QD3D12_MatrixMultiplyCM(modelView, worldToModel, worldToView); + + float viewToClip[16]; + QD3D12_ConvertGLProjectionToD3DClipCM(glProjection, viewToClip); + + float clipToView[16]; + if (!QD3D12_MatrixInvertCM(viewToClip, clipToView)) + return false; + + float viewToWorld[16]; + if (!QD3D12_MatrixInvertCM(worldToView, viewToWorld)) + return false; + + if (!g_qd3d12AutoCamera.haveFramePrevious || + g_qd3d12AutoCamera.frameSerial != g_gl.frameSerial) + { + if (!g_qd3d12AutoCamera.haveLastCamera || g_gl.motionHistoryReset) + { + QD3D12_MatrixCopy(g_qd3d12AutoCamera.framePreviousViewToClip, viewToClip); + QD3D12_MatrixCopy(g_qd3d12AutoCamera.framePreviousWorldToView, worldToView); + } + else + { + QD3D12_MatrixCopy(g_qd3d12AutoCamera.framePreviousViewToClip, g_qd3d12AutoCamera.lastViewToClip); + QD3D12_MatrixCopy(g_qd3d12AutoCamera.framePreviousWorldToView, g_qd3d12AutoCamera.lastWorldToView); + } + + g_qd3d12AutoCamera.frameSerial = g_gl.frameSerial; + g_qd3d12AutoCamera.haveFramePrevious = true; + } + + float currentClipToWorld[16]; + QD3D12_MatrixMultiplyCM(viewToWorld, clipToView, currentClipToWorld); + + float previousClipToView[16]; + float previousViewToWorld[16]; + if (!QD3D12_MatrixInvertCM(g_qd3d12AutoCamera.framePreviousViewToClip, previousClipToView) || + !QD3D12_MatrixInvertCM(g_qd3d12AutoCamera.framePreviousWorldToView, previousViewToWorld)) + { + QD3D12_MatrixIdentity(previousClipToView); + QD3D12_MatrixCopy(previousViewToWorld, viewToWorld); + } + + float previousClipToWorld[16]; + QD3D12_MatrixMultiplyCM(previousViewToWorld, previousClipToView, previousClipToWorld); + + float worldToCurrentClip[16]; + QD3D12_MatrixMultiplyCM(viewToClip, worldToView, worldToCurrentClip); + + float worldToPreviousClip[16]; + QD3D12_MatrixMultiplyCM( + g_qd3d12AutoCamera.framePreviousViewToClip, + g_qd3d12AutoCamera.framePreviousWorldToView, + worldToPreviousClip); + + float clipToPrevClip[16]; + float prevClipToClip[16]; + QD3D12_MatrixMultiplyCM(worldToPreviousClip, currentClipToWorld, clipToPrevClip); + QD3D12_MatrixMultiplyCM(worldToCurrentClip, previousClipToWorld, prevClipToClip); + + float cameraPos[3] = { viewToWorld[12], viewToWorld[13], viewToWorld[14] }; + float cameraRight[3] = { viewToWorld[0], viewToWorld[1], viewToWorld[2] }; + float cameraUp[3] = { viewToWorld[4], viewToWorld[5], viewToWorld[6] }; + float cameraForward[3] = { -viewToWorld[8], -viewToWorld[9], -viewToWorld[10] }; + const float rightFallback[3] = { 1.0f, 0.0f, 0.0f }; + const float upFallback[3] = { 0.0f, 1.0f, 0.0f }; + const float forwardFallback[3] = { 0.0f, 0.0f, -1.0f }; + QD3D12_Normalize3(cameraRight, rightFallback); + QD3D12_Normalize3(cameraUp, upFallback); + QD3D12_Normalize3(cameraForward, forwardFallback); + + float fallbackAspect = 1.0f; + if (g_currentWindow && g_currentWindow->renderHeight > 0) + fallbackAspect = (float)g_currentWindow->renderWidth / (float)g_currentWindow->renderHeight; + + float nearPlane = 0.01f; + float farPlane = 4096.0f; + float verticalFovRadians = 1.0471975512f; + float aspectRatio = fallbackAspect; + QD3D12_DeriveProjectionScalars( + glProjection, + fallbackAspect, + &nearPlane, + &farPlane, + &verticalFovRadians, + &aspectRatio); + + QD3D12_SetCameraInfo( + viewToClip, + clipToView, + clipToPrevClip, + prevClipToClip, + cameraPos, + cameraRight, + cameraUp, + cameraForward, + nearPlane, + farPlane, + verticalFovRadians, + aspectRatio); + + QD3D12_MatrixCopy(g_qd3d12AutoCamera.lastViewToClip, viewToClip); + QD3D12_MatrixCopy(g_qd3d12AutoCamera.lastWorldToView, worldToView); + g_qd3d12AutoCamera.haveLastCamera = true; + return true; +} + + static inline uint32_t QD3D12_ClampRayMaterialFlags(uint32_t flags) { return flags & GL_RAYTRACING_MATERIAL_FLAG_MASK_QD3D12; @@ -2630,6 +3003,7 @@ static BatchKey BuildCurrentBatchKey(GLenum originalMode, const TextureResource* key.motionObjectId = g_gl.currentMotionObjectId; key.prevMvp = QD3D12_GetPreviousMVPForObject(key.motionObjectId, key.mvp); key.modelMatrix = CurrentModelMatrix(); + QD3D12_UpdateCameraInfoFromCurrentMatrices(); key.geometryFlag = QD3D12_CurrentEffectiveGeometryFlag(); key.roughness = g_gl.currentSurfaceRoughness; key.materialType = QD3D12_CurrentEffectiveMaterialType(); @@ -3338,10 +3712,76 @@ struct QD3D12StreamlineState { bool initialized = false; bool deviceBound = false; + bool dlssSupported = false; + bool dlssRrSupported = false; sl::ViewportHandle viewport = { 1 }; }; static QD3D12StreamlineState g_qd3d12Sl; +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); + +struct QD3D12StreamlineInterposerState +{ + HMODULE module = nullptr; + QD3D12_PFN_CreateDXGIFactory1 createDXGIFactory1 = nullptr; + QD3D12_PFN_D3D12CreateDevice d3d12CreateDevice = nullptr; +}; +static QD3D12StreamlineInterposerState g_qd3d12SlInterposer; + +static void QD3D12_LoadStreamlineInterposer() +{ + if (g_qd3d12SlInterposer.module) + return; + + HMODULE module = GetModuleHandleA("sl.interposer.dll"); + if (!module) + module = LoadLibraryA("sl.interposer.dll"); + + if (!module) + { + QD3D12_Log("sl.interposer.dll not found; using raw D3D12/DXGI entry points."); + return; + } + + g_qd3d12SlInterposer.module = module; + g_qd3d12SlInterposer.createDXGIFactory1 = + reinterpret_cast(GetProcAddress(module, "CreateDXGIFactory1")); + g_qd3d12SlInterposer.d3d12CreateDevice = + reinterpret_cast(GetProcAddress(module, "D3D12CreateDevice")); + + if (!g_qd3d12SlInterposer.createDXGIFactory1 || !g_qd3d12SlInterposer.d3d12CreateDevice) + { + QD3D12_Log("sl.interposer.dll is loaded but required D3D12/DXGI exports are missing; using raw entry points."); + g_qd3d12SlInterposer.createDXGIFactory1 = nullptr; + g_qd3d12SlInterposer.d3d12CreateDevice = nullptr; + } +} + +static HRESULT QD3D12_CreateDXGIFactory1ForStreamline(REFIID riid, void** ppFactory) +{ + if (g_qd3d12Sl.initialized) + { + QD3D12_LoadStreamlineInterposer(); + if (g_qd3d12SlInterposer.createDXGIFactory1) + return g_qd3d12SlInterposer.createDXGIFactory1(riid, ppFactory); + } + + return CreateDXGIFactory1(riid, ppFactory); +} + +static HRESULT QD3D12_D3D12CreateDeviceForStreamline(IUnknown* adapter, D3D_FEATURE_LEVEL featureLevel, REFIID riid, void** ppDevice) +{ + if (g_qd3d12Sl.initialized) + { + QD3D12_LoadStreamlineInterposer(); + if (g_qd3d12SlInterposer.d3d12CreateDevice) + return g_qd3d12SlInterposer.d3d12CreateDevice(adapter, featureLevel, riid, ppDevice); + } + + return D3D12CreateDevice(adapter, featureLevel, riid, ppDevice); +} + static sl::DLSSMode QD3D12_MapDLSSMode(QD3D12UpscalerQuality quality) { switch (quality) @@ -3366,7 +3806,6 @@ static void QD3D12_InitStreamlineEarly() pref.engine = sl::EngineType::eCustom; pref.engineVersion = "IceBridge 1.0"; pref.flags |= sl::PreferenceFlags::eUseFrameBasedResourceTagging; - pref.flags |= sl::PreferenceFlags::eAllowOTA | sl::PreferenceFlags::eLoadDownloadedPlugins; sl::Feature features[] = { @@ -3387,6 +3826,33 @@ static void QD3D12_InitStreamlineEarly() g_qd3d12Sl.initialized = true; } +static void QD3D12_CheckStreamlineFeatureSupport() +{ + g_qd3d12Sl.dlssSupported = false; + g_qd3d12Sl.dlssRrSupported = false; + + if (!g_qd3d12Sl.initialized || !g_gl.device) + return; + + LUID luid = g_gl.device->GetAdapterLuid(); + sl::AdapterInfo adapterInfo{}; + adapterInfo.deviceLUID = reinterpret_cast(&luid); + adapterInfo.deviceLUIDSizeInBytes = sizeof(luid); + + 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); + if (!g_qd3d12Sl.dlssRrSupported) + { + QD3D12_Log("slIsFeatureSupported(DLSS_RR) failed (%d); disabling Ray Reconstruction.", int(rrSupport)); + g_gl.enableDLSSRayReconstruction = false; + } +} + static void QD3D12_StreamlineOnDeviceCreated() { if (!g_qd3d12Sl.initialized || g_qd3d12Sl.deviceBound || !g_gl.device) @@ -3400,6 +3866,7 @@ static void QD3D12_StreamlineOnDeviceCreated() } g_qd3d12Sl.deviceBound = true; + QD3D12_CheckStreamlineFeatureSupport(); } static bool QD3D12_WantsDLSSRayReconstruction() @@ -3451,6 +3918,16 @@ static sl::Result QD3D12_SetStreamlineCommonConstants(sl::FrameToken& frameToken return slSetConstants(consts, frameToken, g_qd3d12Sl.viewport); } #else +static HRESULT QD3D12_CreateDXGIFactory1ForStreamline(REFIID riid, void** ppFactory) +{ + return CreateDXGIFactory1(riid, ppFactory); +} + +static HRESULT QD3D12_D3D12CreateDeviceForStreamline(IUnknown* adapter, D3D_FEATURE_LEVEL featureLevel, REFIID riid, void** ppDevice) +{ + return D3D12CreateDevice(adapter, featureLevel, riid, ppDevice); +} + static void QD3D12_InitStreamlineEarly() {} static void QD3D12_StreamlineOnDeviceCreated() {} static bool QD3D12_WantsDLSSRayReconstruction() { return false; } @@ -3607,6 +4084,63 @@ static void QD3D12_PostFullscreenPass(ID3D12GraphicsCommandList* cl, cl->DrawInstanced(3, 1, 0, 0); } +static ID3D12Resource* QD3D12_PrepareStreamlineOutputForWrite(QD3D12Window& w) +{ + ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); + if (!cl) + return nullptr; + + ID3D12Resource* output = w.slOutputBuffers[w.frameIndex].Get(); + if (!output) + { + QD3D12_Log("Streamline output texture is missing for frame %u.", w.frameIndex); + return nullptr; + } + + QD3D12_TransitionResource( + cl, + output, + w.slOutputState[w.frameIndex], + D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + + return output; +} + +static bool QD3D12_CopyStreamlineOutputToBackBuffer( + QD3D12Window& w, + const D3D12_VIEWPORT& outputViewport, + const D3D12_RECT& outputScissor) +{ + ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); + if (!cl || !w.slOutputBuffers[w.frameIndex] || !g_gl.postCopyPSO) + return false; + + QD3D12_TransitionResource( + cl, + w.slOutputBuffers[w.frameIndex].Get(), + w.slOutputState[w.frameIndex], + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + + QD3D12_TransitionResource( + cl, + w.backBuffers[w.frameIndex].Get(), + w.backBufferState[w.frameIndex], + D3D12_RESOURCE_STATE_RENDER_TARGET); + + const float clearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + cl->ClearRenderTargetView(CurrentBackBufferRTV(), clearColor, 0, nullptr); + + QD3D12_PostFullscreenPass( + cl, + g_gl.postCopyPSO.Get(), + w.slOutputSrvGpu[w.frameIndex], + CurrentBackBufferRTV(), + outputViewport, + outputScissor); + + return true; +} + static void QD3D12_ExecuteMainCommandListAndWait(QD3D12Window& w) { QD3D12_CHECK(g_gl.cmdList->Close()); @@ -3654,6 +4188,8 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) if (!cl) return; + QD3D12_UpdateCameraInfoFromCurrentMatrices(); + const bool haveTemporalCameraInputs = (!w.isPbuffer) && g_gl.cameraState.valid; const bool useLightingUpscaleInput = (!w.isPbuffer) && QD3D12_UseLightingTextureAsUpscaleInput(w); @@ -3681,7 +4217,7 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) outputScissor.bottom = (LONG)w.height; #if defined(QD3D12_ENABLE_STREAMLINE) - if (haveTemporalCameraInputs && useLightingUpscaleInput && g_gl.upscalerBackend == QD3D12_UPSCALER_DLSS && g_qd3d12Sl.deviceBound) + if (haveTemporalCameraInputs && useLightingUpscaleInput && g_gl.upscalerBackend == QD3D12_UPSCALER_DLSS && g_qd3d12Sl.deviceBound && w.slOutputBuffers[w.frameIndex]) { sl::FrameToken* frameToken = nullptr; uint32_t frameIndex = (uint32_t)g_gl.frameSerial; @@ -3700,8 +4236,10 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) outputExtent.width = w.width; outputExtent.height = w.height; + 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, w.backBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_RENDER_TARGET) }; + 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) }; @@ -3772,10 +4310,15 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) const sl::Result evalResult = slEvaluateFeature(sl::kFeatureDLSS_RR, *frameToken, inputs, _countof(inputs), cl); if (evalResult == sl::Result::eOk) { - return; - } + if (QD3D12_CopyStreamlineOutputToBackBuffer(w, outputViewport, outputScissor)) + return; - QD3D12_Log("slEvaluateFeature(DLSS_RR) failed (%d), falling back.", int(evalResult)); + QD3D12_Log("DLSS_RR succeeded but copying Streamline output to backbuffer failed; falling back."); + } + else + { + QD3D12_Log("slEvaluateFeature(DLSS_RR) failed (%d), falling back.", int(evalResult)); + } } } } @@ -3786,7 +4329,7 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) } } - if (haveTemporalCameraInputs && g_gl.upscalerBackend == QD3D12_UPSCALER_DLSS && g_qd3d12Sl.deviceBound) + if (haveTemporalCameraInputs && g_gl.upscalerBackend == QD3D12_UPSCALER_DLSS && g_qd3d12Sl.deviceBound && w.slOutputBuffers[w.frameIndex]) { sl::FrameToken* frameToken = nullptr; uint32_t frameIndex = (uint32_t)g_gl.frameSerial; @@ -3805,8 +4348,10 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) outputExtent.width = w.width; outputExtent.height = w.height; + 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, w.backBuffers[w.frameIndex].Get(), nullptr, nullptr, uint32_t(D3D12_RESOURCE_STATE_RENDER_TARGET) }; + 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) }; @@ -3841,10 +4386,15 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) const sl::Result evalResult = slEvaluateFeature(sl::kFeatureDLSS, *frameToken, inputs, _countof(inputs), cl); if (evalResult == sl::Result::eOk) { - return; - } + if (QD3D12_CopyStreamlineOutputToBackBuffer(w, outputViewport, outputScissor)) + return; - QD3D12_Log("slEvaluateFeature(DLSS) failed (%d), falling back.", int(evalResult)); + QD3D12_Log("DLSS succeeded but copying Streamline output to backbuffer failed; falling back."); + } + else + { + QD3D12_Log("slEvaluateFeature(DLSS) failed (%d), falling back.", int(evalResult)); + } } } } @@ -3983,8 +4533,8 @@ static void QD3D12_CreateDevice() //} #endif - QD3D12_CHECK(CreateDXGIFactory1(IID_PPV_ARGS(&g_gl.factory))); - QD3D12_CHECK(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&g_gl.device))); + QD3D12_CHECK(QD3D12_CreateDXGIFactory1ForStreamline(IID_PPV_ARGS(&g_gl.factory))); + QD3D12_CHECK(QD3D12_D3D12CreateDeviceForStreamline(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&g_gl.device))); QD3D12_SelectGBufferSampleCount(); D3D12_COMMAND_QUEUE_DESC qd{}; @@ -4221,6 +4771,23 @@ static void QD3D12_CreateRTVsForWindow(QD3D12Window& w) } } + for (UINT i = 0; i < QD3D12_FrameCount; ++i) + { + CreateTexture2D( + w.slOutputBuffers[i], + w.slOutputState[i], + QD3D12_StreamlineOutputFormat, + colorClear, + false, + w.width, + w.height, + 1, + D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + {}, + false); + } + auto CreateTextureSrv = [&](ID3D12Resource* res, DXGI_FORMAT format, UINT& srvIndex, D3D12_CPU_DESCRIPTOR_HANDLE& cpu, D3D12_GPU_DESCRIPTOR_HANDLE& gpu) { @@ -4274,6 +4841,9 @@ static void QD3D12_CreateRTVsForWindow(QD3D12Window& w) for (UINT i = 0; i < QD3D12_FrameCount; ++i) CreateTextureSrv(w.velocityBuffers[i].Get(), QD3D12_VelocityFormat, w.velocitySrvIndex[i], w.velocitySrvCpu[i], w.velocitySrvGpu[i]); + for (UINT i = 0; i < QD3D12_FrameCount; ++i) + CreateTextureSrv(w.slOutputBuffers[i].Get(), QD3D12_StreamlineOutputFormat, w.slOutputSrvIndex[i], w.slOutputSrvCpu[i], w.slOutputSrvGpu[i]); + if (useMsaa) { for (UINT i = 0; i < QD3D12_FrameCount; ++i) @@ -5375,6 +5945,7 @@ void QD3D12_ShutdownForQuake() if (g_qd3d12Sl.initialized) { slShutdown(); + g_qd3d12Sl = QD3D12StreamlineState{}; } #endif @@ -5382,6 +5953,7 @@ void QD3D12_ShutdownForQuake() CloseHandle(g_gl.fenceEvent); QD3D12ARB_Shutdown(); + QD3D12_ResetAutoCameraHistory(); g_arbPsoCache.clear(); g_qd3d12RaytracingMeshMaterialFlags.clear(); @@ -8748,6 +9320,7 @@ void QD3D12_ReleaseWindowSizeResources(QD3D12Window& w) w.normalMsaaBuffers[i].Reset(); w.positionMsaaBuffers[i].Reset(); w.velocityMsaaBuffers[i].Reset(); + w.slOutputBuffers[i].Reset(); w.sceneColorState[i] = D3D12_RESOURCE_STATE_COMMON; w.backBufferState[i] = D3D12_RESOURCE_STATE_COMMON; @@ -8758,6 +9331,7 @@ void QD3D12_ReleaseWindowSizeResources(QD3D12Window& w) w.normalMsaaState[i] = D3D12_RESOURCE_STATE_COMMON; w.positionMsaaState[i] = D3D12_RESOURCE_STATE_COMMON; w.velocityMsaaState[i] = D3D12_RESOURCE_STATE_COMMON; + w.slOutputState[i] = D3D12_RESOURCE_STATE_COMMON; } w.depthBuffer.Reset(); @@ -10306,6 +10880,7 @@ PROC WINAPI qd3d12_wglGetProcAddress(LPCSTR name) { { "QD3D12_ResolveGBufferNow", (PROC)QD3D12_ResolveGBufferNow }, { "QD3D12_SetPathTracingQuality", (PROC)QD3D12_SetPathTracingQuality }, { "QD3D12_SetPathTracingFallbackSamples", (PROC)QD3D12_SetPathTracingFallbackSamples }, + { "glRaytracingLightingSetVolumetricScattering", (PROC)glRaytracingLightingSetVolumetricScattering }, { "glGenProgramsARB", (PROC)glGenProgramsARB }, { "glDeleteProgramsARB", (PROC)glDeleteProgramsARB }, { "glBindProgramARB", (PROC)glBindProgramARB }, @@ -10904,17 +11479,22 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) QD3D12_ExecuteMainCommandListAndWait(*window); cl = g_gl.cmdList.Get(); + QD3D12_UpdateCameraInfoFromCurrentMatrices(); + const bool useDLSSRayReconstruction = QD3D12_CanUseDLSSRayReconstructionForLighting(*window); + const uint32_t activeMaxBounces = std::max(1u, g_gl.pathTracingMaxBounces); const uint32_t raySpp = useDLSSRayReconstruction ? std::max(1u, g_gl.pathTracingSamplesPerPixel) : std::max(1u, g_gl.pathTracingFallbackSamplesPerPixel); - // DLSS RR consumes raw/noisy radiance as the proper external denoiser. - // Without RR, keep the ray module's new non-temporal a-trous fallback enabled. + // 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); glRaytracingLightingSetPathTracingOptions( raySpp, - std::max(1u, g_gl.pathTracingMaxBounces), + activeMaxBounces, useDLSSRayReconstruction ? 0 : 1, useDLSSRayReconstruction ? 0.0f : 1.0f); @@ -11163,6 +11743,7 @@ void QD3D12_ResetTemporalHistory(void) g_gl.currObjectMVPs.clear(); g_gl.prevJitterX = 0.0f; g_gl.prevJitterY = 0.0f; + QD3D12_ResetAutoCameraHistory(); } void QD3D12_SetProjectionJitterPixels(float jitterX, float jitterY) diff --git a/neo/opengl/opengl.h b/neo/opengl/opengl.h index 85f4c98a..f6478592 100644 --- a/neo/opengl/opengl.h +++ b/neo/opengl/opengl.h @@ -1662,7 +1662,16 @@ typedef struct glRaytracingLight_s // point/spot: 0 disables shadows, non-zero enables them uint32_t twoSided; // rect: 0/1, ignored for point / spot float persistant; - float pad1; + union + { + // Backward-compatible name. Kept so existing code that zeroes/uses pad1 + // still has the same ABI and StructuredBuffer stride. + float pad1; + + // Point/spot volumetric light scattering strength. <= 0 disables it. + // Useful Doom 3-range values are generally 0.25f..1.0f. + float volumetricScattering; + }; glRaytracingVec3_t pointRadius; // point: XYZ attenuation radii // spot: x = near clip, y/z unused @@ -2244,3 +2253,5 @@ extern "C" { #ifdef __cplusplus } #endif + +void glRaytracingLightingSetVolumetricScattering(glRaytracingLight_t* light, float strength); \ No newline at end of file diff --git a/nvngx_deepdvc.dll b/nvngx_deepdvc.dll deleted file mode 100644 index 94a330f1..00000000 Binary files a/nvngx_deepdvc.dll and /dev/null differ diff --git a/nvngx_dlssg.dll b/nvngx_dlssg.dll deleted file mode 100644 index 7d5f328c..00000000 Binary files a/nvngx_dlssg.dll and /dev/null differ diff --git a/sl.common.dll b/sl.common.dll deleted file mode 100644 index f19d55f2..00000000 Binary files a/sl.common.dll and /dev/null differ diff --git a/sl.deepdvc.dll b/sl.deepdvc.dll deleted file mode 100644 index 7c5c709f..00000000 Binary files a/sl.deepdvc.dll and /dev/null differ diff --git a/sl.directsr.dll b/sl.directsr.dll deleted file mode 100644 index f1dbbe32..00000000 Binary files a/sl.directsr.dll and /dev/null differ diff --git a/sl.dlss.dll b/sl.dlss.dll deleted file mode 100644 index 76cad540..00000000 Binary files a/sl.dlss.dll and /dev/null differ diff --git a/sl.dlss_d.dll b/sl.dlss_d.dll deleted file mode 100644 index d732ca7d..00000000 Binary files a/sl.dlss_d.dll and /dev/null differ diff --git a/sl.dlss_g.dll b/sl.dlss_g.dll deleted file mode 100644 index 990497d7..00000000 Binary files a/sl.dlss_g.dll and /dev/null differ diff --git a/sl.nis.dll b/sl.nis.dll deleted file mode 100644 index 746914e6..00000000 Binary files a/sl.nis.dll and /dev/null differ diff --git a/sl.nvperf.dll b/sl.nvperf.dll deleted file mode 100644 index b875da9f..00000000 Binary files a/sl.nvperf.dll and /dev/null differ diff --git a/sl.pcl.dll b/sl.pcl.dll deleted file mode 100644 index 433160a8..00000000 Binary files a/sl.pcl.dll and /dev/null differ diff --git a/sl.reflex.dll b/sl.reflex.dll deleted file mode 100644 index 94caa810..00000000 Binary files a/sl.reflex.dll and /dev/null differ