Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -320,12 +320,24 @@ namespace ExtendedMaterials
# if defined(TERRAIN_VARIATION)
StochasticOffsets sharedOffset, float2 dx, float2 dy,
# endif
out float pixelOffset, out float weights[6])
out float pixelOffset,
# if defined(VR_STEREO_OPT)
out bool hasPOM,
# endif
out float weights[6])
#else
float2 GetParallaxCoords(float distance, float2 coords, float mipLevel, float3 viewDir, float3x3 tbn, float noise, Texture2D<float4> tex, SamplerState texSampler, uint channel, DisplacementParams params, out float pixelOffset)
float2 GetParallaxCoords(float distance, float2 coords, float mipLevel, float3 viewDir, float3x3 tbn, float noise, Texture2D<float4> tex, SamplerState texSampler, uint channel, DisplacementParams params, out float pixelOffset
# if defined(VR_STEREO_OPT)
,
out bool hasPOM
# endif
)
#endif
{
pixelOffset = 0.5;
pixelOffset = 0.0;
#if defined(VR_STEREO_OPT)
hasPOM = false;
#endif
float3 viewDirTS = normalize(mul(tbn, viewDir));
#if defined(LANDSCAPE)
viewDirTS.xy /= viewDirTS.z * 0.7 + 0.3 + params[0].FlattenAmount; // Fix for objects at extreme viewing angles
Expand Down Expand Up @@ -498,6 +510,9 @@ namespace ExtendedMaterials
nearBlendToFar *= nearBlendToFar;
float offset = (1.0 - parallaxAmount) * -maxHeight + minHeight;
pixelOffset = saturate(lerp(parallaxAmount, 0.5, nearBlendToFar));
#if defined(VR_STEREO_OPT)
hasPOM = true;
#endif
return lerp(viewDirTS.xy * offset + coords.xy, coords, nearBlendToFar);
}

Expand All @@ -510,7 +525,7 @@ namespace ExtendedMaterials
weights[5] = input.LandBlendWeights2.y;
#endif

pixelOffset = 0.5;
pixelOffset = 0.0;
return coords;
}

Expand Down
6 changes: 6 additions & 0 deletions package/Shaders/Common/VR.hlsli
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ cbuffer VRValues : register(b13)

namespace Stereo
{
#ifdef VR_STEREO_OPT
/// Sentinel written to PomOffsetTex when a pixel's Lighting PS did not run POM.
/// Convention: -1.0 = no POM; >= 0.0 = POM ran (StereoBlendCS detects by sign).
/// Must match kPomOffsetNoData in VRStereoOptimizations.h.
static const float POM_NO_DATA = -1.0;
#endif
/**
Converts to the eye specific uv [0,1].
In VR, texture buffers include the left and right eye in the same buffer. Flat
Expand Down
59 changes: 46 additions & 13 deletions package/Shaders/Lighting.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "Common/SharedData.hlsli"
#include "Common/Skinned.hlsli"
#include "Common/Triplanar.hlsli"
#include "Common/VR.hlsli"

#if defined(FACEGEN) || defined(FACEGEN_RGB_TINT)
# define SKIN
Expand Down Expand Up @@ -357,6 +358,13 @@ struct PS_OUTPUT

#ifdef PSHADER

# if defined(VR_STEREO_OPT) && !defined(SNOW)
// POM depth offset UAV — written per-pixel for StereoBlendCS depth-aware reprojection.
// Bound at u7 (after the 7 deferred MRT slots 0-6) via OMSetRenderTargetsAndUnorderedAccessViews.
// -1.0 = no POM (sentinel, matches ClearPomOffsetTexture); >= 0 = POM ran (StereoBlendCS checks >= 0).
RWTexture2D<float> PomOffsetTex : register(u7);
# endif
Comment thread
coderabbitai[bot] marked this conversation as resolved.

SamplerState SampTerrainParallaxSampler : register(s1);

# if defined(LANDSCAPE)
Expand Down Expand Up @@ -1020,6 +1028,9 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace)
# endif // LANDSCAPE
float sh0 = 0;
float pixelOffset = 0;
# if defined(VR_STEREO_OPT) && !defined(SNOW)
bool hasPOM = false;
# endif

# if defined(EMAT)
# if defined(LANDSCAPE)
Expand Down Expand Up @@ -1076,7 +1087,12 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace)
# if defined(PARALLAX)
if (SharedData::extendedMaterialSettings.EnableParallax) {
mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler, screenNoise);
uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, viewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset);
uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, viewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset
# if defined(VR_STEREO_OPT) && !defined(SNOW)
,
hasPOM
# endif
);
if (SharedData::extendedMaterialSettings.EnableShadows && (parallaxShadowQuality > 0.0f || SharedData::extendedMaterialSettings.ExtendShadows))
sh0 = TexParallaxSampler.SampleLevel(SampParallaxSampler, uv, mipLevel).x;
}
Expand Down Expand Up @@ -1104,7 +1120,12 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace)
if (envMaskSample.w > kMaskEpsilon) {
complexMaterialParallax = true;
mipLevel = ExtendedMaterials::GetMipLevel(uv, TexEnvMaskSampler, screenNoise);
uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, viewDirection, tbnTr, screenNoise, TexEnvMaskSampler, SampTerrainParallaxSampler, 3, displacementParams, pixelOffset);
uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, viewDirection, tbnTr, screenNoise, TexEnvMaskSampler, SampTerrainParallaxSampler, 3, displacementParams, pixelOffset
# if defined(VR_STEREO_OPT) && !defined(SNOW)
,
hasPOM
# endif
);
if (SharedData::extendedMaterialSettings.EnableShadows && (parallaxShadowQuality > 0.0f || SharedData::extendedMaterialSettings.ExtendShadows))
sh0 = TexEnvMaskSampler.SampleLevel(SampEnvMaskSampler, uv, mipLevel).w;
complexMaterialColor = TexEnvMaskSampler.Sample(SampEnvMaskSampler, uv);
Expand Down Expand Up @@ -1151,7 +1172,12 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace)
displacementParams.HeightScale *= PBRParams1.y;
}
mipLevel = ExtendedMaterials::GetMipLevel(uv, TexParallaxSampler, screenNoise);
uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, refractedViewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset);
uv = ExtendedMaterials::GetParallaxCoords(viewPosition.z, uv, mipLevel, refractedViewDirection, tbnTr, screenNoise, TexParallaxSampler, SampParallaxSampler, 0, displacementParams, pixelOffset
# if defined(VR_STEREO_OPT) && !defined(SNOW)
,
hasPOM
# endif
);
if (SharedData::extendedMaterialSettings.EnableShadows && (parallaxShadowQuality > 0.0f || SharedData::extendedMaterialSettings.ExtendShadows))
sh0 = TexParallaxSampler.SampleLevel(SampParallaxSampler, uv, mipLevel).x;
}
Expand Down Expand Up @@ -1246,9 +1272,17 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace)
// Initialize weights array
weights[0] = weights[1] = weights[2] = weights[3] = weights[4] = weights[5] = 0.0;
# if defined(TERRAIN_VARIATION)
uv = ExtendedMaterials::GetParallaxCoords(input, viewPosition.z, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, sharedOffset, dx, dy, pixelOffset, weights);
uv = ExtendedMaterials::GetParallaxCoords(input, viewPosition.z, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, sharedOffset, dx, dy, pixelOffset,
# if defined(VR_STEREO_OPT) && !defined(SNOW)
hasPOM,
# endif
weights);
# else
uv = ExtendedMaterials::GetParallaxCoords(input, viewPosition.z, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, pixelOffset, weights);
uv = ExtendedMaterials::GetParallaxCoords(input, viewPosition.z, uv, mipLevels, viewDirection, tbnTr, screenNoise, displacementParams, pixelOffset,
# if defined(VR_STEREO_OPT) && !defined(SNOW)
hasPOM,
# endif
weights);
# endif
if (SharedData::extendedMaterialSettings.EnableHeightBlending) {
input.LandBlendWeights1.x = weights[0];
Expand Down Expand Up @@ -3202,17 +3236,16 @@ PS_OUTPUT main(PS_INPUT input, bool frontFace : SV_IsFrontFace)
}
# endif

# if defined(VR) && (defined(EMAT) || defined(TRUE_PBR)) && (defined(PARALLAX) || defined(LANDSCAPE) || defined(EMAT_ENVMAP) || defined(TRUE_PBR))
// VR: store POM parallax amount for stereo reprojection depth correction.
// Read by StereoBlendCS to adjust Eye 1 (right eye) reprojection depth
// at POM-displaced surfaces. Not consumed on flat (SE/AE).
psout.Reflectance = float4(indirectLobeWeights.specular,
(pixelOffset > 0.0) ? saturate(pixelOffset) : psout.Diffuse.w);
# else
psout.Reflectance = float4(indirectLobeWeights.specular, psout.Diffuse.w);
# endif
psout.NormalGlossiness = float4(GBuffer::EncodeNormal(screenSpaceNormal), saturate(1.0 - material.Roughness), psout.Diffuse.w);

# if defined(VR_STEREO_OPT) && !defined(SNOW)
// VR stereo reprojection: write POM depth offset to dedicated texture (u7) for StereoBlendCS.
// hasPOM disambiguates "POM ran at geometry plane (pixelOffset=0.5)" from "POM did not run".
// -1.0 is the explicit no-POM sentinel (R16_FLOAT supports negatives); StereoBlendCS checks >= 0.
PomOffsetTex[uint2(input.Position.xy)] = hasPOM ? pixelOffset : Stereo::POM_NO_DATA;
# endif

# if defined(SNOW)
# if defined(TRUE_PBR)
psout.Parameters.x = Color::RGBToLuminanceAlternative(specularColor);
Expand Down
26 changes: 13 additions & 13 deletions package/Shaders/VR/StereoBlendCS.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ RWTexture2D<float4> OutputRW : register(u0);
#ifdef STEREO_OVERWRITE
RWTexture2D<float2> MotionRW : register(u1);
Texture2D<uint> ModeTexture : register(t2);
Texture2D<float4> ReflectanceTexture : register(t3); // .w = POM pixelOffset from Lighting pass
Texture2D<float> PomOffsetTexture : register(t3); // R16_FLOAT: Stereo::POM_NO_DATA (-1.0) = no POM; >= 0.0 = POM ran
SamplerState LinearSampler : register(s0);

# include "VRStereoOptimizations/modes.hlsli"
Expand Down Expand Up @@ -121,17 +121,17 @@ float4 SampleCrossDepths(int2 center, int offset, uint eyeIndex)
return;
}

// Debug mode 3: POM depth data visualizer — show Reflectance.w as color
// Debug mode 3: POM depth data visualizer — show PomOffsetTexture as color
if (DebugMode == 3) {
float pomVal = ReflectanceTexture[dtid].w;
float pomVal = PomOffsetTexture[dtid];
float4 c = ColorTexture[dtid];
if (pomVal > 1e-2) {
if (pomVal >= 0.0) {
// POM pixel: red-to-green gradient based on parallaxAmount
// Red = peak (high pomVal, closer to camera), Green = valley (low pomVal, farther), Yellow = geometry plane
float3 pomColor = float3(pomVal, 1.0 - pomVal, 0);
OutputRW[dtid] = float4(lerp(c.rgb, pomColor, 0.7), c.a);
}
// Non-POM pixels (pomVal ~ 0) left untouched
// Non-POM pixels store -1.0 sentinel, left untouched
return;
}

Expand All @@ -148,10 +148,10 @@ float4 SampleCrossDepths(int2 center, int offset, uint eyeIndex)
// Values > 0.5 are peaks (closer to camera), < 0.5 are valleys (farther from camera).
// Correction: high pomVal should push depth closer (smaller linear depth),
// so we use (0.5 - pomOffset) to get a negative correction for peaks.
// Non-POM pixels store 0.0, so threshold > 1e-2 distinguishes them.
// Non-POM pixels store -1.0 (sentinel); hasPOM is encoded by sign: >= 0 means POM ran.
float reprojDepthFB = centerDepth;
float pomOffsetFB = ReflectanceTexture[dtid].w;
if (pomOffsetFB > 1e-2 && POMDepthScale > 0) {
float pomOffsetFB = PomOffsetTexture[dtid];
if (pomOffsetFB >= 0.0 && POMDepthScale > 0) {
float linDepthFB = SharedData::GetScreenDepth(centerDepth);
float depthCorrectionFB = (0.5 - pomOffsetFB) * POMDepthScale;
float newLinDepthFB = max(linDepthFB + depthCorrectionFB, 1e-4);
Expand Down Expand Up @@ -214,14 +214,14 @@ float4 SampleCrossDepths(int2 center, int offset, uint eyeIndex)
// Save first-pass result as fallback before POM adjustment
Stereo::StereoBilateralResult firstPassR = r;

// Read POM offset from Eye 0 source's reflectance.w
// Read POM offset from dedicated POM texture (R16_FLOAT, written by Lighting PS at u7).
// pixelOffset = parallaxAmount (0-1) from ExtendedMaterials, 0.5 = geometry plane.
// Values > 0.5 are peaks (closer to camera), < 0.5 are valleys (farther from camera).
// Correction: high pomVal should push depth closer (smaller linear depth),
// Correction: high pomOffset should push depth closer (smaller linear depth),
// so we use (0.5 - pomOffset) to get a negative correction for peaks.
// Non-POM pixels store 0.0, so threshold > 1e-2 distinguishes them.
float pomOffset = ReflectanceTexture[r.otherPx].w;
if (pomOffset > 1e-2) {
// Non-POM pixels store -1.0 (sentinel); hasPOM is encoded by sign: >= 0 means POM ran.
float pomOffset = PomOffsetTexture[r.otherPx];
if (pomOffset >= 0.0) {
// Re-reproject with POM-adjusted depth centered at geometry plane
float linearDepth = SharedData::GetScreenDepth(centerDepth);
float depthCorrection = (0.5 - pomOffset) * POMDepthScale;
Expand Down
8 changes: 4 additions & 4 deletions src/Deferred.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ void Deferred::StartDeferred()
{
auto context = globals::d3d::context;

// Clear POM offset texture to -1.0 sentinel so pixels the Lighting PS never touches read "no POM"
if (globals::features::vr.stereoOpt.loaded)
globals::features::vr.stereoOpt.ClearPomOffsetTexture();
Comment thread
alandtse marked this conversation as resolved.

ID3D11Buffer* buffers[1] = { *globals::game::perFrame.get() };

ID3D11Buffer* vrBuffer = nullptr;
Expand Down Expand Up @@ -506,10 +510,6 @@ void Deferred::OverrideBlendStates()
blendDesc.RenderTarget[i].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
}

// RT[5] = REFLECTANCE: enable alpha writes for POM depth data
// stored in Reflectance.w, used by StereoBlendCS for depth-aware reprojection
blendDesc.RenderTarget[5].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;

DX::ThrowIfFailed(device->CreateBlendState(&blendDesc, &deferredBlendStates[a][b][c][d]));
} else {
deferredBlendStates[a][b][c][d] = nullptr;
Expand Down
5 changes: 4 additions & 1 deletion src/Features/VR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ void VR::SetupResources()

if (globals::game::isVR && stereoOpt.settings.stereoMode != VRStereoOptimizations::StereoMode::Off) {
stereoOpt.SetupResources();
stereoOpt.loaded = stereoOpt.GetModeTextureSRV() != nullptr;
stereoOpt.loaded =
stereoOpt.GetModeTextureSRV() != nullptr &&
stereoOpt.GetPomOffsetSRV() != nullptr &&
stereoOpt.GetPomOffsetUAV() != nullptr;
} else {
stereoOpt.loaded = false;
Comment thread
alandtse marked this conversation as resolved.
}
Expand Down
2 changes: 1 addition & 1 deletion src/Features/VR.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ struct VR : OverlayFeature
}

virtual inline std::string_view GetShaderDefineName() override { return "VR_STEREO_OPT"; }
virtual inline bool HasShaderDefine(RE::BSShader::Type t) override { return stereoOpt.CanDispatchStencil() && t == RE::BSShader::Type::Utility; }
virtual inline bool HasShaderDefine(RE::BSShader::Type t) override { return stereoOpt.CanDispatchStencil() && (t == RE::BSShader::Type::Utility || t == RE::BSShader::Type::Lighting); }
virtual void Reset() override;
virtual void SetupResources() override;
virtual void ClearShaderCache() override;
Expand Down
6 changes: 3 additions & 3 deletions src/Features/VR/StereoBlend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,9 @@ void VR::DrawStereoBlend()
ID3D11ShaderResourceView* modeSRV = globals::features::vr.stereoOpt.GetModeTextureSRV();
context->CSSetShaderResources(2, 1, &modeSRV);

// Bind REFLECTANCE SRV for POM depth offset (stored in .w by Lighting pass)
auto& reflectanceRT = renderer->GetRuntimeData().renderTargets[REFLECTANCE];
context->CSSetShaderResources(3, 1, &reflectanceRT.SRV);
// Bind dedicated POM offset SRV (R16_FLOAT, written by Lighting PS at u7)
auto* pomSRV = globals::features::vr.stereoOpt.GetPomOffsetSRV();
context->CSSetShaderResources(3, 1, &pomSRV);

ID3D11UnorderedAccessView* uavs[2]{ main.UAV, motionVectors.UAV };
context->CSSetUnorderedAccessViews(0, 2, uavs, nullptr);
Expand Down
42 changes: 42 additions & 0 deletions src/Features/VRStereoOptimizations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "ExtendedMaterials.h"
#include "Globals.h"
#include "Menu.h"
#include "State.h"
#include "Utils/D3D.h"
#include "Utils/Game.h"
Expand Down Expand Up @@ -128,6 +129,34 @@ void VRStereoOptimizations::SetupResources()
.Texture2D = { .MipSlice = 0 } });
}

// POM offset texture (R16_FLOAT, full SBS resolution)
// Written by Lighting PS (u7) for POM-active pixels, read by StereoBlendCS for depth-aware reprojection.
// Replaces the former overloading of Reflectance.w, so Reflectance stays R11G11B10 with no alpha.
{
D3D11_TEXTURE2D_DESC pomDesc{};
pomDesc.Width = mainDesc.Width;
pomDesc.Height = mainDesc.Height;
pomDesc.MipLevels = 1;
pomDesc.ArraySize = 1;
pomDesc.Format = DXGI_FORMAT_R16_FLOAT;
pomDesc.SampleDesc.Count = 1;
pomDesc.SampleDesc.Quality = 0;
pomDesc.Usage = D3D11_USAGE_DEFAULT;
pomDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS;
pomDesc.CPUAccessFlags = 0;
pomDesc.MiscFlags = 0;

texPomOffset = eastl::make_unique<Texture2D>(pomDesc);
texPomOffset->CreateSRV(D3D11_SHADER_RESOURCE_VIEW_DESC{
.Format = DXGI_FORMAT_R16_FLOAT,
.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D,
.Texture2D = { .MostDetailedMip = 0, .MipLevels = 1 } });
texPomOffset->CreateUAV(D3D11_UNORDERED_ACCESS_VIEW_DESC{
.Format = DXGI_FORMAT_R16_FLOAT,
.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D,
.Texture2D = { .MipSlice = 0 } });
}

// Depth-stencil state for stencil write pass:
// Depth test OFF (not rendering geometry), depth writes OFF, stencil ALWAYS + REPLACE with ref=1.
// We use the normal (writable) kMAIN DSV — no simultaneous SRV binding needed.
Expand Down Expand Up @@ -213,6 +242,14 @@ void VRStereoOptimizations::Reset()
stencilSwapCount = 0;
}

void VRStereoOptimizations::ClearPomOffsetTexture()
{
if (!texPomOffset)
return;
const float clearValue[4] = { kPomOffsetNoData, kPomOffsetNoData, kPomOffsetNoData, kPomOffsetNoData };
globals::d3d::context->ClearUnorderedAccessViewFloat(texPomOffset->uav.get(), clearValue);
Comment thread
alandtse marked this conversation as resolved.
}

//=============================================================================
// IMGUI SETTINGS
//=============================================================================
Expand All @@ -226,6 +263,11 @@ void VRStereoOptimizations::DrawSettings()
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Reprojects Eye 0 (left) pixels into Eye 1 (right) using depth and motion data,\nskipping redundant full shading where the views overlap.\nReduces GPU cost in VR by shading each pixel fewer times per frame.");

if (globals::game::isVR && settings.stereoMode == StereoMode::Enable && !loaded) {
const auto& themeSettings = Menu::GetSingleton()->GetTheme();
ImGui::TextColored(themeSettings.StatusPalette.RestartNeeded,
"Restart is required to enable VR stereo reprojection.");
}
if (settings.stereoMode == StereoMode::Off)
return;

Expand Down
Loading
Loading