Fixed a bug with ray reconstruction.

This commit is contained in:
Justin Marshall
2026-05-03 19:06:55 -07:00
parent 38536b4a33
commit 1a7a463c80
17 changed files with 859 additions and 80 deletions
Binary file not shown.
BIN
View File
Binary file not shown.
+249 -62
View File
@@ -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<float>(strength, 0.0f, 16.0f);
}
void glRaytracingLightingSetExternalDenoiser(int enabled)
{
std::lock_guard<std::mutex> 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;
}
+598 -17
View File
@@ -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<ComPtr<ID3D12Resource>, 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<ComPtr<ID3D12Resource>, QD3D12_FrameCount> slOutputBuffers;
D3D12_RESOURCE_STATES slOutputState[QD3D12_FrameCount] = {};
ComPtr<ID3D12DescriptorHeap> dsvHeap;
ComPtr<ID3D12Resource> 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<uint32_t, uint32_t> 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<QD3D12_PFN_CreateDXGIFactory1>(GetProcAddress(module, "CreateDXGIFactory1"));
g_qd3d12SlInterposer.d3d12CreateDevice =
reinterpret_cast<QD3D12_PFN_D3D12CreateDevice>(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<uint8_t*>(&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<uint32_t>(1u, g_gl.pathTracingMaxBounces);
const uint32_t raySpp = useDLSSRayReconstruction
? std::max<uint32_t>(1u, g_gl.pathTracingSamplesPerPixel)
: std::max<uint32_t>(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<uint32_t>(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)
+12 -1
View File
@@ -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);
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.