diff --git a/OPENXR.md b/OPENXR.md index 9b2763e..3c719f3 100644 --- a/OPENXR.md +++ b/OPENXR.md @@ -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. diff --git a/aurora-main/lib/aurora.cpp b/aurora-main/lib/aurora.cpp index 7685f44..3c74e95 100644 --- a/aurora-main/lib/aurora.cpp +++ b/aurora-main/lib/aurora.cpp @@ -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)); diff --git a/aurora-main/lib/dolphin/gx/GXFrameBuffer.cpp b/aurora-main/lib/dolphin/gx/GXFrameBuffer.cpp index 3a6662e..8303c57 100644 --- a/aurora-main/lib/dolphin/gx/GXFrameBuffer.cpp +++ b/aurora-main/lib/dolphin/gx/GXFrameBuffer.cpp @@ -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}, }); } diff --git a/aurora-main/lib/gfx/clear.cpp b/aurora-main/lib/gfx/clear.cpp index f5964f9..5fec493 100644 --- a/aurora-main/lib/gfx/clear.cpp +++ b/aurora-main/lib/gfx/clear.cpp @@ -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, }; diff --git a/aurora-main/lib/gfx/clear.hpp b/aurora-main/lib/gfx/clear.hpp index 47b5628..49a2af6 100644 --- a/aurora-main/lib/gfx/clear.hpp +++ b/aurora-main/lib/gfx/clear.hpp @@ -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); diff --git a/aurora-main/lib/gfx/cockpit.hpp b/aurora-main/lib/gfx/cockpit.hpp index d60ddc1..e3a6672 100644 --- a/aurora-main/lib/gfx/cockpit.hpp +++ b/aurora-main/lib/gfx/cockpit.hpp @@ -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 vertexBuffers; inline std::array 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 diff --git a/aurora-main/lib/gfx/common.cpp b/aurora-main/lib/gfx/common.cpp index 2d09ebe..2214df6 100644 --- a/aurora-main/lib/gfx/common.cpp +++ b/aurora-main/lib/gfx/common.cpp @@ -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& 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(invocation.replayLastPass)) { @@ -1821,11 +1831,16 @@ static void render_impl(std::vector& 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& 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 || diff --git a/aurora-main/lib/gfx/common.hpp b/aurora-main/lib/gfx/common.hpp index b86c6c8..7e7cc26 100644 --- a/aurora-main/lib/gfx/common.hpp +++ b/aurora-main/lib/gfx/common.hpp @@ -291,6 +291,7 @@ struct ReplayTarget { wgpu::TextureView copySourceDepthView; wgpu::Extent3D size{}; uint32_t msaaSamples = 1; + wgpu::TextureFormat depthFormat = wgpu::TextureFormat::Depth32Float; }; struct StereoReplayEye { diff --git a/aurora-main/lib/gx/command_processor.cpp b/aurora-main/lib/gx/command_processor.cpp index 1f1309f..b8eb13a 100644 --- a/aurora-main/lib/gx/command_processor.cpp +++ b/aurora-main/lib/gx/command_processor.cpp @@ -2206,6 +2206,10 @@ static ArrayRef 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(0); - GXVtxFmt fmt = static_cast(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, diff --git a/aurora-main/lib/gx/gx.cpp b/aurora-main/lib/gx/gx.cpp index b58e540..c85a2cb 100644 --- a/aurora-main/lib/gx/gx.cpp +++ b/aurora-main/lib/gx/gx.cpp @@ -1609,10 +1609,18 @@ static inline wgpu::PrimitiveState to_primitive_state(GXCullMode gx_cullMode) { wgpu::RenderPipeline build_pipeline(const PipelineConfig& config, ArrayRef 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); diff --git a/aurora-main/lib/gx/pipeline.hpp b/aurora-main/lib/gx/pipeline.hpp index e5567a0..ff6abd8 100644 --- a/aurora-main/lib/gx/pipeline.hpp +++ b/aurora-main/lib/gx/pipeline.hpp @@ -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 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); diff --git a/aurora-main/tests/cockpit_gpu_smoke.cpp b/aurora-main/tests/cockpit_gpu_smoke.cpp index a8189c2..00fc1c1 100644 --- a/aurora-main/tests/cockpit_gpu_smoke.cpp +++ b/aurora-main/tests/cockpit_gpu_smoke.cpp @@ -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(-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(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+64expected+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;