From 81faf3c09d46cdec740c80367e1206f3519162cc Mon Sep 17 00:00:00 2001 From: Musa Haji Date: Thu, 7 May 2026 11:46:29 -0400 Subject: [PATCH 1/6] refactor(Quantum Break): move post sr encode into taa resolve shader --- .../Quantum Break/Luma_QB_PostSREncode.hlsl | 18 --------- .../temporal_resolve_0x99274617.ps_5_0.hlsl | 1 + Source/Games/Quantum Break/UpscalingNotes.md | 16 +++----- Source/Games/Quantum Break/main.cpp | 39 +++++-------------- 4 files changed, 15 insertions(+), 59 deletions(-) delete mode 100644 Shaders/Quantum Break/Luma_QB_PostSREncode.hlsl diff --git a/Shaders/Quantum Break/Luma_QB_PostSREncode.hlsl b/Shaders/Quantum Break/Luma_QB_PostSREncode.hlsl deleted file mode 100644 index c49fb5a5..00000000 --- a/Shaders/Quantum Break/Luma_QB_PostSREncode.hlsl +++ /dev/null @@ -1,18 +0,0 @@ -#include "../Includes/Color.hlsl" - -/* - * Encodes the linear super-resolution output back to Quantum Break's - * gamma-space temporal resolve contract before the original resolve shader. - */ -Texture2D g_PostSRLinearColor : register(t0); - -void main(float4 pixel_position : SV_Position0, out float4 output_color : SV_Target0) -{ - const int3 pixel_coord = int3(pixel_position.xy, 0); - const float4 linear_color = g_PostSRLinearColor.Load(pixel_coord); - - float4 gamma_color = linear_color; - gamma_color.rgb = linear_to_sRGB_gamma(gamma_color.rgb, GCT_POSITIVE); - - output_color = gamma_color; -} diff --git a/Shaders/Quantum Break/temporal_resolve_0x99274617.ps_5_0.hlsl b/Shaders/Quantum Break/temporal_resolve_0x99274617.ps_5_0.hlsl index 57c756b8..78e47fe4 100644 --- a/Shaders/Quantum Break/temporal_resolve_0x99274617.ps_5_0.hlsl +++ b/Shaders/Quantum Break/temporal_resolve_0x99274617.ps_5_0.hlsl @@ -79,6 +79,7 @@ void frag_main() float4 sr_depth = g_tClipDepth.Sample(g_sLinearClamp, scaled_uv); gl_FragDepth = sr_depth.x; SV_Target = sr_color.rgb; + SV_Target.rgb = linear_to_sRGB_gamma(SV_Target.rgb, GCT_POSITIVE); if (CUSTOM_GRAIN_TYPE == 0.f) { SV_Target = float3( diff --git a/Source/Games/Quantum Break/UpscalingNotes.md b/Source/Games/Quantum Break/UpscalingNotes.md index 543e9220..25efd278 100644 --- a/Source/Games/Quantum Break/UpscalingNotes.md +++ b/Source/Games/Quantum Break/UpscalingNotes.md @@ -8,7 +8,6 @@ Relevant files: - `Source/Games/Quantum Break/main.cpp` - `Shaders/Quantum Break/Luma_QB_PreSRDecode.hlsl` -- `Shaders/Quantum Break/Luma_QB_PostSREncode.hlsl` - `Shaders/Quantum Break/temporal_resolve_0x99274617.ps_5_0.hlsl` - `Shaders/Quantum Break/unused/history_reprojection_clamp_0xE8337D48.cs_5_0.hlsl` - `Shaders/Quantum Break/Includes/CBuffer_cb_update_1.hlsli` @@ -92,9 +91,7 @@ QB gamma source color -> linear SR input texture -> DLSS/FSR -> linear SR output texture --> Luma_QB_PostSREncode.hlsl --> QB gamma SR color --> original temporal resolve shader +-> original temporal resolve shader (encodes to gamma in SR branch) ``` Pre-SR decode: @@ -104,14 +101,11 @@ Pre-SR decode: - Operation: `gamma_sRGB_to_linear(..., GCT_POSITIVE)`. - Output: `game_device_data.sr_linear_input_color`. -Post-SR encode: +Post-SR encode is now in `temporal_resolve_0x99274617.ps_5_0.hlsl` when `LumaSettings.SRType > 0`: -- Shader: `Luma_QB_PostSREncode.hlsl` -- Input: `device_data.sr_output_color`. -- Operation: `linear_to_sRGB_gamma(..., GCT_POSITIVE)`. -- Output: `game_device_data.sr_gamma_output_color`. - -The original temporal resolve draw receives `sr_gamma_output_color_srv` in `PS SRV slot 2`, so the shader contract remains gamma-space. +- Input: `device_data.sr_output_color` (bound as `PS SRV slot 2`). +- Operation: `linear_to_sRGB_gamma(..., GCT_POSITIVE)` in the SR branch. +- Output: gamma-space color that continues through the normal resolve/display-map path. ## Current SR Settings diff --git a/Source/Games/Quantum Break/main.cpp b/Source/Games/Quantum Break/main.cpp index 15ee5761..c4510c3b 100644 --- a/Source/Games/Quantum Break/main.cpp +++ b/Source/Games/Quantum Break/main.cpp @@ -15,7 +15,7 @@ // Quantum Break's SR hook sits inside the temporal resolve path: // history reprojection provides motion vectors, temporal resolve provides color/depth/cbuffer inputs, -// and two fullscreen conversion passes let DLSS run on linear color while the game remains gamma-space. +// and a fullscreen pre-decode pass lets DLSS run on linear color while the game remains gamma-space. namespace { // Pass hashes used to identify the two parts of QB's temporal pipeline we need to observe/replace. @@ -364,13 +364,10 @@ struct GameDeviceDataQuantumBreak final : public GameDeviceData com_ptr sr_motion_vectors; com_ptr cb_update_1_readback; com_ptr ssaa_readback; - // Conversion scratch textures: game gamma color -> DLSS linear input -> game gamma resolve input. + // Conversion scratch texture: game gamma color -> DLSS linear input. com_ptr sr_linear_input_color; com_ptr sr_linear_input_color_srv; com_ptr sr_linear_input_color_rtv; - com_ptr sr_gamma_output_color; - com_ptr sr_gamma_output_color_srv; - com_ptr sr_gamma_output_color_rtv; com_ptr sr_output_color_srv; // Last captured frame constants that feed SR. @@ -621,7 +618,7 @@ class QuantumBreakGame final : public Game static bool SetupSROutput(ID3D11Device* native_device, DeviceData& device_data, GameDeviceDataQuantumBreak& game_device_data, const D3D11_TEXTURE2D_DESC& output_desc) { - // DLSS writes linear color here; a later fullscreen pass encodes it back to QB's gamma-space resolve input. + // DLSS writes linear color here; temporal_resolve encodes to gamma when SRType > 0. game_device_data.output_changed = false; bool recreated_output_texture = false; @@ -697,7 +694,7 @@ class QuantumBreakGame final : public Game static bool SetupSRConversionTexture(ID3D11Device* native_device, D3D11_TEXTURE2D_DESC desc, com_ptr& texture, com_ptr& srv, com_ptr& rtv) { - // Scratch textures are simple single-mip render targets used only by the pre/post SR conversion passes. + // Scratch textures are simple single-mip render targets used by SR conversion passes. desc.Format = ResolveSRColorViewFormat(desc.Format); if (desc.Width == 0u || desc.Height == 0u || desc.Format == DXGI_FORMAT_UNKNOWN) { @@ -800,9 +797,8 @@ class QuantumBreakGame final : public Game #if ENABLE_SR sr_game_tooltip = "Super Resolution engages during the temporal resolve pass.\n"; - // Native fullscreen passes bridge QB's gamma-space post stack with DLSS' preferred linear input. + // Native fullscreen pass bridges QB's gamma-space post stack with DLSS' preferred linear input. native_shaders_definitions.emplace(CompileTimeStringHash("QB Pre SR Decode"), ShaderDefinition{"Luma_QB_PreSRDecode", reshade::api::pipeline_subobject_type::pixel_shader}); - native_shaders_definitions.emplace(CompileTimeStringHash("QB Post SR Encode"), ShaderDefinition{"Luma_QB_PostSREncode", reshade::api::pipeline_subobject_type::pixel_shader}); #endif Settings::Initialize(); @@ -965,10 +961,8 @@ class QuantumBreakGame final : public Game D3D11_TEXTURE2D_DESC sr_linear_input_desc = source_desc; sr_linear_input_desc.Format = source_srv_desc.Format != DXGI_FORMAT_UNKNOWN ? source_srv_desc.Format : source_desc.Format; - D3D11_TEXTURE2D_DESC sr_gamma_output_desc = output_desc; const bool conversion_resources_ready = - SetupSRConversionTexture(native_device, sr_linear_input_desc, game_device_data.sr_linear_input_color, game_device_data.sr_linear_input_color_srv, game_device_data.sr_linear_input_color_rtv) && - SetupSRConversionTexture(native_device, sr_gamma_output_desc, game_device_data.sr_gamma_output_color, game_device_data.sr_gamma_output_color_srv, game_device_data.sr_gamma_output_color_rtv); + SetupSRConversionTexture(native_device, sr_linear_input_desc, game_device_data.sr_linear_input_color, game_device_data.sr_linear_input_color_srv, game_device_data.sr_linear_input_color_rtv); const bool settings_updated = conversion_resources_ready && sr_implementations[device_data.sr_type]->UpdateSettings(sr_instance_data, native_device_context, settings_data); if (settings_updated) @@ -1009,7 +1003,7 @@ class QuantumBreakGame final : public Game draw_state_stack.Cache(native_device_context, device_data.uav_max_count); compute_state_stack.Cache(native_device_context, device_data.uav_max_count); - // Feed DLSS linear color, then encode its linear result back to QB's gamma-space resolve input. + // Feed DLSS linear color; temporal_resolve encodes SR output back to gamma-space. const bool pre_sr_encoded = DrawSRConversionPass( native_device_context, device_data, @@ -1031,18 +1025,6 @@ class QuantumBreakGame final : public Game native_device_context->OMSetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, null_rtvs, nullptr); } - if (sr_succeeded) - { - sr_succeeded = DrawSRConversionPass( - native_device_context, - device_data, - CompileTimeStringHash("QB Post SR Encode"), - game_device_data.sr_output_color_srv.get(), - game_device_data.sr_gamma_output_color_rtv.get(), - output_width, - output_height); - } - draw_state_stack.Restore(native_device_context); compute_state_stack.Restore(native_device_context); @@ -1101,7 +1083,7 @@ class QuantumBreakGame final : public Game #if ENABLE_SR if (sr_succeeded) { - ID3D11ShaderResourceView* sr_output_srv = game_device_data.sr_gamma_output_color_srv.get(); + ID3D11ShaderResourceView* sr_output_srv = game_device_data.sr_output_color_srv.get(); native_device_context->PSSetShaderResources(2, 1, &sr_output_srv); } #endif @@ -1132,7 +1114,7 @@ class QuantumBreakGame final : public Game #if ENABLE_SR if (sr_succeeded) { - ID3D11ShaderResourceView* sr_output_srv = game_device_data.sr_gamma_output_color_srv.get(); + ID3D11ShaderResourceView* sr_output_srv = game_device_data.sr_output_color_srv.get(); native_device_context->PSSetShaderResources(2, 1, &sr_output_srv); } #endif @@ -1153,9 +1135,6 @@ class QuantumBreakGame final : public Game game_device_data.sr_linear_input_color = nullptr; game_device_data.sr_linear_input_color_srv = nullptr; game_device_data.sr_linear_input_color_rtv = nullptr; - game_device_data.sr_gamma_output_color = nullptr; - game_device_data.sr_gamma_output_color_srv = nullptr; - game_device_data.sr_gamma_output_color_rtv = nullptr; game_device_data.sr_output_color_srv = nullptr; game_device_data.output_changed = false; From 651432de52e7b4584bb39dd62b2c8205ea8b6bcb Mon Sep 17 00:00:00 2001 From: Musa Haji Date: Tue, 19 May 2026 00:47:57 -0400 Subject: [PATCH 2/6] feat(quantumbreak): disable black bars --- .../BlackBars_0x3C685302.ps_5_0.hlsl | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Shaders/Quantum Break/BlackBars_0x3C685302.ps_5_0.hlsl diff --git a/Shaders/Quantum Break/BlackBars_0x3C685302.ps_5_0.hlsl b/Shaders/Quantum Break/BlackBars_0x3C685302.ps_5_0.hlsl new file mode 100644 index 00000000..e8090228 --- /dev/null +++ b/Shaders/Quantum Break/BlackBars_0x3C685302.ps_5_0.hlsl @@ -0,0 +1,19 @@ +#include "../Includes/Common.hlsl" + +#ifndef DISABLE_BLACK_BARS +#define DISABLE_BLACK_BARS 1 +#endif + +void main( + float3 v0 : TEXCOORD0, + float4 v1 : COLOR0, + out float4 o0 : SV_Target0) +{ +#if DISABLE_BLACK_BARS + if (all(v1.xyz == 0.0)) + { + v1.w = 0.0; + } +#endif // DISABLE_BLACK_BARS + o0.xyzw = v1.xyzw; +} \ No newline at end of file From 54fedb45c63d024cf4da0ef97d893196a1aae2e9 Mon Sep 17 00:00:00 2001 From: Musa Haji Date: Tue, 19 May 2026 00:48:42 -0400 Subject: [PATCH 3/6] refactor(Quantum Break): move upscaling code to separate hpp --- .../Games/Quantum Break/Quantum Break.vcxproj | 2 + .../Quantum Break.vcxproj.filters | 2 + Source/Games/Quantum Break/Upscaling.hpp | 908 ++++++++++++++++++ Source/Games/Quantum Break/main.cpp | 867 +---------------- Source/Games/Quantum Break/shared.h | 17 + 5 files changed, 957 insertions(+), 839 deletions(-) create mode 100644 Source/Games/Quantum Break/Upscaling.hpp create mode 100644 Source/Games/Quantum Break/shared.h diff --git a/Source/Games/Quantum Break/Quantum Break.vcxproj b/Source/Games/Quantum Break/Quantum Break.vcxproj index 27dec7d3..784b7646 100644 --- a/Source/Games/Quantum Break/Quantum Break.vcxproj +++ b/Source/Games/Quantum Break/Quantum Break.vcxproj @@ -307,6 +307,8 @@ + + diff --git a/Source/Games/Quantum Break/Quantum Break.vcxproj.filters b/Source/Games/Quantum Break/Quantum Break.vcxproj.filters index d05485a3..0a3e9ecb 100644 --- a/Source/Games/Quantum Break/Quantum Break.vcxproj.filters +++ b/Source/Games/Quantum Break/Quantum Break.vcxproj.filters @@ -3,5 +3,7 @@ + + \ No newline at end of file diff --git a/Source/Games/Quantum Break/Upscaling.hpp b/Source/Games/Quantum Break/Upscaling.hpp new file mode 100644 index 00000000..915cd64a --- /dev/null +++ b/Source/Games/Quantum Break/Upscaling.hpp @@ -0,0 +1,908 @@ +#pragma once + +#include "shared.h" + +// Quantum Break's SR hook sits inside the temporal resolve path: +// history reprojection provides motion vectors, temporal resolve provides color/depth/cbuffer inputs, +// and a fullscreen pre-decode pass lets DLSS run on linear color while the game remains gamma-space. +namespace QuantumBreakUpscaling +{ + // Pass hashes used to identify the two parts of QB's temporal pipeline we need to observe/replace. + inline ShaderHashesList<>& HistoryReprojectionHashes() + { + static ShaderHashesList<> hashes; + return hashes; + } + + inline ShaderHashesList<>& TemporalResolveHashes() + { + static ShaderHashesList<> hashes; + return hashes; + } + + inline void RegisterShaderHashes() + { + HistoryReprojectionHashes().compute_shaders.emplace(std::stoul("E8337D48", nullptr, 16)); + TemporalResolveHashes().pixel_shaders.emplace(std::stoul("99274617", nullptr, 16)); + } + + inline bool IsHistoryReprojectionPass(const ShaderHashesList& original_shader_hashes) + { + return original_shader_hashes.Contains(HistoryReprojectionHashes()); + } + + inline bool IsTemporalResolvePass(const ShaderHashesList& original_shader_hashes) + { + return original_shader_hashes.Contains(TemporalResolveHashes()); + } + + constexpr float vertical_fov_fallback = 0.775934f; // ~44.46 degrees + // Byte offsets into QB's cb_update_1 cbuffer for the SR inputs that are not available from textures. + constexpr uint32_t cb_update_1_inv_near_offset = 47u * 16u; + constexpr uint32_t cb_update_1_view_to_clip_offset = 10u * 16u; + constexpr uint32_t cb_update_1_tess_view_to_clip_11_offset = 112u * 16u + 12u; + constexpr uint32_t cb_update_1_jitter_offset = 121u * 16u; + constexpr uint32_t cb_update_1_min_size = cb_update_1_jitter_offset + sizeof(float) * 2u; + // The temporal resolve samples current color with g_vSSAAJitterOffset[0]. + constexpr uint32_t ssaa_jitter_offset = 12u * 16u; + constexpr uint32_t ssaa_min_size = ssaa_jitter_offset + sizeof(float) * 2u; + + struct Data + { + bool debug_prev_had_motion_vectors = false; + +#if ENABLE_SR + // Resources captured or created around the temporal resolve pass. + com_ptr sr_motion_vectors; + com_ptr cb_update_1_readback; + com_ptr ssaa_readback; + // Conversion scratch texture: game gamma color -> DLSS linear input. + com_ptr sr_linear_input_color; + com_ptr sr_linear_input_color_srv; + com_ptr sr_linear_input_color_rtv; + com_ptr sr_output_color_srv; + + // Last captured frame constants that feed SR. + float sr_jitter_x = 0.f; + float sr_jitter_y = 0.f; + float sr_cb_jitter_x = 0.f; + float sr_cb_jitter_y = 0.f; + float sr_vertical_fov = vertical_fov_fallback; + float sr_near_plane = 0.1f; + float sr_far_plane = 1000.f; + + // Per-resource history used to decide when DLSS history must reset. + bool has_ssaa_data = false; + bool output_changed = false; + bool has_previous_source_desc = false; + bool has_previous_depth_desc = false; + bool has_previous_motion_vectors_desc = false; + D3D11_TEXTURE2D_DESC previous_source_desc = {}; + D3D11_TEXTURE2D_DESC previous_depth_desc = {}; + D3D11_TEXTURE2D_DESC previous_motion_vectors_desc = {}; + uint32_t previous_render_width = 0u; + uint32_t previous_render_height = 0u; + uint32_t previous_output_width = 0u; + uint32_t previous_output_height = 0u; +#endif + }; + + namespace Settings + { + constexpr float fsr_sharpness = 0.f; + constexpr float mv_scale = 1.f; + constexpr float jitter_scale = 1.f; + + inline void Initialize() + { + } + + inline void Load(reshade::api::effect_runtime* runtime) + { + (void)runtime; + } + + inline void Draw(DeviceData& device_data, reshade::api::effect_runtime* runtime) + { + (void)runtime; + +#if DEVELOPMENT || TEST +#if ENABLE_SR + ImGui::NewLine(); + ImGui::Text("Super Resolution"); + + if (ImGui::Button("Reset SR History")) + { + device_data.force_reset_sr = true; + } +#else + (void)device_data; + ImGui::TextDisabled("Super Resolution is disabled in this build."); +#endif +#else + (void)device_data; +#endif + } + + inline void SetRenderData(uint32_t render_width, uint32_t render_height, uint32_t output_width, uint32_t output_height, float jitter_x, float jitter_y, DeviceData& device_data) + { + // Shaders need both SR input and final output sizes so the temporal resolve can sample the correct buffer. + const float render_width_f = static_cast(render_width); + const float render_height_f = static_cast(render_height); + const float output_width_f = static_cast(output_width); + const float output_height_f = static_cast(output_height); + + cb_luma_global_settings.GameSettings.RenderRes = float2{render_width_f, render_height_f}; + cb_luma_global_settings.GameSettings.InvRenderRes = float2{render_width_f > 0.f ? (1.f / render_width_f) : 0.f, render_height_f > 0.f ? (1.f / render_height_f) : 0.f}; + cb_luma_global_settings.GameSettings.OutputRes = float2{output_width_f, output_height_f}; + cb_luma_global_settings.GameSettings.InvOutputRes = float2{output_width_f > 0.f ? (1.f / output_width_f) : 0.f, output_height_f > 0.f ? (1.f / output_height_f) : 0.f}; + + const float render_scale = output_height_f > 0.f ? (render_height_f / output_height_f) : 1.f; + cb_luma_global_settings.GameSettings.RenderScale = render_scale; + cb_luma_global_settings.GameSettings.InvRenderScale = render_scale != 0.f ? (1.f / render_scale) : 1.f; + cb_luma_global_settings.GameSettings.JitterOffset = float2{jitter_x, jitter_y}; + + device_data.cb_luma_global_settings_dirty = true; + } + } // namespace Settings + + struct TemporalResolveResult + { + bool requested = false; + bool succeeded = false; + bool stop_processing = false; + }; + + inline float ComputeVerticalFovFromProjectionScale(float projection_scale) + { + // Projection matrix scale is 1 / tan(fov / 2). Invalid values fall back to the previous FOV. + if (!std::isfinite(projection_scale)) + { + return 0.f; + } + + const float abs_projection_scale = std::fabs(projection_scale); + if (abs_projection_scale <= 1e-6f) + { + return 0.f; + } + + const float fov = 2.f * std::atan(1.f / abs_projection_scale); + return (std::isfinite(fov) && fov > 0.f && fov < 3.13f) ? fov : 0.f; + } + + inline bool HasTextureShapeChanged(const D3D11_TEXTURE2D_DESC& current_desc, const D3D11_TEXTURE2D_DESC& previous_desc) + { + return current_desc.Width != previous_desc.Width || current_desc.Height != previous_desc.Height || current_desc.Format != previous_desc.Format || current_desc.ArraySize != previous_desc.ArraySize || current_desc.MipLevels != previous_desc.MipLevels || current_desc.SampleDesc.Count != previous_desc.SampleDesc.Count || current_desc.SampleDesc.Quality != previous_desc.SampleDesc.Quality; + } + + inline bool UpdatePreviousTextureDesc(const D3D11_TEXTURE2D_DESC& current_desc, D3D11_TEXTURE2D_DESC& previous_desc, bool& has_previous_desc) + { + // DLSS history must reset when any SR input/output texture shape changes. + const bool changed = has_previous_desc && HasTextureShapeChanged(current_desc, previous_desc); + previous_desc = current_desc; + has_previous_desc = true; + return changed; + } + + template + inline T ReadCBufferValue(const uint8_t* base, uint32_t offset) + { + T value = {}; + std::memcpy(&value, base + offset, sizeof(T)); + return value; + } + + inline void ReadCBufferFloat2(const uint8_t* base, uint32_t offset, float& x, float& y) + { + x = ReadCBufferValue(base, offset); + y = ReadCBufferValue(base, offset + sizeof(float)); + } + + inline void OnInit() + { +#if ENABLE_SR + sr_game_tooltip = "Super Resolution engages during the temporal resolve pass.\n"; + // Native fullscreen pass bridges QB's gamma-space post stack with DLSS' preferred linear input. + native_shaders_definitions.emplace(CompileTimeStringHash("QB Pre SR Decode"), ShaderDefinition{"Luma_QB_PreSRDecode", reshade::api::pipeline_subobject_type::pixel_shader}); +#endif + } + + inline bool IsRequested(const DeviceData& device_data) + { +#if ENABLE_SR + return device_data.sr_type != SR::Type::None && !device_data.sr_suppressed; +#else + (void)device_data; + return false; +#endif + } + + inline void CaptureMotionVectors(ID3D11DeviceContext* native_device_context, Data& data) + { +#if ENABLE_SR + // The history reprojection pass has the motion-vector resource bound as CS SRV 0. + com_ptr motion_vectors_srv; + native_device_context->CSGetShaderResources(0, 1, &motion_vectors_srv); + if (motion_vectors_srv.get()) + { + data.sr_motion_vectors = nullptr; + motion_vectors_srv->GetResource(&data.sr_motion_vectors); + } +#else + (void)native_device_context; + (void)data; +#endif + } + +#if ENABLE_SR + inline bool MapPixelShaderConstantBufferForReadback( + ID3D11Device* native_device, + ID3D11DeviceContext* native_device_context, + UINT slot, + uint32_t min_size, + com_ptr& readback_buffer, + D3D11_MAPPED_SUBRESOURCE& mapped) + { + // Constant buffers are GPU-only, so copy them into a staging buffer before CPU-side parsing. + com_ptr constant_buffer; + native_device_context->PSGetConstantBuffers(slot, 1, &constant_buffer); + if (!constant_buffer.get()) + { + return false; + } + + D3D11_BUFFER_DESC source_desc = {}; + constant_buffer->GetDesc(&source_desc); + if (source_desc.ByteWidth < min_size) + { + return false; + } + + bool needs_recreate = !readback_buffer.get(); + if (!needs_recreate) + { + D3D11_BUFFER_DESC readback_desc = {}; + readback_buffer->GetDesc(&readback_desc); + needs_recreate = readback_desc.ByteWidth != source_desc.ByteWidth; + } + + if (needs_recreate) + { + D3D11_BUFFER_DESC readback_desc = source_desc; + readback_desc.BindFlags = 0; + readback_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + readback_desc.Usage = D3D11_USAGE_STAGING; + readback_desc.MiscFlags = 0; + readback_desc.StructureByteStride = 0; + + readback_buffer = nullptr; + HRESULT hr = native_device->CreateBuffer(&readback_desc, nullptr, &readback_buffer); + if (FAILED(hr) || !readback_buffer.get()) + { + return false; + } + } + + native_device_context->CopyResource(readback_buffer.get(), constant_buffer.get()); + + HRESULT hr = native_device_context->Map(readback_buffer.get(), 0, D3D11_MAP_READ, 0, &mapped); + return SUCCEEDED(hr) && mapped.pData != nullptr; + } + + inline bool CaptureCBUpdate1Data(ID3D11Device* native_device, ID3D11DeviceContext* native_device_context, Data& data) + { + // cb_update_1 supplies fallback jitter, projection scale, and near plane for SR. + data.sr_cb_jitter_x = 0.f; + data.sr_cb_jitter_y = 0.f; + D3D11_MAPPED_SUBRESOURCE mapped = {}; + if (!MapPixelShaderConstantBufferForReadback(native_device, native_device_context, 0, cb_update_1_min_size, data.cb_update_1_readback, mapped)) + { + return false; + } + + const auto* base = static_cast(mapped.pData); + ReadCBufferFloat2(base, cb_update_1_jitter_offset, data.sr_cb_jitter_x, data.sr_cb_jitter_y); + const float inv_near = ReadCBufferValue(base, cb_update_1_inv_near_offset); + const float projection_scale_x = ReadCBufferValue(base, cb_update_1_view_to_clip_offset); + const float projection_scale_y = ReadCBufferValue(base, cb_update_1_view_to_clip_offset + sizeof(float) * 5u); + const float tess_view_to_clip_11 = ReadCBufferValue(base, cb_update_1_tess_view_to_clip_11_offset); + if (std::isfinite(inv_near) && inv_near > 0.f) + { + data.sr_near_plane = 1.f / inv_near; + } + + data.sr_cb_jitter_x = std::isfinite(data.sr_cb_jitter_x) ? data.sr_cb_jitter_x : 0.f; + data.sr_cb_jitter_y = std::isfinite(data.sr_cb_jitter_y) ? data.sr_cb_jitter_y : 0.f; + + float vertical_fov = ComputeVerticalFovFromProjectionScale(projection_scale_y); + if (vertical_fov <= 0.f) + { + vertical_fov = ComputeVerticalFovFromProjectionScale(projection_scale_x); + } + if (vertical_fov <= 0.f) + { + vertical_fov = ComputeVerticalFovFromProjectionScale(tess_view_to_clip_11); + } + if (vertical_fov > 0.f) + { + data.sr_vertical_fov = vertical_fov; + } + + native_device_context->Unmap(data.cb_update_1_readback.get(), 0); + return true; + } + + inline bool CaptureSSAAData(ID3D11Device* native_device, ID3D11DeviceContext* native_device_context, Data& data) + { + // The temporal resolve samples current color with g_vSSAAJitterOffset[0], so keep this as the jitter source. + data.has_ssaa_data = false; + data.sr_jitter_x = 0.f; + data.sr_jitter_y = 0.f; + + D3D11_MAPPED_SUBRESOURCE mapped = {}; + if (!MapPixelShaderConstantBufferForReadback(native_device, native_device_context, 1, ssaa_min_size, data.ssaa_readback, mapped)) + { + return false; + } + + const auto* base = static_cast(mapped.pData); + ReadCBufferFloat2(base, ssaa_jitter_offset, data.sr_jitter_x, data.sr_jitter_y); + data.sr_jitter_x = std::isfinite(data.sr_jitter_x) ? data.sr_jitter_x : 0.f; + data.sr_jitter_y = std::isfinite(data.sr_jitter_y) ? data.sr_jitter_y : 0.f; + + data.has_ssaa_data = true; + + native_device_context->Unmap(data.ssaa_readback.get(), 0); + return true; + } + + inline bool SetupOutput(ID3D11Device* native_device, DeviceData& device_data, Data& data, const D3D11_TEXTURE2D_DESC& output_desc) + { + // DLSS writes linear color here; temporal_resolve encodes to gamma when SRType > 0. + data.output_changed = false; + bool recreated_output_texture = false; + + auto* sr_instance_data = device_data.GetSRInstanceData(); + if (!sr_instance_data) + { + return false; + } + if (output_desc.Width < sr_instance_data->min_resolution || output_desc.Height < sr_instance_data->min_resolution) + { + return false; + } + + D3D11_TEXTURE2D_DESC sr_output_desc = output_desc; + sr_output_desc.BindFlags |= D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE; + + if (device_data.sr_output_color.get()) + { + D3D11_TEXTURE2D_DESC prev_desc = {}; + device_data.sr_output_color->GetDesc(&prev_desc); + data.output_changed = prev_desc.Width != sr_output_desc.Width || prev_desc.Height != sr_output_desc.Height || prev_desc.Format != sr_output_desc.Format; + } + + if (!device_data.sr_output_color.get() || data.output_changed) + { + device_data.sr_output_color = nullptr; + HRESULT hr = native_device->CreateTexture2D(&sr_output_desc, nullptr, &device_data.sr_output_color); + if (FAILED(hr) || !device_data.sr_output_color.get()) + { + return false; + } + + recreated_output_texture = true; + } + + if (!data.sr_output_color_srv.get() || data.output_changed || recreated_output_texture) + { + data.sr_output_color_srv = nullptr; + HRESULT hr = native_device->CreateShaderResourceView(device_data.sr_output_color.get(), nullptr, &data.sr_output_color_srv); + if (FAILED(hr) || !data.sr_output_color_srv.get()) + { + return false; + } + } + + return true; + } + + inline DXGI_FORMAT ResolveColorViewFormat(DXGI_FORMAT format) + { + // Conversion passes need RTV/SRV-compatible typed color formats, not typeless or sRGB view formats. + switch (format) + { + case DXGI_FORMAT_R32G32B32A32_TYPELESS: + return DXGI_FORMAT_R32G32B32A32_FLOAT; + case DXGI_FORMAT_R16G16B16A16_TYPELESS: + return DXGI_FORMAT_R16G16B16A16_FLOAT; + case DXGI_FORMAT_R10G10B10A2_TYPELESS: + return DXGI_FORMAT_R10G10B10A2_UNORM; + case DXGI_FORMAT_R8G8B8A8_TYPELESS: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + return DXGI_FORMAT_R8G8B8A8_UNORM; + case DXGI_FORMAT_B8G8R8A8_TYPELESS: + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + return DXGI_FORMAT_B8G8R8A8_UNORM; + case DXGI_FORMAT_B8G8R8X8_TYPELESS: + case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: + return DXGI_FORMAT_B8G8R8X8_UNORM; + default: + return format; + } + } + + inline bool SetupConversionTexture(ID3D11Device* native_device, D3D11_TEXTURE2D_DESC desc, com_ptr& texture, com_ptr& srv, com_ptr& rtv) + { + // Scratch textures are simple single-mip render targets used by SR conversion passes. + desc.Format = ResolveColorViewFormat(desc.Format); + if (desc.Width == 0u || desc.Height == 0u || desc.Format == DXGI_FORMAT_UNKNOWN) + { + return false; + } + + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + desc.CPUAccessFlags = 0; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.MiscFlags = 0; + desc.MipLevels = 1u; + desc.ArraySize = 1u; + desc.SampleDesc.Count = 1u; + desc.SampleDesc.Quality = 0u; + + bool recreate_texture = !texture.get(); + if (!recreate_texture) + { + D3D11_TEXTURE2D_DESC previous_desc = {}; + texture->GetDesc(&previous_desc); + recreate_texture = HasTextureShapeChanged(desc, previous_desc); + } + + if (recreate_texture) + { + texture = nullptr; + srv = nullptr; + rtv = nullptr; + + HRESULT hr = native_device->CreateTexture2D(&desc, nullptr, &texture); + if (FAILED(hr) || !texture.get()) + { + return false; + } + } + + if (!srv.get()) + { + HRESULT hr = native_device->CreateShaderResourceView(texture.get(), nullptr, &srv); + if (FAILED(hr) || !srv.get()) + { + return false; + } + } + + if (!rtv.get()) + { + HRESULT hr = native_device->CreateRenderTargetView(texture.get(), nullptr, &rtv); + if (FAILED(hr) || !rtv.get()) + { + return false; + } + } + + return true; + } + + inline bool DrawConversionPass(ID3D11DeviceContext* native_device_context, DeviceData& device_data, uint32_t pixel_shader_hash, ID3D11ShaderResourceView* source_srv, ID3D11RenderTargetView* target_rtv, uint32_t width, uint32_t height) + { + // Shared fullscreen draw for gamma->linear and linear->gamma SR color conversion. + const auto vs_it = device_data.native_vertex_shaders.find(CompileTimeStringHash("Copy VS")); + const auto ps_it = device_data.native_pixel_shaders.find(pixel_shader_hash); + if (vs_it == device_data.native_vertex_shaders.end() || !vs_it->second.get() || + ps_it == device_data.native_pixel_shaders.end() || !ps_it->second.get() || + !source_srv || !target_rtv || width == 0u || height == 0u) + { + return false; + } + + native_device_context->OMSetRenderTargets(0, nullptr, nullptr); + DrawCustomPixelShader( + native_device_context, + device_data.default_depth_stencil_state.get(), + device_data.default_blend_state.get(), + nullptr, + vs_it->second.get(), + ps_it->second.get(), + source_srv, + target_rtv, + width, + height, + false); + + ID3D11ShaderResourceView* null_srv = nullptr; + native_device_context->PSSetShaderResources(0, 1, &null_srv); + ID3D11RenderTargetView* null_rtv = nullptr; + native_device_context->OMSetRenderTargets(1, &null_rtv, nullptr); + return true; + } +#endif // ENABLE_SR + + inline TemporalResolveResult RunTemporalResolve(ID3D11Device* native_device, ID3D11DeviceContext* native_device_context, DeviceData& device_data, Data& data) + { + TemporalResolveResult result = {}; + result.requested = IsRequested(device_data); + +#if ENABLE_SR + com_ptr ps_shader_resources[3]; + com_ptr render_target_views[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT]; + com_ptr depth_stencil_view; + const bool immediate_context = native_device_context->GetType() == D3D11_DEVICE_CONTEXT_IMMEDIATE; + const bool has_main_temporal_resolve_bindings = [&]() + { + if (!immediate_context) + { + return false; + } + + // UI/menu-only resolves can hit the same shader without the scene color/depth/RTV bindings SR needs. + native_device_context->PSGetShaderResources(0, ARRAYSIZE(ps_shader_resources), reinterpret_cast(ps_shader_resources)); + native_device_context->OMGetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, &render_target_views[0], &depth_stencil_view); + return ps_shader_resources[0].get() && ps_shader_resources[2].get() && render_target_views[0].get(); + }(); + + if (has_main_temporal_resolve_bindings) + { + CaptureCBUpdate1Data(native_device, native_device_context, data); + CaptureSSAAData(native_device, native_device_context, data); + } + + if (result.requested && immediate_context && data.sr_motion_vectors.get() && has_main_temporal_resolve_bindings) + { + if (ps_shader_resources[0].get() && ps_shader_resources[2].get() && render_target_views[0].get()) + { + com_ptr source_color_resource; + ps_shader_resources[2]->GetResource(&source_color_resource); + + com_ptr depth_resource; + ps_shader_resources[0]->GetResource(&depth_resource); + + // The temporal resolve RTV is the final SR output target size. + com_ptr output_resource; + render_target_views[0]->GetResource(&output_resource); + + com_ptr source_color_texture; + com_ptr output_texture; + com_ptr depth_texture; + com_ptr motion_vectors_texture; + + const HRESULT source_hr = source_color_resource.get() ? source_color_resource->QueryInterface(&source_color_texture) : E_FAIL; + const HRESULT output_hr = output_resource.get() ? output_resource->QueryInterface(&output_texture) : E_FAIL; + const HRESULT depth_hr = depth_resource.get() ? depth_resource->QueryInterface(&depth_texture) : E_FAIL; + const HRESULT motion_vectors_hr = data.sr_motion_vectors.get() ? data.sr_motion_vectors->QueryInterface(&motion_vectors_texture) : E_FAIL; + + if (SUCCEEDED(source_hr) && SUCCEEDED(output_hr) && SUCCEEDED(depth_hr) && SUCCEEDED(motion_vectors_hr) && source_color_texture.get() && output_texture.get() && depth_texture.get() && motion_vectors_texture.get()) + { + // Descs drive DLSS settings, conversion texture allocation, and history reset decisions. + D3D11_TEXTURE2D_DESC source_desc = {}; + D3D11_TEXTURE2D_DESC depth_desc = {}; + D3D11_TEXTURE2D_DESC motion_vectors_desc = {}; + D3D11_TEXTURE2D_DESC output_desc = {}; + // Source/depth/MV descs bound the DLSS input resolution; output desc drives the upscaled target. + source_color_texture->GetDesc(&source_desc); + depth_texture->GetDesc(&depth_desc); + motion_vectors_texture->GetDesc(&motion_vectors_desc); + output_texture->GetDesc(&output_desc); + + if (SetupOutput(native_device, device_data, data, output_desc)) + { + auto* sr_instance_data = device_data.GetSRInstanceData(); + if (sr_instance_data) + { + // Use the smallest SR input texture so color, depth, and motion vectors cover the full render area. + const uint32_t max_input_width = (std::min)(source_desc.Width, (std::min)(depth_desc.Width, motion_vectors_desc.Width)); + const uint32_t max_input_height = (std::min)(source_desc.Height, (std::min)(depth_desc.Height, motion_vectors_desc.Height)); + const uint32_t render_width = max_input_width; + const uint32_t render_height = max_input_height; + + const uint32_t output_width = output_desc.Width; + const uint32_t output_height = output_desc.Height; + const float jitter_x = data.has_ssaa_data ? data.sr_jitter_x : data.sr_cb_jitter_x; + const float jitter_y = data.has_ssaa_data ? data.sr_jitter_y : data.sr_cb_jitter_y; + + if (render_width == 0u || render_height == 0u || output_width == 0u || output_height == 0u) + { + device_data.force_reset_sr = true; + result.stop_processing = true; + return result; + } + + Settings::SetRenderData(render_width, render_height, output_width, output_height, jitter_x, jitter_y, device_data); + + SR::SettingsData settings_data = {}; + settings_data.output_width = output_width; + settings_data.output_height = output_height; + settings_data.render_width = render_width; + settings_data.render_height = render_height; + settings_data.dynamic_resolution = false; + // The pre-SR decode pass makes the color input linear; exposure is already baked by QB. + settings_data.hdr = true; + settings_data.auto_exposure = false; + settings_data.inverted_depth = false; + settings_data.mvs_jittered = false; + settings_data.mvs_x_scale = static_cast(render_width) * Settings::mv_scale; + settings_data.mvs_y_scale = static_cast(render_height) * Settings::mv_scale; + settings_data.render_preset = dlss_render_preset; + + D3D11_SHADER_RESOURCE_VIEW_DESC source_srv_desc = {}; + ps_shader_resources[2]->GetDesc(&source_srv_desc); + + D3D11_TEXTURE2D_DESC sr_linear_input_desc = source_desc; + sr_linear_input_desc.Format = source_srv_desc.Format != DXGI_FORMAT_UNKNOWN ? source_srv_desc.Format : source_desc.Format; + + const bool conversion_resources_ready = + SetupConversionTexture(native_device, sr_linear_input_desc, data.sr_linear_input_color, data.sr_linear_input_color_srv, data.sr_linear_input_color_rtv); + + const bool settings_updated = conversion_resources_ready && sr_implementations[device_data.sr_type]->UpdateSettings(sr_instance_data, native_device_context, settings_data); + if (settings_updated) + { + // Reset DLSS history when any physical resource or logical render size changes. + const bool source_changed = UpdatePreviousTextureDesc(source_desc, data.previous_source_desc, data.has_previous_source_desc); + const bool depth_changed = UpdatePreviousTextureDesc(depth_desc, data.previous_depth_desc, data.has_previous_depth_desc); + const bool motion_vectors_changed = UpdatePreviousTextureDesc(motion_vectors_desc, data.previous_motion_vectors_desc, data.has_previous_motion_vectors_desc); + const bool render_size_changed = data.previous_render_width != 0u && data.previous_render_height != 0u && (data.previous_render_width != render_width || data.previous_render_height != render_height); + data.previous_render_width = render_width; + data.previous_render_height = render_height; + data.previous_output_width = output_width; + data.previous_output_height = output_height; + + const bool reset_sr = device_data.force_reset_sr || data.output_changed || source_changed || depth_changed || motion_vectors_changed || render_size_changed; + device_data.force_reset_sr = false; + + SR::SuperResolutionImpl::DrawData draw_data = {}; + draw_data.source_color = data.sr_linear_input_color.get(); + draw_data.output_color = device_data.sr_output_color.get(); + draw_data.motion_vectors = data.sr_motion_vectors.get(); + draw_data.depth_buffer = depth_resource.get(); + draw_data.pre_exposure = 1.f; + draw_data.jitter_x = jitter_x * Settings::jitter_scale; + draw_data.jitter_y = jitter_y * Settings::jitter_scale; + draw_data.vert_fov = (std::isfinite(data.sr_vertical_fov) && data.sr_vertical_fov > 0.f) + ? data.sr_vertical_fov + : vertical_fov_fallback; + draw_data.near_plane = data.sr_near_plane; + draw_data.far_plane = data.sr_far_plane; + draw_data.reset = reset_sr; + draw_data.render_width = render_width; + draw_data.render_height = render_height; + draw_data.user_sharpness = device_data.sr_type == SR::Type::FSR ? Settings::fsr_sharpness : -1.f; + + DrawStateStack draw_state_stack; + DrawStateStack compute_state_stack; + draw_state_stack.Cache(native_device_context, device_data.uav_max_count); + compute_state_stack.Cache(native_device_context, device_data.uav_max_count); + + // Feed DLSS linear color; temporal_resolve encodes SR output back to gamma-space. + const bool pre_sr_encoded = DrawConversionPass( + native_device_context, + device_data, + CompileTimeStringHash("QB Pre SR Decode"), + ps_shader_resources[2].get(), + data.sr_linear_input_color_rtv.get(), + source_desc.Width, + source_desc.Height); + + result.succeeded = pre_sr_encoded && sr_implementations[device_data.sr_type]->Draw(sr_instance_data, native_device_context, draw_data); + + { + ID3D11ShaderResourceView* null_srvs[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] = {}; + native_device_context->PSSetShaderResources(0, D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT, null_srvs); + native_device_context->CSSetShaderResources(0, D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT, null_srvs); + ID3D11UnorderedAccessView* null_uavs[D3D11_1_UAV_SLOT_COUNT] = {}; + native_device_context->CSSetUnorderedAccessViews(0, D3D11_1_UAV_SLOT_COUNT, null_uavs, nullptr); + ID3D11RenderTargetView* null_rtvs[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT] = {}; + native_device_context->OMSetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, null_rtvs, nullptr); + } + + draw_state_stack.Restore(native_device_context); + compute_state_stack.Restore(native_device_context); + + if (result.succeeded) + { + device_data.has_drawn_sr = true; + } + else + { + device_data.force_reset_sr = true; + } + } + } + } + } + } + } +#else + (void)native_device; + (void)native_device_context; + (void)data; +#endif + + return result; + } + + inline void SetSRTypeForTemporalResolve(DeviceData& device_data, bool sr_succeeded) + { +#if ENABLE_SR + const uint32_t sr_type_for_pass = sr_succeeded ? (static_cast(device_data.sr_type) + 1u) : 0u; +#else + const uint32_t sr_type_for_pass = 0u; +#endif + if (cb_luma_global_settings.SRType != sr_type_for_pass) + { + cb_luma_global_settings.SRType = sr_type_for_pass; + device_data.cb_luma_global_settings_dirty = true; + } + } + + inline void ForceResetIfRequestedAndFailed(DeviceData& device_data, const TemporalResolveResult& result) + { +#if ENABLE_SR + if (!result.succeeded && result.requested) + { + device_data.force_reset_sr = true; + } +#else + (void)device_data; + (void)result; +#endif + } + + inline void BindOutputToTemporalResolve(ID3D11DeviceContext* native_device_context, Data& data, bool sr_succeeded) + { +#if ENABLE_SR + if (sr_succeeded) + { + ID3D11ShaderResourceView* sr_output_srv = data.sr_output_color_srv.get(); + native_device_context->PSSetShaderResources(2, 1, &sr_output_srv); + } +#else + (void)native_device_context; + (void)data; + (void)sr_succeeded; +#endif + } + + inline void CleanResources(DeviceData& device_data, Data& data) + { +#if ENABLE_SR + // Drop all transient SR resources so the next valid scene frame rebuilds them and resets DLSS history. + device_data.force_reset_sr = true; + device_data.has_drawn_sr = false; + + data.sr_motion_vectors = nullptr; + data.sr_linear_input_color = nullptr; + data.sr_linear_input_color_srv = nullptr; + data.sr_linear_input_color_rtv = nullptr; + data.sr_output_color_srv = nullptr; + data.output_changed = false; + + data.has_previous_source_desc = false; + data.has_previous_depth_desc = false; + data.has_previous_motion_vectors_desc = false; + data.previous_source_desc = {}; + data.previous_depth_desc = {}; + data.previous_motion_vectors_desc = {}; + data.previous_render_width = 0u; + data.previous_render_height = 0u; + data.previous_output_width = 0u; + data.previous_output_height = 0u; +#else + (void)device_data; + (void)data; +#endif + } + + inline void OnPresent(DeviceData& device_data, Data& data) + { +#if ENABLE_SR + // If SR was requested but no scene resolve produced SR output, force a history reset for the next scene frame. + if (device_data.sr_type != SR::Type::None && !device_data.has_drawn_sr) + { + device_data.force_reset_sr = true; + } + + data.debug_prev_had_motion_vectors = data.sr_motion_vectors.get() != nullptr; + data.sr_motion_vectors = nullptr; + data.output_changed = false; +#else + data.debug_prev_had_motion_vectors = false; + (void)device_data; +#endif + + if (cb_luma_global_settings.SRType != 0u) + { + cb_luma_global_settings.SRType = 0u; + device_data.cb_luma_global_settings_dirty = true; + } + } + + inline void DrawDebug(const Data& data, bool saw_history_reprojection_pass, bool saw_temporal_resolve_pass, bool had_scene_temporal_resolve_last_frame, uint32_t ui_only_frame_hold_counter) + { +#if ENABLE_SR && (DEVELOPMENT || TEST) + // Runtime SR status only; detailed cbuffer dumps are intentionally kept out of the user-facing UI. + auto begin_table = [](const char* id) + { + constexpr ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_NoSavedSettings; + if (!ImGui::BeginTable(id, 2, flags)) + { + return false; + } + + const float field_column_width = (std::max)(420.f, + ImGui::CalcTextSize("Had Scene Temporal Resolve Last Frame:").x + ImGui::GetStyle().CellPadding.x * 2.f + 48.f); + ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthFixed, field_column_width); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); + return true; + }; + + auto table_row_label = [](const char* label) + { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::TextUnformatted(label); + ImGui::TableSetColumnIndex(1); + }; + + auto table_row_bool = [&](const char* label, bool value) + { + table_row_label(label); + ImGui::TextUnformatted(value ? "Yes" : "No"); + }; + + auto table_row_uint = [&](const char* label, uint32_t value) + { + table_row_label(label); + ImGui::Text("%u", value); + }; + + auto table_row_float = [&](const char* label, float value) + { + table_row_label(label); + ImGui::Text("%.6f", value); + }; + + ImGui::NewLine(); + if (ImGui::CollapsingHeader("Super Resolution Debug")) + { + if (begin_table("QB_SR_Debug_Overview")) + { + table_row_bool("History Reprojection Pass Seen:", saw_history_reprojection_pass); + table_row_bool("Temporal Resolve Pass Seen:", saw_temporal_resolve_pass); + table_row_bool("Motion Vectors Captured:", data.debug_prev_had_motion_vectors); + table_row_bool("Had Scene Temporal Resolve Last Frame:", had_scene_temporal_resolve_last_frame); + table_row_uint("UI-Only Hold Frames:", ui_only_frame_hold_counter); + ImGui::EndTable(); + } + } + + if (ImGui::CollapsingHeader("Active SR Inputs")) + { + if (begin_table("QB_SR_Debug_Active")) + { + table_row_uint("Last SR Render Width:", data.previous_render_width); + table_row_uint("Last SR Render Height:", data.previous_render_height); + table_row_uint("Last SR Output Width:", data.previous_output_width); + table_row_uint("Last SR Output Height:", data.previous_output_height); + table_row_float("Active MV Scale Multiplier:", Settings::mv_scale); + table_row_float("Active Jitter Scale Multiplier:", Settings::jitter_scale); + ImGui::EndTable(); + } + } +#else + (void)data; + (void)saw_history_reprojection_pass; + (void)saw_temporal_resolve_pass; + (void)had_scene_temporal_resolve_last_frame; + (void)ui_only_frame_hold_counter; +#endif + } +} // namespace QuantumBreakUpscaling diff --git a/Source/Games/Quantum Break/main.cpp b/Source/Games/Quantum Break/main.cpp index c4510c3b..34a86ddd 100644 --- a/Source/Games/Quantum Break/main.cpp +++ b/Source/Games/Quantum Break/main.cpp @@ -1,84 +1,8 @@ -#define GAME_QUANTUM_BREAK 1 +#include "shared.h" +#include "Upscaling.hpp" -#define ENABLE_NGX 1 -#define ENABLE_FIDELITY_SK 1 -#define ENABLE_POST_DRAW_DISPATCH_CALLBACK 1 - -#include -#include -#include -#include -#include - -#include "../../../Shaders/Quantum Break/Includes/GameCBuffers.hlsl" -#include "../../Core/core.hpp" - -// Quantum Break's SR hook sits inside the temporal resolve path: -// history reprojection provides motion vectors, temporal resolve provides color/depth/cbuffer inputs, -// and a fullscreen pre-decode pass lets DLSS run on linear color while the game remains gamma-space. namespace { - // Pass hashes used to identify the two parts of QB's temporal pipeline we need to observe/replace. - ShaderHashesList shader_hashes_history_reprojection; - ShaderHashesList shader_hashes_temporal_resolve; - - constexpr float sr_vertical_fov_fallback = 0.775934f; // ~44.46 degrees - // Byte offsets into QB's cb_update_1 cbuffer for the SR inputs that are not available from textures. - constexpr uint32_t cb_update_1_inv_near_offset = 47u * 16u; - constexpr uint32_t cb_update_1_view_to_clip_offset = 10u * 16u; - constexpr uint32_t cb_update_1_tess_view_to_clip_11_offset = 112u * 16u + 12u; - constexpr uint32_t cb_update_1_jitter_offset = 121u * 16u; - constexpr uint32_t cb_update_1_min_size = cb_update_1_jitter_offset + sizeof(float) * 2u; - // The temporal resolve samples current color with g_vSSAAJitterOffset[0]. - constexpr uint32_t ssaa_jitter_offset = 12u * 16u; - constexpr uint32_t ssaa_min_size = ssaa_jitter_offset + sizeof(float) * 2u; - - float ComputeVerticalFovFromProjectionScale(float projection_scale) - { - // Projection matrix scale is 1 / tan(fov / 2). Invalid values fall back to the previous FOV. - if (!std::isfinite(projection_scale)) - { - return 0.f; - } - - const float abs_projection_scale = std::fabs(projection_scale); - if (abs_projection_scale <= 1e-6f) - { - return 0.f; - } - - const float fov = 2.f * std::atan(1.f / abs_projection_scale); - return (std::isfinite(fov) && fov > 0.f && fov < 3.13f) ? fov : 0.f; - } - - bool HasTextureShapeChanged(const D3D11_TEXTURE2D_DESC& current_desc, const D3D11_TEXTURE2D_DESC& previous_desc) - { - return current_desc.Width != previous_desc.Width || current_desc.Height != previous_desc.Height || current_desc.Format != previous_desc.Format || current_desc.ArraySize != previous_desc.ArraySize || current_desc.MipLevels != previous_desc.MipLevels || current_desc.SampleDesc.Count != previous_desc.SampleDesc.Count || current_desc.SampleDesc.Quality != previous_desc.SampleDesc.Quality; - } - - bool UpdatePreviousTextureDesc(const D3D11_TEXTURE2D_DESC& current_desc, D3D11_TEXTURE2D_DESC& previous_desc, bool& has_previous_desc) - { - // DLSS history must reset when any SR input/output texture shape changes. - const bool changed = has_previous_desc && HasTextureShapeChanged(current_desc, previous_desc); - previous_desc = current_desc; - has_previous_desc = true; - return changed; - } - - template - T ReadCBufferValue(const uint8_t* base, uint32_t offset) - { - T value = {}; - std::memcpy(&value, base + offset, sizeof(T)); - return value; - } - - void ReadCBufferFloat2(const uint8_t* base, uint32_t offset, float& x, float& y) - { - x = ReadCBufferValue(base, offset); - y = ReadCBufferValue(base, offset + sizeof(float)); - } - namespace Settings { // Slider descriptors keep defaults, config keys, UI labels, and cbuffer members in one table. @@ -171,44 +95,6 @@ namespace }, }; - namespace SuperResolution - { - constexpr float fsr_sharpness = 0.f; - constexpr float mv_scale = 1.f; - constexpr float jitter_scale = 1.f; - - void Initialize() - { - } - - void Load(reshade::api::effect_runtime* runtime) - { - (void)runtime; - } - - void Draw(DeviceData& device_data, reshade::api::effect_runtime* runtime) - { - (void)runtime; - -#if DEVELOPMENT || TEST -#if ENABLE_SR - ImGui::NewLine(); - ImGui::Text("Super Resolution"); - - if (ImGui::Button("Reset SR History")) - { - device_data.force_reset_sr = true; - } -#else - (void)device_data; - ImGui::TextDisabled("Super Resolution is disabled in this build."); -#endif -#else - (void)device_data; -#endif - } - } // namespace SuperResolution - int IntegerSliderMin(const Descriptor& setting) { return setting.labels.empty() @@ -236,7 +122,7 @@ namespace cb_luma_global_settings.GameSettings.*(setting.member) = setting.default_value; } - SuperResolution::Initialize(); + QuantumBreakUpscaling::Settings::Initialize(); } void Load(reshade::api::effect_runtime* runtime) @@ -247,7 +133,7 @@ namespace reshade::get_config_value(runtime, NAME, setting.label, value); } - SuperResolution::Load(runtime); + QuantumBreakUpscaling::Settings::Load(runtime); } void DrawIntegerSetting(const Descriptor& setting, float& value, reshade::api::effect_runtime* runtime) @@ -318,26 +204,6 @@ namespace } } - void SetRenderData(uint32_t render_width, uint32_t render_height, uint32_t output_width, uint32_t output_height, float jitter_x, float jitter_y, DeviceData& device_data) - { - // Shaders need both SR input and final output sizes so the temporal resolve can sample the correct buffer. - const float render_width_f = static_cast(render_width); - const float render_height_f = static_cast(render_height); - const float output_width_f = static_cast(output_width); - const float output_height_f = static_cast(output_height); - - cb_luma_global_settings.GameSettings.RenderRes = float2{render_width_f, render_height_f}; - cb_luma_global_settings.GameSettings.InvRenderRes = float2{render_width_f > 0.f ? (1.f / render_width_f) : 0.f, render_height_f > 0.f ? (1.f / render_height_f) : 0.f}; - cb_luma_global_settings.GameSettings.OutputRes = float2{output_width_f, output_height_f}; - cb_luma_global_settings.GameSettings.InvOutputRes = float2{output_width_f > 0.f ? (1.f / output_width_f) : 0.f, output_height_f > 0.f ? (1.f / output_height_f) : 0.f}; - - const float render_scale = output_height_f > 0.f ? (render_height_f / output_height_f) : 1.f; - cb_luma_global_settings.GameSettings.RenderScale = render_scale; - cb_luma_global_settings.GameSettings.InvRenderScale = render_scale != 0.f ? (1.f / render_scale) : 1.f; - cb_luma_global_settings.GameSettings.JitterOffset = float2{jitter_x, jitter_y}; - - device_data.cb_luma_global_settings_dirty = true; - } } // namespace Settings namespace RuntimeConfig @@ -359,47 +225,13 @@ namespace struct GameDeviceDataQuantumBreak final : public GameDeviceData { -#if ENABLE_SR - // Resources captured or created around the temporal resolve pass. - com_ptr sr_motion_vectors; - com_ptr cb_update_1_readback; - com_ptr ssaa_readback; - // Conversion scratch texture: game gamma color -> DLSS linear input. - com_ptr sr_linear_input_color; - com_ptr sr_linear_input_color_srv; - com_ptr sr_linear_input_color_rtv; - com_ptr sr_output_color_srv; - - // Last captured frame constants that feed SR. - float sr_jitter_x = 0.f; - float sr_jitter_y = 0.f; - float sr_cb_jitter_x = 0.f; - float sr_cb_jitter_y = 0.f; - float sr_vertical_fov = sr_vertical_fov_fallback; - float sr_near_plane = 0.1f; - float sr_far_plane = 1000.f; - - // Per-resource history used to decide when DLSS history must reset. - bool has_ssaa_data = false; - bool output_changed = false; - bool has_previous_source_desc = false; - bool has_previous_depth_desc = false; - bool has_previous_motion_vectors_desc = false; - D3D11_TEXTURE2D_DESC previous_source_desc = {}; - D3D11_TEXTURE2D_DESC previous_depth_desc = {}; - D3D11_TEXTURE2D_DESC previous_motion_vectors_desc = {}; - uint32_t previous_render_width = 0u; - uint32_t previous_render_height = 0u; - uint32_t previous_output_width = 0u; - uint32_t previous_output_height = 0u; -#endif + QuantumBreakUpscaling::Data upscaling; // Frame markers are latched on present so pause/menu frames can be distinguished from scene frames. bool had_scene_temporal_resolve_last_frame = false; uint32_t ui_only_frame_hold_counter = 0u; bool debug_prev_saw_history_reprojection_pass = false; bool debug_prev_saw_temporal_resolve_pass = false; - bool debug_prev_had_motion_vectors = false; bool saw_history_reprojection_pass = false; bool saw_temporal_resolve_pass = false; }; @@ -416,376 +248,6 @@ class QuantumBreakGame final : public Game return *static_cast(device_data.game); } -#if ENABLE_SR && (DEVELOPMENT || TEST) - static void DrawSuperResolutionDebug(DeviceData& device_data) - { - // Runtime SR status only; detailed cbuffer dumps are intentionally kept out of the user-facing UI. - auto& game_device_data = GetGameDeviceData(device_data); - - auto begin_table = [](const char* id) - { - constexpr ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_NoSavedSettings; - if (!ImGui::BeginTable(id, 2, flags)) - { - return false; - } - - const float field_column_width = (std::max)( - 420.f, - ImGui::CalcTextSize("Had Scene Temporal Resolve Last Frame:").x + ImGui::GetStyle().CellPadding.x * 2.f + 48.f); - ImGui::TableSetupColumn("Field", ImGuiTableColumnFlags_WidthFixed, field_column_width); - ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); - return true; - }; - - auto table_row_label = [](const char* label) - { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted(label); - ImGui::TableSetColumnIndex(1); - }; - - auto table_row_bool = [&](const char* label, bool value) - { - table_row_label(label); - ImGui::TextUnformatted(value ? "Yes" : "No"); - }; - - auto table_row_uint = [&](const char* label, uint32_t value) - { - table_row_label(label); - ImGui::Text("%u", value); - }; - - auto table_row_float = [&](const char* label, float value) - { - table_row_label(label); - ImGui::Text("%.6f", value); - }; - - ImGui::NewLine(); - if (ImGui::CollapsingHeader("Super Resolution Debug")) - { - if (begin_table("QB_SR_Debug_Overview")) - { - table_row_bool("History Reprojection Pass Seen:", game_device_data.debug_prev_saw_history_reprojection_pass); - table_row_bool("Temporal Resolve Pass Seen:", game_device_data.debug_prev_saw_temporal_resolve_pass); - table_row_bool("Motion Vectors Captured:", game_device_data.debug_prev_had_motion_vectors); - table_row_bool("Had Scene Temporal Resolve Last Frame:", game_device_data.had_scene_temporal_resolve_last_frame); - table_row_uint("UI-Only Hold Frames:", game_device_data.ui_only_frame_hold_counter); - ImGui::EndTable(); - } - } - - if (ImGui::CollapsingHeader("Active SR Inputs")) - { - if (begin_table("QB_SR_Debug_Active")) - { - table_row_uint("Last SR Render Width:", game_device_data.previous_render_width); - table_row_uint("Last SR Render Height:", game_device_data.previous_render_height); - table_row_uint("Last SR Output Width:", game_device_data.previous_output_width); - table_row_uint("Last SR Output Height:", game_device_data.previous_output_height); - table_row_float("Active MV Scale Multiplier:", Settings::SuperResolution::mv_scale); - table_row_float("Active Jitter Scale Multiplier:", Settings::SuperResolution::jitter_scale); - ImGui::EndTable(); - } - } - } -#endif - -#if ENABLE_SR - static bool MapPixelShaderConstantBufferForReadback( - ID3D11Device* native_device, - ID3D11DeviceContext* native_device_context, - UINT slot, - uint32_t min_size, - com_ptr& readback_buffer, - D3D11_MAPPED_SUBRESOURCE& mapped) - { - // Constant buffers are GPU-only, so copy them into a staging buffer before CPU-side parsing. - com_ptr constant_buffer; - native_device_context->PSGetConstantBuffers(slot, 1, &constant_buffer); - if (!constant_buffer.get()) - { - return false; - } - - D3D11_BUFFER_DESC source_desc = {}; - constant_buffer->GetDesc(&source_desc); - if (source_desc.ByteWidth < min_size) - { - return false; - } - - bool needs_recreate = !readback_buffer.get(); - if (!needs_recreate) - { - D3D11_BUFFER_DESC readback_desc = {}; - readback_buffer->GetDesc(&readback_desc); - needs_recreate = readback_desc.ByteWidth != source_desc.ByteWidth; - } - - if (needs_recreate) - { - D3D11_BUFFER_DESC readback_desc = source_desc; - readback_desc.BindFlags = 0; - readback_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; - readback_desc.Usage = D3D11_USAGE_STAGING; - readback_desc.MiscFlags = 0; - readback_desc.StructureByteStride = 0; - - readback_buffer = nullptr; - HRESULT hr = native_device->CreateBuffer(&readback_desc, nullptr, &readback_buffer); - if (FAILED(hr) || !readback_buffer.get()) - { - return false; - } - } - - native_device_context->CopyResource(readback_buffer.get(), constant_buffer.get()); - - HRESULT hr = native_device_context->Map(readback_buffer.get(), 0, D3D11_MAP_READ, 0, &mapped); - return SUCCEEDED(hr) && mapped.pData != nullptr; - } - - static bool CaptureCBUpdate1Data(ID3D11Device* native_device, ID3D11DeviceContext* native_device_context, GameDeviceDataQuantumBreak& game_device_data) - { - // cb_update_1 supplies fallback jitter, projection scale, and near plane for SR. - game_device_data.sr_cb_jitter_x = 0.f; - game_device_data.sr_cb_jitter_y = 0.f; - D3D11_MAPPED_SUBRESOURCE mapped = {}; - if (!MapPixelShaderConstantBufferForReadback(native_device, native_device_context, 0, cb_update_1_min_size, game_device_data.cb_update_1_readback, mapped)) - { - return false; - } - - const auto* base = static_cast(mapped.pData); - ReadCBufferFloat2(base, cb_update_1_jitter_offset, game_device_data.sr_cb_jitter_x, game_device_data.sr_cb_jitter_y); - const float inv_near = ReadCBufferValue(base, cb_update_1_inv_near_offset); - const float projection_scale_x = ReadCBufferValue(base, cb_update_1_view_to_clip_offset); - const float projection_scale_y = ReadCBufferValue(base, cb_update_1_view_to_clip_offset + sizeof(float) * 5u); - const float tess_view_to_clip_11 = ReadCBufferValue(base, cb_update_1_tess_view_to_clip_11_offset); - if (std::isfinite(inv_near) && inv_near > 0.f) - { - game_device_data.sr_near_plane = 1.f / inv_near; - } - - game_device_data.sr_cb_jitter_x = std::isfinite(game_device_data.sr_cb_jitter_x) ? game_device_data.sr_cb_jitter_x : 0.f; - game_device_data.sr_cb_jitter_y = std::isfinite(game_device_data.sr_cb_jitter_y) ? game_device_data.sr_cb_jitter_y : 0.f; - - float vertical_fov = ComputeVerticalFovFromProjectionScale(projection_scale_y); - if (vertical_fov <= 0.f) - { - vertical_fov = ComputeVerticalFovFromProjectionScale(projection_scale_x); - } - if (vertical_fov <= 0.f) - { - vertical_fov = ComputeVerticalFovFromProjectionScale(tess_view_to_clip_11); - } - if (vertical_fov > 0.f) - { - game_device_data.sr_vertical_fov = vertical_fov; - } - - native_device_context->Unmap(game_device_data.cb_update_1_readback.get(), 0); - return true; - } - - static bool CaptureSSAAData(ID3D11Device* native_device, ID3D11DeviceContext* native_device_context, GameDeviceDataQuantumBreak& game_device_data) - { - // The temporal resolve samples current color with g_vSSAAJitterOffset[0], so keep this as the jitter source. - game_device_data.has_ssaa_data = false; - game_device_data.sr_jitter_x = 0.f; - game_device_data.sr_jitter_y = 0.f; - - D3D11_MAPPED_SUBRESOURCE mapped = {}; - if (!MapPixelShaderConstantBufferForReadback(native_device, native_device_context, 1, ssaa_min_size, game_device_data.ssaa_readback, mapped)) - { - return false; - } - - const auto* base = static_cast(mapped.pData); - ReadCBufferFloat2(base, ssaa_jitter_offset, game_device_data.sr_jitter_x, game_device_data.sr_jitter_y); - game_device_data.sr_jitter_x = std::isfinite(game_device_data.sr_jitter_x) ? game_device_data.sr_jitter_x : 0.f; - game_device_data.sr_jitter_y = std::isfinite(game_device_data.sr_jitter_y) ? game_device_data.sr_jitter_y : 0.f; - - game_device_data.has_ssaa_data = true; - - native_device_context->Unmap(game_device_data.ssaa_readback.get(), 0); - return true; - } - - static bool SetupSROutput(ID3D11Device* native_device, DeviceData& device_data, GameDeviceDataQuantumBreak& game_device_data, const D3D11_TEXTURE2D_DESC& output_desc) - { - // DLSS writes linear color here; temporal_resolve encodes to gamma when SRType > 0. - game_device_data.output_changed = false; - bool recreated_output_texture = false; - - auto* sr_instance_data = device_data.GetSRInstanceData(); - if (!sr_instance_data) - { - return false; - } - if (output_desc.Width < sr_instance_data->min_resolution || output_desc.Height < sr_instance_data->min_resolution) - { - return false; - } - - D3D11_TEXTURE2D_DESC sr_output_desc = output_desc; - sr_output_desc.BindFlags |= D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE; - - if (device_data.sr_output_color.get()) - { - D3D11_TEXTURE2D_DESC prev_desc = {}; - device_data.sr_output_color->GetDesc(&prev_desc); - game_device_data.output_changed = prev_desc.Width != sr_output_desc.Width || prev_desc.Height != sr_output_desc.Height || prev_desc.Format != sr_output_desc.Format; - } - - if (!device_data.sr_output_color.get() || game_device_data.output_changed) - { - device_data.sr_output_color = nullptr; - HRESULT hr = native_device->CreateTexture2D(&sr_output_desc, nullptr, &device_data.sr_output_color); - if (FAILED(hr) || !device_data.sr_output_color.get()) - { - return false; - } - - recreated_output_texture = true; - } - - if (!game_device_data.sr_output_color_srv.get() || game_device_data.output_changed || recreated_output_texture) - { - game_device_data.sr_output_color_srv = nullptr; - HRESULT hr = native_device->CreateShaderResourceView(device_data.sr_output_color.get(), nullptr, &game_device_data.sr_output_color_srv); - if (FAILED(hr) || !game_device_data.sr_output_color_srv.get()) - { - return false; - } - } - - return true; - } - - static DXGI_FORMAT ResolveSRColorViewFormat(DXGI_FORMAT format) - { - // Conversion passes need RTV/SRV-compatible typed color formats, not typeless or sRGB view formats. - switch (format) - { - case DXGI_FORMAT_R32G32B32A32_TYPELESS: - return DXGI_FORMAT_R32G32B32A32_FLOAT; - case DXGI_FORMAT_R16G16B16A16_TYPELESS: - return DXGI_FORMAT_R16G16B16A16_FLOAT; - case DXGI_FORMAT_R10G10B10A2_TYPELESS: - return DXGI_FORMAT_R10G10B10A2_UNORM; - case DXGI_FORMAT_R8G8B8A8_TYPELESS: - case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: - return DXGI_FORMAT_R8G8B8A8_UNORM; - case DXGI_FORMAT_B8G8R8A8_TYPELESS: - case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: - return DXGI_FORMAT_B8G8R8A8_UNORM; - case DXGI_FORMAT_B8G8R8X8_TYPELESS: - case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: - return DXGI_FORMAT_B8G8R8X8_UNORM; - default: - return format; - } - } - - static bool SetupSRConversionTexture(ID3D11Device* native_device, D3D11_TEXTURE2D_DESC desc, com_ptr& texture, com_ptr& srv, com_ptr& rtv) - { - // Scratch textures are simple single-mip render targets used by SR conversion passes. - desc.Format = ResolveSRColorViewFormat(desc.Format); - if (desc.Width == 0u || desc.Height == 0u || desc.Format == DXGI_FORMAT_UNKNOWN) - { - return false; - } - - desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; - desc.CPUAccessFlags = 0; - desc.Usage = D3D11_USAGE_DEFAULT; - desc.MiscFlags = 0; - desc.MipLevels = 1u; - desc.ArraySize = 1u; - desc.SampleDesc.Count = 1u; - desc.SampleDesc.Quality = 0u; - - bool recreate_texture = !texture.get(); - if (!recreate_texture) - { - D3D11_TEXTURE2D_DESC previous_desc = {}; - texture->GetDesc(&previous_desc); - recreate_texture = HasTextureShapeChanged(desc, previous_desc); - } - - if (recreate_texture) - { - texture = nullptr; - srv = nullptr; - rtv = nullptr; - - HRESULT hr = native_device->CreateTexture2D(&desc, nullptr, &texture); - if (FAILED(hr) || !texture.get()) - { - return false; - } - } - - if (!srv.get()) - { - HRESULT hr = native_device->CreateShaderResourceView(texture.get(), nullptr, &srv); - if (FAILED(hr) || !srv.get()) - { - return false; - } - } - - if (!rtv.get()) - { - HRESULT hr = native_device->CreateRenderTargetView(texture.get(), nullptr, &rtv); - if (FAILED(hr) || !rtv.get()) - { - return false; - } - } - - return true; - } - - static bool DrawSRConversionPass(ID3D11DeviceContext* native_device_context, DeviceData& device_data, uint32_t pixel_shader_hash, ID3D11ShaderResourceView* source_srv, ID3D11RenderTargetView* target_rtv, uint32_t width, uint32_t height) - { - // Shared fullscreen draw for gamma->linear and linear->gamma SR color conversion. - const auto vs_it = device_data.native_vertex_shaders.find(CompileTimeStringHash("Copy VS")); - const auto ps_it = device_data.native_pixel_shaders.find(pixel_shader_hash); - if (vs_it == device_data.native_vertex_shaders.end() || !vs_it->second.get() || - ps_it == device_data.native_pixel_shaders.end() || !ps_it->second.get() || - !source_srv || !target_rtv || width == 0u || height == 0u) - { - return false; - } - - native_device_context->OMSetRenderTargets(0, nullptr, nullptr); - DrawCustomPixelShader( - native_device_context, - device_data.default_depth_stencil_state.get(), - device_data.default_blend_state.get(), - nullptr, - vs_it->second.get(), - ps_it->second.get(), - source_srv, - target_rtv, - width, - height, - false); - - ID3D11ShaderResourceView* null_srv = nullptr; - native_device_context->PSSetShaderResources(0, 1, &null_srv); - ID3D11RenderTargetView* null_rtv = nullptr; - native_device_context->OMSetRenderTargets(1, &null_rtv, nullptr); - return true; - } -#endif - public: void OnInit(bool async) override { @@ -795,11 +257,7 @@ class QuantumBreakGame final : public Game luma_settings_cbuffer_index = 13; luma_data_cbuffer_index = 12; -#if ENABLE_SR - sr_game_tooltip = "Super Resolution engages during the temporal resolve pass.\n"; - // Native fullscreen pass bridges QB's gamma-space post stack with DLSS' preferred linear input. - native_shaders_definitions.emplace(CompileTimeStringHash("QB Pre SR Decode"), ShaderDefinition{"Luma_QB_PreSRDecode", reshade::api::pipeline_subobject_type::pixel_shader}); -#endif + QuantumBreakUpscaling::OnInit(); Settings::Initialize(); } @@ -820,26 +278,16 @@ class QuantumBreakGame final : public Game { auto& game_device_data = GetGameDeviceData(device_data); - if (original_shader_hashes.Contains(shader_hashes_history_reprojection)) + if (QuantumBreakUpscaling::IsHistoryReprojectionPass(original_shader_hashes)) { - // The history reprojection pass has the motion-vector resource bound as CS SRV 0. game_device_data.saw_history_reprojection_pass = true; device_data.taa_detected = true; - -#if ENABLE_SR - com_ptr motion_vectors_srv; - native_device_context->CSGetShaderResources(0, 1, &motion_vectors_srv); - if (motion_vectors_srv.get()) - { - game_device_data.sr_motion_vectors = nullptr; - motion_vectors_srv->GetResource(&game_device_data.sr_motion_vectors); - } -#endif + QuantumBreakUpscaling::CaptureMotionVectors(native_device_context, game_device_data.upscaling); return DrawOrDispatchOverrideType::None; } - if (!original_shader_hashes.Contains(shader_hashes_temporal_resolve)) + if (!QuantumBreakUpscaling::IsTemporalResolvePass(original_shader_hashes)) { return DrawOrDispatchOverrideType::None; } @@ -848,219 +296,14 @@ class QuantumBreakGame final : public Game game_device_data.saw_temporal_resolve_pass = true; device_data.has_drawn_main_post_processing = true; -#if ENABLE_SR - const bool sr_requested = device_data.sr_type != SR::Type::None && !device_data.sr_suppressed; -#else - const bool sr_requested = false; -#endif - bool sr_succeeded = false; - -#if ENABLE_SR - com_ptr ps_shader_resources[3]; - com_ptr render_target_views[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT]; - com_ptr depth_stencil_view; - const bool immediate_context = native_device_context->GetType() == D3D11_DEVICE_CONTEXT_IMMEDIATE; - const bool has_main_temporal_resolve_bindings = [&]() + const auto sr_result = QuantumBreakUpscaling::RunTemporalResolve(native_device, native_device_context, device_data, game_device_data.upscaling); + if (sr_result.stop_processing) { - if (!immediate_context) - { - return false; - } - - // UI/menu-only resolves can hit the same shader without the scene color/depth/RTV bindings SR needs. - native_device_context->PSGetShaderResources(0, ARRAYSIZE(ps_shader_resources), reinterpret_cast(ps_shader_resources)); - native_device_context->OMGetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, &render_target_views[0], &depth_stencil_view); - return ps_shader_resources[0].get() && ps_shader_resources[2].get() && render_target_views[0].get(); - }(); - - if (has_main_temporal_resolve_bindings) - { - CaptureCBUpdate1Data(native_device, native_device_context, game_device_data); - CaptureSSAAData(native_device, native_device_context, game_device_data); - } - - if (sr_requested && immediate_context && game_device_data.sr_motion_vectors.get() && has_main_temporal_resolve_bindings) - { - if (ps_shader_resources[0].get() && ps_shader_resources[2].get() && render_target_views[0].get()) - { - com_ptr source_color_resource; - ps_shader_resources[2]->GetResource(&source_color_resource); - - com_ptr depth_resource; - ps_shader_resources[0]->GetResource(&depth_resource); - - // The temporal resolve RTV is the final SR output target size. - com_ptr output_resource; - render_target_views[0]->GetResource(&output_resource); - - com_ptr source_color_texture; - com_ptr output_texture; - com_ptr depth_texture; - com_ptr motion_vectors_texture; - - const HRESULT source_hr = source_color_resource.get() ? source_color_resource->QueryInterface(&source_color_texture) : E_FAIL; - const HRESULT output_hr = output_resource.get() ? output_resource->QueryInterface(&output_texture) : E_FAIL; - const HRESULT depth_hr = depth_resource.get() ? depth_resource->QueryInterface(&depth_texture) : E_FAIL; - const HRESULT motion_vectors_hr = game_device_data.sr_motion_vectors.get() ? game_device_data.sr_motion_vectors->QueryInterface(&motion_vectors_texture) : E_FAIL; - - if (SUCCEEDED(source_hr) && SUCCEEDED(output_hr) && SUCCEEDED(depth_hr) && SUCCEEDED(motion_vectors_hr) && source_color_texture.get() && output_texture.get() && depth_texture.get() && motion_vectors_texture.get()) - { - // Descs drive DLSS settings, conversion texture allocation, and history reset decisions. - D3D11_TEXTURE2D_DESC source_desc = {}; - D3D11_TEXTURE2D_DESC depth_desc = {}; - D3D11_TEXTURE2D_DESC motion_vectors_desc = {}; - D3D11_TEXTURE2D_DESC output_desc = {}; - // Source/depth/MV descs bound the DLSS input resolution; output desc drives the upscaled target. - source_color_texture->GetDesc(&source_desc); - depth_texture->GetDesc(&depth_desc); - motion_vectors_texture->GetDesc(&motion_vectors_desc); - output_texture->GetDesc(&output_desc); - - if (SetupSROutput(native_device, device_data, game_device_data, output_desc)) - { - auto* sr_instance_data = device_data.GetSRInstanceData(); - if (sr_instance_data) - { - // Use the smallest SR input texture so color, depth, and motion vectors cover the full render area. - const uint32_t max_input_width = (std::min)(source_desc.Width, (std::min)(depth_desc.Width, motion_vectors_desc.Width)); - const uint32_t max_input_height = (std::min)(source_desc.Height, (std::min)(depth_desc.Height, motion_vectors_desc.Height)); - const uint32_t render_width = max_input_width; - const uint32_t render_height = max_input_height; - - const uint32_t output_width = output_desc.Width; - const uint32_t output_height = output_desc.Height; - const float jitter_x = game_device_data.has_ssaa_data ? game_device_data.sr_jitter_x : game_device_data.sr_cb_jitter_x; - const float jitter_y = game_device_data.has_ssaa_data ? game_device_data.sr_jitter_y : game_device_data.sr_cb_jitter_y; - - if (render_width == 0u || render_height == 0u || output_width == 0u || output_height == 0u) - { - device_data.force_reset_sr = true; - return DrawOrDispatchOverrideType::None; - } - - Settings::SetRenderData(render_width, render_height, output_width, output_height, jitter_x, jitter_y, device_data); - - SR::SettingsData settings_data = {}; - settings_data.output_width = output_width; - settings_data.output_height = output_height; - settings_data.render_width = render_width; - settings_data.render_height = render_height; - settings_data.dynamic_resolution = false; - // The pre-SR decode pass makes the color input linear; exposure is already baked by QB. - settings_data.hdr = true; - settings_data.auto_exposure = false; - settings_data.inverted_depth = false; - settings_data.mvs_jittered = false; - settings_data.mvs_x_scale = static_cast(render_width) * Settings::SuperResolution::mv_scale; - settings_data.mvs_y_scale = static_cast(render_height) * Settings::SuperResolution::mv_scale; - settings_data.render_preset = dlss_render_preset; - - D3D11_SHADER_RESOURCE_VIEW_DESC source_srv_desc = {}; - ps_shader_resources[2]->GetDesc(&source_srv_desc); - - D3D11_TEXTURE2D_DESC sr_linear_input_desc = source_desc; - sr_linear_input_desc.Format = source_srv_desc.Format != DXGI_FORMAT_UNKNOWN ? source_srv_desc.Format : source_desc.Format; - - const bool conversion_resources_ready = - SetupSRConversionTexture(native_device, sr_linear_input_desc, game_device_data.sr_linear_input_color, game_device_data.sr_linear_input_color_srv, game_device_data.sr_linear_input_color_rtv); - - const bool settings_updated = conversion_resources_ready && sr_implementations[device_data.sr_type]->UpdateSettings(sr_instance_data, native_device_context, settings_data); - if (settings_updated) - { - // Reset DLSS history when any physical resource or logical render size changes. - const bool source_changed = UpdatePreviousTextureDesc(source_desc, game_device_data.previous_source_desc, game_device_data.has_previous_source_desc); - const bool depth_changed = UpdatePreviousTextureDesc(depth_desc, game_device_data.previous_depth_desc, game_device_data.has_previous_depth_desc); - const bool motion_vectors_changed = UpdatePreviousTextureDesc(motion_vectors_desc, game_device_data.previous_motion_vectors_desc, game_device_data.has_previous_motion_vectors_desc); - const bool render_size_changed = game_device_data.previous_render_width != 0u && game_device_data.previous_render_height != 0u && (game_device_data.previous_render_width != render_width || game_device_data.previous_render_height != render_height); - game_device_data.previous_render_width = render_width; - game_device_data.previous_render_height = render_height; - game_device_data.previous_output_width = output_width; - game_device_data.previous_output_height = output_height; - - const bool reset_sr = device_data.force_reset_sr || game_device_data.output_changed || source_changed || depth_changed || motion_vectors_changed || render_size_changed; - device_data.force_reset_sr = false; - - SR::SuperResolutionImpl::DrawData draw_data = {}; - draw_data.source_color = game_device_data.sr_linear_input_color.get(); - draw_data.output_color = device_data.sr_output_color.get(); - draw_data.motion_vectors = game_device_data.sr_motion_vectors.get(); - draw_data.depth_buffer = depth_resource.get(); - draw_data.pre_exposure = 1.f; - draw_data.jitter_x = jitter_x * Settings::SuperResolution::jitter_scale; - draw_data.jitter_y = jitter_y * Settings::SuperResolution::jitter_scale; - draw_data.vert_fov = (std::isfinite(game_device_data.sr_vertical_fov) && game_device_data.sr_vertical_fov > 0.f) - ? game_device_data.sr_vertical_fov - : sr_vertical_fov_fallback; - draw_data.near_plane = game_device_data.sr_near_plane; - draw_data.far_plane = game_device_data.sr_far_plane; - draw_data.reset = reset_sr; - draw_data.render_width = render_width; - draw_data.render_height = render_height; - draw_data.user_sharpness = device_data.sr_type == SR::Type::FSR ? Settings::SuperResolution::fsr_sharpness : -1.f; - - DrawStateStack draw_state_stack; - DrawStateStack compute_state_stack; - draw_state_stack.Cache(native_device_context, device_data.uav_max_count); - compute_state_stack.Cache(native_device_context, device_data.uav_max_count); - - // Feed DLSS linear color; temporal_resolve encodes SR output back to gamma-space. - const bool pre_sr_encoded = DrawSRConversionPass( - native_device_context, - device_data, - CompileTimeStringHash("QB Pre SR Decode"), - ps_shader_resources[2].get(), - game_device_data.sr_linear_input_color_rtv.get(), - source_desc.Width, - source_desc.Height); - - sr_succeeded = pre_sr_encoded && sr_implementations[device_data.sr_type]->Draw(sr_instance_data, native_device_context, draw_data); - - { - ID3D11ShaderResourceView* null_srvs[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT] = {}; - native_device_context->PSSetShaderResources(0, D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT, null_srvs); - native_device_context->CSSetShaderResources(0, D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT, null_srvs); - ID3D11UnorderedAccessView* null_uavs[D3D11_1_UAV_SLOT_COUNT] = {}; - native_device_context->CSSetUnorderedAccessViews(0, D3D11_1_UAV_SLOT_COUNT, null_uavs, nullptr); - ID3D11RenderTargetView* null_rtvs[D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT] = {}; - native_device_context->OMSetRenderTargets(D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT, null_rtvs, nullptr); - } - - draw_state_stack.Restore(native_device_context); - compute_state_stack.Restore(native_device_context); - - if (sr_succeeded) - { - device_data.has_drawn_sr = true; - } - else - { - device_data.force_reset_sr = true; - } - } - } - } - } - } - } -#endif - -#if ENABLE_SR - const uint32_t sr_type_for_pass = sr_succeeded ? (static_cast(device_data.sr_type) + 1u) : 0u; -#else - const uint32_t sr_type_for_pass = 0u; -#endif - if (cb_luma_global_settings.SRType != sr_type_for_pass) - { - cb_luma_global_settings.SRType = sr_type_for_pass; - device_data.cb_luma_global_settings_dirty = true; + return DrawOrDispatchOverrideType::None; } -#if ENABLE_SR - if (!sr_succeeded && sr_requested) - { - device_data.force_reset_sr = true; - } -#endif + QuantumBreakUpscaling::SetSRTypeForTemporalResolve(device_data, sr_result.succeeded); + QuantumBreakUpscaling::ForceResetIfRequestedAndFailed(device_data, sr_result); if (original_draw_dispatch_func && *original_draw_dispatch_func) { @@ -1080,13 +323,7 @@ class QuantumBreakGame final : public Game updated_cbuffers = true; } -#if ENABLE_SR - if (sr_succeeded) - { - ID3D11ShaderResourceView* sr_output_srv = game_device_data.sr_output_color_srv.get(); - native_device_context->PSSetShaderResources(2, 1, &sr_output_srv); - } -#endif + QuantumBreakUpscaling::BindOutputToTemporalResolve(native_device_context, game_device_data.upscaling, sr_result.succeeded); (*original_draw_dispatch_func)(); @@ -1111,46 +348,15 @@ class QuantumBreakGame final : public Game updated_cbuffers = true; } -#if ENABLE_SR - if (sr_succeeded) - { - ID3D11ShaderResourceView* sr_output_srv = game_device_data.sr_output_color_srv.get(); - native_device_context->PSSetShaderResources(2, 1, &sr_output_srv); - } -#endif + QuantumBreakUpscaling::BindOutputToTemporalResolve(native_device_context, game_device_data.upscaling, sr_result.succeeded); return DrawOrDispatchOverrideType::None; } void CleanExtraSRResources(DeviceData& device_data) override { -#if ENABLE_SR - // Drop all transient SR resources so the next valid scene frame rebuilds them and resets DLSS history. auto& game_device_data = GetGameDeviceData(device_data); - - device_data.force_reset_sr = true; - device_data.has_drawn_sr = false; - - game_device_data.sr_motion_vectors = nullptr; - game_device_data.sr_linear_input_color = nullptr; - game_device_data.sr_linear_input_color_srv = nullptr; - game_device_data.sr_linear_input_color_rtv = nullptr; - game_device_data.sr_output_color_srv = nullptr; - game_device_data.output_changed = false; - - game_device_data.has_previous_source_desc = false; - game_device_data.has_previous_depth_desc = false; - game_device_data.has_previous_motion_vectors_desc = false; - game_device_data.previous_source_desc = {}; - game_device_data.previous_depth_desc = {}; - game_device_data.previous_motion_vectors_desc = {}; - game_device_data.previous_render_width = 0u; - game_device_data.previous_render_height = 0u; - game_device_data.previous_output_width = 0u; - game_device_data.previous_output_height = 0u; -#else - (void)device_data; -#endif + QuantumBreakUpscaling::CleanResources(device_data, game_device_data.upscaling); } bool IsGamePaused(const DeviceData& device_data) const override @@ -1165,21 +371,10 @@ class QuantumBreakGame final : public Game (void)native_device; auto& game_device_data = GetGameDeviceData(device_data); -#if ENABLE_SR - // If SR was requested but no scene resolve produced SR output, force a history reset for the next scene frame. - if (device_data.sr_type != SR::Type::None && !device_data.has_drawn_sr) - { - device_data.force_reset_sr = true; - } -#endif + QuantumBreakUpscaling::OnPresent(device_data, game_device_data.upscaling); game_device_data.debug_prev_saw_history_reprojection_pass = game_device_data.saw_history_reprojection_pass; game_device_data.debug_prev_saw_temporal_resolve_pass = game_device_data.saw_temporal_resolve_pass; -#if ENABLE_SR - game_device_data.debug_prev_had_motion_vectors = game_device_data.sr_motion_vectors.get() != nullptr; -#else - game_device_data.debug_prev_had_motion_vectors = false; -#endif device_data.taa_detected = game_device_data.saw_history_reprojection_pass; device_data.has_drawn_sr = false; @@ -1206,18 +401,8 @@ class QuantumBreakGame final : public Game game_device_data.had_scene_temporal_resolve_last_frame = false; } - if (cb_luma_global_settings.SRType != 0u) - { - cb_luma_global_settings.SRType = 0u; - device_data.cb_luma_global_settings_dirty = true; - } - game_device_data.saw_history_reprojection_pass = false; game_device_data.saw_temporal_resolve_pass = false; -#if ENABLE_SR - game_device_data.sr_motion_vectors = nullptr; - game_device_data.output_changed = false; -#endif } void DrawImGuiSettings(DeviceData& device_data) override @@ -1228,10 +413,15 @@ class QuantumBreakGame final : public Game // Keep user-facing grading/effects controls above the SR tuning/debug block. Settings::DrawAll(runtime); - Settings::SuperResolution::Draw(device_data, runtime); -#if ENABLE_SR && (DEVELOPMENT || TEST) - DrawSuperResolutionDebug(device_data); -#endif + QuantumBreakUpscaling::Settings::Draw(device_data, runtime); + + auto& game_device_data = GetGameDeviceData(device_data); + QuantumBreakUpscaling::DrawDebug( + game_device_data.upscaling, + game_device_data.debug_prev_saw_history_reprojection_pass, + game_device_data.debug_prev_saw_temporal_resolve_pass, + game_device_data.had_scene_temporal_resolve_last_frame, + game_device_data.ui_only_frame_hold_counter); } void PrintImGuiAbout() override @@ -1289,8 +479,7 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv Globals::SetGlobals(PROJECT_NAME, "Quantum Break Luma mod", "https://ko-fi.com/musaqh"); Globals::VERSION = 1; - shader_hashes_history_reprojection.compute_shaders.emplace(std::stoul("E8337D48", nullptr, 16)); - shader_hashes_temporal_resolve.pixel_shaders.emplace(std::stoul("99274617", nullptr, 16)); + QuantumBreakUpscaling::RegisterShaderHashes(); RuntimeConfig::ConfigureSwapchainAndFormatUpgrades(); diff --git a/Source/Games/Quantum Break/shared.h b/Source/Games/Quantum Break/shared.h new file mode 100644 index 00000000..e65fa305 --- /dev/null +++ b/Source/Games/Quantum Break/shared.h @@ -0,0 +1,17 @@ +#pragma once + +#define GAME_QUANTUM_BREAK 1 + +#define ENABLE_NGX 1 +#define ENABLE_FIDELITY_SK 1 +#define ENABLE_POST_DRAW_DISPATCH_CALLBACK 1 + +#include +#include +#include +#include +#include +#include + +#include "../../../Shaders/Quantum Break/Includes/GameCBuffers.hlsl" +#include "../../Core/core.hpp" \ No newline at end of file From 6668ecaa7386b17e5b8425515980c25198d3c471 Mon Sep 17 00:00:00 2001 From: Musa Haji Date: Tue, 19 May 2026 00:59:32 -0400 Subject: [PATCH 4/6] feat(Quantum Break): add extended ultrawide support --- Source/Games/Quantum Break/main.cpp | 76 +++++++++++++++++++++++++++++ Source/Games/Quantum Break/shared.h | 3 ++ 2 files changed, 79 insertions(+) diff --git a/Source/Games/Quantum Break/main.cpp b/Source/Games/Quantum Break/main.cpp index 34a86ddd..b0320edf 100644 --- a/Source/Games/Quantum Break/main.cpp +++ b/Source/Games/Quantum Break/main.cpp @@ -208,6 +208,80 @@ namespace namespace RuntimeConfig { + void ApplyUltrawidePatches() + { + HMODULE module_handle = GetModuleHandle(nullptr); + if (!module_handle) + { + assert(false); + return; + } + + auto* dos_header = reinterpret_cast(module_handle); + if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) + { + assert(false); + return; + } + + auto* nt_headers = reinterpret_cast(reinterpret_cast(module_handle) + dos_header->e_lfanew); + if (nt_headers->Signature != IMAGE_NT_SIGNATURE) + { + assert(false); + return; + } + + std::byte* base = reinterpret_cast(module_handle); + const std::size_t section_size = nt_headers->OptionalHeader.SizeOfImage; + + // Ultrawide patch: QB hardcodes its borderless/fullscreen resolution list and clamps aspect ratio. + // Replace the highest built-in ultrawide option with the current primary monitor resolution. + const int hardcoded_res_width = 3440; + const int hardcoded_res_height = 1440; + const char hardcoded_res_str[] = "3440 x 1440"; + + std::vector hardcoded_res_width_addresses = System::ScanMemoryForPattern(base, section_size, reinterpret_cast(&hardcoded_res_width), sizeof(hardcoded_res_width)); + assert(!hardcoded_res_width_addresses.empty()); + for (std::byte* hardcoded_res_width_address : hardcoded_res_width_addresses) + { + // Scan the next three 32-bit values to find the matching height. There is usually one 32-bit gap. + std::vector hardcoded_res_height_addresses = System::ScanMemoryForPattern(hardcoded_res_width_address, sizeof(hardcoded_res_height) * 3u, reinterpret_cast(&hardcoded_res_height), sizeof(hardcoded_res_height)); + if (!hardcoded_res_height_addresses.empty()) + { + const int screen_width = GetSystemMetrics(SM_CXSCREEN); + const int screen_height = GetSystemMetrics(SM_CYSCREEN); + + System::PatchMemory(hardcoded_res_width_address, &screen_width, sizeof(screen_width), System::PatchMemoryType::Code); + System::PatchMemory(hardcoded_res_height_addresses[0], &screen_height, sizeof(screen_height), System::PatchMemoryType::Code); + + // Patch the menu label as well. QB stores these resolution strings in 16-byte slots. + const std::vector hardcoded_res_str_pattern(reinterpret_cast(hardcoded_res_str), reinterpret_cast(hardcoded_res_str) + std::strlen(hardcoded_res_str) + 1u); + std::vector hardcoded_res_str_addresses = System::ScanMemoryForPattern(base, section_size, hardcoded_res_str_pattern, true); + if (!hardcoded_res_str_addresses.empty()) + { + char screen_res_str[0x10] = {}; + const int written = std::snprintf(screen_res_str, sizeof(screen_res_str), "%i x %i", screen_width, screen_height); + if (written > 0 && written < static_cast(sizeof(screen_res_str))) + { + System::PatchMemory(hardcoded_res_str_addresses[0], screen_res_str, sizeof(screen_res_str), System::PatchMemoryType::Data); + } + } + + break; + } + } + + // Patch the aspect ratio limit from 21:9-ish to 48:9. + const std::vector pattern_aspect_ratio_limit = {0x26, 0xB4, 0x17, 0x40}; // 2.37037038803101 (2560x1080) + std::vector aspect_ratio_limit_addresses = System::ScanMemoryForPattern(base, section_size, pattern_aspect_ratio_limit); + assert(aspect_ratio_limit_addresses.size() == 1); // Only one of these in the Steam build as of early 2026. + if (aspect_ratio_limit_addresses.size() == 1) + { + const float new_aspect_ratio_limit = 48.f / 9.f; + System::PatchMemory(aspect_ratio_limit_addresses[0], &new_aspect_ratio_limit, sizeof(new_aspect_ratio_limit), System::PatchMemoryType::Data); + } + } + void ConfigureSwapchainAndFormatUpgrades() { swapchain_format_upgrade_type = TextureFormatUpgradesType::AllowedEnabled; @@ -253,6 +327,8 @@ class QuantumBreakGame final : public Game { (void)async; + RuntimeConfig::ApplyUltrawidePatches(); + // QB custom shaders reserve these slots for Luma settings/data during the temporal resolve replacement. luma_settings_cbuffer_index = 13; luma_data_cbuffer_index = 12; diff --git a/Source/Games/Quantum Break/shared.h b/Source/Games/Quantum Break/shared.h index e65fa305..5c416f47 100644 --- a/Source/Games/Quantum Break/shared.h +++ b/Source/Games/Quantum Break/shared.h @@ -7,8 +7,11 @@ #define ENABLE_POST_DRAW_DISPATCH_CALLBACK 1 #include +#include #include +#include #include +#include #include #include #include From d55c6e3a37699544e0c7e3f0a7a156bc5cd14020 Mon Sep 17 00:00:00 2001 From: Musa Haji Date: Tue, 19 May 2026 01:11:17 -0400 Subject: [PATCH 5/6] feat(Quantum Break): boost sky brightness --- .../Quantum Break/Sky_0x0468C6D7.ps_5_0.hlsl | 275 ++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 Shaders/Quantum Break/Sky_0x0468C6D7.ps_5_0.hlsl diff --git a/Shaders/Quantum Break/Sky_0x0468C6D7.ps_5_0.hlsl b/Shaders/Quantum Break/Sky_0x0468C6D7.ps_5_0.hlsl new file mode 100644 index 00000000..351d7125 --- /dev/null +++ b/Shaders/Quantum Break/Sky_0x0468C6D7.ps_5_0.hlsl @@ -0,0 +1,275 @@ +#include "../Includes/Common.hlsl" + +cbuffer cb_update_1 : register(b0) +{ + float2 g_vScreenRes : packoffset(c0); + float2 g_vInvScreenRes : packoffset(c0.z); + float2 g_vOutputRes : packoffset(c1); + float2 g_vInvOutputRes : packoffset(c1.z); + float4x4 g_mWorldToView : packoffset(c2); + float4x4 g_mViewToWorld : packoffset(c6); + float4x4 g_mViewToClip : packoffset(c10); + float4x4 g_mClipToView : packoffset(c14); + float4x4 g_mWorldToClip : packoffset(c18); + float4x4 g_mClipToWorld : packoffset(c22); + float4x4 g_mClipToPreviousClip : packoffset(c26); + float4x4 g_mViewToPreviousClip : packoffset(c30); + float4x4 g_mPreviousViewToView : packoffset(c34); + float4x4 g_mPreviousWorldToClip : packoffset(c38); + float4x4 g_mPreviousViewToClip : packoffset(c42); + float4 g_vViewPoint : packoffset(c46); + float g_fInvNear : packoffset(c47); + float g_fSimulationTime : packoffset(c47.y); + float g_fSimulationTimeDelta : packoffset(c47.z); + float g_fSimulationTimeStep : packoffset(c47.w); + uint g_uTemporalFrame : packoffset(c48); + uint g_uCurrentFrame : packoffset(c48.y); + + struct + { + float4 vSunDir; + float4 vSunE; + float4 vExtinction; + float4 vRayleigh; + float4 vMie; + float4 vSchlickConstants; + float4 vFog; + } g_atmosphere : packoffset(c49); + + float3 g_vFogColor : packoffset(c56); + float3 g_vFogColorOpposite : packoffset(c57); + float g_fFogExp : packoffset(c57.w); + float g_fFogGroundDensityAtViewer : packoffset(c58); + float g_fFogGroundHeight : packoffset(c58.y); + float g_fFogGroundFalloff : packoffset(c58.z); + float g_fFogGroundDensity : packoffset(c58.w); + float2 g_vFogGroundDensityMapRange : packoffset(c59); + float3 g_vFogGroundSimulationVelocityAndScale : packoffset(c60); + uint g_uCharacterLightRigsBindOffset : packoffset(c60.w); + float4 g_fTileDepthClipRanges[5] : packoffset(c61); + float4 g_fTileDepthRanges[5] : packoffset(c66); + float2 g_vDepthTileResolve : packoffset(c71); + uint g_uDepthTileCount : packoffset(c71.z); + uint2 g_vTileResolution : packoffset(c72); + uint3 g_vTileWidthHeightDepth : packoffset(c73); + float2 g_vTileResolutionPerScreenResolution : packoffset(c74); + float2 g_vTileDepthNearFar : packoffset(c74.z); + uint g_uMaxPointLightsPerTile : packoffset(c75); + uint g_uMaxSpotLightsPerTile : packoffset(c75.y); + uint g_uAmbientLightTotalCount : packoffset(c75.z); + float g_fAmbientEnvIntensity : packoffset(c75.w); + float g_fAmbientSkyIntensity : packoffset(c76); + float g_fAmbientLocalIntensity : packoffset(c76.y); + uint g_uPointLightTotalCount : packoffset(c76.z); + uint g_uSpotLightTotalCount : packoffset(c76.w); + uint g_uSunLightTotalCount : packoffset(c77); + uint g_uAmbientLightEnabled : packoffset(c77.y); + float g_fEnvReflectionEdgeLength : packoffset(c77.z); + float g_fEnvReflectionMipCount : packoffset(c77.w); + float g_fInnerRadius : packoffset(c78); + float g_fOuterRadius : packoffset(c78.y); + float g_fFadeout : packoffset(c78.z); + float4 g_vPlayerViewPosition : packoffset(c79); + float4 g_vPlayerWorldPosition : packoffset(c80); + float3 g_vDistortionUpInView : packoffset(c81); + float3 g_vDistortionUpInWorld : packoffset(c82); + float4x4 g_mViewToGeomDistortionViewClip : packoffset(c83); + float4x4 g_mWorldToGeomDistortionViewClip : packoffset(c87); + float g_fFlakeSpawnThreshold : packoffset(c91); + float g_fFlakeSpawnProbability : packoffset(c91.y); + float g_fParticleLifetime : packoffset(c91.z); + float g_fParticleLifetimeDeviation : packoffset(c91.w); + float3 g_vParticleVelocity : packoffset(c92); + float g_fParticleSpeedDeviation : packoffset(c92.w); + float g_fParticleDirectionDeviation : packoffset(c93); + float3 g_vParticleDirectionDeviationScale : packoffset(c93.y); + float g_fParticleEmissionFrequency : packoffset(c94); + uint4 g_vRandomInts : packoffset(c95); + float2 g_vHalfResolutionJitter : packoffset(c96); + float g_fInvEnvironmentMapsPerRow : packoffset(c96.z); + float g_fEnvironmentMapsPerRow : packoffset(c96.w); + float g_fEnvironmentMapColSize : packoffset(c97); + float g_fEnvironmentMapRowSize : packoffset(c97.y); + float2 g_fInvEnvironmentMapAtlasSize : packoffset(c97.z); + uint4 g_vVolumeLightDimensions : packoffset(c98); + float4 g_vVolumeLightProjectionConstants : packoffset(c99); + float4 g_vHalfResVolumeLightProjectionConstants : packoffset(c100); + float3 g_vOnePerVolumeLightDimensions : packoffset(c101); + float2 g_vVolumeLightXYToTileXY : packoffset(c102); + float3 g_vVolumeLightDepthResolve : packoffset(c103); + float g_fVolumeLightOnePerDepthMinusOne : packoffset(c103.w); + float3 g_vVolumeLightNearSplit0Far : packoffset(c104); + float2 g_vVolumeLightSchlickPhaseConstants : packoffset(c105); + float g_fVolumeLightKernelWidth : packoffset(c105.z); + float g_fOnePerTranslucencyKernelCount : packoffset(c105.w); + float4 g_vTessellation_Density_MaxEdge_MinDst_MaxDst : packoffset(c106); + float4x4 g_mTessellationWorldToClip : packoffset(c107); + float3 g_fTessellationViewPosition : packoffset(c111); + float3 g_fTessellationViewDirection : packoffset(c112); + float g_fTessellationViewToClip11 : packoffset(c112.w); + float g_fVignetteExp : packoffset(c113); + float g_fTonemapKeyValue : packoffset(c113.y); + float g_fTonemapGamma : packoffset(c113.z); + float g_fTonemapSaturation : packoffset(c113.w); + float3 g_vTonemapColorBalanceShadows : packoffset(c114); + float3 g_vTonemapColorBalanceHighlights : packoffset(c115); + float2 g_vTonemapLevels : packoffset(c116); + float g_fTonemapNoiseIntensity : packoffset(c116.z); + int2 g_vTonemapNoiseOffset : packoffset(c117); + float2 g_vTonemapChromaticAberration : packoffset(c117.z); + float g_fTonemapBrightness : packoffset(c118); + bool g_bUseWBOIT : packoffset(c118.y); + float2 g_vViewportRes : packoffset(c118.z); + float2 g_vInvViewportRes : packoffset(c119); + float2 g_vViewportOffset : packoffset(c119.z); + float2 g_vShadowMapRes : packoffset(c120); + float2 g_vShadowMapVSMRes : packoffset(c120.z); + float2 g_vJitterOffset : packoffset(c121); + int2 g_vSnapOffset : packoffset(c121.z); + float g_fGIVolumeIntensity : packoffset(c122); + float4 g_vScreenToView : packoffset(c123); + float4x4 g_mViewToPreviousScreen : packoffset(c124); + float g_fViewVolumeFilterTemporalWeight : packoffset(c128); + float g_fViewVolumeOpticalThickness : packoffset(c128.y); + float3 g_vViewVolumeParticipatingMediaColor : packoffset(c129); + float g_fViewVolumeDebugDepth : packoffset(c129.w); + float3 g_fViewVolumeDebugDirection : packoffset(c130); + float3 g_fViewVolumeDebugPosition : packoffset(c131); + float3 g_vSunDirVS : packoffset(c132); + float3 g_vSunRightVS : packoffset(c133); + float3 g_vSunUpVS : packoffset(c134); + float3 g_vSunColor : packoffset(c135); +} + +cbuffer skydome : register(b1) +{ + float g_fSkydomeIntensity : packoffset(c0); + float4 g_vSkydomeMultiplier : packoffset(c1); +} + +SamplerState g_sLinearClamp_s : register(s0); +SamplerState g_sAmbientSHTerms_s : register(s1); +SamplerState g_sSkydomeMap_s : register(s2); +Texture2D g_sSkydomeMap : register(t0); +Texture2D g_sAmbientSHTerms : register(t1); +Texture3D g_tParticipatingMedia : register(t2); + +#define cmp + +void main( + float4 v0 : TEXCOORD0, + float2 v1 : TEXCOORD1, + float4 v2 : SV_Position0, + out float4 o0 : SV_Target0) +{ + float4 r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13; + r0.xyzw = g_sSkydomeMap.Sample(g_sSkydomeMap_s, v1.xy).xyzw; + r0.xyzw = g_vSkydomeMultiplier.xyzw * r0.xyzw; + r0.xyzw = g_fSkydomeIntensity * r0.xyzw; + r0.xyzw = g_fAmbientSkyIntensity * r0.xyzw; + +#if 1 // Luma: make sky brighter, it was overly dim + float normalizationPoint = 0.001; // Found empyrically + float fakeHDRIntensity = 0.15; + r0.xyz = FakeHDR(r0.xyz * 1, normalizationPoint, fakeHDRIntensity) / 1; +#endif + + r1.x = dot(v0.xyz, v0.xyz); + r1.x = sqrt(r1.x); + r1.x = -g_vVolumeLightNearSplit0Far.z + r1.x; + r1.x = max(0, r1.x); + r1.y = cmp(r1.x != 0.000000); + if (r1.y != 0) { + r1.yzw = g_mViewToWorld._m01_m21_m11 * v0.yyy; + r1.yzw = g_mViewToWorld._m00_m20_m10 * v0.xxx + r1.yzw; + r1.yzw = g_mViewToWorld._m02_m22_m12 * v0.zzz + r1.yzw; + r1.yzw = g_mViewToWorld._m03_m23_m13 + r1.yzw; + r1.yzw = -g_vViewPoint.xzy + r1.yzw; + r2.x = dot(r1.yzw, r1.yzw); + r2.x = rsqrt(r2.x); + r1.yzw = r2.xxx * r1.yzw; + r2.x = g_vVolumeLightNearSplit0Far.z * r1.w + g_vViewPoint.y; + r2.x = -g_fFogGroundHeight + r2.x; + r2.x = -g_fFogGroundFalloff * r2.x; + r2.x = 1.44269502 * r2.x; + r2.x = exp2(r2.x); + r2.y = g_fFogGroundFalloff * r1.w; + r2.xy = r2.xy * r1.xx; + r2.z = cmp(0.00999999978 < abs(r2.y)); + r2.w = -1.44269502 * r2.y; + r2.w = exp2(r2.w); + r2.w = 1 + -r2.w; + r2.y = r2.w / r2.y; + r2.y = r2.x * r2.y; + r2.x = r2.z ? r2.y : r2.x; + r2.x = g_fFogGroundDensity * r2.x; + r2.x = max(0, r2.x); + r2.yzw = g_atmosphere.vFog.xxx * g_atmosphere.vExtinction.xyz; + r2.xyz = r2.yzw * r1.xxx + r2.xxx; + r1.x = dot(g_atmosphere.vSunDir.xzy, r1.yzw); + r2.w = r1.x * r1.x + 1; + r2.w = 0.0596830994 * r2.w; + r1.x = g_atmosphere.vSchlickConstants.y * r1.x + 1; + r1.x = r1.x * r1.x; + r1.x = g_atmosphere.vSchlickConstants.x / r1.x; + r3.xyz = g_atmosphere.vMie.xyz * r1.xxx; + r3.xyz = g_atmosphere.vRayleigh.xyz * r2.www + r3.xyz; + r3.xyz = g_atmosphere.vSunE.xyz * r3.xyz; + if (g_uAmbientLightEnabled != 0) { + r1.x = 1 + g_atmosphere.vSchlickConstants.y; + r1.x = r1.x * r1.x; + r1.x = g_atmosphere.vSchlickConstants.x / r1.x; + r4.xyz = g_atmosphere.vMie.xyz * r1.xxx; + r4.xyz = g_atmosphere.vRayleigh.xyz * float3(0.119366206,0.119366206,0.119366206) + r4.xyz; + r5.xyz = g_sAmbientSHTerms.SampleLevel(g_sAmbientSHTerms_s, float2(0.0555559993,0), 0).xyz; + r6.xyz = g_sAmbientSHTerms.SampleLevel(g_sAmbientSHTerms_s, float2(0.166666999,0), 0).xyz; + r7.xyz = g_sAmbientSHTerms.SampleLevel(g_sAmbientSHTerms_s, float2(0.277778,0), 0).xyz; + r8.xyz = g_sAmbientSHTerms.SampleLevel(g_sAmbientSHTerms_s, float2(0.388889015,0), 0).xyz; + r9.xyz = g_sAmbientSHTerms.SampleLevel(g_sAmbientSHTerms_s, float2(0.5,0), 0).xyz; + r10.xyz = g_sAmbientSHTerms.SampleLevel(g_sAmbientSHTerms_s, float2(0.611110985,0), 0).xyz; + r11.xyz = g_sAmbientSHTerms.SampleLevel(g_sAmbientSHTerms_s, float2(0.722221971,0), 0).xyz; + r12.xyz = g_sAmbientSHTerms.SampleLevel(g_sAmbientSHTerms_s, float2(0.833333015,0), 0).xyz; + r13.xyz = g_sAmbientSHTerms.SampleLevel(g_sAmbientSHTerms_s, float2(0.944444001,0), 0).xyz; + r8.xyz = r8.xyz * -r1.zzz; + r6.xyz = r6.xyz * -r1.yyy + r8.xyz; + r6.xyz = r7.xyz * -r1.www + r6.xyz; + r6.xyz = float3(0.488602519,0.488602519,0.488602519) * r6.xyz; + r5.xyz = r5.xyz * float3(0.282094777,0.282094777,0.282094777) + r6.xyz; + r6.xyz = r11.xyz * -r1.yyy; + r7.xyz = r9.xyz * -r1.yyy; + r7.xyz = r7.xyz * -r1.www; + r6.xyz = r6.xyz * -r1.zzz + r7.xyz; + r7.xyz = r10.xyz * -r1.zzz; + r6.xyz = r7.xyz * -r1.www + r6.xyz; + r5.xyz = r6.xyz * float3(1.09254801,1.09254801,1.09254801) + r5.xyz; + r6.xyz = float3(0.546274006,0.546274006,0.546274006) * r13.xyz; + r1.xz = r1.zw * r1.zw; + r1.x = r1.y * r1.y + -r1.x; + r1.xyw = r6.xyz * r1.xxx + r5.xyz; + r5.xyz = float3(0.315391988,0.315391988,0.315391988) * r12.xyz; + r1.z = r1.z * 3 + -1; + r1.xyz = r5.xyz * r1.zzz + r1.xyw; + r3.xyz = r4.xyz * r1.xyz + r3.xyz; + } + r1.xyz = float3(-1.44269502,-1.44269502,-1.44269502) * r2.xyz; + r1.xyz = exp2(r1.xyz); + r2.xyz = float3(1,1,1) + -r1.xyz; + r2.xyz = r2.xyz * r3.xyz; + } else { + r1.xyz = float3(1,1,1); + r2.xyz = float3(0,0,0); + } + r0.xyz = r0.xyz * r1.xyz + r2.xyz; + r1.x = v0.z / v0.w; + r2.xy = g_vInvScreenRes.xy * v2.xy; + r1.y = cmp(g_vVolumeLightNearSplit0Far.y < r1.x); + r1.x = log2(r1.x); + r1.x = -r1.x * 0.693147182 + g_vVolumeLightDepthResolve.y; + r1.x = g_vVolumeLightDepthResolve.x * r1.x; + r1.x = r1.y ? r1.x : 0; + r1.x = 0.5 + r1.x; + r2.z = r1.x / (float)g_vVolumeLightDimensions.z; + r1.xyzw = g_tParticipatingMedia.Sample(g_sLinearClamp_s, r2.xyz).xyzw; + o0.xyz = r0.xyz * r1.www + r1.xyz; + o0.w = r0.w; +} \ No newline at end of file From 7501998923ee1543af6f99b357b38eb99bd9e1d2 Mon Sep 17 00:00:00 2001 From: Musa Haji Date: Tue, 19 May 2026 01:11:58 -0400 Subject: [PATCH 6/6] chore(Quantum Break): fix typo --- Shaders/Quantum Break/Sky_0x0468C6D7.ps_5_0.hlsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Shaders/Quantum Break/Sky_0x0468C6D7.ps_5_0.hlsl b/Shaders/Quantum Break/Sky_0x0468C6D7.ps_5_0.hlsl index 351d7125..239a2346 100644 --- a/Shaders/Quantum Break/Sky_0x0468C6D7.ps_5_0.hlsl +++ b/Shaders/Quantum Break/Sky_0x0468C6D7.ps_5_0.hlsl @@ -169,7 +169,7 @@ void main( r0.xyzw = g_fAmbientSkyIntensity * r0.xyzw; #if 1 // Luma: make sky brighter, it was overly dim - float normalizationPoint = 0.001; // Found empyrically + float normalizationPoint = 0.001; // Found empirically float fakeHDRIntensity = 0.15; r0.xyz = FakeHDR(r0.xyz * 1, normalizationPoint, fakeHDRIntensity) / 1; #endif