diff --git a/package/Shaders/Common/FoveatedMask.hlsli b/package/Shaders/Common/FoveatedMask.hlsli new file mode 100644 index 0000000000..d82a141aa8 --- /dev/null +++ b/package/Shaders/Common/FoveatedMask.hlsli @@ -0,0 +1,84 @@ +#ifndef FOVEATED_MASK_HLSLI +#define FOVEATED_MASK_HLSLI + +// Superellipse (shape power 4 = squircle) foveation mask: a smooth center->periphery weight that +// avoids the hard seam of a rect mask. CPU mirror of the clamping is in src/Features/FoveatedCommon.h. + +#ifndef FOVEATED_CENTER_SCALE_MIN +# define FOVEATED_CENTER_SCALE_MIN 0.25 +#endif +#ifndef FOVEATED_CENTER_SCALE_MAX +# define FOVEATED_CENTER_SCALE_MAX 1.0 +#endif +#ifndef FOVEATED_CENTER_FEATHER_MIN +# define FOVEATED_CENTER_FEATHER_MIN 1e-4 +#endif +#ifndef FOVEATED_CENTER_HORIZONTAL_SCALE_MIN +# define FOVEATED_CENTER_HORIZONTAL_SCALE_MIN 1.0 +#endif +#ifndef FOVEATED_CENTER_HORIZONTAL_SCALE_MAX +# define FOVEATED_CENTER_HORIZONTAL_SCALE_MAX 2.0 +#endif +#ifndef FOVEATED_MASK_SHAPE_POWER +# define FOVEATED_MASK_SHAPE_POWER 4 +#endif + +float FoveatedClampCenterScale(float centerScale) +{ + return clamp(centerScale, FOVEATED_CENTER_SCALE_MIN, FOVEATED_CENTER_SCALE_MAX); +} + +float2 FoveatedComputeCenterUV(float2 centerOffset) +{ + return clamp(float2(0.5, 0.5) + centerOffset, float2(0.0, 0.0), float2(1.0, 1.0)); +} + +float FoveatedClampCenterHorizontalScale(float centerHorizontalScale) +{ + return clamp(centerHorizontalScale, FOVEATED_CENTER_HORIZONTAL_SCALE_MIN, FOVEATED_CENTER_HORIZONTAL_SCALE_MAX); +} + +float2 FoveatedComputeMaskRadii(float centerScale, float centerHorizontalScale) +{ + float clampedCenterScale = FoveatedClampCenterScale(centerScale); + float clampedCenterHorizontalScale = FoveatedClampCenterHorizontalScale(centerHorizontalScale); + float2 radii = float2(clampedCenterScale * clampedCenterHorizontalScale * 0.5, clampedCenterScale * 0.5); + return max(radii, FOVEATED_CENTER_FEATHER_MIN.xx); +} + +float FoveatedComputeNormalizedFeather(float centerScale, float centerFeather, float centerHorizontalScale) +{ + float2 radii = FoveatedComputeMaskRadii(centerScale, centerHorizontalScale); + float baseRadius = max(min(radii.x, radii.y), FOVEATED_CENTER_FEATHER_MIN); + return max(centerFeather, FOVEATED_CENTER_FEATHER_MIN) / baseRadius; +} + +float FoveatedComputeMaskDistance(float2 eyeUv, float centerScale, float centerHorizontalScale, float2 centerOffset) +{ + float2 radii = FoveatedComputeMaskRadii(centerScale, centerHorizontalScale); + float2 centerUv = FoveatedComputeCenterUV(centerOffset); + float2 normalized = (eyeUv - centerUv) / radii; + float2 absoluteNormalized = abs(normalized); +#if FOVEATED_MASK_SHAPE_POWER == 4 + // Squircle fast path: pow(t,4) == (t*t)^2 and pow(sum,1/4) == sqrt(sqrt(sum)), + // so the default shape avoids three transcendental pow() calls per pixel in + // the SSR path that is explicitly trying to save GPU time. + float2 sq = absoluteNormalized * absoluteNormalized; + float pNorm = sq.x * sq.x + sq.y * sq.y; + return sqrt(sqrt(max(pNorm, 0.0))); +#else + float shapePower = max((float)FOVEATED_MASK_SHAPE_POWER, 1.0); + float invShapePower = 1.0 / shapePower; + float pNorm = pow(absoluteNormalized.x, shapePower) + pow(absoluteNormalized.y, shapePower); + return pow(max(pNorm, 0.0), invShapePower); +#endif +} + +float FoveatedComputeCenterBlendWeight(float2 eyeUv, float centerScale, float centerFeather, float centerHorizontalScale, float2 centerOffset) +{ + float normalizedFeather = FoveatedComputeNormalizedFeather(centerScale, centerFeather, centerHorizontalScale); + float maskDistance = FoveatedComputeMaskDistance(eyeUv, centerScale, centerHorizontalScale, centerOffset); + return 1.0 - smoothstep(1.0, 1.0 + normalizedFeather, maskDistance); +} + +#endif diff --git a/package/Shaders/Common/FoveatedShaderDetail.hlsli b/package/Shaders/Common/FoveatedShaderDetail.hlsli new file mode 100644 index 0000000000..e92bdb3ac1 --- /dev/null +++ b/package/Shaders/Common/FoveatedShaderDetail.hlsli @@ -0,0 +1,32 @@ +#ifndef FOVEATED_SHADER_DETAIL_HLSLI +#define FOVEATED_SHADER_DETAIL_HLSLI + +// Per-pixel detail weight for foveated effects: pass the mode (0=off/1=feathered/2=hard, see +// FoveatedCommon::GetShaderMode) + mask params, get a 0..1 weight (0 outside the mask = skip). + +#include "Common/FoveatedMask.hlsli" + +static const float FOVEATED_SHADER_DETAIL_MODE_FEATHERED = 1.0; +static const float FOVEATED_SHADER_DETAIL_MODE_HARD_CUTOFF = 2.0; + +float FoveatedEvaluateShaderDetailWeight(float mode, float2 eyeUv, float centerScale, float centerFeather, float centerHorizontalScale, float2 centerOffset) +{ + bool detailModeEnabled = mode >= FOVEATED_SHADER_DETAIL_MODE_FEATHERED; + if (!detailModeEnabled) + return 1.0f; + + bool hardCutoffMode = mode >= FOVEATED_SHADER_DETAIL_MODE_HARD_CUTOFF; + if (hardCutoffMode) { + float edgeDistance = FoveatedComputeMaskDistance(eyeUv, centerScale, centerHorizontalScale, centerOffset); + return edgeDistance > 1.0f ? 0.0f : 1.0f; + } + + return FoveatedComputeCenterBlendWeight(eyeUv, centerScale, centerFeather, centerHorizontalScale, centerOffset); +} + +bool FoveatedIsShaderDetailActive(float detailWeight) +{ + return detailWeight > 0.0001f; +} + +#endif diff --git a/package/Shaders/Common/SharedData.hlsli b/package/Shaders/Common/SharedData.hlsli index 78df0422fd..b33cd6aba0 100644 --- a/package/Shaders/Common/SharedData.hlsli +++ b/package/Shaders/Common/SharedData.hlsli @@ -30,6 +30,8 @@ namespace SharedData float4 AmbientSHR; float4 AmbientSHG; float4 AmbientSHB; + float4 VRFoveationData0; // x=center coverage scale, y=feather, z=horizontal scale, w=SSR raymarch mode (0 off, 1 feathered, 2 hard cutoff) + float4 VRFoveationCenterOffsets; // xy=left eye center offset, zw=right eye center offset float4 HDRData; }; diff --git a/package/Shaders/ISReflectionsRayTracing.hlsl b/package/Shaders/ISReflectionsRayTracing.hlsl index 0f9c45d397..3d4986ca5f 100644 --- a/package/Shaders/ISReflectionsRayTracing.hlsl +++ b/package/Shaders/ISReflectionsRayTracing.hlsl @@ -33,6 +33,40 @@ static const int binaryIterations = ceil(log2(iterations)); static const float rayLength = 1.0; +# if defined(VR) +# include "Common/FoveatedShaderDetail.hlsli" + +static const int minFoveatedIterations = 16; + +// Per-pixel SSR foveation weight from the active foveation mask (VRFoveationData0 +// + per-eye center offset). 1 in the center, falling to 0 in the periphery. +float GetVRSSRFoveationWeight(float ssrFoveationMode, float2 eyeUv, uint eyeIndex) +{ + float2 centerOffset = eyeIndex == 0 ? SharedData::VRFoveationCenterOffsets.xy : SharedData::VRFoveationCenterOffsets.zw; + return FoveatedEvaluateShaderDetailWeight( + ssrFoveationMode, + eyeUv, + SharedData::VRFoveationData0.x, + SharedData::VRFoveationData0.y, + SharedData::VRFoveationData0.z, + centerOffset); +} + +// Scale the raymarch count by the foveation weight: full in the center, down to +// minFoveatedIterations toward the periphery. +int GetSSRRaymarchIterations(float foveationWeight) +{ + int iterationCount = (int)ceil(lerp((float)minFoveatedIterations, (float)iterations, saturate(foveationWeight))); + return min(max(iterationCount, minFoveatedIterations), iterations); +} + +int GetSSRBinaryIterations(int raymarchIterations) +{ + int iterationCount = (int)ceil(log2((float)raymarchIterations)); + return min(max(iterationCount, 1), binaryIterations); +} +# endif + float2 ConvertRaySample(float2 raySample, uint eyeIndex) { return FrameBuffer::GetDynamicResolutionAdjustedScreenPosition(Stereo::ConvertToStereoUV(raySample, eyeIndex)); @@ -46,14 +80,34 @@ float2 ConvertRaySamplePrevious(float2 raySample, uint eyeIndex) float4 GetReflectionColor( float3 projReflectionDirection, float3 projPosition, - uint eyeIndex) + uint eyeIndex +# if defined(VR) + , + int raymarchIterations, + int binaryIterationsCount, + float foveationWeight +# endif +) { float3 prevRaySample; float3 raySample = projPosition; - for (int i = 0; i < iterations; i++) { + // VR scales the raymarch/binary counts by the foveation weight and fades the result; non-VR uses + // full counts. Bounds are runtime in VR, so [loop] is required (cannot unroll). +# if defined(VR) + int rayCount = raymarchIterations; + int binCount = binaryIterationsCount; + float fovWeight = foveationWeight; + [loop] for (int i = 0; i < rayCount; i++) + { +# else + int rayCount = iterations; + int binCount = binaryIterations; + float fovWeight = 1.0; + for (int i = 0; i < rayCount; i++) { +# endif prevRaySample = raySample; - raySample = projPosition + (float(i) / float(iterations)) * projReflectionDirection; + raySample = projPosition + (float(i) / float(rayCount)) * projReflectionDirection; float2 sampleUV; uint sampleEyeIndex; @@ -71,7 +125,12 @@ float4 GetReflectionColor( float depthThicknessFactor; uint hitEyeIndex = sampleEyeIndex; - for (int k = 0; k < binaryIterations; k++) { +# if defined(VR) + [loop] for (int k = 0; k < binCount; k++) + { +# else + for (int k = 0; k < binCount; k++) { +# endif binaryRaySample = lerp(binaryMinRaySample, binaryMaxRaySample, 0.5); Stereo::ResolveMonoUVForEye(binaryRaySample, eyeIndex, sampleUV, hitEyeIndex); @@ -134,7 +193,7 @@ float4 GetReflectionColor( alpha = float4(AlphaTex.SampleLevel(AlphaSampler, ConvertRaySamplePrevious(reprojectedRaySample.xy, finalEyeIndex), 0).xyz, 1.0); float3 reflectionColor = color + SSRParams.z * alpha.xyz * alpha.w; - return float4(reflectionColor, fadeFactor); + return float4(reflectionColor, fadeFactor * fovWeight); } return 0.0; @@ -160,6 +219,18 @@ PS_OUTPUT main(PS_INPUT input) uv = Stereo::ConvertFromStereoUV(uv, eyeIndex); +# if defined(VR) + float ssrFoveationWeight = 1.0; + float ssrFoveationMode = SharedData::VRFoveationData0.w; + [branch] if (ssrFoveationMode >= FOVEATED_SHADER_DETAIL_MODE_FEATHERED) + { + ssrFoveationWeight = GetVRSSRFoveationWeight(ssrFoveationMode, uv, eyeIndex); + // Outside the foveation mask: skip SSR entirely. The cubemap/water + // reflection fallback already covers these pixels. + [branch] if (!FoveatedIsShaderDetailActive(ssrFoveationWeight)) return psout; + } +# endif + [branch] if (NormalTex.Sample(NormalSampler, screenPosition).z <= 0) { return psout; @@ -191,7 +262,18 @@ PS_OUTPUT main(PS_INPUT input) float3 projPosition = float3(uv, depth); float3 projReflectionDirection = normalize(projReflectionPosition.xyz - projPosition) * rayLength; +# if defined(VR) + int raymarchIterations = iterations; + int binaryIterationsCount = binaryIterations; + [branch] if (ssrFoveationWeight < 0.9999) + { + raymarchIterations = GetSSRRaymarchIterations(ssrFoveationWeight); + binaryIterationsCount = GetSSRBinaryIterations(raymarchIterations); + } + psout.Color = GetReflectionColor(projReflectionDirection, projPosition, eyeIndex, raymarchIterations, binaryIterationsCount, ssrFoveationWeight); +# else psout.Color = GetReflectionColor(projReflectionDirection, projPosition, eyeIndex); +# endif return psout; } diff --git a/src/Features/FoveatedCommon.h b/src/Features/FoveatedCommon.h new file mode 100644 index 0000000000..7832b1fd59 --- /dev/null +++ b/src/Features/FoveatedCommon.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include + +// Shared CPU-side helpers for VR foveated shader detail; GPU mirror in FoveatedMask.hlsli, with +// GetShaderMode's 0/1/2 encoding as the contract. Only SSR-consumed pieces exist (compute helpers omitted). +namespace FoveatedCommon +{ + constexpr float kCenterScaleMin = 0.25f; + constexpr float kCenterScaleMax = 1.0f; + constexpr float kCenterFeather = 0.05f; + constexpr float kCenterHorizontalScaleMin = 1.0f; + constexpr float kCenterHorizontalScaleMax = 2.0f; + constexpr float kFullCoverageThreshold = 0.999f; + + enum class DetailMode + { + Off, + Feathered, + HardCutoff + }; + + constexpr DetailMode GetDetailMode(bool a_enabled, bool a_hardCutoff) + { + if (!a_enabled) + return DetailMode::Off; + return a_hardCutoff ? DetailMode::HardCutoff : DetailMode::Feathered; + } + + // 0 = off, 1 = feathered, 2 = hard cutoff. Must match the + // FOVEATED_SHADER_DETAIL_MODE_* constants in FoveatedShaderDetail.hlsli. + constexpr float GetShaderMode(DetailMode a_mode) + { + switch (a_mode) { + case DetailMode::Feathered: + return 1.0f; + case DetailMode::HardCutoff: + return 2.0f; + case DetailMode::Off: + default: + return 0.0f; + } + } + + inline float ClampCenterScale(float a_value) + { + if (!std::isfinite(a_value)) + return kCenterScaleMax; + return std::clamp(a_value, kCenterScaleMin, kCenterScaleMax); + } + + // A near-full center means foveation does nothing useful — callers skip the + // per-pixel mask in that case to avoid paying its cost for no saving. + inline bool IsActiveCoverage(float a_centerScale) + { + return ClampCenterScale(a_centerScale) < kFullCoverageThreshold; + } + + inline float ClampCenterHorizontalScale(float a_value) + { + if (!std::isfinite(a_value)) + return 1.0f; + return std::clamp(a_value, kCenterHorizontalScaleMin, kCenterHorizontalScaleMax); + } +} diff --git a/src/Features/Upscaling/FoveatedRender.cpp b/src/Features/Upscaling/FoveatedRender.cpp index 8641521fa4..e8f0265e62 100644 --- a/src/Features/Upscaling/FoveatedRender.cpp +++ b/src/Features/Upscaling/FoveatedRender.cpp @@ -3,6 +3,7 @@ #include "../../Globals.h" #include "../../Utils/Subrect.h" #include "../../Utils/UI.h" +#include "../FoveatedCommon.h" #include "../Upscaling.h" #include "FoveatedRender/Core.h" @@ -118,6 +119,40 @@ bool FoveatedRender::IsRuntimeSupported() const return globals::game::isVR && globals::features::upscaling.streamline.featureDLSS; } +FoveatedRender::FoveationProfile FoveatedRender::GetFoveationProfile() const +{ + FoveationProfile profile; + if (!IsActive()) + return profile; + + const auto& leftUV = subrectController.GetUV(); + const auto& rightUV = subrectController.GetRightEyeUV(); + + // Map the rectangular subrect onto the centered superellipse the mask helper expects: vertical + // extent drives coverageScale (radiusY = coverageScale/2), the rect aspect drives the horizontal + // stretch (radiusX = coverageScale * hScale/2). The mask carries one scale for both eyes (only + // the center offset is per-eye), so size comes from the less-foveated (larger) extent of the two: + // the center is the superset enclosing both eyes' full-quality regions, so neither eye's sharp + // zone is ever foveated (min would shrink it below an eye's sharp region and foveate it). A + // full eye therefore yields full coverage and disables foveation (the gate below). + const float coverageH = std::max(leftUV.h, rightUV.h); + const float coverageW = std::max(leftUV.w, rightUV.w); + const float coverageScale = FoveatedCommon::ClampCenterScale(coverageH); + + // Availability keys off the clamped scale: if the larger eye rounds up to full coverage there is + // nothing to foveate, leave the default (available == false). + if (!FoveatedCommon::IsActiveCoverage(coverageScale)) + return profile; + + profile.available = true; + profile.coverageScale = coverageScale; + profile.centerHorizontalScale = FoveatedCommon::ClampCenterHorizontalScale( + coverageH > 1e-4f ? coverageW / coverageH : 1.0f); + profile.centerOffsets[0] = { (leftUV.x + leftUV.w * 0.5f) - 0.5f, (leftUV.y + leftUV.h * 0.5f) - 0.5f }; + profile.centerOffsets[1] = { (rightUV.x + rightUV.w * 0.5f) - 0.5f, (rightUV.y + rightUV.h * 0.5f) - 0.5f }; + return profile; +} + void FoveatedRender::LatchQualityMode() { qualityModeAtBoot = std::clamp(globals::features::upscaling.settings.qualityMode, 1u, 4u); diff --git a/src/Features/Upscaling/FoveatedRender.h b/src/Features/Upscaling/FoveatedRender.h index 9adb915705..2d249411a4 100644 --- a/src/Features/Upscaling/FoveatedRender.h +++ b/src/Features/Upscaling/FoveatedRender.h @@ -97,6 +97,17 @@ struct FoveatedRender bool IsActive() const; bool IsLoaded() const { return enabledAtBoot; } + // Foveation region for per-pixel foveated effects (e.g. SSR): the rectangular DLSS subrect mapped + // to centered-superellipse params. available is false when foveation is inactive or full-eye. + struct FoveationProfile + { + bool available = false; + float coverageScale = 1.0f; // linear center coverage scale [0.25, 1.0] + float centerHorizontalScale = 1.0f; // [1.0, 2.0] + float2 centerOffsets[2] = {}; // [0]=left eye, [1]=right eye + }; + FoveationProfile GetFoveationProfile() const; + // Main enable: latched at boot, change requires restart void LatchEnabled() { enabledAtBoot = (settings.enabled != 0); } diff --git a/src/Features/VR.cpp b/src/Features/VR.cpp index 49a5f099f3..13d2111d0d 100644 --- a/src/Features/VR.cpp +++ b/src/Features/VR.cpp @@ -46,7 +46,9 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( StereoBlendDepthSigma, StereoBlendMaxFactor, StereoBlendColorThreshold, - StereoBlendDebugMode) + StereoBlendDebugMode, + EnableSSRFoveation, + EnableSSRFoveationHardCutoff) //============================================================================= // FEATURE BASE CLASS OVERRIDES diff --git a/src/Features/VR.h b/src/Features/VR.h index e910a38db8..a8a789190e 100644 --- a/src/Features/VR.h +++ b/src/Features/VR.h @@ -178,6 +178,11 @@ struct VR : OverlayFeature float StereoBlendColorThreshold = 0.02f; ///< Minimum color difference to trigger blending (luminance) int StereoBlendDebugMode = 0; ///< 0=off, 1=back-check, 2=blend weight, 3=edge detection, 4=overwrite, 5=overwrite Eye1 + // VR foveated shader detail: render expensive screen-space effects at reduced detail in the + // periphery, driven by the active Foveated DLSS region. Consumed by foveated SSR. + bool EnableSSRFoveation = false; ///< Foveate screen-space reflection raymarching in the periphery + bool EnableSSRFoveationHardCutoff = false; ///< Hard-skip SSR outside the center (vs feathered falloff) + // VR Menu Overlay positioning settings float VRMenuScale = Config::kDefaultMenuScale; ///< Scale factor for overlay UI (0.5-2.0) int VRMenuPositioningMethod = 1; ///< 0 = HMD relative, 1 = Fixed world position diff --git a/src/Features/VR/SettingsUI.cpp b/src/Features/VR/SettingsUI.cpp index b237379377..58f87649e0 100644 --- a/src/Features/VR/SettingsUI.cpp +++ b/src/Features/VR/SettingsUI.cpp @@ -2,6 +2,7 @@ #include "Features/DynamicCubemaps.h" #include "Features/ScreenSpaceGI.h" #include "Features/ScreenSpaceShadows.h" +#include "Features/Upscaling.h" #include "Features/VR.h" #include "Menu.h" #include "Menu/Fonts.h" @@ -368,6 +369,37 @@ namespace "Overwrite Eye1: POM depth heatmap for Eye 1 (auto-enables Reprojection -- restart required)"); } } + + if (ImGui::CollapsingHeader("Foveated Effects")) { + auto& upscaling = globals::features::upscaling; + auto& dynamicCubemaps = globals::features::dynamicCubemaps; + const bool foveatedDLSSActive = upscaling.foveatedRender.IsActive(); + const bool ssrEnabled = dynamicCubemaps.loaded && dynamicCubemaps.settings.EnabledSSR; + + if (!foveatedDLSSActive) + ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.3f, 1.0f), "Requires Foveated DLSS to be active (Upscaling settings)."); + if (!ssrEnabled) + ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.3f, 1.0f), "Requires Screen Space Reflections (Dynamic Cubemaps)."); + + ImGui::BeginDisabled(!foveatedDLSSActive || !ssrEnabled); + ImGui::Checkbox("Foveate SSR Raymarching", &settings.EnableSSRFoveation); + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Reduces screen-space reflection raymarching toward the periphery, using the\n" + "active Foveated DLSS region. Central reflections stay full quality; peripheral\n" + "pixels fall back to the cubemap / water reflection. VR only."); + } + + ImGui::BeginDisabled(!settings.EnableSSRFoveation); + ImGui::Checkbox("Hard Cutoff Outside Center##SSRFoveation", &settings.EnableSSRFoveationHardCutoff); + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Hard-skip SSR outside the center region instead of a feathered falloff.\n" + "Cheaper, but the transition edge may be visible. Default off (feathered)."); + } + ImGui::EndDisabled(); + ImGui::EndDisabled(); + } } void DrawGeneralVRSettings() diff --git a/src/State.cpp b/src/State.cpp index 48d184ad89..82342b5824 100644 --- a/src/State.cpp +++ b/src/State.cpp @@ -7,12 +7,15 @@ #include "Deferred.h" #include "FeatureIssues.h" #include "Features/CloudShadows.h" +#include "Features/DynamicCubemaps.h" +#include "Features/FoveatedCommon.h" #include "Features/HDRDisplay.h" #include "Features/InteriorSun.h" #include "Features/PerformanceOverlay.h" #include "Features/TerrainBlending.h" #include "Features/TerrainHelper.h" #include "Features/Upscaling.h" +#include "Features/VR.h" #include "Features/VRStereoOptimizations.h" #include "Features/VolumetricShadows.h" #include "Features/WeatherEditor.h" @@ -1008,6 +1011,31 @@ void State::UpdateSharedData([[maybe_unused]] bool a_inWorld, [[maybe_unused]] b data.HDRData = globals::features::hdrDisplay.GetSharedDataHDR(); + // VR foveated shader detail (consumed by foveated SSR). Default to off; populate from the + // active foveation region only when SSR foveation is enabled and SSR is actually running. + data.VRFoveationData0 = { FoveatedCommon::kCenterScaleMax, FoveatedCommon::kCenterFeather, 1.0f, + FoveatedCommon::GetShaderMode(FoveatedCommon::DetailMode::Off) }; + data.VRFoveationCenterOffsets = { 0.0f, 0.0f, 0.0f, 0.0f }; + if (globals::game::isVR) { + const auto& vr = globals::features::vr; + const auto& dynamicCubemaps = globals::features::dynamicCubemaps; + const bool ssrFoveationEnabled = vr.loaded && vr.settings.EnableSSRFoveation && + dynamicCubemaps.loaded && dynamicCubemaps.settings.EnabledSSR; + if (ssrFoveationEnabled) { + const auto profile = upscaling.foveatedRender.GetFoveationProfile(); + if (profile.available) { // available already implies an active (non-full) coverage scale + const float ssrMode = FoveatedCommon::GetShaderMode(FoveatedCommon::GetDetailMode( + true, vr.settings.EnableSSRFoveationHardCutoff)); + data.VRFoveationData0 = { profile.coverageScale, FoveatedCommon::kCenterFeather, + profile.centerHorizontalScale, ssrMode }; + data.VRFoveationCenterOffsets = { + profile.centerOffsets[0].x, profile.centerOffsets[0].y, + profile.centerOffsets[1].x, profile.centerOffsets[1].y + }; + } + } + } + sharedDataCB->Update(data); } diff --git a/src/State.h b/src/State.h index 5d0987e0c2..487a321307 100644 --- a/src/State.h +++ b/src/State.h @@ -256,9 +256,16 @@ class State float4 AmbientSHR; float4 AmbientSHG; float4 AmbientSHB; - float4 HDRData; // xyz + menu scene encoding in w — see HDRDisplay::GetSharedDataHDR + float4 VRFoveationData0; // x=center coverage scale, y=feather, z=horizontal scale, w=SSR raymarch mode (0 off, 1 feathered, 2 hard cutoff) + float4 VRFoveationCenterOffsets; // xy=left eye center offset, zw=right eye center offset + float4 HDRData; // xyz + menu scene encoding in w — see HDRDisplay::GetSharedDataHDR }; STATIC_ASSERT_ALIGNAS_16(SharedDataCB); + // Each float4 cbuffer field must start on a 16-byte boundary to match the HLSL SharedData + // layout — a stray scalar inserted above would silently shift these and corrupt shader reads. + static_assert(offsetof(SharedDataCB, VRFoveationData0) % 16 == 0); + static_assert(offsetof(SharedDataCB, VRFoveationCenterOffsets) % 16 == 0); + static_assert(offsetof(SharedDataCB, HDRData) % 16 == 0); ConstantBuffer* sharedDataCB = nullptr; ConstantBuffer* featureDataCB = nullptr;