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
+147 -61

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
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
world's own depth mapping, so the kart and the track hide them and the HUD cannot
(`aurora-main/lib/gfx/cockpit.hpp`). The anchor also carries the frame's exact world scale
world's own depth mapping, so the kart and the track hide them. Visible cockpit samples
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
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;
if (target.color.texture && target.requestedWidth == width && target.requestedHeight == height &&
target.samples == samples && target.colorFormat == webgpu::g_graphicsConfig.surfaceConfiguration.format &&
target.depthFormat == webgpu::g_graphicsConfig.depthFormat) {
target.depthFormat == wgpu::TextureFormat::Depth24PlusStencil8) {
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);
}
// 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{
.label = eyeIndex == 0 ? "Stereo left eye depth" : "Stereo right eye depth",
.usage = wgpu::TextureUsage::RenderAttachment,
.dimension = wgpu::TextureDimension::e2D,
.size = target.color.size,
.format = webgpu::g_graphicsConfig.depthFormat,
.format = wgpu::TextureFormat::Depth24PlusStencil8,
.mipLevelCount = 1,
.sampleCount = samples,
};
target.depth.texture = g_device.CreateTexture(&depthDescriptor);
target.depth.view = target.depth.texture.CreateView();
target.depth.size = target.color.size;
target.depth.format = webgpu::g_graphicsConfig.depthFormat;
target.depth.format = wgpu::TextureFormat::Depth24PlusStencil8;
target.requestedWidth = width;
target.requestedHeight = height;
target.samples = samples;
@@ -768,6 +770,7 @@ gfx::StereoReplayFrame make_stereo_replay_frame(const AuroraStereoFrame& input,
.copySourceDepthView = owned.depth.view,
.size = owned.color.size,
.msaaSamples = webgpu::g_graphicsConfig.msaaSamples,
.depthFormat = owned.depth.format,
};
std::memcpy(&view.projection, input.eyes[eye].projection, sizeof(view.projection));
std::memcpy(&view.viewFromCenter, input.eyes[eye].viewFromCenter, sizeof(view.viewFromCenter));
@@ -505,6 +505,13 @@ void GXCopyTex(void* dest, GXBool clear) {
.clearAlpha = true,
.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},
});
}
+1 -1
View File
@@ -105,7 +105,7 @@ fn fs_main() -> FragmentOutput {
.targets = &colorTarget,
};
const wgpu::DepthStencilState depthStencil{
.format = g_graphicsConfig.depthFormat,
.format = config.stereoStencil ? wgpu::TextureFormat::Depth24PlusStencil8 : g_graphicsConfig.depthFormat,
.depthWriteEnabled = config.clearDepth,
.depthCompare = wgpu::CompareFunction::Always,
};
+3 -2
View File
@@ -7,6 +7,7 @@
namespace aurora::gfx::clear {
struct DrawData {
PipelineRef pipeline;
PipelineRef stereoPipeline = 0;
Range uniformRange;
wgpu::Color color;
float depth = 0.f;
@@ -18,14 +19,14 @@ struct DrawData {
ClipRect scissor{};
};
constexpr uint32_t ClearPipelineConfigVersion = 3;
constexpr uint32_t ClearPipelineConfigVersion = 4;
struct PipelineConfig {
uint32_t version = ClearPipelineConfigVersion;
uint32_t msaaSamples = 1;
bool clearColor = true;
bool clearAlpha = true;
bool clearDepth = true;
uint8_t _pad = 0;
bool stereoStencil = false;
};
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 bool pipelineReversedDepth=false;
inline wgpu::TextureFormat pipelineFormat{};
inline wgpu::TextureFormat pipelineFormat{}, pipelineDepthFormat{};
inline std::array<wgpu::Buffer,2> vertexBuffers;
inline std::array<uint64_t,2> vertexCapacity{};
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
// global reversed-Z convention. The final 1/d coefficient is authoritative.
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{};
source.code=R"(
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::ColorTargetState color{.format=format};
const wgpu::FragmentState fragment{.module=shader,.entryPoint="fs",.targetCount=1,.targets=&color};
const wgpu::DepthStencilState depth{.format=g_graphicsConfig.depthFormat,.depthWriteEnabled=true,
.depthCompare=reversedDepth?wgpu::CompareFunction::GreaterEqual:wgpu::CompareFunction::LessEqual};
const bool stencil=target.depthFormat==wgpu::TextureFormat::Depth24PlusStencil8;
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";
desc.vertex={.module=shader,.entryPoint="vs",.bufferCount=1,.buffers=&layout};
desc.fragment=&fragment;desc.depthStencil=&depth;desc.multisample.count=target.msaaSamples;
desc.primitive.topology=wgpu::PrimitiveTopology::TriangleList;
pipeline=g_device.CreateRenderPipeline(&desc);pipelineSamples=target.msaaSamples;pipelineFormat=format;
pipelineReversedDepth=reversedDepth;
pipelineReversedDepth=reversedDepth;pipelineDepthFormat=target.depthFormat;
}
const auto revision=meshRevision.load();
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,
.loadOp=wgpu::LoadOp::Load,.storeOp=wgpu::StoreOp::Store};
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};
auto pass=existingPass?*existingPass:cmd.BeginRenderPass(&pd);
pass.SetViewport(0,0,float(target.size.width),float(target.size.height),0,1);
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.SetStencilReference(0);
if(!existingPass) pass.End();
}
} // 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,
.clearDepth = clearDepth,
}),
.stereoPipeline = aurora::stereo_frame_provider_active() ? pipeline_ref(clear::PipelineConfig{
.msaaSamples = msaaSamples,
.clearColor = clearColor,
.clearAlpha = clearAlpha,
.clearDepth = clearDepth,
.stereoStencil = true,
}) : 0,
.color =
wgpu::Color{
.r = clearColorValue.x(),
@@ -1769,6 +1776,9 @@ static void render_impl(std::vector<RenderPass>& renderPasses, wgpu::CommandEnco
ZoneScoped;
// 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.
// 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) {
const auto& passInfo = renderPasses[i];
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{
.view = depthView,
.depthLoadOp = passInfo.clearDepth && !dropCopyClear ? wgpu::LoadOp::Clear : wgpu::LoadOp::Load,
.depthStoreOp = wgpu::StoreOp::Store,
.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
: invocation.stereoEye == 1 ? GpuTimingCategory::EyeRight
@@ -1839,6 +1854,7 @@ static void render_impl(std::vector<RenderPass>& renderPasses, wgpu::CommandEnco
.timestampWrites = gpu_timing_pass(timingCategory),
};
if (stereoStencil) stencilInitialized = true;
auto pass = cmd.BeginRenderPass(&renderPassDescriptor);
render_pass_impl(pass, renderPasses, i, invocation);
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) {
uniformOverride = &draw.gx.interpolatedUniformRanges[invocation.interpolatedFrame];
}
// Draw the VR cockpit against the world's depth before the first HUD
// draw can write a screen-plane depth over it, inside this open pass.
// Draw against world depth and mark visible cockpit samples before HUD
// depth replaces it. The screen pipelines reject those stencil samples.
if (invocation.cockpitFrame != nullptr && overrideTarget) {
if (draw.gx.uniformReplayLayout.perspective) {
*invocation.sceneDrawn = true;
@@ -2535,10 +2551,15 @@ static void render_pass_impl(const wgpu::RenderPassEncoder& pass, const std::vec
apply_viewport(fullEyeDraw);
}
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;
case ShaderType::Clear: {
auto clearDraw = draw.clear;
if (overrideTarget && invocation.target->depthFormat == wgpu::TextureFormat::Depth24PlusStencil8) {
clearDraw.pipeline = clearDraw.stereoPipeline;
}
if (multiplayer) {
const auto& sc = clearDraw.scissor;
if (clearDraw.copyClear ||
+1
View File
@@ -291,6 +291,7 @@ struct ReplayTarget {
wgpu::TextureView copySourceDepthView;
wgpu::Extent3D size{};
uint32_t msaaSamples = 1;
wgpu::TextureFormat depthFormat = wgpu::TextureFormat::Depth32Float;
};
struct StereoReplayEye {
+25 -36
View File
@@ -2206,6 +2206,10 @@ static ArrayRef<u16> offset_index_template(const CachedIndexTemplate& indexTempl
struct CachedPipelineState {
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;
// 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.
@@ -2231,6 +2235,7 @@ static const CachedPipelineState& cached_pipeline_state(const PipelineConfig& co
entry.config = config;
entry.state = {
.ref = gfx::pipeline_ref(config),
.config = &entry.config,
.configHash = hash,
.dstAlpha = config.dstAlpha,
.shaderInfo = build_shader_info(config.shaderConfig),
@@ -2273,37 +2278,20 @@ static const CachedPipelineState& resolve_pipeline_state(GXPrimitive prim, GXVtx
return state;
}
// Exact Screen Depth changes shader outputs but no GX pipeline state. Resolve a
// sibling pipeline only for orthographic draws that can actually reach the VR
// screen, and memoize it with the same state epoch as the ordinary pipeline.
static gfx::PipelineRef resolve_exact_screen_depth_pipeline(GXPrimitive prim, GXVtxFmt fmt) {
struct Memo {
gfx::PipelineRef ref = 0;
u32 generation = 0;
u32 sampleCount = 0;
GXPrimitive prim = static_cast<GXPrimitive>(0);
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;
const gfx::PipelineRef ref = gfx::pipeline_ref(config);
memo = Memo{
.ref = ref,
.generation = generation,
.sampleCount = sampleCount,
.prim = prim,
.fmt = fmt,
};
return ref;
// Lazily cache eye-format siblings alongside the ordinary pipeline. Steady-state
// draws only read the refs: no extra config population/hashing on the Quest CPU.
// Shader modules are shared by the depth-format variants.
static void resolve_replay_pipelines(const CachedPipelineState& state, bool screen) {
if (state.stereoRef && (!screen || state.stereoScreenRef)) return;
PipelineConfig config = *state.config;
config.stereoStencil = 1;
if (!state.stereoRef) state.stereoRef = gfx::pipeline_ref(config);
if (screen && !state.stereoScreenRef) {
config.shaderConfig.exactScreenDepth = 1;
state.stereoScreenRef = gfx::pipeline_ref(config);
config.stereoStencil = 0;
state.screenRef = gfx::pipeline_ref(config);
}
}
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 auto uniformRanges = build_uniform(info, vertRange.offset, ranges, drawIdentity, perspective, usedPnMtxMask);
const auto& replayLayout = uniformRanges.replayLayout;
const gfx::PipelineRef exactScreenDepthPipeline =
aurora::stereo_frame_provider_active() && !replayLayout.perspective && !replayLayout.nativeEfbEffect
? resolve_exact_screen_depth_pipeline(prim, fmt)
: 0;
const bool stereo = aurora::stereo_frame_provider_active();
const bool screen = !replayLayout.perspective && !replayLayout.nativeEfbEffect;
if (stereo) resolve_replay_pipelines(pipelineState, screen);
s_lastDrawRecordedInterpolation = interpolationIdentityActive;
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{
.pipeline = pipeline,
.exactScreenDepthPipeline = exactScreenDepthPipeline,
.exactScreenDepthPipeline = stereo && screen ? pipelineState.screenRef : 0,
.stereoPipeline = stereo ? pipelineState.stereoRef : 0,
.stereoScreenPipeline = stereo && screen ? pipelineState.stereoScreenRef : 0,
.vertRange = vertRange,
.idxRange = idxRange,
.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::ShaderModule shader, const char* label) noexcept {
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{
.format = g_graphicsConfig.depthFormat,
.format = config.stereoStencil ? wgpu::TextureFormat::Depth24PlusStencil8 : g_graphicsConfig.depthFormat,
.depthWriteEnabled = config.depthUpdate,
.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,
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
// this draw is actually reprojected onto the VR virtual screen.
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 idxRange;
gfx::Range uniformRange;
@@ -29,7 +32,7 @@ struct DrawData {
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,
GXBlendMode blendMode, GXLogicOp logicOp) noexcept {
@@ -41,6 +44,7 @@ constexpr GXFogType effective_pipeline_fog_type(GXFogType fogType, GXZTexOp zTex
struct PipelineConfig {
uint32_t version = GXPipelineConfigVersion;
uint32_t msaaSamples = 1;
uint32_t stereoStencil = 0;
ShaderConfig shaderConfig;
GXCompare depthFunc;
GXCullMode cullMode;
@@ -61,7 +65,7 @@ inline bool valid_pipeline_config(const PipelineConfig& config) noexcept {
};
const bool validSamples =
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.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);
+43 -5
View File
@@ -41,7 +41,7 @@ int main() {
native.nativeWheel=false;
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(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;
gfx::StereoReplayFrame frame{};
frame.cockpit.unitsPerMeter=100;
@@ -60,10 +60,10 @@ int main() {
auto output=g_device.CreateTexture(&td);
td.sampleCount=samples;td.usage=wgpu::TextureUsage::RenderAttachment;
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();
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[2]=eyeIndex?0.06f:-0.06f;
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,
.loadOp=wgpu::LoadOp::Clear,.storeOp=wgpu::StoreOp::Store,.clearValue={0.06,0.09,0.13,1}};
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};
auto pass=encoder.BeginRenderPass(&pd);
if(coverage==2) {
@@ -88,7 +89,7 @@ int main() {
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::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"};
desc.fragment=&fragment;desc.depthStencil=&ds;desc.multisample.count=samples;
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);
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(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};
auto readback=g_device.CreateBuffer(&bd);
const wgpu::TexelCopyTextureInfo src{.texture=output};
@@ -109,6 +140,13 @@ int main() {
const auto* bytes=static_cast<const unsigned char*>(readback.GetConstMappedRange());
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;
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(coverage==2) {
size_t left=0,right=0;