From ef706b8cc3580c33a5d27ab9f9b3a491f08bf758 Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Wed, 13 May 2026 22:13:49 -0700 Subject: [PATCH] Lots of fixes. --- neo/engine/opengl/gl_d3d12raylight.cpp | 6 +- neo/engine/opengl/gl_d3d12shim.cpp | 1064 +++++++++++++++++++-- neo/engine/renderer/RenderSystem_init.cpp | 16 +- neo/engine/renderer/draw_dx.cpp | 8 +- neo/quake4/game.vcxproj | 9 +- 5 files changed, 1014 insertions(+), 89 deletions(-) diff --git a/neo/engine/opengl/gl_d3d12raylight.cpp b/neo/engine/opengl/gl_d3d12raylight.cpp index 02ae86c4..71e0e740 100644 --- a/neo/engine/opengl/gl_d3d12raylight.cpp +++ b/neo/engine/opengl/gl_d3d12raylight.cpp @@ -2738,7 +2738,7 @@ float TraceShadow(float3 origin, float3 dir, float maxT) TraceRay( gSceneBVH, - RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_CULL_FRONT_FACING_TRIANGLES, + RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH, 0xFF, 0, 0, @@ -2765,7 +2765,7 @@ bool TraceBounce(float3 origin, float3 dir, float maxT, out float hitT, out uint TraceRay( gSceneBVH, - RAY_FLAG_NONE, + RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH, 0xFF, 1, 0, @@ -4996,6 +4996,8 @@ void RayGen() finalColor += baseAlbedo * reactiveFinalGather; } + finalColor *= clamp(ao + 0.6, 0.0, 1.0); + // 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 diff --git a/neo/engine/opengl/gl_d3d12shim.cpp b/neo/engine/opengl/gl_d3d12shim.cpp index d2074db3..97436f30 100644 --- a/neo/engine/opengl/gl_d3d12shim.cpp +++ b/neo/engine/opengl/gl_d3d12shim.cpp @@ -309,6 +309,8 @@ static void QD3D12_SubmitOpenFrameNoPresentAndWait(); static void QD3D12_EnsureFrameOpen(); static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w); static void QD3D12_ExecuteMainCommandListAndWait(QD3D12Window& w); +static bool QD3D12_RunInternalTAA(QD3D12Window& w, D3D12_GPU_DESCRIPTOR_HANDLE inputSrv, D3D12_GPU_DESCRIPTOR_HANDLE& outputSrv); +static bool QD3D12_FinalBlitToBackBuffer(QD3D12Window& w, D3D12_GPU_DESCRIPTOR_HANDLE inputSrv, const D3D12_VIEWPORT& outputViewport, const D3D12_RECT& outputScissor, bool toneMapInput = false); static void QD3D12_RunRayAIDenoiseIfEnabled(ID3D12GraphicsCommandList* cl, QD3D12Window& w, ID3D12Resource* lightingResource); static void QD3D12_TransitionResource(ID3D12GraphicsCommandList* cl, ID3D12Resource* res, D3D12_RESOURCE_STATES& trackedState, D3D12_RESOURCE_STATES newState); static UINT QD3D12_GBufferSampleCount(); @@ -393,7 +395,13 @@ static const UINT QD3D12_MaxTextureUnits = 8; static const UINT QD3D12_FrameCount = 2; static const UINT QD3D12_MaxTextures = 4096; static const UINT QD3D12_UploadBufferSize = 128 * 1024 * 1024; -static const UINT QD3D12_RequestedGBufferSampleCount = 4; +// MSAA has been removed from the G-buffer path. Temporal AA now owns edge cleanup +// after lighting, so all scene/deferred render targets remain single-sample. +static const UINT QD3D12_RequestedGBufferSampleCount = 1; +static constexpr UINT QD3D12_PostSrvCount = 9; +static constexpr UINT QD3D12_PostRootConstants = QD3D12_PostSrvCount; +static constexpr UINT QD3D12_PostConstantDwords = 8; +static constexpr UINT QD3D12_ToneMapTileDim = 64; enum QD3D12RTVSlotGroup { @@ -410,7 +418,10 @@ enum QD3D12RTVSlotGroup QD3D12_RTV_BACKBUFFER = 10, QD3D12_RTV_SPECULAR_RENDER = 11, QD3D12_RTV_SPECULAR_RESOLVED = 12, - QD3D12_RTV_GROUP_COUNT = 13 + QD3D12_RTV_TAA_OUTPUT = 13, + QD3D12_RTV_TONEMAP_TILE_MAX = 14, + QD3D12_RTV_TONEMAP_SCENE_MAX = 15, + QD3D12_RTV_GROUP_COUNT = 16 }; static const DXGI_FORMAT QD3D12_SceneColorFormat = DXGI_FORMAT_R8G8B8A8_UNORM; @@ -598,6 +609,7 @@ struct DrawConstants float texComb1Alpha[4]; float texComb1Operand[4]; float texEnvColor1[4]; + float cameraPomPad[4]; // Appended for translated ARBvp/ARBfp programs. Fixed-function HLSL ignores // these fields; ARB-generated HLSL uses them as program.env/local storage. @@ -738,6 +750,8 @@ struct BatchKey float roughness = 0.5f; float materialType = 0.0f; GLuint motionObjectId = 0; + float cameraWorldPos[3] = { 0.0f, 0.0f, 0.0f }; + float cameraValid = 0.0f; }; @@ -832,6 +846,8 @@ static bool BatchKeyEquals(const BatchKey& a, const BatchKey& b) a.roughness == b.roughness && a.materialType == b.materialType && a.motionObjectId == b.motionObjectId && + a.cameraValid == b.cameraValid && + memcmp(a.cameraWorldPos, b.cameraWorldPos, sizeof(a.cameraWorldPos)) == 0 && TextureSrvArrayEquals(a.textureSrvIndex, b.textureSrvIndex) && a.useARBPrograms == b.useARBPrograms && a.arbVertexProgram == b.arbVertexProgram && @@ -925,6 +941,19 @@ struct QD3D12Window std::array, QD3D12_FrameCount> slOutputBuffers; D3D12_RESOURCE_STATES slOutputState[QD3D12_FrameCount] = {}; + // Internal TAA history/output. This pass runs after lighting and before the + // native 2D phase, then the final sharpen pass samples this buffer. + std::array, QD3D12_FrameCount> taaBuffers; + D3D12_RESOURCE_STATES taaBufferState[QD3D12_FrameCount] = {}; + bool taaHistoryValid[QD3D12_FrameCount] = {}; + + // Tone mapping uses two tiny FP16 reduction targets: 64x64 tile maxima, then + // a 1x1 scene max. The tone-map pass treats that 1x1 value as the white point. + std::array, QD3D12_FrameCount> toneMapTileMaxBuffers; + D3D12_RESOURCE_STATES toneMapTileMaxState[QD3D12_FrameCount] = {}; + std::array, QD3D12_FrameCount> toneMapSceneMaxBuffers; + D3D12_RESOURCE_STATES toneMapSceneMaxState[QD3D12_FrameCount] = {}; + ComPtr dsvHeap; ComPtr depthBuffer; D3D12_RESOURCE_STATES depthState = D3D12_RESOURCE_STATE_DEPTH_WRITE; @@ -961,6 +990,18 @@ struct QD3D12Window D3D12_CPU_DESCRIPTOR_HANDLE slOutputSrvCpu[QD3D12_FrameCount]{}; D3D12_GPU_DESCRIPTOR_HANDLE slOutputSrvGpu[QD3D12_FrameCount]{}; + UINT taaSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; + D3D12_CPU_DESCRIPTOR_HANDLE taaSrvCpu[QD3D12_FrameCount]{}; + D3D12_GPU_DESCRIPTOR_HANDLE taaSrvGpu[QD3D12_FrameCount]{}; + + UINT toneMapTileMaxSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; + D3D12_CPU_DESCRIPTOR_HANDLE toneMapTileMaxSrvCpu[QD3D12_FrameCount]{}; + D3D12_GPU_DESCRIPTOR_HANDLE toneMapTileMaxSrvGpu[QD3D12_FrameCount]{}; + + UINT toneMapSceneMaxSrvIndex[QD3D12_FrameCount] = { UINT_MAX, UINT_MAX }; + D3D12_CPU_DESCRIPTOR_HANDLE toneMapSceneMaxSrvCpu[QD3D12_FrameCount]{}; + D3D12_GPU_DESCRIPTOR_HANDLE toneMapSceneMaxSrvGpu[QD3D12_FrameCount]{}; + UINT depthSrvIndex = UINT_MAX; D3D12_CPU_DESCRIPTOR_HANDLE depthSrvCpu{}; D3D12_GPU_DESCRIPTOR_HANDLE depthSrvGpu{}; @@ -1130,7 +1171,7 @@ struct GLState bool motionHistoryReset = true; QD3D12UpscalerBackend upscalerBackend = QD3D12_UPSCALER_DLSS; - QD3D12UpscalerQuality upscalerQuality = QD3D12_QUALITY_DLAA; + QD3D12UpscalerQuality upscalerQuality = QD3D12_QUALITY_PERFORMANCE; bool enableRayAIDenoise = false; bool enableDLSSRayReconstruction = true; bool enableFSRRayRegeneration = false; @@ -1290,6 +1331,11 @@ struct GLState ComPtr rootSig; ComPtr postRootSig; ComPtr postCopyPSO; + ComPtr postSharpenPSO; + ComPtr postToneMapPSO; + ComPtr postToneMapTileMaxPSO; + ComPtr postToneMapFinalMaxPSO; + ComPtr postTaaPSO; ComPtr postAdditivePSO; ComPtr postDepthCopyPSO; ComPtr postDepthResolveMsaaPSO; @@ -1306,6 +1352,11 @@ struct GLState ComPtr psUntexturedColorOnlyBlob; ComPtr postVsBlob; ComPtr postPsCopyBlob; + ComPtr postPsSharpenBlob; + ComPtr postPsToneMapBlob; + ComPtr postPsToneMapTileMaxBlob; + ComPtr postPsToneMapFinalMaxBlob; + ComPtr postPsTaaBlob; ComPtr postPsAddBlob; ComPtr postPsDepthCopyBlob; ComPtr postPsDepthResolveMsaaBlob; @@ -1781,12 +1832,13 @@ static UINT g_qd3d12GBufferSampleCount = 1; static UINT QD3D12_GBufferSampleCount() { - return std::max(1u, g_qd3d12GBufferSampleCount); + (void)g_qd3d12GBufferSampleCount; + return 1u; } static bool QD3D12_GBufferMsaaEnabled() { - return QD3D12_GBufferSampleCount() > 1; + return false; } static D3D12_CPU_DESCRIPTOR_HANDLE QD3D12_RtvAt(QD3D12Window& w, UINT group, UINT frameIndex) @@ -1936,6 +1988,24 @@ static D3D12_CPU_DESCRIPTOR_HANDLE CurrentBackBufferRTV() return QD3D12_RtvAt(w, QD3D12_RTV_BACKBUFFER, w.frameIndex); } +static D3D12_CPU_DESCRIPTOR_HANDLE CurrentTaaOutputRTV() +{ + QD3D12Window& w = *g_currentWindow; + return QD3D12_RtvAt(w, QD3D12_RTV_TAA_OUTPUT, w.frameIndex); +} + +static D3D12_CPU_DESCRIPTOR_HANDLE CurrentToneMapTileMaxRTV() +{ + QD3D12Window& w = *g_currentWindow; + return QD3D12_RtvAt(w, QD3D12_RTV_TONEMAP_TILE_MAX, w.frameIndex); +} + +static D3D12_CPU_DESCRIPTOR_HANDLE CurrentToneMapSceneMaxRTV() +{ + QD3D12Window& w = *g_currentWindow; + return QD3D12_RtvAt(w, QD3D12_RTV_TONEMAP_SCENE_MAX, w.frameIndex); +} + static D3D12_CPU_DESCRIPTOR_HANDLE CurrentSceneDepthDSV() { return g_currentWindow->dsvHeap->GetCPUDescriptorHandleForHeapStart(); @@ -2294,12 +2364,18 @@ cbuffer DrawCB : register(b0) float4 gTexComb1Alpha; float4 gTexComb1Operand; float4 gTexEnvColor1; + float4 gCameraPomPad; }; #define gUseNormalMap gMotionPad.x #define gNormalMapStrength gMotionPad.y #define gNormalMapYSign gMotionPad.z -#define gParallaxScale 0.0 +// Pixel-shader POM depth in UV space. Keep this conservative: legacy idTech +// normal maps often do not have a true height channel, so the POM path below +// gates fallback height by local detail before applying any offset. +#define gParallaxScale (clamp(gNormalMapStrength, 0.0, 4.0) * 0.026) +#define gCameraWorldPos gCameraPomPad.xyz +#define gCameraPomValid gCameraPomPad.w #define gUseGlowMap gMotionPad.w #define gGlowMapStrength gMotionPad.w #define gUseSpecularMap gMaterialMapPad.x @@ -2311,9 +2387,14 @@ cbuffer DrawCB : register(b0) // Raised from the old 0.35 cap so stronger materials can push deeper, while // the hull shader below uses shared-edge tess factors to prevent crack artifacts. #define gTessellationDisplacement clamp(gMaterialMapPad.w, 0.0, 0.50) -#define QD3D12_TESS_MIN_FACTOR 2.0 +#define QD3D12_TESS_MIN_FACTOR 1.0 +#define QD3D12_TESS_NEAR_MIN_FACTOR 2.0 #define QD3D12_TESS_MAX_FACTOR 15.0 #define QD3D12_TESS_TARGET_EDGE_PIXELS 28.0 +#define QD3D12_TESS_DISTANCE_NEAR 512.0 +#define QD3D12_TESS_DISTANCE_FAR 2200.0 +#define QD3D12_POM_DISTANCE_NEAR 384.0 +#define QD3D12_POM_DISTANCE_FAR 1800.0 Texture2D gTex0 : register(t0); Texture2D gTex1 : register(t1); @@ -2621,33 +2702,241 @@ void QD3D12_BuildTBN(VSOut i, out float3 n, out float3 t, out float3 b) b = QD3D12_SafeNormalize(b, bFallback); } +void QD3D12_BuildPixelTBN(VSOut i, out float3 n, out float3 t, out float3 b) +{ + // Prefer a per-pixel UV-derived basis. A lot of legacy paths do not provide + // reliable tangent/binormal arrays, and that was a major cause of POM moving + // sideways on some walls/floors. The fallback still handles degenerate UVs. + QD3D12_BuildTBN(i, n, t, b); + + float3 dpdx = ddx(i.worldPos); + float3 dpdy = ddy(i.worldPos); + float2 duvdx = ddx(i.uv0); + float2 duvdy = ddy(i.uv0); + + float det = duvdx.x * duvdy.y - duvdx.y * duvdy.x; + if (abs(det) <= 1.0e-7) + return; + + float invDet = 1.0 / det; + float3 derivedT = (dpdx * duvdy.y - dpdy * duvdx.y) * invDet; + float3 derivedB = (dpdy * duvdx.x - dpdx * duvdy.x) * invDet; + + derivedT = derivedT - n * dot(n, derivedT); + derivedT = QD3D12_SafeNormalize(derivedT, t); + + derivedB = derivedB - n * dot(n, derivedB) - derivedT * dot(derivedT, derivedB); + derivedB = QD3D12_SafeNormalize(derivedB, QD3D12_SafeNormalize(cross(n, derivedT), b)); + + t = derivedT; + b = derivedB; +} + +float QD3D12_AuthoredPomAlphaWeight(float alphaValue) +{ + // Alpha == 1 is frequently forced by RGB/DXT uploads in this shim. Treat only + // non-trivial alpha as an authored height channel. + return (alphaValue > 0.0001 && alphaValue < 0.999) ? 1.0 : 0.0; +} + +float QD3D12_Luma(float3 c) +{ + return dot(saturate(c), float3(0.299, 0.587, 0.114)); +} + +float QD3D12_GetPomHeightFromSamples(float2 uv) +{ + float4 nm = gNormalMap.SampleLevel(gSamp2, uv, 0.0); + float authored = QD3D12_AuthoredPomAlphaWeight(nm.a); + + if (authored > 0.5) + return saturate(nm.a); + + // RGB-only normal maps do not contain absolute height. Use a conservative + // pseudo-height from the normal-map slope. This is intentionally centered + // and local-detail-gated below; otherwise a uniformly tilted normal map would + // slide the whole diffuse texture, which was the main visible offset bug. + float3 decoded = nm.xyz * 2.0 - 1.0; + decoded.y *= gNormalMapYSign; + + float slope = saturate(length(decoded.xy)); + float normalHeight = saturate(0.5 + (pow(slope, 0.80) - 0.35) * 0.42); + + // Optional diffuse assist, but only when the normal map itself has some + // actual slope. This helps old RGB-only bump maps read without letting a + // plain diffuse texture behave like a fake height map by itself. + if (gUseTex0 > 0.5) + { + float normalDetailGate = saturate((slope - 0.040) * 8.0); + if (normalDetailGate > 0.001) + { + float2 texel = QD3D12_NormalMapTexelSize(); + float lumC = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv, 0.0).rgb); + float lumL = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv - float2(texel.x, 0.0), 0.0).rgb); + float lumR = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv + float2(texel.x, 0.0), 0.0).rgb); + float lumU = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv - float2(0.0, texel.y), 0.0).rgb); + float lumD = QD3D12_Luma(gTex0.SampleLevel(gSamp0, uv + float2(0.0, texel.y), 0.0).rgb); + + float lumAvg = (lumL + lumR + lumU + lumD) * 0.25; + float localContrast = abs(lumC - lumAvg); + float diffuseWeight = saturate((localContrast - 0.018) * 16.0) * normalDetailGate; + + // Local contrast only; never absolute brightness. Keep it subtle so + // brick/grate detail can pop, but signs/posters do not shear sideways. + float diffuseHeight = saturate(0.5 + (lumC - lumAvg) * 1.85); + normalHeight = lerp(normalHeight, diffuseHeight, diffuseWeight * 0.28); + } + } + + return saturate(normalHeight); +} +)HLSL" +R"HLSL( +void QD3D12_GetPomHeightStats(float2 uv, out float minHeight, out float maxHeight, out float authoredWeight) +{ + float2 texel = QD3D12_NormalMapTexelSize(); + + float4 nmC = gNormalMap.SampleLevel(gSamp2, uv, 0.0); + float4 nmL = gNormalMap.SampleLevel(gSamp2, uv - float2(texel.x, 0.0), 0.0); + float4 nmR = gNormalMap.SampleLevel(gSamp2, uv + float2(texel.x, 0.0), 0.0); + float4 nmU = gNormalMap.SampleLevel(gSamp2, uv - float2(0.0, texel.y), 0.0); + float4 nmD = gNormalMap.SampleLevel(gSamp2, uv + float2(0.0, texel.y), 0.0); + + authoredWeight = max( + QD3D12_AuthoredPomAlphaWeight(nmC.a), + max( + max(QD3D12_AuthoredPomAlphaWeight(nmL.a), QD3D12_AuthoredPomAlphaWeight(nmR.a)), + max(QD3D12_AuthoredPomAlphaWeight(nmU.a), QD3D12_AuthoredPomAlphaWeight(nmD.a)))); + + float hC = QD3D12_GetPomHeightFromSamples(uv); + float hL = QD3D12_GetPomHeightFromSamples(uv - float2(texel.x, 0.0)); + float hR = QD3D12_GetPomHeightFromSamples(uv + float2(texel.x, 0.0)); + float hU = QD3D12_GetPomHeightFromSamples(uv - float2(0.0, texel.y)); + float hD = QD3D12_GetPomHeightFromSamples(uv + float2(0.0, texel.y)); + + minHeight = min(hC, min(min(hL, hR), min(hU, hD))); + maxHeight = max(hC, max(max(hL, hR), max(hU, hD))); +} + +float QD3D12_GetPomDepth(float2 uv) +{ + return saturate(1.0 - QD3D12_GetPomHeightFromSamples(uv)); +} + +float QD3D12_GetPomConfidence(float2 uv) +{ + float minHeight = 0.5; + float maxHeight = 0.5; + float authoredWeight = 0.0; + QD3D12_GetPomHeightStats(uv, minHeight, maxHeight, authoredWeight); + + // Authored alpha height can use smaller height deltas. RGB-derived height + // needs more local range because it is estimated from a normal map, not a + // true displacement texture. + float heightRange = maxHeight - minHeight; + float threshold = lerp(0.055, 0.014, authoredWeight); + float confidence = saturate((heightRange - threshold) / max(0.18 - threshold, 0.001)); + + // Make the transition less binary so low-detail areas fade instead of popping. + return confidence * confidence * (3.0 - 2.0 * confidence); +} + float2 QD3D12_ComputeParallaxUV(VSOut i, float2 baseUv) { if (gUseNormalMap < 0.5) return baseUv; - float parallaxScale = gParallaxScale; + float confidence = QD3D12_GetPomConfidence(baseUv); + if (confidence <= 0.03) + return baseUv; + + float parallaxScale = gParallaxScale * confidence; if (abs(parallaxScale) < 1e-6) return baseUv; float3 n, t, b; - QD3D12_BuildTBN(i, n, t, b); + QD3D12_BuildPixelTBN(i, n, t, b); - float3 viewWS = QD3D12_SafeNormalize(-i.worldPos, n); + // Correct view vector. The old code used -worldPos, which only works when + // the camera is exactly at world origin and causes the obvious offset bugs + // as soon as the camera moves. Keep -worldPos only as a reduced compatibility + // fallback for older paths that cannot derive a perspective camera. + float cameraConfidence = (gCameraPomValid >= 0.5) ? 1.0 : 0.35; + float3 rawViewWS = (gCameraPomValid >= 0.5) ? (gCameraWorldPos - i.worldPos) : (-i.worldPos); + float3 viewWS = QD3D12_SafeNormalize(rawViewWS, n); + float NoV = dot(n, viewWS); + if (NoV <= 0.035) + return baseUv; - float3 viewTS = float3( + float3 viewTS = QD3D12_SafeNormalize(float3( dot(viewWS, t), dot(viewWS, b), - dot(viewWS, n) - ); + NoV + ), float3(0.0, 0.0, 1.0)); - float height = QD3D12_GetHeightFromNormalMap(baseUv); - float parallaxAmount = (height - 0.5) * parallaxScale; + float ndotv = saturate(viewTS.z); + float vz = max(ndotv, 0.22); - float vz = max(abs(viewTS.z), 0.15); - float2 uvOffset = (viewTS.xy / vz) * parallaxAmount; + // Fade out before far surfaces shimmer or when the viewing angle is too + // grazing for estimated height maps. + float viewDistance = (gCameraPomValid >= 0.5) ? max(length(gCameraWorldPos - i.worldPos), 1.0) : max(abs(i.currClip.w), 1.0); + float distanceFade = 1.0 - smoothstep(QD3D12_POM_DISTANCE_NEAR, QD3D12_POM_DISTANCE_FAR, viewDistance); + float grazingFade = smoothstep(0.08, 0.22, ndotv); - return baseUv - uvOffset; + float scale = parallaxScale * distanceFade * grazingFade * cameraConfidence; + if (scale <= 0.00045) + return baseUv; + + float layerCountF = lerp(10.0, 30.0, saturate(1.0 - ndotv)); + layerCountF = lerp(8.0, layerCountF, saturate(distanceFade)); + uint layerCount = (uint)clamp(layerCountF + 0.5, 8.0, 32.0); + + // Offset-limited POM. Divide by a softened z term and clamp the max UV walk; + // this removes the extreme stretched-offset artifacts on steep angles. + // Standard POM walks opposite the view vector in tangent space. The shift + // cap is intentionally tight for generated RGB-only height, looser for real + // authored alpha height. + float2 parallaxVector = (viewTS.xy / vz) * scale; + float parallaxLen = length(parallaxVector); + float maxParallaxShift = lerp(0.024, 0.058, confidence); + if (parallaxLen > maxParallaxShift && parallaxLen > 1e-6) + parallaxVector *= maxParallaxShift / parallaxLen; + + float invLayerCount = rcp((float)layerCount); + float2 deltaUv = parallaxVector * invLayerCount; + + float2 uv = baseUv; + float2 prevUv = uv; + float currentLayerDepth = 0.0; + float prevLayerDepth = 0.0; + float currentDepth = QD3D12_GetPomDepth(uv); + float prevDepth = currentDepth; + + [loop] + for (uint layer = 0u; layer < 32u; ++layer) + { + if (layer >= layerCount || currentLayerDepth >= currentDepth) + break; + + prevUv = uv; + prevLayerDepth = currentLayerDepth; + prevDepth = currentDepth; + + uv -= deltaUv; + currentLayerDepth += invLayerCount; + currentDepth = QD3D12_GetPomDepth(uv); + } + + float afterDepth = currentDepth - currentLayerDepth; + float beforeDepth = prevDepth - prevLayerDepth; + float denom = afterDepth - beforeDepth; + float weight = (abs(denom) > 1e-5) ? saturate(afterDepth / denom) : 0.0; + float2 refinedUv = lerp(uv, prevUv, weight); + + // Blend the final result in instead of applying a manual half-vector center + // correction. The center correction was the source of several texture-offset + // bugs on flat/low-confidence areas. + return lerp(baseUv, refinedUv, saturate(distanceFade * grazingFade)); } float3 BuildGBufferNormal(VSOut i) @@ -2658,7 +2947,7 @@ float3 BuildGBufferNormal(VSOut i) return n; float3 t, b; - QD3D12_BuildTBN(i, n, t, b); + QD3D12_BuildPixelTBN(i, n, t, b); float2 uv0 = QD3D12_ComputeParallaxUV(i, i.uv0); float3 tangentNormal = QD3D12_DecodeTangentSpaceNormal(gNormalMap.Sample(gSamp2, uv0).xyz); @@ -2673,12 +2962,18 @@ float3 BuildGBufferNormal(VSOut i) float2 QD3D12_BuildMaterialUV0(VSOut i) { float2 uv0 = i.uv0; - float n = TinyNoise(int2(i.pos.xy)) * 0.0005; - uv0 += float2(n, -n); if (gUseNormalMap > 0.5) uv0 = QD3D12_ComputeParallaxUV(i, uv0); + // Do not dither POM UVs. Even tiny post-POM noise makes relief edges look + // like a texture offset mismatch. Keep the old dither only for non-POM draws. + if (gUseNormalMap <= 0.5) + { + float n = TinyNoise(int2(i.pos.xy)) * 0.0005; + uv0 += float2(n, -n); + } + return uv0; } @@ -2888,11 +3183,40 @@ float QD3D12_EdgeLengthPixels(float4 clipA, float4 clipB) return length((ndcA - ndcB) * max(gRenderSize, float2(1.0, 1.0)) * 0.5); } +float QD3D12_ViewDistanceFromClip(float4 clipPos) +{ + // Clip-space w is proportional to view distance for the perspective camera + // path this tessellation route is restricted to. + return max(abs(clipPos.w), 1.0); +} + +float QD3D12_DistanceFadeFromClip(float4 clipPos, float nearDistance, float farDistance) +{ + return 1.0 - smoothstep(nearDistance, farDistance, QD3D12_ViewDistanceFromClip(clipPos)); +} + +float QD3D12_TessellationEdgeDistanceFade(VSOut a, VSOut b) +{ + // Edge-only distance keeps neighboring triangles crack-free: both triangles + // sharing an edge use the same two control points to compute the fade. + float edgeDistance = 0.5 * (QD3D12_ViewDistanceFromClip(a.currClip) + QD3D12_ViewDistanceFromClip(b.currClip)); + return 1.0 - smoothstep(QD3D12_TESS_DISTANCE_NEAR, QD3D12_TESS_DISTANCE_FAR, edgeDistance); +} + +float QD3D12_TessellationDisplacementFade(float4 clipPos) +{ + return QD3D12_DistanceFadeFromClip(clipPos, QD3D12_TESS_DISTANCE_NEAR, QD3D12_TESS_DISTANCE_FAR); +} + float QD3D12_ComputeNormalMapTessEdgeFactor(VSOut a, VSOut b) { if (gUseNormalMap <= 0.5) return 1.0; + float distanceFade = QD3D12_TessellationEdgeDistanceFade(a, b); + if (distanceFade <= 0.001) + return 1.0; + float ha = QD3D12_GetFilteredTessHeight(a.uv0); float hb = QD3D12_GetFilteredTessHeight(b.uv0); float hm = QD3D12_GetFilteredTessHeight((a.uv0 + b.uv0) * 0.5); @@ -2901,6 +3225,14 @@ float QD3D12_ComputeNormalMapTessEdgeFactor(VSOut a, VSOut b) // this edge compute the same tessellation factor because they use only the // two shared vertices and the same midpoint sample. float heightRange = max(abs(ha - hb), abs(hm - (ha + hb) * 0.5) * 2.0); + + // Do not subdivide flat/low-relief normal maps just because a normal map is + // bound. This is the matching geometry-side fix for the flat-area POM gate. + float edgeRelief = saturate((heightRange - 0.018) / 0.16); + edgeRelief = edgeRelief * edgeRelief * (3.0 - 2.0 * edgeRelief); + if (edgeRelief <= 0.001) + return 1.0; + float edgePixels = QD3D12_EdgeLengthPixels(a.currClip, b.currClip); float uvSpan = length(a.uv0 - b.uv0); @@ -2910,7 +3242,12 @@ float QD3D12_ComputeNormalMapTessEdgeFactor(VSOut a, VSOut b) float uvTerm = saturate(uvSpan * 4.0) * 2.0; float displacementTerm = saturate(gTessellationDisplacement * 6.0) * 2.0; - float factor = 1.0 + strength * 0.75 + screenTerm * 3.0 + heightTerm + uvTerm + displacementTerm; + float nearFactor = 1.0 + edgeRelief * (strength * 0.75 + screenTerm * 3.0 + heightTerm + uvTerm + displacementTerm); + nearFactor = clamp(nearFactor, QD3D12_TESS_NEAR_MIN_FACTOR, QD3D12_TESS_MAX_FACTOR); + + // Fade all subdivision pressure toward 1.0 with distance. Near surfaces keep + // the detailed factor; far surfaces become ordinary triangles. + float factor = lerp(1.0, nearFactor, distanceFade); return clamp(factor, QD3D12_TESS_MIN_FACTOR, QD3D12_TESS_MAX_FACTOR); } @@ -2991,7 +3328,9 @@ VSOut DSMain(HSConstOut tessFactors, float3 bary : SV_DomainLocation, const Outp float3 objNormal = QD3D12_SafeNormalize(i.objNormal, float3(0.0, 0.0, 1.0)); float height = QD3D12_GetFilteredTessHeight(i.uv0); float centeredHeight = QD3D12_CleanCenteredTessHeight(height); - float displacement = centeredHeight * gTessellationDisplacement; + float displacementFade = QD3D12_TessellationDisplacementFade(i.currClip); + float reliefConfidence = QD3D12_GetPomConfidence(i.uv0); + float displacement = centeredHeight * gTessellationDisplacement * displacementFade * reliefConfidence; float3 displacedObjPos = i.objPos + objNormal * displacement; float4 worldPos = mul(gModelMatrix, float4(displacedObjPos, 1.0)); @@ -3088,8 +3427,25 @@ Texture2DMS gPositionMS : register(t3); Texture2DMS gVelocityMS : register(t4); Texture2DMS gEmissiveMS : register(t5); Texture2DMS gSpecularMS : register(t6); +Texture2D gTaaHistory : register(t7); +Texture2D gTaaVelocity : register(t8); SamplerState gSamp0 : register(s0); +cbuffer PostCB : register(b0) +{ + // gPostParams0: x = sharpen strength, y = history weight, z = TAA reset flag. + // gPostParams1: x = tone-map exposure, y = white-point scale. + float4 gPostParams0; + float4 gPostParams1; +}; + +#define gPostSharpness gPostParams0.x +#define gTaaHistoryWeight gPostParams0.y +#define gTaaReset gPostParams0.z +#define gToneMapExposure gPostParams1.x +#define gToneMapWhiteScale gPostParams1.y +#define QD3D12_TONEMAP_TILE_DIM 64u + struct VSOut { float4 pos : SV_Position; @@ -3114,6 +3470,189 @@ float4 PSCopy(VSOut i) : SV_Target0 return gTex0.Sample(gSamp0, i.uv); } +float4 PSSharpen(VSOut i) : SV_Target0 +{ + float4 center = gTex0.Sample(gSamp0, i.uv); + float amount = saturate(gPostSharpness); + if (amount <= 0.0001) + return center; + + uint srcW, srcH; + gTex0.GetDimensions(srcW, srcH); + float2 texel = 1.0 / max(float2((float)srcW, (float)srcH), float2(1.0, 1.0)); + + float3 n = gTex0.Sample(gSamp0, saturate(i.uv + float2(0.0, -texel.y))).rgb; + float3 s = gTex0.Sample(gSamp0, saturate(i.uv + float2(0.0, texel.y))).rgb; + float3 e = gTex0.Sample(gSamp0, saturate(i.uv + float2( texel.x, 0.0))).rgb; + float3 w = gTex0.Sample(gSamp0, saturate(i.uv + float2(-texel.x, 0.0))).rgb; + float3 blur = (n + s + e + w) * 0.25; + + // Sharpen strictly after temporal reconstruction. Keep it conservative so + // the TAA resolve is not undone by ringing on high-contrast UI/geometry edges. + float3 sharp = center.rgb + (center.rgb - blur) * amount; + return float4(saturate(sharp), center.a); +} + + +float QD3D12_ToneMapBrightness(float3 rgb) +{ + rgb = max(rgb, 0.0); + // Use max channel instead of weighted luma so saturated colored lights still + // count as a bright pixel for white-point selection. + return max(max(rgb.r, rgb.g), rgb.b); +} + +float4 PSToneMapTileMax(VSOut i) : SV_Target0 +{ + uint srcW, srcH; + gTex0.GetDimensions(srcW, srcH); + if (srcW == 0u || srcH == 0u) + return float4(1.0, 0.0, 0.0, 1.0); + + uint2 tile = min( + uint2((uint)i.pos.x, (uint)i.pos.y), + uint2(QD3D12_TONEMAP_TILE_DIM - 1u, QD3D12_TONEMAP_TILE_DIM - 1u)); + + uint x0 = (tile.x * srcW) / QD3D12_TONEMAP_TILE_DIM; + uint y0 = (tile.y * srcH) / QD3D12_TONEMAP_TILE_DIM; + uint x1 = ((tile.x + 1u) * srcW + QD3D12_TONEMAP_TILE_DIM - 1u) / QD3D12_TONEMAP_TILE_DIM; + uint y1 = ((tile.y + 1u) * srcH + QD3D12_TONEMAP_TILE_DIM - 1u) / QD3D12_TONEMAP_TILE_DIM; + + x0 = min(x0, srcW - 1u); + y0 = min(y0, srcH - 1u); + x1 = min(max(x1, x0 + 1u), srcW); + y1 = min(max(y1, y0 + 1u), srcH); + + float maxBrightness = 0.0; + [loop] + for (uint y = y0; y < y1; ++y) + { + [loop] + for (uint x = x0; x < x1; ++x) + { + float3 hdr = gTex0.Load(int3(int2(x, y), 0)).rgb; + maxBrightness = max(maxBrightness, QD3D12_ToneMapBrightness(hdr)); + } + } + + return float4(min(maxBrightness, 65504.0), 0.0, 0.0, 1.0); +} + +float4 PSToneMapFinalMax(VSOut i) : SV_Target0 +{ + uint srcW, srcH; + gTex0.GetDimensions(srcW, srcH); + + float maxBrightness = 1.0; + [loop] + for (uint y = 0u; y < srcH; ++y) + { + [loop] + for (uint x = 0u; x < srcW; ++x) + { + maxBrightness = max(maxBrightness, gTex0.Load(int3(int2(x, y), 0)).r); + } + } + + return float4(min(maxBrightness, 65504.0), 0.0, 0.0, 1.0); +} + +float3 QD3D12_PostSharpenHDR(float2 uv, float3 center) +{ + float amount = saturate(gPostSharpness); + if (amount <= 0.0001) + return center; + + uint srcW, srcH; + gTex0.GetDimensions(srcW, srcH); + float2 texel = 1.0 / max(float2((float)srcW, (float)srcH), float2(1.0, 1.0)); + + float3 n = max(gTex0.Sample(gSamp0, saturate(uv + float2(0.0, -texel.y))).rgb, 0.0); + float3 s = max(gTex0.Sample(gSamp0, saturate(uv + float2(0.0, texel.y))).rgb, 0.0); + float3 e = max(gTex0.Sample(gSamp0, saturate(uv + float2( texel.x, 0.0))).rgb, 0.0); + float3 w = max(gTex0.Sample(gSamp0, saturate(uv + float2(-texel.x, 0.0))).rgb, 0.0); + float3 blur = (n + s + e + w) * 0.25; + + // Sharpen before tone mapping so highlights keep their shoulder instead of + // ringing after they have already been compressed to display range. + return min(max(center + (center - blur) * amount, 0.0), 65504.0); +} + +float3 QD3D12_ExtendedReinhard(float3 hdr, float whitePoint) +{ + hdr = max(hdr * max(gToneMapExposure, 0.0), 0.0); + + // Use the reduced brightest pixel as the white point. The white point is + // clamped to at least 1 so the pass never auto-brightens a dark/LDR frame. + float w = max(whitePoint * max(gToneMapWhiteScale, 0.001), 1.0); + float w2 = max(w * w, 1.0e-4); + float3 mapped = (hdr * (1.0 + hdr / w2)) / (1.0 + hdr); + return saturate(mapped); +} + +float4 PSToneMap(VSOut i) : SV_Target0 +{ + float4 center = gTex0.Sample(gSamp0, i.uv); + + // Root table t7 is the 1x1 brightest-pixel reduction for this shader. It is + // also used as TAA history by PSTAA, but those entries are never active in the + // same PSO. + float whitePoint = max(gTaaHistory.SampleLevel(gSamp0, float2(0.5, 0.5), 0.0).r, 1.0); + + float3 hdr = QD3D12_PostSharpenHDR(i.uv, max(center.rgb, 0.0)); + float3 mapped = QD3D12_ExtendedReinhard(hdr, whitePoint); + + // Do not apply a gamma pow here. This shim's swap chain and scene buffers are + // UNORM, not sRGB formats, and the legacy renderer already authored/displayed + // color in that space. A gamma conversion here is what made the prior patch + // look blown out. + return float4(mapped, saturate(center.a)); +} + +float4 PSTAA(VSOut i) : SV_Target0 +{ + float4 current = gTex0.Sample(gSamp0, i.uv); + + uint srcW, srcH; + gTex0.GetDimensions(srcW, srcH); + float2 texel = 1.0 / max(float2((float)srcW, (float)srcH), float2(1.0, 1.0)); + + float2 velocity = gTaaVelocity.Sample(gSamp0, i.uv).xy; // previousUV - currentUV + float2 prevUv = i.uv + velocity; + bool historyValid = + gTaaReset < 0.5 && + prevUv.x >= 0.0 && prevUv.x <= 1.0 && + prevUv.y >= 0.0 && prevUv.y <= 1.0; + + if (!historyValid) + return current; + + float4 history = gTaaHistory.Sample(gSamp0, prevUv); + + float3 minColor = current.rgb; + float3 maxColor = current.rgb; + [unroll] + for (int oy = -1; oy <= 1; ++oy) + { + [unroll] + for (int ox = -1; ox <= 1; ++ox) + { + float3 tap = gTex0.Sample(gSamp0, saturate(i.uv + float2((float)ox, (float)oy) * texel)).rgb; + minColor = min(minColor, tap); + maxColor = max(maxColor, tap); + } + } + + // Clip history to the current neighborhood to avoid ghost trails. + history.rgb = clamp(history.rgb, minColor - 0.015, maxColor + 0.015); + + float speed = saturate(length(velocity) * max(float(srcW), float(srcH)) * 0.075); + float historyWeight = saturate(gTaaHistoryWeight) * (1.0 - speed * 0.65); + float4 resolved = lerp(current, history, historyWeight); + resolved.a = current.a; + return resolved; +} + float3 QD3D12_LoadPostRadiance(float2 uv) { // Clamp bloom taps so a single bad HDR edge sample cannot flood the LDR @@ -3774,7 +4313,11 @@ 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(); + const bool cameraUpdatedForBatch = QD3D12_UpdateCameraInfoFromCurrentMatrices(); + key.cameraValid = (cameraUpdatedForBatch && g_gl.cameraState.valid) ? 1.0f : 0.0f; + key.cameraWorldPos[0] = g_gl.cameraState.cameraPos[0]; + key.cameraWorldPos[1] = g_gl.cameraState.cameraPos[1]; + key.cameraWorldPos[2] = g_gl.cameraState.cameraPos[2]; key.geometryFlag = QD3D12_CurrentEffectiveGeometryFlag(); key.roughness = g_gl.currentSurfaceRoughness; key.materialType = QD3D12_CurrentEffectiveMaterialType(); @@ -4919,7 +5462,7 @@ static void QD3D12_SelectRenderResolution(QD3D12Window& w, UINT outputWidth, UIN rrOptions.mode = QD3D12_MapDLSSMode(g_gl.upscalerQuality); rrOptions.outputWidth = outputWidth; rrOptions.outputHeight = outputHeight; - rrOptions.sharpness = g_gl.upscalerSharpness; + rrOptions.sharpness = 0.0f; // final sharpening happens after temporal reconstruction rrOptions.preExposure = 1.0f; rrOptions.exposureScale = 1.0f; rrOptions.colorBuffersHDR = sl::Boolean::eTrue; @@ -4958,12 +5501,38 @@ static void QD3D12_SelectRenderResolution(QD3D12Window& w, UINT outputWidth, UIN w.renderHeight = std::max(1, (UINT)std::lround((double)outputHeight * ratio)); } +static void QD3D12_SetPostConstants( + ID3D12GraphicsCommandList* cl, + float sharpness, + float taaHistoryWeight, + float taaReset, + float toneMapExposure = 1.0f, + float toneMapWhiteScale = 1.0f) +{ + if (!cl) + return; + + float constants[QD3D12_PostConstantDwords] = + { + sharpness, + taaHistoryWeight, + taaReset, + 0.0f, + toneMapExposure, + toneMapWhiteScale, + 0.0f, + 0.0f + }; + cl->SetGraphicsRoot32BitConstants(QD3D12_PostRootConstants, QD3D12_PostConstantDwords, constants, 0); +} + static void QD3D12_PostFullscreenPass(ID3D12GraphicsCommandList* cl, ID3D12PipelineState* pso, D3D12_GPU_DESCRIPTOR_HANDLE inputSrv, D3D12_CPU_DESCRIPTOR_HANDLE outputRtv, const D3D12_VIEWPORT& viewport, - const D3D12_RECT& scissor) + const D3D12_RECT& scissor, + float sharpnessOverride = -1.0f) { if (!cl || !pso) return; @@ -4971,6 +5540,8 @@ static void QD3D12_PostFullscreenPass(ID3D12GraphicsCommandList* cl, ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() }; cl->SetDescriptorHeaps(_countof(heaps), heaps); cl->SetGraphicsRootSignature(g_gl.postRootSig.Get()); + const float passSharpness = (sharpnessOverride >= 0.0f) ? sharpnessOverride : g_gl.upscalerSharpness; + QD3D12_SetPostConstants(cl, passSharpness, 0.0f, 1.0f); cl->SetPipelineState(pso); cl->OMSetRenderTargets(1, &outputRtv, FALSE, nullptr); cl->RSSetViewports(1, &viewport); @@ -4980,6 +5551,124 @@ static void QD3D12_PostFullscreenPass(ID3D12GraphicsCommandList* cl, cl->DrawInstanced(3, 1, 0, 0); } + +static bool QD3D12_GenerateToneMapMaxBrightness(QD3D12Window& w, D3D12_GPU_DESCRIPTOR_HANDLE hdrInputSrv) +{ + ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); + const UINT frame = w.frameIndex; + + if (!cl || !g_gl.postToneMapTileMaxPSO || !g_gl.postToneMapFinalMaxPSO) + return false; + + if (!w.toneMapTileMaxBuffers[frame] || !w.toneMapSceneMaxBuffers[frame]) + return false; + + QD3D12_TransitionResource( + cl, + w.toneMapTileMaxBuffers[frame].Get(), + w.toneMapTileMaxState[frame], + D3D12_RESOURCE_STATE_RENDER_TARGET); + + D3D12_VIEWPORT tileViewport{}; + tileViewport.TopLeftX = 0.0f; + tileViewport.TopLeftY = 0.0f; + tileViewport.Width = (float)QD3D12_ToneMapTileDim; + tileViewport.Height = (float)QD3D12_ToneMapTileDim; + tileViewport.MinDepth = 0.0f; + tileViewport.MaxDepth = 1.0f; + + D3D12_RECT tileScissor{}; + tileScissor.left = 0; + tileScissor.top = 0; + tileScissor.right = (LONG)QD3D12_ToneMapTileDim; + tileScissor.bottom = (LONG)QD3D12_ToneMapTileDim; + + QD3D12_PostFullscreenPass( + cl, + g_gl.postToneMapTileMaxPSO.Get(), + hdrInputSrv, + CurrentToneMapTileMaxRTV(), + tileViewport, + tileScissor, + 0.0f); + + QD3D12_TransitionResource( + cl, + w.toneMapTileMaxBuffers[frame].Get(), + w.toneMapTileMaxState[frame], + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + + QD3D12_TransitionResource( + cl, + w.toneMapSceneMaxBuffers[frame].Get(), + w.toneMapSceneMaxState[frame], + D3D12_RESOURCE_STATE_RENDER_TARGET); + + D3D12_VIEWPORT onePixelViewport{}; + onePixelViewport.TopLeftX = 0.0f; + onePixelViewport.TopLeftY = 0.0f; + onePixelViewport.Width = 1.0f; + onePixelViewport.Height = 1.0f; + onePixelViewport.MinDepth = 0.0f; + onePixelViewport.MaxDepth = 1.0f; + + D3D12_RECT onePixelScissor{}; + onePixelScissor.left = 0; + onePixelScissor.top = 0; + onePixelScissor.right = 1; + onePixelScissor.bottom = 1; + + QD3D12_PostFullscreenPass( + cl, + g_gl.postToneMapFinalMaxPSO.Get(), + w.toneMapTileMaxSrvGpu[frame], + CurrentToneMapSceneMaxRTV(), + onePixelViewport, + onePixelScissor, + 0.0f); + + QD3D12_TransitionResource( + cl, + w.toneMapSceneMaxBuffers[frame].Get(), + w.toneMapSceneMaxState[frame], + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + + return true; +} + +static bool QD3D12_ToneMapFullscreenPass( + QD3D12Window& w, + D3D12_GPU_DESCRIPTOR_HANDLE hdrInputSrv, + D3D12_CPU_DESCRIPTOR_HANDLE outputRtv, + const D3D12_VIEWPORT& viewport, + const D3D12_RECT& scissor, + float sharpnessOverride = -1.0f) +{ + ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); + const UINT frame = w.frameIndex; + + if (!cl || !g_gl.postToneMapPSO) + return false; + + if (!QD3D12_GenerateToneMapMaxBrightness(w, hdrInputSrv)) + return false; + + ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() }; + cl->SetDescriptorHeaps(_countof(heaps), heaps); + cl->SetGraphicsRootSignature(g_gl.postRootSig.Get()); + const float passSharpness = (sharpnessOverride >= 0.0f) ? sharpnessOverride : g_gl.upscalerSharpness; + QD3D12_SetPostConstants(cl, passSharpness, 0.0f, 1.0f, 1.0f, 1.0f); + cl->SetPipelineState(g_gl.postToneMapPSO.Get()); + cl->OMSetRenderTargets(1, &outputRtv, FALSE, nullptr); + cl->RSSetViewports(1, &viewport); + cl->RSSetScissorRects(1, &scissor); + cl->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + cl->SetGraphicsRootDescriptorTable(0, hdrInputSrv); + cl->SetGraphicsRootDescriptorTable(7, w.toneMapSceneMaxSrvGpu[frame]); + cl->DrawInstanced(3, 1, 0, 0); + return true; +} + static void QD3D12_CompositeEmissiveIntoSceneColor(QD3D12Window& w) { ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); @@ -5038,7 +5727,8 @@ static ID3D12Resource* QD3D12_PrepareStreamlineOutputForWrite(QD3D12Window& w) static bool QD3D12_CopyStreamlineOutputToBackBuffer( QD3D12Window& w, const D3D12_VIEWPORT& outputViewport, - const D3D12_RECT& outputScissor) + const D3D12_RECT& outputScissor, + bool toneMapInput) { ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); if (!cl || !w.slOutputBuffers[w.frameIndex] || !g_gl.postCopyPSO) @@ -5050,6 +5740,55 @@ static bool QD3D12_CopyStreamlineOutputToBackBuffer( w.slOutputState[w.frameIndex], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + return QD3D12_FinalBlitToBackBuffer(w, w.slOutputSrvGpu[w.frameIndex], outputViewport, outputScissor, toneMapInput); +} + + +static bool QD3D12_ExternalTemporalUpscalerActive(const QD3D12Window& w) +{ + if (w.isPbuffer) + return false; + +#if defined(QD3D12_ENABLE_STREAMLINE) + if (g_gl.upscalerBackend == QD3D12_UPSCALER_DLSS && + g_qd3d12Sl.deviceBound && + g_gl.upscalerQuality != QD3D12_QUALITY_NATIVE) + { + return true; + } +#endif + +#if defined(QD3D12_ENABLE_FFX) + if (g_gl.upscalerBackend == QD3D12_UPSCALER_FSR && + !QD3D12_IsDLAAQuality(g_gl.upscalerQuality) && + g_gl.upscalerQuality != QD3D12_QUALITY_NATIVE) + { + return true; + } +#endif + + return false; +} + + +static bool QD3D12_ShouldRunInternalTAA(const QD3D12Window& w) +{ + // DLSS/DLSS-RR/FSR are already temporal. The internal TAA pass replaces MSAA + // for the native/no-upscaler path and runs after lighting but before 2D. + return !w.isPbuffer && !QD3D12_ExternalTemporalUpscalerActive(w); +} + +static bool QD3D12_FinalBlitToBackBuffer( + QD3D12Window& w, + D3D12_GPU_DESCRIPTOR_HANDLE inputSrv, + const D3D12_VIEWPORT& outputViewport, + const D3D12_RECT& outputScissor, + bool toneMapInput) +{ + ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); + if (!cl || !g_gl.postCopyPSO) + return false; + QD3D12_TransitionResource( cl, w.backBuffers[w.frameIndex].Get(), @@ -5059,10 +5798,24 @@ static bool QD3D12_CopyStreamlineOutputToBackBuffer( const float clearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; cl->ClearRenderTargetView(CurrentBackBufferRTV(), clearColor, 0, nullptr); + if (toneMapInput) + { + // HDR inputs get reduced to a brightest-pixel white point, then tone mapped. + // If that path is unavailable, fall through to the old copy/sharpen path. + if (QD3D12_ToneMapFullscreenPass(w, inputSrv, CurrentBackBufferRTV(), outputViewport, outputScissor)) + return true; + } + + ID3D12PipelineState* finalPso = (g_gl.upscalerSharpness > 0.0001f && g_gl.postSharpenPSO) + ? g_gl.postSharpenPSO.Get() + : g_gl.postCopyPSO.Get(); + + // This is the only final sharpen point. All temporal work (internal TAA, + // DLSS/DLSS-RR, or FSR) has already completed before this fullscreen pass. QD3D12_PostFullscreenPass( cl, - g_gl.postCopyPSO.Get(), - w.slOutputSrvGpu[w.frameIndex], + finalPso, + inputSrv, CurrentBackBufferRTV(), outputViewport, outputScissor); @@ -5070,6 +5823,65 @@ static bool QD3D12_CopyStreamlineOutputToBackBuffer( return true; } +static bool QD3D12_RunInternalTAA(QD3D12Window& w, D3D12_GPU_DESCRIPTOR_HANDLE inputSrv, D3D12_GPU_DESCRIPTOR_HANDLE& outputSrv) +{ + outputSrv = inputSrv; + + ID3D12GraphicsCommandList* cl = g_gl.cmdList.Get(); + const UINT frame = w.frameIndex; + const UINT historyFrame = frame ^ 1u; + if (!cl || !g_gl.postTaaPSO || !w.taaBuffers[frame] || !w.velocityBuffers[frame]) + return false; + + if (!QD3D12_ShouldRunInternalTAA(w)) + return false; + + QD3D12_TransitionResource(cl, w.taaBuffers[frame].Get(), w.taaBufferState[frame], D3D12_RESOURCE_STATE_RENDER_TARGET); + QD3D12_TransitionResource(cl, w.velocityBuffers[frame].Get(), w.velocityBufferState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + + const bool resetHistory = g_gl.motionHistoryReset || !w.taaHistoryValid[historyFrame] || !w.taaBuffers[historyFrame]; + D3D12_GPU_DESCRIPTOR_HANDLE historySrv = inputSrv; + if (!resetHistory) + { + QD3D12_TransitionResource(cl, w.taaBuffers[historyFrame].Get(), w.taaBufferState[historyFrame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + historySrv = w.taaSrvGpu[historyFrame]; + } + + D3D12_VIEWPORT viewport{}; + viewport.TopLeftX = 0.0f; + viewport.TopLeftY = 0.0f; + viewport.Width = (float)w.renderWidth; + viewport.Height = (float)w.renderHeight; + viewport.MinDepth = 0.0f; + viewport.MaxDepth = 1.0f; + + D3D12_RECT scissor{}; + scissor.left = 0; + scissor.top = 0; + scissor.right = (LONG)w.renderWidth; + scissor.bottom = (LONG)w.renderHeight; + + ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() }; + cl->SetDescriptorHeaps(_countof(heaps), heaps); + cl->SetGraphicsRootSignature(g_gl.postRootSig.Get()); + QD3D12_SetPostConstants(cl, g_gl.upscalerSharpness, 0.90f, resetHistory ? 1.0f : 0.0f); + cl->SetPipelineState(g_gl.postTaaPSO.Get()); + D3D12_CPU_DESCRIPTOR_HANDLE rtv = CurrentTaaOutputRTV(); + cl->OMSetRenderTargets(1, &rtv, FALSE, nullptr); + cl->RSSetViewports(1, &viewport); + cl->RSSetScissorRects(1, &scissor); + cl->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + cl->SetGraphicsRootDescriptorTable(0, inputSrv); + cl->SetGraphicsRootDescriptorTable(7, historySrv); + cl->SetGraphicsRootDescriptorTable(8, w.velocitySrvGpu[frame]); + cl->DrawInstanced(3, 1, 0, 0); + + QD3D12_TransitionResource(cl, w.taaBuffers[frame].Get(), w.taaBufferState[frame], D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + w.taaHistoryValid[frame] = true; + outputSrv = w.taaSrvGpu[frame]; + return true; +} + static void QD3D12_ExecuteMainCommandListAndWait(QD3D12Window& w) { QD3D12_CHECK(g_gl.cmdList->Close()); @@ -5194,7 +6006,7 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) dlssOptions.mode = QD3D12_MapDLSSMode(g_gl.upscalerQuality); dlssOptions.outputWidth = w.width; dlssOptions.outputHeight = w.height; - dlssOptions.sharpness = g_gl.upscalerSharpness; + dlssOptions.sharpness = 0.0f; // final sharpening happens after temporal reconstruction dlssOptions.colorBuffersHDR = sl::Boolean::eTrue; dlssOptions.useAutoExposure = sl::Boolean::eFalse; dlssOptions.alphaUpscalingEnabled = sl::Boolean::eFalse; @@ -5210,7 +6022,7 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) rrOptions.mode = QD3D12_MapDLSSMode(g_gl.upscalerQuality); rrOptions.outputWidth = w.width; rrOptions.outputHeight = w.height; - rrOptions.sharpness = g_gl.upscalerSharpness; + rrOptions.sharpness = 0.0f; // final sharpening happens after temporal reconstruction rrOptions.preExposure = 1.0f; rrOptions.exposureScale = 1.0f; rrOptions.colorBuffersHDR = sl::Boolean::eTrue; @@ -5239,7 +6051,7 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) const sl::Result evalResult = slEvaluateFeature(sl::kFeatureDLSS_RR, *frameToken, inputs, _countof(inputs), cl); if (evalResult == sl::Result::eOk) { - if (QD3D12_CopyStreamlineOutputToBackBuffer(w, outputViewport, outputScissor)) + if (QD3D12_CopyStreamlineOutputToBackBuffer(w, outputViewport, outputScissor, true)) return; QD3D12_Log("DLSS_RR succeeded but copying Streamline output to backbuffer failed; falling back."); @@ -5298,7 +6110,7 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) options.mode = QD3D12_MapDLSSMode(g_gl.upscalerQuality); options.outputWidth = w.width; options.outputHeight = w.height; - options.sharpness = g_gl.upscalerSharpness; + options.sharpness = 0.0f; // final sharpening happens after temporal reconstruction options.colorBuffersHDR = useLightingUpscaleInput ? sl::Boolean::eTrue : sl::Boolean::eFalse; options.useAutoExposure = useLightingUpscaleInput ? sl::Boolean::eFalse : sl::Boolean::eTrue; options.alphaUpscalingEnabled = sl::Boolean::eFalse; @@ -5315,7 +6127,7 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) const sl::Result evalResult = slEvaluateFeature(sl::kFeatureDLSS, *frameToken, inputs, _countof(inputs), cl); if (evalResult == sl::Result::eOk) { - if (QD3D12_CopyStreamlineOutputToBackBuffer(w, outputViewport, outputScissor)) + if (QD3D12_CopyStreamlineOutputToBackBuffer(w, outputViewport, outputScissor, useLightingUpscaleInput)) return; QD3D12_Log("DLSS succeeded but copying Streamline output to backbuffer failed; falling back."); @@ -5343,7 +6155,10 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) dispatch.color = ffxApiGetResourceDX12(w.sceneColorBuffers[w.frameIndex].Get(), FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); dispatch.depth = ffxApiGetResourceDX12(w.depthBuffer.Get(), FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); dispatch.motionVectors = ffxApiGetResourceDX12(w.velocityBuffers[w.frameIndex].Get(), FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); - dispatch.output = ffxApiGetResourceDX12(w.backBuffers[w.frameIndex].Get(), FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); + ID3D12Resource* ffxOutputResource = QD3D12_PrepareStreamlineOutputForWrite(w); + if (!ffxOutputResource) + return; + dispatch.output = ffxApiGetResourceDX12(ffxOutputResource, FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); dispatch.jitterOffset.x = -g_gl.jitterX; dispatch.jitterOffset.y = -g_gl.jitterY; dispatch.motionVectorScale.x = (float)w.renderWidth; @@ -5352,8 +6167,8 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) dispatch.renderSize.height = w.renderHeight; dispatch.upscaleSize.width = w.width; dispatch.upscaleSize.height = w.height; - dispatch.enableSharpening = true; - dispatch.sharpness = g_gl.upscalerSharpness; + dispatch.enableSharpening = false; + dispatch.sharpness = 0.0f; dispatch.frameTimeDelta = 16.6667f; dispatch.preExposure = 1.0f; dispatch.reset = g_gl.motionHistoryReset; @@ -5364,14 +6179,17 @@ static void QD3D12_RunUpscalerOrBlit(QD3D12Window& w) dispatch.flags = 0; if (ffx::Dispatch(g_qd3d12Ffx.upscaleContext, dispatch) == ffx::ReturnCode::Ok) - return; + { + if (QD3D12_CopyStreamlineOutputToBackBuffer(w, outputViewport, outputScissor, false)) + return; + } } } #endif - const float clearColor[4] = { 0, 0, 0, 1 }; - cl->ClearRenderTargetView(CurrentBackBufferRTV(), clearColor, 0, nullptr); - QD3D12_PostFullscreenPass(cl, g_gl.postCopyPSO.Get(), upscaleInputSrv, CurrentBackBufferRTV(), outputViewport, outputScissor); + D3D12_GPU_DESCRIPTOR_HANDLE finalInputSrv = upscaleInputSrv; + QD3D12_RunInternalTAA(w, upscaleInputSrv, finalInputSrv); + QD3D12_FinalBlitToBackBuffer(w, finalInputSrv, outputViewport, outputScissor, useLightingUpscaleInput); } static bool QD3D12_FormatSupportsSampleCount(DXGI_FORMAT format, UINT sampleCount) @@ -5431,27 +6249,7 @@ static bool QD3D12_CanUseGBufferSampleCount(UINT sampleCount) static void QD3D12_SelectGBufferSampleCount() { g_qd3d12GBufferSampleCount = 1; - - const UINT candidates[] = - { - QD3D12_RequestedGBufferSampleCount, - 2u, - 1u - }; - - for (UINT sampleCount : candidates) - { - if (QD3D12_CanUseGBufferSampleCount(sampleCount)) - { - g_qd3d12GBufferSampleCount = sampleCount; - break; - } - } - - if (g_qd3d12GBufferSampleCount > 1) - QD3D12_Log("QD3D12 G-buffer MSAA enabled: %ux", g_qd3d12GBufferSampleCount); - else - QD3D12_Log("QD3D12 G-buffer MSAA disabled/fallback: 1x"); + QD3D12_Log("QD3D12 G-buffer MSAA removed: single-sample render targets active."); } static void QD3D12_CreateDevice() @@ -5759,6 +6557,56 @@ static void QD3D12_CreateRTVsForWindow(QD3D12Window& w) false); } + for (UINT i = 0; i < QD3D12_FrameCount; ++i) + { + CreateTexture2D( + w.taaBuffers[i], + w.taaBufferState[i], + QD3D12_StreamlineOutputFormat, + colorClear, + false, + w.renderWidth, + w.renderHeight, + 1, + D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + QD3D12_RtvAt(w, QD3D12_RTV_TAA_OUTPUT, i), + true); + w.taaHistoryValid[i] = false; + } + + + for (UINT i = 0; i < QD3D12_FrameCount; ++i) + { + CreateTexture2D( + w.toneMapTileMaxBuffers[i], + w.toneMapTileMaxState[i], + QD3D12_StreamlineOutputFormat, + colorClear, + false, + QD3D12_ToneMapTileDim, + QD3D12_ToneMapTileDim, + 1, + D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + QD3D12_RtvAt(w, QD3D12_RTV_TONEMAP_TILE_MAX, i), + true); + + CreateTexture2D( + w.toneMapSceneMaxBuffers[i], + w.toneMapSceneMaxState[i], + QD3D12_StreamlineOutputFormat, + colorClear, + false, + 1, + 1, + 1, + D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + QD3D12_RtvAt(w, QD3D12_RTV_TONEMAP_SCENE_MAX, i), + true); + } + auto CreateTextureSrv = [&](ID3D12Resource* res, DXGI_FORMAT format, UINT& srvIndex, D3D12_CPU_DESCRIPTOR_HANDLE& cpu, D3D12_GPU_DESCRIPTOR_HANDLE& gpu) { @@ -5821,6 +6669,16 @@ static void QD3D12_CreateRTVsForWindow(QD3D12Window& w) for (UINT i = 0; i < QD3D12_FrameCount; ++i) CreateTextureSrv(w.slOutputBuffers[i].Get(), QD3D12_StreamlineOutputFormat, w.slOutputSrvIndex[i], w.slOutputSrvCpu[i], w.slOutputSrvGpu[i]); + for (UINT i = 0; i < QD3D12_FrameCount; ++i) + CreateTextureSrv(w.taaBuffers[i].Get(), QD3D12_StreamlineOutputFormat, w.taaSrvIndex[i], w.taaSrvCpu[i], w.taaSrvGpu[i]); + + + for (UINT i = 0; i < QD3D12_FrameCount; ++i) + CreateTextureSrv(w.toneMapTileMaxBuffers[i].Get(), QD3D12_StreamlineOutputFormat, w.toneMapTileMaxSrvIndex[i], w.toneMapTileMaxSrvCpu[i], w.toneMapTileMaxSrvGpu[i]); + + for (UINT i = 0; i < QD3D12_FrameCount; ++i) + CreateTextureSrv(w.toneMapSceneMaxBuffers[i].Get(), QD3D12_StreamlineOutputFormat, w.toneMapSceneMaxSrvIndex[i], w.toneMapSceneMaxSrvCpu[i], w.toneMapSceneMaxSrvGpu[i]); + if (useMsaa) { for (UINT i = 0; i < QD3D12_FrameCount; ++i) @@ -6158,7 +7016,7 @@ static ComPtr CompileShaderVariant(const char* entry, const char* targ static void QD3D12_CreatePostRootSignature() { - D3D12_DESCRIPTOR_RANGE ranges[7]{}; + D3D12_DESCRIPTOR_RANGE ranges[QD3D12_PostSrvCount]{}; for (UINT i = 0; i < _countof(ranges); ++i) { ranges[i].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; @@ -6168,8 +7026,8 @@ static void QD3D12_CreatePostRootSignature() ranges[i].OffsetInDescriptorsFromTableStart = 0; } - D3D12_ROOT_PARAMETER params[7]{}; - for (UINT i = 0; i < _countof(params); ++i) + D3D12_ROOT_PARAMETER params[QD3D12_PostSrvCount + 1]{}; + for (UINT i = 0; i < QD3D12_PostSrvCount; ++i) { params[i].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; params[i].DescriptorTable.NumDescriptorRanges = 1; @@ -6177,6 +7035,12 @@ static void QD3D12_CreatePostRootSignature() params[i].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; } + params[QD3D12_PostRootConstants].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; + params[QD3D12_PostRootConstants].Constants.Num32BitValues = QD3D12_PostConstantDwords; + params[QD3D12_PostRootConstants].Constants.ShaderRegister = 0; + params[QD3D12_PostRootConstants].Constants.RegisterSpace = 0; + params[QD3D12_PostRootConstants].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; + D3D12_STATIC_SAMPLER_DESC samp{}; samp.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; samp.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; @@ -6265,6 +7129,11 @@ static void QD3D12_CompileShaders() g_gl.psUntexturedColorOnlyBlob = CompileShaderVariant("PSMainUntexturedColorOnly", "ps_6_0"); g_gl.postVsBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "VSMain", "vs_6_0"); g_gl.postPsCopyBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSCopy", "ps_6_0"); + g_gl.postPsSharpenBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSSharpen", "ps_6_0"); + g_gl.postPsToneMapBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSToneMap", "ps_6_0"); + g_gl.postPsToneMapTileMaxBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSToneMapTileMax", "ps_6_0"); + g_gl.postPsToneMapFinalMaxBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSToneMapFinalMax", "ps_6_0"); + g_gl.postPsTaaBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSTAA", "ps_6_0"); g_gl.postPsAddBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSAdd", "ps_6_0"); g_gl.postPsDepthCopyBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSDepthCopy", "ps_6_0"); g_gl.postPsDepthResolveMsaaBlob = CompileShaderSourceVariant(kQD3D12PostHLSL, "PSDepthResolveMS", "ps_6_0"); @@ -6450,6 +7319,24 @@ static void QD3D12_CreatePSOs() QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&d, IID_PPV_ARGS(&g_gl.postCopyPSO))); + d.PS = { g_gl.postPsSharpenBlob->GetBufferPointer(), g_gl.postPsSharpenBlob->GetBufferSize() }; + QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&d, IID_PPV_ARGS(&g_gl.postSharpenPSO))); + + d.PS = { g_gl.postPsToneMapBlob->GetBufferPointer(), g_gl.postPsToneMapBlob->GetBufferSize() }; + QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&d, IID_PPV_ARGS(&g_gl.postToneMapPSO))); + + D3D12_GRAPHICS_PIPELINE_STATE_DESC reduceDesc = d; + reduceDesc.RTVFormats[0] = QD3D12_StreamlineOutputFormat; + reduceDesc.PS = { g_gl.postPsToneMapTileMaxBlob->GetBufferPointer(), g_gl.postPsToneMapTileMaxBlob->GetBufferSize() }; + QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&reduceDesc, IID_PPV_ARGS(&g_gl.postToneMapTileMaxPSO))); + reduceDesc.PS = { g_gl.postPsToneMapFinalMaxBlob->GetBufferPointer(), g_gl.postPsToneMapFinalMaxBlob->GetBufferSize() }; + QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&reduceDesc, IID_PPV_ARGS(&g_gl.postToneMapFinalMaxPSO))); + + D3D12_GRAPHICS_PIPELINE_STATE_DESC taaDesc = d; + taaDesc.PS = { g_gl.postPsTaaBlob->GetBufferPointer(), g_gl.postPsTaaBlob->GetBufferSize() }; + taaDesc.RTVFormats[0] = QD3D12_StreamlineOutputFormat; + QD3D12_CHECK(g_gl.device->CreateGraphicsPipelineState(&taaDesc, IID_PPV_ARGS(&g_gl.postTaaPSO))); + d.PS = { g_gl.postPsAddBlob->GetBufferPointer(), g_gl.postPsAddBlob->GetBufferSize() }; d.BlendState.RenderTarget[0].BlendEnable = TRUE; d.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_ONE; @@ -6946,7 +7833,7 @@ bool QD3D12_InitForQuakeWindow(struct QD3D12Window* window, HWND hwnd, int width } g_gl.motionHistoryReset = true; - QD3D12_Log("QD3D12 initialized: output=%ux%u render=%ux%u gbufferMSAA=%ux", window->width, window->height, window->renderWidth, window->renderHeight, QD3D12_GBufferSampleCount()); + QD3D12_Log("QD3D12 initialized: output=%ux%u render=%ux%u gbufferSamples=%ux", window->width, window->height, window->renderWidth, window->renderHeight, QD3D12_GBufferSampleCount()); return true; } @@ -9015,13 +9902,17 @@ static void QD3D12_FlushQueuedBatches() // Normal-map strength now drives a visibly stronger object-space displacement. // The hull shader can now subdivide much deeper, so the CPU side permits a // deeper push too while the shader filters/suppresses tiny bump-map noise. - // Default strength 1.0 gives about +/-0.12 object units for authored alpha - // height maps and outward lift for RGB-only bump detail; glNormalMapStrengthf() - // can push it higher up to the hard 0.50 object-unit safety cap. + // Default strength 1.0 gives about +/-0.08 object units before shader-side + // relief/distance gating. This keeps close relief visible but avoids the + // large outward slab offsets that RGB-only pseudo-height can create. const float tessStrength = ClampValue(batch.key.normalMapStrength, 0.0f, 4.0f); - const float tessDisplacementScale = 0.12f; + const float tessDisplacementScale = 0.08f; dc->materialMapPad[3] = QD3D12_UseNormalMapTessellationPSO(batch.key, nativeColorOnly) ? ClampValue(tessStrength * tessDisplacementScale, 0.0f, 0.50f) : 0.0f; + dc->cameraPomPad[0] = batch.key.cameraWorldPos[0]; + dc->cameraPomPad[1] = batch.key.cameraWorldPos[1]; + dc->cameraPomPad[2] = batch.key.cameraWorldPos[2]; + dc->cameraPomPad[3] = batch.key.cameraValid; if (batch.key.useARBPrograms) { @@ -10947,6 +11838,8 @@ void QD3D12_ReleaseWindowSizeResources(QD3D12Window& w) w.emissiveMsaaBuffers[i].Reset(); w.specularMsaaBuffers[i].Reset(); w.slOutputBuffers[i].Reset(); + w.taaBuffers[i].Reset(); + w.taaHistoryValid[i] = false; w.sceneColorState[i] = D3D12_RESOURCE_STATE_COMMON; w.backBufferState[i] = D3D12_RESOURCE_STATE_COMMON; @@ -10962,6 +11855,7 @@ void QD3D12_ReleaseWindowSizeResources(QD3D12Window& w) w.emissiveMsaaState[i] = D3D12_RESOURCE_STATE_COMMON; w.specularMsaaState[i] = D3D12_RESOURCE_STATE_COMMON; w.slOutputState[i] = D3D12_RESOURCE_STATE_COMMON; + w.taaBufferState[i] = D3D12_RESOURCE_STATE_COMMON; } w.depthBuffer.Reset(); @@ -12773,6 +13667,7 @@ static void QD3D12_ResolveMsaaDepthToSceneDepth(QD3D12Window& w) ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() }; cl->SetDescriptorHeaps(_countof(heaps), heaps); cl->SetGraphicsRootSignature(g_gl.postRootSig.Get()); + QD3D12_SetPostConstants(cl, g_gl.upscalerSharpness, 0.0f, 1.0f); cl->SetPipelineState(g_gl.postDepthResolveMsaaPSO.Get()); cl->OMSetRenderTargets(0, nullptr, FALSE, &dsv); cl->RSSetViewports(1, &viewport); @@ -12839,6 +13734,7 @@ static void QD3D12_PointResolveMsaaGBufferToSingleSample(QD3D12Window& w) ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() }; cl->SetDescriptorHeaps(_countof(heaps), heaps); cl->SetGraphicsRootSignature(g_gl.postRootSig.Get()); + QD3D12_SetPostConstants(cl, g_gl.upscalerSharpness, 0.0f, 1.0f); cl->SetPipelineState(g_gl.postGBufferPointResolveMsaaPSO.Get()); cl->OMSetRenderTargets(5, rtvs, FALSE, nullptr); cl->RSSetViewports(1, &viewport); @@ -12990,6 +13886,7 @@ static void QD3D12_CopySceneDepthToNative(QD3D12Window& w) ID3D12DescriptorHeap* heaps[] = { g_gl.srvHeap.Get() }; cl->SetDescriptorHeaps(_countof(heaps), heaps); cl->SetGraphicsRootSignature(g_gl.postRootSig.Get()); + QD3D12_SetPostConstants(cl, g_gl.upscalerSharpness, 0.0f, 1.0f); cl->SetPipelineState(g_gl.postDepthCopyPSO.Get()); cl->OMSetRenderTargets(0, nullptr, FALSE, &dsv); cl->RSSetViewports(1, &viewport); @@ -13190,7 +14087,7 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) raySpp, activeMaxBounces, useDLSSRayReconstruction ? 0 : 1, - useDLSSRayReconstruction ? 0.0f : 0.45f); + useDLSSRayReconstruction ? 0.0f : 0.75f); glRaytracingLightingSetEmissiveInput(sceneEmissive, QD3D12_EmissiveFormat); glRaytracingLightingSetSpecularInput(sceneSpecular, QD3D12_SpecularAlbedoFormat); @@ -13310,16 +14207,27 @@ void glLightScene(glRaytracingSceneHandle_t sceneHandle) scissor.right = (LONG)window->renderWidth; scissor.bottom = (LONG)window->renderHeight; - // Non-RR path: replace the low-resolution scene color with the lit result, - // then upscale that scene color into the backbuffer. Use the texture returned - // for this window instead of a separate global texture pointer. - QD3D12_PostFullscreenPass( - cl, - g_gl.postCopyPSO.Get(), + // Non-RR path: replace the low-resolution scene color with the lit result + // after measuring the brightest HDR pixel and using it as the tone-map + // white point. Keep sharpness at zero here; final output sharpening stays + // in QD3D12_FinalBlitToBackBuffer(). + if (!QD3D12_ToneMapFullscreenPass( + *window, lightingTex->srvGpu, CurrentResolvedSceneColorRTV(), viewport, - scissor); + scissor, + 0.0f)) + { + QD3D12_PostFullscreenPass( + cl, + g_gl.postCopyPSO.Get(), + lightingTex->srvGpu, + CurrentResolvedSceneColorRTV(), + viewport, + scissor, + 0.0f); + } } else { diff --git a/neo/engine/renderer/RenderSystem_init.cpp b/neo/engine/renderer/RenderSystem_init.cpp index 190923ce..37663736 100644 --- a/neo/engine/renderer/RenderSystem_init.cpp +++ b/neo/engine/renderer/RenderSystem_init.cpp @@ -371,8 +371,20 @@ typedef struct vidmode_s { } vidmode_t; vidmode_t r_vidModes[] = { - { "Mode 0: 1280x720", 1280, 720 }, - { "Mode 1: 1920x1080", 1920, 1080 } + { "Mode 0: 640x360", 640, 360 }, + { "Mode 1: 854x480", 854, 480 }, + { "Mode 2: 960x540", 960, 540 }, + { "Mode 3: 1024x576", 1024, 576 }, + { "Mode 4: 1280x720", 1280, 720 }, + { "Mode 5: 1366x768", 1366, 768 }, + { "Mode 6: 1600x900", 1600, 900 }, + { "Mode 7: 1920x1080", 1920, 1080 }, + { "Mode 8: 2048x1152", 2048, 1152 }, + { "Mode 9: 2560x1440", 2560, 1440 }, + { "Mode 10: 3200x1800", 3200, 1800 }, + { "Mode 11: 3840x2160", 3840, 2160 }, + { "Mode 12: 5120x2880", 5120, 2880 }, + { "Mode 13: 7680x4320", 7680, 4320 } }; static int s_numVidModes = ( sizeof( r_vidModes ) / sizeof( r_vidModes[0] ) ); diff --git a/neo/engine/renderer/draw_dx.cpp b/neo/engine/renderer/draw_dx.cpp index d0af3dd9..ac694535 100644 --- a/neo/engine/renderer/draw_dx.cpp +++ b/neo/engine/renderer/draw_dx.cpp @@ -176,7 +176,7 @@ void RB_DXDrawInteractions(void) const float r = srcLight.shaderParms[SHADERPARM_RED]; const float g = srcLight.shaderParms[SHADERPARM_GREEN]; const float b = srcLight.shaderParms[SHADERPARM_BLUE]; - const float intensity = 4.0f; + const float intensity = 6.0f; glRaytracingLight_t light = {}; bool supported = true; @@ -187,9 +187,9 @@ void RB_DXDrawInteractions(void) vLight->globalLightOrigin.x, vLight->globalLightOrigin.y, vLight->globalLightOrigin.z, - srcLight.lightRadius[0] * 1.25f, - srcLight.lightRadius[1] * 1.25f, - srcLight.lightRadius[2] * 1.4f, + srcLight.lightRadius[0] * 1.3f, + srcLight.lightRadius[1] * 1.3f, + srcLight.lightRadius[2] * 2.0f, r, g, b, intensity); diff --git a/neo/quake4/game.vcxproj b/neo/quake4/game.vcxproj index 6e81474a..42792101 100644 --- a/neo/quake4/game.vcxproj +++ b/neo/quake4/game.vcxproj @@ -17,10 +17,13 @@ {57CFA45E-E1FD-4DFF-997D-68DD2700C7D7} Game - Perforce Project + + - .. - MSSCCI:NXN alienbrain + + + + Win32Proj 10.0 q4game