diff --git a/cmake/aurora_gx.cmake b/cmake/aurora_gx.cmake index 91de0bf..f59423f 100644 --- a/cmake/aurora_gx.cmake +++ b/cmake/aurora_gx.cmake @@ -1,6 +1,7 @@ add_library(aurora_gx STATIC lib/gfx/clear.cpp lib/gfx/common.cpp + lib/gfx/depth_peek.cpp lib/gfx/pipeline_cache.cpp lib/gfx/dds_io.cpp lib/gfx/tex_copy_conv.cpp @@ -16,6 +17,7 @@ add_library(aurora_gx STATIC lib/gx/shader_info.cpp lib/dolphin/gx/GXBump.cpp lib/dolphin/gx/GXCull.cpp + lib/dolphin/gx/GXCpu2Efb.cpp lib/dolphin/gx/GXDispList.cpp lib/dolphin/gx/GXDraw.cpp lib/dolphin/gx/GXExtra.cpp diff --git a/lib/aurora.cpp b/lib/aurora.cpp index 5a4e6a5..9ba514a 100644 --- a/lib/aurora.cpp +++ b/lib/aurora.cpp @@ -294,6 +294,7 @@ void end_frame() noexcept { const wgpu::CommandBufferDescriptor cmdBufDescriptor{.label = "Redraw command buffer"}; const auto buffer = encoder.Finish(&cmdBufDescriptor); g_queue.Submit(1, &buffer); + gfx::after_submit(); auto presentStatus = g_surface.Present(); if (presentStatus != wgpu::Status::Success) { Log.warn("Surface present failed: {}", static_cast(presentStatus)); diff --git a/lib/dolphin/gx/GXCpu2Efb.cpp b/lib/dolphin/gx/GXCpu2Efb.cpp new file mode 100644 index 0000000..9815bd4 --- /dev/null +++ b/lib/dolphin/gx/GXCpu2Efb.cpp @@ -0,0 +1,20 @@ +#include "gx.hpp" + +#include "../../gfx/depth_peek.hpp" + +#include + +void GXPeekZ(u16 x, u16 y, u32* z) { + aurora::gfx::depth_peek::poll(); + + if (z != nullptr) { + u32 value = 0; + if (aurora::gfx::depth_peek::read_latest(x, y, value)) { + *z = value; + } else { + *z = g_gxState.clearDepth & 0x00ffffffu; + } + } + + aurora::gfx::depth_peek::request_snapshot(); +} diff --git a/lib/gfx/common.cpp b/lib/gfx/common.cpp index 0ac03e0..3ee901c 100644 --- a/lib/gfx/common.cpp +++ b/lib/gfx/common.cpp @@ -1,6 +1,7 @@ #include "common.hpp" #include "clear.hpp" +#include "depth_peek.hpp" #include "../internal.hpp" #include "../webgpu/gpu.hpp" #include "../gx/pipeline.hpp" @@ -454,6 +455,7 @@ PipelineRef pipeline_ref(const gx::PipelineConfig& config) { void initialize() { g_frameIndex = 0; + depth_peek::initialize(); tex_copy_conv::initialize(); tex_palette_conv::initialize(); texture_replacement::initialize(); @@ -574,6 +576,7 @@ void initialize() { void shutdown() { shutdown_pipeline_cache(); + depth_peek::shutdown(); tex_copy_conv::shutdown(); tex_palette_conv::shutdown(); texture_replacement::shutdown(); @@ -771,6 +774,10 @@ void render(wgpu::CommandEncoder& cmd) { render_pass(pass, i); pass.End(); + if (i == g_renderPasses.size() - 1) { + depth_peek::encode_frame_snapshot(cmd, passInfo.copySourceDepthView, passInfo.targetSize, passInfo.msaaSamples); + } + if (passInfo.resolveTarget) { const auto& dstSize = passInfo.resolveTarget->size; const bool needsConversion = tex_copy_conv::needs_conversion(passInfo.resolveFormat); @@ -829,6 +836,8 @@ void render(wgpu::CommandEncoder& cmd) { #endif } +void after_submit() noexcept { depth_peek::after_submit(); } + void render_pass(const wgpu::RenderPassEncoder& pass, u32 idx) { g_currentPipeline = UINTPTR_MAX; #ifdef AURORA_GFX_DEBUG_GROUPS diff --git a/lib/gfx/common.hpp b/lib/gfx/common.hpp index 01dd585..96584be 100644 --- a/lib/gfx/common.hpp +++ b/lib/gfx/common.hpp @@ -239,6 +239,7 @@ void end_frame(const wgpu::CommandEncoder& cmd); uint32_t current_frame() noexcept; void render(wgpu::CommandEncoder& cmd); void render_pass(const wgpu::RenderPassEncoder& pass, uint32_t idx); +void after_submit() noexcept; void map_staging_buffer(); void resolve_pass(TextureHandle texture, ClipRect rect, bool clearColor, bool clearAlpha, bool clearDepth, Vec4 clearColorValue, float clearDepthValue, GXTexFmt resolveFormat = GX_TF_RGBA8); diff --git a/lib/gfx/depth_peek.cpp b/lib/gfx/depth_peek.cpp new file mode 100644 index 0000000..66a7d5e --- /dev/null +++ b/lib/gfx/depth_peek.cpp @@ -0,0 +1,469 @@ +#include "depth_peek.hpp" + +#include "../dolphin/vi/vi_internal.hpp" +#include "../gx/gx.hpp" +#include "../webgpu/gpu.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +namespace aurora::gfx::depth_peek { +namespace { +Module Log("aurora::gfx::depth_peek"); + +using webgpu::g_device; +using webgpu::g_instance; +using webgpu::g_queue; + +using Clock = std::chrono::steady_clock; + +constexpr size_t SlotCount = 3; +constexpr uint32_t WorkgroupSizeX = 8; +constexpr uint32_t WorkgroupSizeY = 8; +constexpr uint32_t DepthPeekSnapshotHz = 30; +constexpr auto SnapshotInterval = std::chrono::nanoseconds{1'000'000'000 / DepthPeekSnapshotHz}; + +struct Params { + uint32_t dstWidth = 0; + uint32_t dstHeight = 0; + uint32_t srcWidth = 0; + uint32_t srcHeight = 0; + float offsetX = 0.f; + float offsetY = 0.f; + float scaleX = 1.f; + float scaleY = 1.f; +}; +static_assert(sizeof(Params) == 32); + +struct LatestSnapshot { + uint32_t width = 0; + uint32_t height = 0; + std::vector data; +}; + +enum class SlotState : uint8_t { + Available, + CopySubmitted, + MapPending, +}; + +struct Slot { + wgpu::Buffer storageBuffer; + wgpu::Buffer readbackBuffer; + wgpu::Buffer paramsBuffer; + uint32_t width = 0; + uint32_t height = 0; + uint64_t byteSize = 0; + SlotState state = SlotState::Available; +}; + +struct PendingMap { + size_t slotIdx = 0; + wgpu::Buffer readbackBuffer; + uint64_t byteSize = 0; +}; + +std::array g_slots; +size_t g_nextSlot = 0; +wgpu::BindGroupLayout g_bindGroupLayout; +wgpu::ComputePipeline g_pipeline; +bool g_snapshotRequested = false; +Clock::time_point g_nextSnapshotTime; +LatestSnapshot g_latest; +std::mutex g_mutex; + +constexpr std::string_view ShaderPreamble = R"( +struct Params { + dstSize: vec2u, + srcSize: vec2u, + offset: vec2f, + scale: vec2f, +}; + +@group(0) @binding(1) var out_z: array; +@group(0) @binding(2) var params: Params; +)"sv; + +constexpr std::string_view ReversedZBody = R"( +fn gx_z24(depth: f32) -> u32 { + return min(u32(clamp(1.0 - depth, 0.0, 1.0) * 16777215.0 + 0.5), 0x00ffffffu); +} +)"sv; + +constexpr std::string_view ForwardZBody = R"( +fn gx_z24(depth: f32) -> u32 { + return min(u32(clamp(depth, 0.0, 1.0) * 16777215.0 + 0.5), 0x00ffffffu); +} +)"sv; + +constexpr std::string_view ShaderMain = R"( +@group(0) @binding(0) var src: texture_depth_2d; + +fn load_depth(coord: vec2i) -> f32 { + return textureLoad(src, coord, 0); +} + +@compute @workgroup_size(8, 8, 1) +fn cs_main(@builtin(global_invocation_id) id: vec3u) { + if (id.x >= params.dstSize.x || id.y >= params.dstSize.y) { + return; + } + + let dstCenter = vec2f(vec2u(id.xy)) + vec2f(0.5, 0.5); + let srcPixel = clamp(vec2i(floor(params.offset + dstCenter * params.scale)), vec2i(0, 0), + vec2i(params.srcSize) - vec2i(1, 1)); + let depth = load_depth(srcPixel); + out_z[id.y * params.dstSize.x + id.x] = gx_z24(depth); +} +)"sv; + +std::string build_shader_source() { + std::string source; + source.reserve(ShaderPreamble.size() + ReversedZBody.size() + ShaderMain.size()); + source += ShaderPreamble; + source += gx::UseReversedZ ? ReversedZBody : ForwardZBody; + source += ShaderMain; + return source; +} + +wgpu::ComputePipeline create_pipeline(const wgpu::BindGroupLayout& bindGroupLayout, const char* label) { + const auto shaderSource = build_shader_source(); + const wgpu::ShaderSourceWGSL wgslSource{wgpu::ShaderSourceWGSL::Init{ + .code = shaderSource.c_str(), + }}; + const wgpu::ShaderModuleDescriptor moduleDescriptor{ + .nextInChain = &wgslSource, + .label = label, + }; + const auto module = g_device.CreateShaderModule(&moduleDescriptor); + + const wgpu::PipelineLayoutDescriptor layoutDescriptor{ + .bindGroupLayoutCount = 1, + .bindGroupLayouts = &bindGroupLayout, + }; + const auto pipelineLayout = g_device.CreatePipelineLayout(&layoutDescriptor); + const wgpu::ComputePipelineDescriptor pipelineDescriptor{ + .label = label, + .layout = pipelineLayout, + .compute = + wgpu::ComputeState{ + .module = module, + .entryPoint = "cs_main", + }, + }; + return g_device.CreateComputePipeline(&pipelineDescriptor); +} + +wgpu::BindGroupLayout create_bind_group_layout(const char* label) { + constexpr std::array entries{ + wgpu::BindGroupLayoutEntry{ + .binding = 0, + .visibility = wgpu::ShaderStage::Compute, + .texture = + wgpu::TextureBindingLayout{ + .sampleType = wgpu::TextureSampleType::Depth, + .viewDimension = wgpu::TextureViewDimension::e2D, + }, + }, + wgpu::BindGroupLayoutEntry{ + .binding = 1, + .visibility = wgpu::ShaderStage::Compute, + .buffer = + wgpu::BufferBindingLayout{ + .type = wgpu::BufferBindingType::Storage, + }, + }, + wgpu::BindGroupLayoutEntry{ + .binding = 2, + .visibility = wgpu::ShaderStage::Compute, + .buffer = + wgpu::BufferBindingLayout{ + .type = wgpu::BufferBindingType::Uniform, + }, + }, + }; + const wgpu::BindGroupLayoutDescriptor descriptor{ + .label = label, + .entryCount = entries.size(), + .entries = entries.data(), + }; + return g_device.CreateBindGroupLayout(&descriptor); +} + +Params make_params(wgpu::Extent3D sourceSize, Vec2 dstSize) noexcept { + Params params{ + .dstWidth = dstSize.x, + .dstHeight = dstSize.y, + .srcWidth = sourceSize.width, + .srcHeight = sourceSize.height, + }; + + if (gx::g_gxState.viewportPolicy == AURORA_VIEWPORT_NATIVE) { + return params; + } + + const auto logicalSize = vi::configured_fb_size(); + if (logicalSize.x == 0 || logicalSize.y == 0 || sourceSize.width == 0 || sourceSize.height == 0) { + return params; + } + + const bool stretch = gx::g_gxState.viewportPolicy == AURORA_VIEWPORT_STRETCH; + const float scaleX = static_cast(sourceSize.width) / static_cast(logicalSize.x); + const float scaleY = static_cast(sourceSize.height) / static_cast(logicalSize.y); + const float scale = std::min(scaleX, scaleY); + params.scaleX = stretch ? scaleX : scale; + params.scaleY = stretch ? scaleY : scale; + params.offsetX = + stretch ? 0.f : (static_cast(sourceSize.width) - static_cast(logicalSize.x) * scale) * 0.5f; + params.offsetY = + stretch ? 0.f : (static_cast(sourceSize.height) - static_cast(logicalSize.y) * scale) * 0.5f; + return params; +} + +bool ensure_slot(Slot& slot, uint32_t width, uint32_t height) { + const uint64_t byteSize = static_cast(width) * static_cast(height) * sizeof(uint32_t); + if (slot.storageBuffer && slot.width == width && slot.height == height && slot.byteSize == byteSize) { + return true; + } + + slot.storageBuffer = {}; + slot.readbackBuffer = {}; + slot.paramsBuffer = {}; + slot.width = width; + slot.height = height; + slot.byteSize = byteSize; + + if (byteSize == 0 || byteSize > UINT32_MAX) { + return false; + } + + const wgpu::BufferDescriptor storageDescriptor{ + .label = "Depth Peek Storage Buffer", + .usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc, + .size = byteSize, + }; + slot.storageBuffer = g_device.CreateBuffer(&storageDescriptor); + + const wgpu::BufferDescriptor readbackDescriptor{ + .label = "Depth Peek Readback Buffer", + .usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst, + .size = byteSize, + }; + slot.readbackBuffer = g_device.CreateBuffer(&readbackDescriptor); + + const wgpu::BufferDescriptor paramsDescriptor{ + .label = "Depth Peek Params Buffer", + .usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst, + .size = sizeof(Params), + }; + slot.paramsBuffer = g_device.CreateBuffer(¶msDescriptor); + return slot.storageBuffer && slot.readbackBuffer && slot.paramsBuffer; +} + +Slot* find_available_slot(uint32_t width, uint32_t height) { + for (size_t i = 0; i < g_slots.size(); ++i) { + const size_t idx = (g_nextSlot + i) % g_slots.size(); + auto& slot = g_slots[idx]; + if (slot.state != SlotState::Available) { + continue; + } + if (!ensure_slot(slot, width, height)) { + continue; + } + g_nextSlot = (idx + 1) % g_slots.size(); + return &slot; + } + return nullptr; +} + +void complete_slot(size_t slotIdx, wgpu::MapAsyncStatus status, wgpu::StringView message) { + std::lock_guard lock{g_mutex}; + auto& slot = g_slots[slotIdx]; + if (status == wgpu::MapAsyncStatus::Success) { + const auto valueCount = static_cast(slot.width) * static_cast(slot.height); + const auto* mapped = + static_cast(slot.readbackBuffer.GetConstMappedRange(0, valueCount * sizeof(uint32_t))); + if (mapped != nullptr) { + g_latest.width = slot.width; + g_latest.height = slot.height; + g_latest.data.assign(mapped, mapped + valueCount); + } + slot.readbackBuffer.Unmap(); + } else if (status != wgpu::MapAsyncStatus::CallbackCancelled && status != wgpu::MapAsyncStatus::Aborted) { + Log.warn("Depth Peek readback mapping failed {}: {}", magic_enum::enum_name(status), message); + } + slot.state = SlotState::Available; +} +} // namespace + +void initialize() { + g_bindGroupLayout = create_bind_group_layout("Depth Peek Bind Group Layout"); + g_pipeline = create_pipeline(g_bindGroupLayout, "Depth Peek Pipeline"); +} + +void shutdown() { + testing::reset(); + g_pipeline = {}; + g_bindGroupLayout = {}; + for (auto& slot : g_slots) { + slot = {}; + } +} + +void request_snapshot() noexcept { + std::lock_guard lock{g_mutex}; + g_snapshotRequested = true; +} + +bool read_latest(uint16_t x, uint16_t y, uint32_t& z) noexcept { + std::lock_guard lock{g_mutex}; + if (x >= g_latest.width || y >= g_latest.height || g_latest.data.empty()) { + return false; + } + z = g_latest.data[static_cast(y) * g_latest.width + x] & 0x00ffffffu; + return true; +} + +void poll() noexcept { + if (g_instance) { + g_instance.ProcessEvents(); + } +} + +void encode_frame_snapshot(const wgpu::CommandEncoder& cmd, const wgpu::TextureView& depthView, + wgpu::Extent3D sourceSize, uint32_t msaaSamples) noexcept { + ZoneScoped; + const auto now = Clock::now(); + { + std::lock_guard lock{g_mutex}; + if (!g_snapshotRequested || now < g_nextSnapshotTime) { + return; + } + g_snapshotRequested = false; + g_nextSnapshotTime = now + SnapshotInterval; + } + + const auto dstSize = vi::configured_fb_size(); + if (!depthView || dstSize.x == 0 || dstSize.y == 0 || sourceSize.width == 0 || sourceSize.height == 0) { + return; + } + if (msaaSamples > 1) { + Log.fatal("Depth Peek from multisampled EFB targets is not supported"); + } + + const Params params = make_params(sourceSize, dstSize); + wgpu::Buffer storageBuffer; + wgpu::Buffer readbackBuffer; + wgpu::Buffer paramsBuffer; + uint64_t byteSize = 0; + { + std::lock_guard lock{g_mutex}; + auto* slot = find_available_slot(dstSize.x, dstSize.y); + if (slot == nullptr) { + return; + } + slot->state = SlotState::CopySubmitted; + storageBuffer = slot->storageBuffer; + readbackBuffer = slot->readbackBuffer; + paramsBuffer = slot->paramsBuffer; + byteSize = slot->byteSize; + } + + g_queue.WriteBuffer(paramsBuffer, 0, ¶ms, sizeof(params)); + + const std::array bindGroupEntries{ + wgpu::BindGroupEntry{ + .binding = 0, + .textureView = depthView, + }, + wgpu::BindGroupEntry{ + .binding = 1, + .buffer = storageBuffer, + .size = byteSize, + }, + wgpu::BindGroupEntry{ + .binding = 2, + .buffer = paramsBuffer, + .size = sizeof(Params), + }, + }; + const wgpu::BindGroupDescriptor bindGroupDescriptor{ + .label = "Depth Peek Bind Group", + .layout = g_bindGroupLayout, + .entryCount = bindGroupEntries.size(), + .entries = bindGroupEntries.data(), + }; + const auto bindGroup = g_device.CreateBindGroup(&bindGroupDescriptor); + + const wgpu::ComputePassDescriptor passDescriptor{ + .label = "Depth Peek Compute Pass", + }; + const auto pass = cmd.BeginComputePass(&passDescriptor); + pass.SetPipeline(g_pipeline); + pass.SetBindGroup(0, bindGroup); + pass.DispatchWorkgroups((dstSize.x + WorkgroupSizeX - 1) / WorkgroupSizeX, + (dstSize.y + WorkgroupSizeY - 1) / WorkgroupSizeY); + pass.End(); + + cmd.CopyBufferToBuffer(storageBuffer, 0, readbackBuffer, 0, byteSize); +} + +void after_submit() noexcept { + std::vector pendingMaps; + { + std::lock_guard lock{g_mutex}; + for (size_t i = 0; i < g_slots.size(); ++i) { + auto& slot = g_slots[i]; + if (slot.state != SlotState::CopySubmitted) { + continue; + } + slot.state = SlotState::MapPending; + pendingMaps.push_back({ + .slotIdx = i, + .readbackBuffer = slot.readbackBuffer, + .byteSize = slot.byteSize, + }); + } + } + + for (const auto& pending : pendingMaps) { + pending.readbackBuffer.MapAsync( + wgpu::MapMode::Read, 0, pending.byteSize, wgpu::CallbackMode::AllowSpontaneous, + [slotIdx = pending.slotIdx](wgpu::MapAsyncStatus status, wgpu::StringView message) { + complete_slot(slotIdx, status, message); + }); + } +} + +namespace testing { +void reset() noexcept { + std::lock_guard lock{g_mutex}; + g_snapshotRequested = false; + g_nextSlot = 0; + g_nextSnapshotTime = {}; + g_latest = {}; + for (auto& slot : g_slots) { + slot.state = SlotState::Available; + } +} + +bool snapshot_requested() noexcept { + std::lock_guard lock{g_mutex}; + return g_snapshotRequested; +} + +void set_latest(uint32_t width, uint32_t height, const std::vector& data) { + std::lock_guard lock{g_mutex}; + g_latest.width = width; + g_latest.height = height; + g_latest.data = data; +} +} // namespace testing + +} // namespace aurora::gfx::depth_peek diff --git a/lib/gfx/depth_peek.hpp b/lib/gfx/depth_peek.hpp new file mode 100644 index 0000000..0f6fec9 --- /dev/null +++ b/lib/gfx/depth_peek.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "common.hpp" + +#include + +namespace aurora::gfx::depth_peek { + +void initialize(); +void shutdown(); + +void request_snapshot() noexcept; +bool read_latest(uint16_t x, uint16_t y, uint32_t& z) noexcept; +void poll() noexcept; + +void encode_frame_snapshot(const wgpu::CommandEncoder& cmd, const wgpu::TextureView& depthView, + wgpu::Extent3D sourceSize, uint32_t msaaSamples) noexcept; +void after_submit() noexcept; + +namespace testing { +void reset() noexcept; +bool snapshot_requested() noexcept; +void set_latest(uint32_t width, uint32_t height, const std::vector& data); +} // namespace testing + +} // namespace aurora::gfx::depth_peek diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b3313db..9b04337 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -20,6 +20,7 @@ if (AURORA_ENABLE_GX) # GX API implementations (encoders) ../lib/dolphin/gx/GXBump.cpp ../lib/dolphin/gx/GXCull.cpp + ../lib/dolphin/gx/GXCpu2Efb.cpp ../lib/dolphin/gx/GXDispList.cpp ../lib/dolphin/gx/GXDraw.cpp ../lib/dolphin/gx/GXExtra.cpp diff --git a/tests/gx_fifo_test.cpp b/tests/gx_fifo_test.cpp index a4488db..416eca4 100644 --- a/tests/gx_fifo_test.cpp +++ b/tests/gx_fifo_test.cpp @@ -2972,6 +2972,37 @@ TEST_F(GXFifoTest, CopyClear_MaxDepth) { EXPECT_EQ(g_gxState.clearDepth, 0xFFFFFFu); } +TEST_F(GXFifoTest, PeekZ_ReturnsClearDepthFallbackAndRequestsSnapshot) { + g_gxState.clearDepth = 0x123456; + + u32 z = 0; + GXPeekZ(10, 20, &z); + + EXPECT_EQ(z, 0x123456u); + EXPECT_TRUE(aurora::gfx::depth_peek::testing::snapshot_requested()); +} + +TEST_F(GXFifoTest, PeekZ_ReturnsLatestCompletedSnapshot) { + aurora::gfx::depth_peek::testing::set_latest(2, 2, {0x000001, 0x000002, 0x000003, 0x01000004}); + + u32 z = 0; + GXPeekZ(1, 1, &z); + + EXPECT_EQ(z, 0x000004u); + EXPECT_TRUE(aurora::gfx::depth_peek::testing::snapshot_requested()); +} + +TEST_F(GXFifoTest, PeekZ_OutOfRangeReturnsClearDepthFallback) { + g_gxState.clearDepth = 0xabcdef; + aurora::gfx::depth_peek::testing::set_latest(1, 1, {0x000001}); + + u32 z = 0; + GXPeekZ(1, 0, &z); + + EXPECT_EQ(z, 0xabcdefu); + EXPECT_TRUE(aurora::gfx::depth_peek::testing::snapshot_requested()); +} + // ============================================================================ // Composite tests (multiple state changes in a single FIFO stream) // ============================================================================ diff --git a/tests/gx_test_common.hpp b/tests/gx_test_common.hpp index 0029783..cd2ccfa 100644 --- a/tests/gx_test_common.hpp +++ b/tests/gx_test_common.hpp @@ -7,6 +7,7 @@ #include "gx/gx.hpp" #include "gx/fifo.hpp" #include "gx/command_processor.hpp" +#include "gfx/depth_peek.hpp" #include #include @@ -20,6 +21,7 @@ protected: GXInit(nullptr, 0); aurora::gx::fifo::clear_buffer(); aurora::gx::g_gxState = aurora::gx::GXState{}; + aurora::gfx::depth_peek::testing::reset(); } // Copy the internal FIFO buffer contents and clear it diff --git a/tests/gx_test_stubs.cpp b/tests/gx_test_stubs.cpp index c6f57cf..8240c12 100644 --- a/tests/gx_test_stubs.cpp +++ b/tests/gx_test_stubs.cpp @@ -5,6 +5,7 @@ #include "gx/gx.hpp" #include "gfx/clear.hpp" #include "gfx/common.hpp" +#include "gfx/depth_peek.hpp" #include "gfx/tex_copy_conv.hpp" #include "gfx/tex_palette_conv.hpp" #include "gfx/texture.hpp" @@ -175,6 +176,48 @@ void end_offscreen() {} bool is_offscreen() noexcept { return false; } } // namespace aurora::gfx +namespace aurora::gfx::depth_peek { +namespace { +bool s_snapshotRequested = false; +uint32_t s_width = 0; +uint32_t s_height = 0; +std::vector s_data; +} // namespace + +void initialize() {} +void shutdown() {} +void request_snapshot() noexcept { s_snapshotRequested = true; } +void poll() noexcept {} +void encode_frame_snapshot(const wgpu::CommandEncoder& cmd, const wgpu::TextureView& depthView, + wgpu::Extent3D sourceSize, uint32_t msaaSamples) noexcept {} +void after_submit() noexcept {} + +bool read_latest(uint16_t x, uint16_t y, uint32_t& z) noexcept { + if (x >= s_width || y >= s_height || s_data.empty()) { + return false; + } + z = s_data[static_cast(y) * s_width + x] & 0x00ffffffu; + return true; +} + +namespace testing { +void reset() noexcept { + s_snapshotRequested = false; + s_width = 0; + s_height = 0; + s_data.clear(); +} + +bool snapshot_requested() noexcept { return s_snapshotRequested; } + +void set_latest(uint32_t width, uint32_t height, const std::vector& data) { + s_width = width; + s_height = height; + s_data = data; +} +} // namespace testing +} // namespace aurora::gfx::depth_peek + namespace aurora::gfx::tex_copy_conv { bool needs_conversion(GXTexFmt fmt) { return false; } } // namespace aurora::gfx::tex_copy_conv