Implement stereo depth and stencil support for VR cockpit rendering to fix hands getting darken by virtual screen

This commit is contained in:
iChris4 committed 2026-09-23 16:03:00 +02:00
1 parent b2353e1410
commit 2a89fac79d
12 files changed
+146 -60

No files matched your search

+7 -2
View File
@@ -471,8 +471,13 @@ offers that mesh without the app declaring hand tracking, and the log says which
joint's own X, on both hands. They and the joint's own X, on both hands. They and the
separate VR wheel or handlebar travel with the stereo packet in metres in the seated frame, and each separate VR wheel or handlebar travel with the stereo packet in metres in the seated frame, and each
eye draws them inside the scene's pass just before the first 2D-layer draw, depth-tested with the eye draws them inside the scene's pass just before the first 2D-layer draw, depth-tested with the
world's own depth mapping, so the kart and the track hide them and the HUD cannot world's own depth mapping, so the kart and the track hide them. Visible cockpit samples
(`aurora-main/lib/gfx/cockpit.hpp`). The anchor also carries the frame's exact world scale also mark one stencil bit; virtual-screen draws test that bit for zero, so even depth-disabled
HUD elements and black screen effects cannot paint over the hands. Only stereo eye targets
use `Depth24PlusStencil8`; desktop/EFB depth stays unchanged. The mask is cleared once per
eye replay and retained across its passes. This adds no draw, full-screen copy, or render pass;
eye-format pipeline siblings share shader modules and are cached when recording the game
frame (`aurora-main/lib/gfx/cockpit.hpp`). The anchor also carries the frame's exact world scale
(`aurora_set_stereo_scene_anchor_scaled`), and Aurora rescales each eye's head translation to it, so (`aurora_set_stereo_scene_anchor_scaled`), and Aurora rescales each eye's head translation to it, so
a scale change between the XR packet and the frame cannot misplace the hands. a scale change between the XR packet and the frame cannot misplace the hands.
+6 -3
View File
@@ -633,7 +633,7 @@ void ensure_stereo_eye_target(uint32_t eyeIndex, uint32_t width, uint32_t height
const uint32_t samples = webgpu::g_graphicsConfig.msaaSamples; const uint32_t samples = webgpu::g_graphicsConfig.msaaSamples;
if (target.color.texture && target.requestedWidth == width && target.requestedHeight == height && if (target.color.texture && target.requestedWidth == width && target.requestedHeight == height &&
target.samples == samples && target.colorFormat == webgpu::g_graphicsConfig.surfaceConfiguration.format && target.samples == samples && target.colorFormat == webgpu::g_graphicsConfig.surfaceConfiguration.format &&
target.depthFormat == webgpu::g_graphicsConfig.depthFormat) { target.depthFormat == wgpu::TextureFormat::Depth24PlusStencil8) {
return; return;
} }
@@ -643,19 +643,21 @@ void ensure_stereo_eye_target(uint32_t eyeIndex, uint32_t width, uint32_t height
target.resolvedColor = webgpu::create_render_texture(target.color.size.width, target.color.size.height, false); target.resolvedColor = webgpu::create_render_texture(target.color.size.width, target.color.size.height, false);
} }
// The cockpit uses one stencil bit to survive later depth-disabled HUD draws.
// Keep this attachment eye-only; native EFB depth sampling is unchanged.
const wgpu::TextureDescriptor depthDescriptor{ const wgpu::TextureDescriptor depthDescriptor{
.label = eyeIndex == 0 ? "Stereo left eye depth" : "Stereo right eye depth", .label = eyeIndex == 0 ? "Stereo left eye depth" : "Stereo right eye depth",
.usage = wgpu::TextureUsage::RenderAttachment, .usage = wgpu::TextureUsage::RenderAttachment,
.dimension = wgpu::TextureDimension::e2D, .dimension = wgpu::TextureDimension::e2D,
.size = target.color.size, .size = target.color.size,
.format = webgpu::g_graphicsConfig.depthFormat, .format = wgpu::TextureFormat::Depth24PlusStencil8,
.mipLevelCount = 1, .mipLevelCount = 1,
.sampleCount = samples, .sampleCount = samples,
}; };
target.depth.texture = g_device.CreateTexture(&depthDescriptor); target.depth.texture = g_device.CreateTexture(&depthDescriptor);
target.depth.view = target.depth.texture.CreateView(); target.depth.view = target.depth.texture.CreateView();
target.depth.size = target.color.size; target.depth.size = target.color.size;
target.depth.format = webgpu::g_graphicsConfig.depthFormat; target.depth.format = wgpu::TextureFormat::Depth24PlusStencil8;
target.requestedWidth = width; target.requestedWidth = width;
target.requestedHeight = height; target.requestedHeight = height;
target.samples = samples; target.samples = samples;
@@ -768,6 +770,7 @@ gfx::StereoReplayFrame make_stereo_replay_frame(const AuroraStereoFrame& input,
.copySourceDepthView = owned.depth.view, .copySourceDepthView = owned.depth.view,
.size = owned.color.size, .size = owned.color.size,
.msaaSamples = webgpu::g_graphicsConfig.msaaSamples, .msaaSamples = webgpu::g_graphicsConfig.msaaSamples,
.depthFormat = owned.depth.format,
}; };
std::memcpy(&view.projection, input.eyes[eye].projection, sizeof(view.projection)); std::memcpy(&view.projection, input.eyes[eye].projection, sizeof(view.projection));
std::memcpy(&view.viewFromCenter, input.eyes[eye].viewFromCenter, sizeof(view.viewFromCenter)); std::memcpy(&view.viewFromCenter, input.eyes[eye].viewFromCenter, sizeof(view.viewFromCenter));
@@ -505,6 +505,13 @@ void GXCopyTex(void* dest, GXBool clear) {
.clearAlpha = true, .clearAlpha = true,
.clearDepth = false, .clearDepth = false,
}), }),
.stereoPipeline = aurora::stereo_frame_provider_active() ? aurora::gfx::pipeline_ref(aurora::gfx::clear::PipelineConfig{
.msaaSamples = aurora::gfx::get_sample_count(),
.clearColor = false,
.clearAlpha = true,
.clearDepth = false,
.stereoStencil = true,
}) : 0,
.color = wgpu::Color{0.f, 0.f, 0.f, g_gxState.dstAlpha / 255.f}, .color = wgpu::Color{0.f, 0.f, 0.f, g_gxState.dstAlpha / 255.f},
}); });
} }
+1 -1
View File
@@ -105,7 +105,7 @@ fn fs_main() -> FragmentOutput {
.targets = &colorTarget, .targets = &colorTarget,
}; };
const wgpu::DepthStencilState depthStencil{ const wgpu::DepthStencilState depthStencil{
.format = g_graphicsConfig.depthFormat, .format = config.stereoStencil ? wgpu::TextureFormat::Depth24PlusStencil8 : g_graphicsConfig.depthFormat,
.depthWriteEnabled = config.clearDepth, .depthWriteEnabled = config.clearDepth,
.depthCompare = wgpu::CompareFunction::Always, .depthCompare = wgpu::CompareFunction::Always,
}; };
+3 -2
View File
@@ -7,6 +7,7 @@
namespace aurora::gfx::clear { namespace aurora::gfx::clear {
struct DrawData { struct DrawData {
PipelineRef pipeline; PipelineRef pipeline;
PipelineRef stereoPipeline = 0;
Range uniformRange; Range uniformRange;
wgpu::Color color; wgpu::Color color;
float depth = 0.f; float depth = 0.f;
@@ -18,14 +19,14 @@ struct DrawData {
ClipRect scissor{}; ClipRect scissor{};
}; };
constexpr uint32_t ClearPipelineConfigVersion = 3; constexpr uint32_t ClearPipelineConfigVersion = 4;
struct PipelineConfig { struct PipelineConfig {
uint32_t version = ClearPipelineConfigVersion; uint32_t version = ClearPipelineConfigVersion;
uint32_t msaaSamples = 1; uint32_t msaaSamples = 1;
bool clearColor = true; bool clearColor = true;
bool clearAlpha = true; bool clearAlpha = true;
bool clearDepth = true; bool clearDepth = true;
uint8_t _pad = 0; bool stereoStencil = false;
}; };
static_assert(std::has_unique_object_representations_v<PipelineConfig>); static_assert(std::has_unique_object_representations_v<PipelineConfig>);
+15 -6
View File
@@ -210,7 +210,7 @@ struct SceneDepth {
}; };
inline uint32_t pipelineSamples=0; inline uint32_t pipelineSamples=0;
inline bool pipelineReversedDepth=false; inline bool pipelineReversedDepth=false;
inline wgpu::TextureFormat pipelineFormat{}; inline wgpu::TextureFormat pipelineFormat{}, pipelineDepthFormat{};
inline std::array<wgpu::Buffer,2> vertexBuffers; inline std::array<wgpu::Buffer,2> vertexBuffers;
inline std::array<uint64_t,2> vertexCapacity{}; inline std::array<uint64_t,2> vertexCapacity{};
inline void shutdown() { pipeline=nullptr;pipelineSamples=0;vertexBuffers={};vertexCapacity={};cachedMeshRevision=0;frameVertices.clear(); } inline void shutdown() { pipeline=nullptr;pipelineSamples=0;vertexBuffers={};vertexCapacity={};cachedMeshRevision=0;frameVertices.clear(); }
@@ -223,7 +223,7 @@ inline void render(wgpu::CommandEncoder& cmd,const StereoReplayFrame& frame,uint
// The guest can reverse its viewport depth independently of Aurora's // The guest can reverse its viewport depth independently of Aurora's
// global reversed-Z convention. The final 1/d coefficient is authoritative. // global reversed-Z convention. The final 1/d coefficient is authoritative.
const bool reversedDepth=sceneDepth.constant>0; const bool reversedDepth=sceneDepth.constant>0;
if(!pipeline||pipelineSamples!=target.msaaSamples||pipelineFormat!=format||pipelineReversedDepth!=reversedDepth) { if(!pipeline||pipelineSamples!=target.msaaSamples||pipelineFormat!=format||pipelineReversedDepth!=reversedDepth||pipelineDepthFormat!=target.depthFormat) {
wgpu::ShaderSourceWGSL source{}; wgpu::ShaderSourceWGSL source{};
source.code=R"( source.code=R"(
struct Out { @builtin(position) position: vec4f, @location(0) color: vec3f }; struct Out { @builtin(position) position: vec4f, @location(0) color: vec3f };
@@ -239,14 +239,18 @@ inline void render(wgpu::CommandEncoder& cmd,const StereoReplayFrame& frame,uint
const wgpu::VertexBufferLayout layout{.arrayStride=28,.attributeCount=2,.attributes=attrs}; const wgpu::VertexBufferLayout layout{.arrayStride=28,.attributeCount=2,.attributes=attrs};
const wgpu::ColorTargetState color{.format=format}; const wgpu::ColorTargetState color{.format=format};
const wgpu::FragmentState fragment{.module=shader,.entryPoint="fs",.targetCount=1,.targets=&color}; const wgpu::FragmentState fragment{.module=shader,.entryPoint="fs",.targetCount=1,.targets=&color};
const wgpu::DepthStencilState depth{.format=g_graphicsConfig.depthFormat,.depthWriteEnabled=true, const bool stencil=target.depthFormat==wgpu::TextureFormat::Depth24PlusStencil8;
.depthCompare=reversedDepth?wgpu::CompareFunction::GreaterEqual:wgpu::CompareFunction::LessEqual}; const wgpu::StencilFaceState mark{.compare=wgpu::CompareFunction::Always,
.passOp=stencil?wgpu::StencilOperation::Replace:wgpu::StencilOperation::Keep};
const wgpu::DepthStencilState depth{.format=target.depthFormat,.depthWriteEnabled=true,
.depthCompare=reversedDepth?wgpu::CompareFunction::GreaterEqual:wgpu::CompareFunction::LessEqual,
.stencilFront=mark,.stencilBack=mark,.stencilReadMask=1,.stencilWriteMask=stencil?1u:0u};
wgpu::RenderPipelineDescriptor desc{};desc.label="VR cockpit"; wgpu::RenderPipelineDescriptor desc{};desc.label="VR cockpit";
desc.vertex={.module=shader,.entryPoint="vs",.bufferCount=1,.buffers=&layout}; desc.vertex={.module=shader,.entryPoint="vs",.bufferCount=1,.buffers=&layout};
desc.fragment=&fragment;desc.depthStencil=&depth;desc.multisample.count=target.msaaSamples; desc.fragment=&fragment;desc.depthStencil=&depth;desc.multisample.count=target.msaaSamples;
desc.primitive.topology=wgpu::PrimitiveTopology::TriangleList; desc.primitive.topology=wgpu::PrimitiveTopology::TriangleList;
pipeline=g_device.CreateRenderPipeline(&desc);pipelineSamples=target.msaaSamples;pipelineFormat=format; pipeline=g_device.CreateRenderPipeline(&desc);pipelineSamples=target.msaaSamples;pipelineFormat=format;
pipelineReversedDepth=reversedDepth; pipelineReversedDepth=reversedDepth;pipelineDepthFormat=target.depthFormat;
} }
const auto revision=meshRevision.load(); const auto revision=meshRevision.load();
if(cachedMeshRevision!=revision || std::memcmp(&cachedCockpit,&frame.cockpit,sizeof(AuroraCockpit))!=0) { if(cachedMeshRevision!=revision || std::memcmp(&cachedCockpit,&frame.cockpit,sizeof(AuroraCockpit))!=0) {
@@ -279,12 +283,17 @@ inline void render(wgpu::CommandEncoder& cmd,const StereoReplayFrame& frame,uint
const wgpu::RenderPassColorAttachment attachment{.view=target.colorView,.resolveTarget=target.resolveView, const wgpu::RenderPassColorAttachment attachment{.view=target.colorView,.resolveTarget=target.resolveView,
.loadOp=wgpu::LoadOp::Load,.storeOp=wgpu::StoreOp::Store}; .loadOp=wgpu::LoadOp::Load,.storeOp=wgpu::StoreOp::Store};
const wgpu::RenderPassDepthStencilAttachment depth{.view=target.depthView,.depthLoadOp=wgpu::LoadOp::Load, const wgpu::RenderPassDepthStencilAttachment depth{.view=target.depthView,.depthLoadOp=wgpu::LoadOp::Load,
.depthStoreOp=wgpu::StoreOp::Store,.depthClearValue=1.0f}; .depthStoreOp=wgpu::StoreOp::Store,.depthClearValue=1.0f,
.stencilLoadOp=target.depthFormat==wgpu::TextureFormat::Depth24PlusStencil8?wgpu::LoadOp::Load:wgpu::LoadOp::Undefined,
.stencilStoreOp=target.depthFormat==wgpu::TextureFormat::Depth24PlusStencil8?wgpu::StoreOp::Store:wgpu::StoreOp::Undefined};
const wgpu::RenderPassDescriptor pd{.label="VR cockpit overlay",.colorAttachmentCount=1,.colorAttachments=&attachment,.depthStencilAttachment=&depth}; const wgpu::RenderPassDescriptor pd{.label="VR cockpit overlay",.colorAttachmentCount=1,.colorAttachments=&attachment,.depthStencilAttachment=&depth};
auto pass=existingPass?*existingPass:cmd.BeginRenderPass(&pd); auto pass=existingPass?*existingPass:cmd.BeginRenderPass(&pd);
pass.SetViewport(0,0,float(target.size.width),float(target.size.height),0,1); pass.SetViewport(0,0,float(target.size.width),float(target.size.height),0,1);
pass.SetScissorRect(0,0,target.size.width,target.size.height); pass.SetScissorRect(0,0,target.size.width,target.size.height);
// Mark only depth-visible samples; later virtual-screen draws test for zero.
pass.SetStencilReference(1);
pass.SetPipeline(pipeline);pass.SetVertexBuffer(0,buffer);pass.Draw(clip.size()); pass.SetPipeline(pipeline);pass.SetVertexBuffer(0,buffer);pass.Draw(clip.size());
pass.SetStencilReference(0);
if(!existingPass) pass.End(); if(!existingPass) pass.End();
} }
} // namespace aurora::gfx::cockpit } // namespace aurora::gfx::cockpit
+24 -3
View File
@@ -737,6 +737,13 @@ void resolve_pass(TextureHandle texture, ClipRect rect, bool clearColor, bool cl
.clearAlpha = clearAlpha, .clearAlpha = clearAlpha,
.clearDepth = clearDepth, .clearDepth = clearDepth,
}), }),
.stereoPipeline = aurora::stereo_frame_provider_active() ? pipeline_ref(clear::PipelineConfig{
.msaaSamples = msaaSamples,
.clearColor = clearColor,
.clearAlpha = clearAlpha,
.clearDepth = clearDepth,
.stereoStencil = true,
}) : 0,
.color = .color =
wgpu::Color{ wgpu::Color{
.r = clearColorValue.x(), .r = clearColorValue.x(),
@@ -1769,6 +1776,9 @@ static void render_impl(std::vector<RenderPass>& renderPasses, wgpu::CommandEnco
ZoneScoped; ZoneScoped;
// Palette conversions, MSAA resolves and EFB copies depend on sealed frame state, not on the // Palette conversions, MSAA resolves and EFB copies depend on sealed frame state, not on the
// interpolation weight, so encode them on the native render and let replay slots sample them. // interpolation weight, so encode them on the native render and let replay slots sample them.
// Eye textures are reused; discard the previous frame's mask, then retain it
// across guest passes even if the HUD clears or replaces guest depth.
bool stencilInitialized = false;
for (u32 i = 0; i < renderPasses.size(); ++i) { for (u32 i = 0; i < renderPasses.size(); ++i) {
const auto& passInfo = renderPasses[i]; const auto& passInfo = renderPasses[i];
if (invocation.replayLastPass >= 0 && i > static_cast<u32>(invocation.replayLastPass)) { if (invocation.replayLastPass >= 0 && i > static_cast<u32>(invocation.replayLastPass)) {
@@ -1821,11 +1831,16 @@ static void render_impl(std::vector<RenderPass>& renderPasses, wgpu::CommandEnco
}, },
}, },
}; };
const bool stereoStencil = overrideTarget &&
invocation.target->depthFormat == wgpu::TextureFormat::Depth24PlusStencil8;
const wgpu::RenderPassDepthStencilAttachment depthStencilAttachment{ const wgpu::RenderPassDepthStencilAttachment depthStencilAttachment{
.view = depthView, .view = depthView,
.depthLoadOp = passInfo.clearDepth && !dropCopyClear ? wgpu::LoadOp::Clear : wgpu::LoadOp::Load, .depthLoadOp = passInfo.clearDepth && !dropCopyClear ? wgpu::LoadOp::Clear : wgpu::LoadOp::Load,
.depthStoreOp = wgpu::StoreOp::Store, .depthStoreOp = wgpu::StoreOp::Store,
.depthClearValue = passInfo.clearDepthValue, .depthClearValue = passInfo.clearDepthValue,
.stencilLoadOp = stereoStencil ? (stencilInitialized ? wgpu::LoadOp::Load : wgpu::LoadOp::Clear) : wgpu::LoadOp::Undefined,
.stencilStoreOp = stereoStencil ? wgpu::StoreOp::Store : wgpu::StoreOp::Undefined,
.stencilClearValue = 0,
}; };
const GpuTimingCategory timingCategory = invocation.stereoEye == 0 ? GpuTimingCategory::EyeLeft const GpuTimingCategory timingCategory = invocation.stereoEye == 0 ? GpuTimingCategory::EyeLeft
: invocation.stereoEye == 1 ? GpuTimingCategory::EyeRight : invocation.stereoEye == 1 ? GpuTimingCategory::EyeRight
@@ -1839,6 +1854,7 @@ static void render_impl(std::vector<RenderPass>& renderPasses, wgpu::CommandEnco
.timestampWrites = gpu_timing_pass(timingCategory), .timestampWrites = gpu_timing_pass(timingCategory),
}; };
if (stereoStencil) stencilInitialized = true;
auto pass = cmd.BeginRenderPass(&renderPassDescriptor); auto pass = cmd.BeginRenderPass(&renderPassDescriptor);
render_pass_impl(pass, renderPasses, i, invocation); render_pass_impl(pass, renderPasses, i, invocation);
pass.End(); pass.End();
@@ -2500,8 +2516,8 @@ static void render_pass_impl(const wgpu::RenderPassEncoder& pass, const std::vec
draw.gx.interpolatedUniformRanges[invocation.interpolatedFrame].size != 0) { draw.gx.interpolatedUniformRanges[invocation.interpolatedFrame].size != 0) {
uniformOverride = &draw.gx.interpolatedUniformRanges[invocation.interpolatedFrame]; uniformOverride = &draw.gx.interpolatedUniformRanges[invocation.interpolatedFrame];
} }
// Draw the VR cockpit against the world's depth before the first HUD // Draw against world depth and mark visible cockpit samples before HUD
// draw can write a screen-plane depth over it, inside this open pass. // depth replaces it. The screen pipelines reject those stencil samples.
if (invocation.cockpitFrame != nullptr && overrideTarget) { if (invocation.cockpitFrame != nullptr && overrideTarget) {
if (draw.gx.uniformReplayLayout.perspective) { if (draw.gx.uniformReplayLayout.perspective) {
*invocation.sceneDrawn = true; *invocation.sceneDrawn = true;
@@ -2535,10 +2551,15 @@ static void render_pass_impl(const wgpu::RenderPassEncoder& pass, const std::vec
apply_viewport(fullEyeDraw); apply_viewport(fullEyeDraw);
} }
gx::render(draw.gx, pass, encodeState, renderPasses[idx].requireReadyPipelines, uniformOverride, gx::render(draw.gx, pass, encodeState, renderPasses[idx].requireReadyPipelines, uniformOverride,
virtualScreenDraw ? draw.gx.exactScreenDepthPipeline : 0); overrideTarget && invocation.target->depthFormat == wgpu::TextureFormat::Depth24PlusStencil8
? (virtualScreenDraw ? draw.gx.stereoScreenPipeline : draw.gx.stereoPipeline)
: (virtualScreenDraw ? draw.gx.exactScreenDepthPipeline : 0));
} break; } break;
case ShaderType::Clear: { case ShaderType::Clear: {
auto clearDraw = draw.clear; auto clearDraw = draw.clear;
if (overrideTarget && invocation.target->depthFormat == wgpu::TextureFormat::Depth24PlusStencil8) {
clearDraw.pipeline = clearDraw.stereoPipeline;
}
if (multiplayer) { if (multiplayer) {
const auto& sc = clearDraw.scissor; const auto& sc = clearDraw.scissor;
if (clearDraw.copyClear || if (clearDraw.copyClear ||
+1
View File
@@ -291,6 +291,7 @@ struct ReplayTarget {
wgpu::TextureView copySourceDepthView; wgpu::TextureView copySourceDepthView;
wgpu::Extent3D size{}; wgpu::Extent3D size{};
uint32_t msaaSamples = 1; uint32_t msaaSamples = 1;
wgpu::TextureFormat depthFormat = wgpu::TextureFormat::Depth32Float;
}; };
struct StereoReplayEye { struct StereoReplayEye {
+24 -35
View File
@@ -2206,6 +2206,10 @@ static ArrayRef<u16> offset_index_template(const CachedIndexTemplate& indexTempl
struct CachedPipelineState { struct CachedPipelineState {
gfx::PipelineRef ref = 0; gfx::PipelineRef ref = 0;
const PipelineConfig* config = nullptr;
mutable gfx::PipelineRef stereoRef = 0;
mutable gfx::PipelineRef screenRef = 0;
mutable gfx::PipelineRef stereoScreenRef = 0;
HashType configHash = 0; HashType configHash = 0;
// Carried here so the draw can be recorded without keeping the PipelineConfig that produced it alive; it is the only // Carried here so the draw can be recorded without keeping the PipelineConfig that produced it alive; it is the only
// field of the config the draw itself still needs. // field of the config the draw itself still needs.
@@ -2231,6 +2235,7 @@ static const CachedPipelineState& cached_pipeline_state(const PipelineConfig& co
entry.config = config; entry.config = config;
entry.state = { entry.state = {
.ref = gfx::pipeline_ref(config), .ref = gfx::pipeline_ref(config),
.config = &entry.config,
.configHash = hash, .configHash = hash,
.dstAlpha = config.dstAlpha, .dstAlpha = config.dstAlpha,
.shaderInfo = build_shader_info(config.shaderConfig), .shaderInfo = build_shader_info(config.shaderConfig),
@@ -2273,37 +2278,20 @@ static const CachedPipelineState& resolve_pipeline_state(GXPrimitive prim, GXVtx
return state; return state;
} }
// Exact Screen Depth changes shader outputs but no GX pipeline state. Resolve a // Lazily cache eye-format siblings alongside the ordinary pipeline. Steady-state
// sibling pipeline only for orthographic draws that can actually reach the VR // draws only read the refs: no extra config population/hashing on the Quest CPU.
// screen, and memoize it with the same state epoch as the ordinary pipeline. // Shader modules are shared by the depth-format variants.
static gfx::PipelineRef resolve_exact_screen_depth_pipeline(GXPrimitive prim, GXVtxFmt fmt) { static void resolve_replay_pipelines(const CachedPipelineState& state, bool screen) {
struct Memo { if (state.stereoRef && (!screen || state.stereoScreenRef)) return;
gfx::PipelineRef ref = 0; PipelineConfig config = *state.config;
u32 generation = 0; config.stereoStencil = 1;
u32 sampleCount = 0; if (!state.stereoRef) state.stereoRef = gfx::pipeline_ref(config);
GXPrimitive prim = static_cast<GXPrimitive>(0); if (screen && !state.stereoScreenRef) {
GXVtxFmt fmt = static_cast<GXVtxFmt>(0);
};
static Memo memo{};
const u32 sampleCount = gfx::get_sample_count();
const u32 generation = g_gxState.pipelineStateGeneration;
if (memo.ref != 0 && memo.generation == generation && memo.sampleCount == sampleCount && memo.prim == prim &&
memo.fmt == fmt)
LIKELY { return memo.ref; }
PipelineConfig config{};
populate_pipeline_config(config, prim, fmt);
config.shaderConfig.exactScreenDepth = 1; config.shaderConfig.exactScreenDepth = 1;
const gfx::PipelineRef ref = gfx::pipeline_ref(config); state.stereoScreenRef = gfx::pipeline_ref(config);
memo = Memo{ config.stereoStencil = 0;
.ref = ref, state.screenRef = gfx::pipeline_ref(config);
.generation = generation, }
.sampleCount = sampleCount,
.prim = prim,
.fmt = fmt,
};
return ref;
} }
bool submit_raw_draw(GXPrimitive prim, GXVtxFmt fmt, const uint8_t* vertices, uint16_t vtxCount, uint32_t vertexBytes) { bool submit_raw_draw(GXPrimitive prim, GXVtxFmt fmt, const uint8_t* vertices, uint16_t vtxCount, uint32_t vertexBytes) {
@@ -2508,10 +2496,9 @@ static void handle_draw_unmerged(GXPrimitive prim, GXVtxFmt fmt, u16 vtxCount, g
const bool perspective = g_gxState.projType == GX_PERSPECTIVE; const bool perspective = g_gxState.projType == GX_PERSPECTIVE;
const auto uniformRanges = build_uniform(info, vertRange.offset, ranges, drawIdentity, perspective, usedPnMtxMask); const auto uniformRanges = build_uniform(info, vertRange.offset, ranges, drawIdentity, perspective, usedPnMtxMask);
const auto& replayLayout = uniformRanges.replayLayout; const auto& replayLayout = uniformRanges.replayLayout;
const gfx::PipelineRef exactScreenDepthPipeline = const bool stereo = aurora::stereo_frame_provider_active();
aurora::stereo_frame_provider_active() && !replayLayout.perspective && !replayLayout.nativeEfbEffect const bool screen = !replayLayout.perspective && !replayLayout.nativeEfbEffect;
? resolve_exact_screen_depth_pipeline(prim, fmt) if (stereo) resolve_replay_pipelines(pipelineState, screen);
: 0;
s_lastDrawRecordedInterpolation = interpolationIdentityActive; s_lastDrawRecordedInterpolation = interpolationIdentityActive;
uint32_t instanceCount = 1; uint32_t instanceCount = 1;
@@ -2524,7 +2511,9 @@ static void handle_draw_unmerged(GXPrimitive prim, GXVtxFmt fmt, u16 vtxCount, g
} }
gfx::push_draw_command(DrawData{ gfx::push_draw_command(DrawData{
.pipeline = pipeline, .pipeline = pipeline,
.exactScreenDepthPipeline = exactScreenDepthPipeline, .exactScreenDepthPipeline = stereo && screen ? pipelineState.screenRef : 0,
.stereoPipeline = stereo ? pipelineState.stereoRef : 0,
.stereoScreenPipeline = stereo && screen ? pipelineState.stereoScreenRef : 0,
.vertRange = vertRange, .vertRange = vertRange,
.idxRange = idxRange, .idxRange = idxRange,
.uniformRange = uniformRanges.current, .uniformRange = uniformRanges.current,
+9 -1
View File
@@ -1609,10 +1609,18 @@ static inline wgpu::PrimitiveState to_primitive_state(GXCullMode gx_cullMode) {
wgpu::RenderPipeline build_pipeline(const PipelineConfig& config, ArrayRef<wgpu::VertexBufferLayout> vtxBuffers, wgpu::RenderPipeline build_pipeline(const PipelineConfig& config, ArrayRef<wgpu::VertexBufferLayout> vtxBuffers,
wgpu::ShaderModule shader, const char* label) noexcept { wgpu::ShaderModule shader, const char* label) noexcept {
ZoneScoped; ZoneScoped;
const bool maskCockpit = config.stereoStencil && config.shaderConfig.exactScreenDepth;
const wgpu::StencilFaceState stencil{
.compare = maskCockpit ? wgpu::CompareFunction::Equal : wgpu::CompareFunction::Always,
};
const wgpu::DepthStencilState depthStencil{ const wgpu::DepthStencilState depthStencil{
.format = g_graphicsConfig.depthFormat, .format = config.stereoStencil ? wgpu::TextureFormat::Depth24PlusStencil8 : g_graphicsConfig.depthFormat,
.depthWriteEnabled = config.depthUpdate, .depthWriteEnabled = config.depthUpdate,
.depthCompare = config.depthCompare ? to_compare_function(config.depthFunc) : wgpu::CompareFunction::Always, .depthCompare = config.depthCompare ? to_compare_function(config.depthFunc) : wgpu::CompareFunction::Always,
.stencilFront = stencil,
.stencilBack = stencil,
.stencilReadMask = 1,
.stencilWriteMask = 0,
}; };
const auto blendState = to_blend_state(config.blendMode, config.blendFacSrc, config.blendFacDst, config.blendOp, const auto blendState = to_blend_state(config.blendMode, config.blendFacSrc, config.blendFacDst, config.blendOp,
config.pixelFmt, config.dstAlpha); config.pixelFmt, config.dstAlpha);
+6 -2
View File
@@ -11,6 +11,9 @@ struct DrawData {
// Same GX state with exact fragment-depth export enabled. Bound only when // Same GX state with exact fragment-depth export enabled. Bound only when
// this draw is actually reprojected onto the VR virtual screen. // this draw is actually reprojected onto the VR virtual screen.
gfx::PipelineRef exactScreenDepthPipeline; gfx::PipelineRef exactScreenDepthPipeline;
// Eye depth/stencil format siblings. Shader modules are shared with mono.
gfx::PipelineRef stereoPipeline = 0;
gfx::PipelineRef stereoScreenPipeline = 0;
gfx::Range vertRange; gfx::Range vertRange;
gfx::Range idxRange; gfx::Range idxRange;
gfx::Range uniformRange; gfx::Range uniformRange;
@@ -29,7 +32,7 @@ struct DrawData {
std::optional<gfx::stereo_replay::SubviewRect> screenRect; std::optional<gfx::stereo_replay::SubviewRect> screenRect;
}; };
constexpr uint32_t GXPipelineConfigVersion = 20; constexpr uint32_t GXPipelineConfigVersion = 21;
constexpr GXFogType effective_pipeline_fog_type(GXFogType fogType, GXZTexOp zTextureOp, bool zCompLocBeforeTex, constexpr GXFogType effective_pipeline_fog_type(GXFogType fogType, GXZTexOp zTextureOp, bool zCompLocBeforeTex,
GXBlendMode blendMode, GXLogicOp logicOp) noexcept { GXBlendMode blendMode, GXLogicOp logicOp) noexcept {
@@ -41,6 +44,7 @@ constexpr GXFogType effective_pipeline_fog_type(GXFogType fogType, GXZTexOp zTex
struct PipelineConfig { struct PipelineConfig {
uint32_t version = GXPipelineConfigVersion; uint32_t version = GXPipelineConfigVersion;
uint32_t msaaSamples = 1; uint32_t msaaSamples = 1;
uint32_t stereoStencil = 0;
ShaderConfig shaderConfig; ShaderConfig shaderConfig;
GXCompare depthFunc; GXCompare depthFunc;
GXCullMode cullMode; GXCullMode cullMode;
@@ -61,7 +65,7 @@ inline bool valid_pipeline_config(const PipelineConfig& config) noexcept {
}; };
const bool validSamples = const bool validSamples =
config.msaaSamples == 1 || config.msaaSamples == 2 || config.msaaSamples == 4 || config.msaaSamples == 8; config.msaaSamples == 1 || config.msaaSamples == 2 || config.msaaSamples == 4 || config.msaaSamples == 8;
return config.version == GXPipelineConfigVersion && validSamples && in_range(config.depthFunc, GX_ALWAYS) && return config.version == GXPipelineConfigVersion && validSamples && config.stereoStencil <= 1 && in_range(config.depthFunc, GX_ALWAYS) &&
in_range(config.cullMode, GX_CULL_ALL) && in_range(config.blendMode, GX_BM_SUBTRACT) && in_range(config.cullMode, GX_CULL_ALL) && in_range(config.blendMode, GX_BM_SUBTRACT) &&
in_range(config.blendFacSrc, GX_BL_INVDSTALPHA) && in_range(config.blendFacDst, GX_BL_INVDSTALPHA) && in_range(config.blendFacSrc, GX_BL_INVDSTALPHA) && in_range(config.blendFacDst, GX_BL_INVDSTALPHA) &&
in_range(config.blendOp, GX_LO_SET) && in_range(config.pixelFmt, GX_PF_YUV420); in_range(config.blendOp, GX_LO_SET) && in_range(config.pixelFmt, GX_PF_YUV420);
+43 -5
View File
@@ -41,7 +41,7 @@ int main() {
native.nativeWheel=false; native.nativeWheel=false;
if(gfx::cockpit::geometry(native).empty()) return 1; if(gfx::cockpit::geometry(native).empty()) return 1;
for(bool bike : {false,true}) for(bool original : {false,true}) for(uint32_t samples : {1u,4u}) for(bool bike : {false,true}) for(bool original : {false,true}) for(uint32_t samples : {1u,4u})
for(int coverage : {0,1,2}) for(bool reversed : {false,true}) for(uint32_t eyeIndex : {0u,1u}) { for(bool hud : {false,true}) for(int coverage : {0,1,2}) for(bool reversed : {false,true}) for(uint32_t eyeIndex : {0u,1u}) {
const bool occluded=coverage==1; const bool occluded=coverage==1;
gfx::StereoReplayFrame frame{}; gfx::StereoReplayFrame frame{};
frame.cockpit.unitsPerMeter=100; frame.cockpit.unitsPerMeter=100;
@@ -60,10 +60,10 @@ int main() {
auto output=g_device.CreateTexture(&td); auto output=g_device.CreateTexture(&td);
td.sampleCount=samples;td.usage=wgpu::TextureUsage::RenderAttachment; td.sampleCount=samples;td.usage=wgpu::TextureUsage::RenderAttachment;
auto color=g_device.CreateTexture(&td); auto color=g_device.CreateTexture(&td);
td.format=wgpu::TextureFormat::Depth32Float;auto depth=g_device.CreateTexture(&td); td.format=wgpu::TextureFormat::Depth24PlusStencil8;auto depth=g_device.CreateTexture(&td);
auto& eye=frame.eyes[eyeIndex];eye.target.colorView=samples==1?output.CreateView():color.CreateView(); auto& eye=frame.eyes[eyeIndex];eye.target.colorView=samples==1?output.CreateView():color.CreateView();
if(samples>1) eye.target.resolveView=output.CreateView(); if(samples>1) eye.target.resolveView=output.CreateView();
eye.target.depthView=depth.CreateView();eye.target.size={512,512,1};eye.target.msaaSamples=samples; eye.target.depthFormat=td.format;eye.target.depthView=depth.CreateView();eye.target.size={512,512,1};eye.target.msaaSamples=samples;
eye.projection.m0[0]=1;eye.projection.m1[1]=1; eye.projection.m0[0]=1;eye.projection.m1[1]=1;
eye.projection.m0[2]=eyeIndex?0.06f:-0.06f; eye.projection.m0[2]=eyeIndex?0.06f:-0.06f;
auto view=gfx::cockpit::identity();view[7]=0.20f; auto view=gfx::cockpit::identity();view[7]=0.20f;
@@ -72,7 +72,8 @@ int main() {
const wgpu::RenderPassColorAttachment clear{.view=eye.target.colorView,.resolveTarget=eye.target.resolveView, const wgpu::RenderPassColorAttachment clear{.view=eye.target.colorView,.resolveTarget=eye.target.resolveView,
.loadOp=wgpu::LoadOp::Clear,.storeOp=wgpu::StoreOp::Store,.clearValue={0.06,0.09,0.13,1}}; .loadOp=wgpu::LoadOp::Clear,.storeOp=wgpu::StoreOp::Store,.clearValue={0.06,0.09,0.13,1}};
const wgpu::RenderPassDepthStencilAttachment sceneDepth{.view=eye.target.depthView, const wgpu::RenderPassDepthStencilAttachment sceneDepth{.view=eye.target.depthView,
.depthLoadOp=wgpu::LoadOp::Clear,.depthStoreOp=wgpu::StoreOp::Store,.depthClearValue=reversed?(occluded?0.8f:0.0f):(occluded?0.2f:1.0f)}; .depthLoadOp=wgpu::LoadOp::Clear,.depthStoreOp=wgpu::StoreOp::Store,.depthClearValue=reversed?(occluded?0.8f:0.0f):(occluded?0.2f:1.0f),
.stencilLoadOp=wgpu::LoadOp::Clear,.stencilStoreOp=wgpu::StoreOp::Store,.stencilClearValue=0};
const wgpu::RenderPassDescriptor pd{.colorAttachmentCount=1,.colorAttachments=&clear,.depthStencilAttachment=&sceneDepth}; const wgpu::RenderPassDescriptor pd{.colorAttachmentCount=1,.colorAttachments=&clear,.depthStencilAttachment=&sceneDepth};
auto pass=encoder.BeginRenderPass(&pd); auto pass=encoder.BeginRenderPass(&pd);
if(coverage==2) { if(coverage==2) {
@@ -88,7 +89,7 @@ int main() {
auto shader=g_device.CreateShaderModule(&md); auto shader=g_device.CreateShaderModule(&md);
const wgpu::ColorTargetState colorState{.format=wgpu::TextureFormat::RGBA8Unorm}; const wgpu::ColorTargetState colorState{.format=wgpu::TextureFormat::RGBA8Unorm};
const wgpu::FragmentState fragment{.module=shader,.entryPoint="fs",.targetCount=1,.targets=&colorState}; const wgpu::FragmentState fragment{.module=shader,.entryPoint="fs",.targetCount=1,.targets=&colorState};
const wgpu::DepthStencilState ds{.format=wgpu::TextureFormat::Depth32Float,.depthWriteEnabled=true,.depthCompare=wgpu::CompareFunction::Always}; const wgpu::DepthStencilState ds{.format=eye.target.depthFormat,.depthWriteEnabled=true,.depthCompare=wgpu::CompareFunction::Always};
wgpu::RenderPipelineDescriptor desc{};desc.vertex={.module=shader,.entryPoint="vs"}; wgpu::RenderPipelineDescriptor desc{};desc.vertex={.module=shader,.entryPoint="vs"};
desc.fragment=&fragment;desc.depthStencil=&ds;desc.multisample.count=samples; desc.fragment=&fragment;desc.depthStencil=&ds;desc.multisample.count=samples;
auto wall=g_device.CreateRenderPipeline(&desc);pass.SetPipeline(wall);pass.Draw(6); auto wall=g_device.CreateRenderPipeline(&desc);pass.SetPipeline(wall);pass.Draw(6);
@@ -96,6 +97,36 @@ int main() {
if(coverage==2) gfx::cockpit::render(encoder,frame,eyeIndex,reversed?gfx::cockpit::SceneDepth{0,2,true}:gfx::cockpit::SceneDepth{-1,-2,true},&pass); if(coverage==2) gfx::cockpit::render(encoder,frame,eyeIndex,reversed?gfx::cockpit::SceneDepth{0,2,true}:gfx::cockpit::SceneDepth{-1,-2,true},&pass);
pass.End(); pass.End();
if(coverage!=2) gfx::cockpit::render(encoder,frame,eyeIndex,reversed?gfx::cockpit::SceneDepth{0,2,true}:gfx::cockpit::SceneDepth{-1,-2,true}); if(coverage!=2) gfx::cockpit::render(encoder,frame,eyeIndex,reversed?gfx::cockpit::SceneDepth{0,2,true}:gfx::cockpit::SceneDepth{-1,-2,true});
if(hud) {
// An opaque black screen and coloured HUD with depth testing disabled used
// to overwrite the hands. Exercise a later pass too: the mask must survive.
const wgpu::RenderPassColorAttachment load{.view=eye.target.colorView,.resolveTarget=eye.target.resolveView,
.loadOp=wgpu::LoadOp::Load,.storeOp=wgpu::StoreOp::Store};
const wgpu::RenderPassDepthStencilAttachment loadDepth{.view=eye.target.depthView,
.depthLoadOp=wgpu::LoadOp::Load,.depthStoreOp=wgpu::StoreOp::Store,
.stencilLoadOp=wgpu::LoadOp::Load,.stencilStoreOp=wgpu::StoreOp::Store};
const wgpu::RenderPassDescriptor hudPass{.colorAttachmentCount=1,.colorAttachments=&load,.depthStencilAttachment=&loadDepth};
auto overlay=encoder.BeginRenderPass(&hudPass);
wgpu::ShaderSourceWGSL code{};
code.code=R"(
@vertex fn vs(@builtin(vertex_index) i:u32) -> @builtin(position) vec4f {
let p=array<vec2f,3>(vec2f(-1,-1),vec2f(3,-1),vec2f(-1,3));
return vec4f(p[i],0.5,1);
}
@fragment fn fs(@builtin(position) p:vec4f) -> @location(0) vec4f {
return select(vec4f(0,0,0,1),vec4f(1,0,0,1),p.x<256);
}
)";
wgpu::ShaderModuleDescriptor md{};md.nextInChain=&code;auto shader=g_device.CreateShaderModule(&md);
const wgpu::ColorTargetState colorState{.format=wgpu::TextureFormat::RGBA8Unorm};
const wgpu::FragmentState fragment{.module=shader,.entryPoint="fs",.targetCount=1,.targets=&colorState};
const wgpu::StencilFaceState mask{.compare=wgpu::CompareFunction::Equal};
const wgpu::DepthStencilState ds{.format=eye.target.depthFormat,.depthWriteEnabled=true,
.depthCompare=wgpu::CompareFunction::Always,.stencilFront=mask,.stencilBack=mask,.stencilReadMask=1,.stencilWriteMask=0};
wgpu::RenderPipelineDescriptor desc{};desc.vertex={.module=shader,.entryPoint="vs"};
desc.fragment=&fragment;desc.depthStencil=&ds;desc.multisample.count=samples;
auto screen=g_device.CreateRenderPipeline(&desc);overlay.SetPipeline(screen);overlay.Draw(3);overlay.End();
}
const wgpu::BufferDescriptor bd{.usage=wgpu::BufferUsage::CopyDst|wgpu::BufferUsage::MapRead,.size=512*512*4}; const wgpu::BufferDescriptor bd{.usage=wgpu::BufferUsage::CopyDst|wgpu::BufferUsage::MapRead,.size=512*512*4};
auto readback=g_device.CreateBuffer(&bd); auto readback=g_device.CreateBuffer(&bd);
const wgpu::TexelCopyTextureInfo src{.texture=output}; const wgpu::TexelCopyTextureInfo src{.texture=output};
@@ -109,6 +140,13 @@ int main() {
const auto* bytes=static_cast<const unsigned char*>(readback.GetConstMappedRange()); const auto* bytes=static_cast<const unsigned char*>(readback.GetConstMappedRange());
size_t bright=0; size_t bright=0;
for(size_t i=0;i<512*512;++i) if(bytes[4*i]>90&&bytes[4*i+1]>90&&bytes[4*i+2]>90) ++bright; for(size_t i=0;i<512*512;++i) if(bytes[4*i]>90&&bytes[4*i+1]>90&&bytes[4*i+2]>90) ++bright;
if(hud && !(bytes[0]>250 && bytes[1]==0 && bytes[2]==0)) {
std::cerr<<"HUD missing outside cockpit mask\n";++errors;
}
static size_t expectedBright[3][2][2]{};
auto& expected=expectedBright[coverage][reversed][eyeIndex];
if(!hud) expected=bright;
else if(bright+64<expected || bright>expected+64) { std::cerr<<"HUD changed visible cockpit pixels\n";++errors; }
if(occluded ? bright!=0 : bright<1000) { std::cerr<<"Incorrect hands/wheel occlusion\n";++errors; } if(occluded ? bright!=0 : bright<1000) { std::cerr<<"Incorrect hands/wheel occlusion\n";++errors; }
if(coverage==2) { if(coverage==2) {
size_t left=0,right=0; size_t left=0,right=0;