mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 06:00:25 +02:00
init
This commit is contained in:
commit
ec226e8348
1099 files changed
+400981
No files matched your search
@@ -0,0 +1,95 @@
|
||||
include(FetchContent)
|
||||
include(GoogleTest)
|
||||
|
||||
if (NOT TARGET gtest)
|
||||
FetchContent_Declare(googletest
|
||||
URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.tar.gz
|
||||
URL_HASH SHA256=65fab701d9829d38cb77c14acdc431d2108bfdbf8979e40eb8ae567edf10b27c
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
endif ()
|
||||
|
||||
if (AURORA_ENABLE_GX)
|
||||
# GX FIFO round-trip tests
|
||||
# Compile GX sources directly to avoid pulling in the full renderer/WebGPU runtime
|
||||
add_executable(gx_fifo_tests
|
||||
gx_fifo_test.cpp
|
||||
gx_test_stubs.cpp
|
||||
texture_bind_group_cache_key_test.cpp
|
||||
../lib/gfx/efb_ram_encoder.cpp
|
||||
# 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
|
||||
../lib/dolphin/gx/GXFifo.cpp
|
||||
../lib/dolphin/gx/GXFrameBuffer.cpp
|
||||
../lib/dolphin/gx/GXGeometry.cpp
|
||||
../lib/dolphin/gx/GXGet.cpp
|
||||
../lib/dolphin/gx/GXLighting.cpp
|
||||
../lib/dolphin/gx/GXManage.cpp
|
||||
../lib/dolphin/gx/GXAurora.cpp
|
||||
../lib/dolphin/gx/GXPixel.cpp
|
||||
../lib/dolphin/gx/GXTev.cpp
|
||||
../lib/dolphin/gx/GXTexture.cpp
|
||||
../lib/dolphin/gx/GXTransform.cpp
|
||||
../lib/dolphin/gx/GXVert.cpp
|
||||
# FIFO/command processor (encoder + decoder)
|
||||
../lib/gx/fifo.cpp
|
||||
../lib/gx/command_processor.cpp
|
||||
../lib/gx/shader_info.cpp
|
||||
../lib/gx/frame_interpolation.cpp
|
||||
)
|
||||
|
||||
target_include_directories(gx_fifo_tests PRIVATE
|
||||
../include
|
||||
../lib
|
||||
../lib/dolphin/gx
|
||||
)
|
||||
|
||||
target_compile_definitions(gx_fifo_tests PRIVATE AURORA TARGET_PC)
|
||||
|
||||
target_link_libraries(gx_fifo_tests PRIVATE
|
||||
gtest
|
||||
gtest_main
|
||||
fmt::fmt
|
||||
xxhash
|
||||
absl::flat_hash_map
|
||||
absl::btree
|
||||
dawn::dawncpp_headers
|
||||
TracyClient
|
||||
${AURORA_SDL3_TARGET}
|
||||
)
|
||||
|
||||
gtest_discover_tests(gx_fifo_tests)
|
||||
endif () # AURORA_ENABLE_GX
|
||||
|
||||
# DVD API tests
|
||||
if (TARGET aurora::dvd)
|
||||
add_executable(dvd_tests test_dvd.cpp)
|
||||
target_link_libraries(dvd_tests PRIVATE aurora::dvd gtest gtest_main)
|
||||
|
||||
# Pass disc image path as a compile definition if provided
|
||||
if (DEFINED DVD_TEST_IMAGE AND EXISTS "${DVD_TEST_IMAGE}")
|
||||
target_compile_definitions(dvd_tests PRIVATE DVD_TEST_IMAGE="${DVD_TEST_IMAGE}")
|
||||
endif ()
|
||||
|
||||
gtest_discover_tests(dvd_tests)
|
||||
endif ()
|
||||
|
||||
add_executable(os_alloc_tests
|
||||
os_alloc_test.cpp
|
||||
os_test_globals.cpp
|
||||
../lib/dolphin/os/OSAlloc.cpp
|
||||
../lib/logging.cpp
|
||||
)
|
||||
target_include_directories(os_alloc_tests PRIVATE
|
||||
../include
|
||||
)
|
||||
target_compile_definitions(os_alloc_tests PRIVATE AURORA TARGET_PC)
|
||||
target_link_libraries(os_alloc_tests PRIVATE gtest gtest_main fmt::fmt)
|
||||
gtest_discover_tests(os_alloc_tests)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <dolphin/gx.h>
|
||||
|
||||
#include "gx/gx.hpp"
|
||||
#include "gx/fifo.hpp"
|
||||
#include "gx/command_processor.hpp"
|
||||
#include "gfx/depth_peek.hpp"
|
||||
#include "internal.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
// Forward declare dirty state flush
|
||||
extern "C" void __GXSetDirtyState();
|
||||
|
||||
namespace aurora::gfx::testing {
|
||||
struct ResolvePassRecord {
|
||||
TextureHandle texture;
|
||||
ClipRect rect;
|
||||
bool clearColor = false;
|
||||
bool clearAlpha = false;
|
||||
bool clearDepth = false;
|
||||
Vec4<float> clearColorValue{0.f, 0.f, 0.f, 0.f};
|
||||
float clearDepthValue = 0.f;
|
||||
GXTexFmt resolveFormat = GX_TF_RGBA8;
|
||||
std::optional<Vec4<float>> sourceRectPixels;
|
||||
bool halfScale = false;
|
||||
std::array<u32, 3> copyFilterCoefficients{0, 64, 0};
|
||||
bool forceOpaqueAlpha = false;
|
||||
float copyFilterRowStride = 1.0f;
|
||||
bool clampTop = false;
|
||||
bool clampBottom = false;
|
||||
bool persistentCopy = false;
|
||||
};
|
||||
|
||||
void reset_resolve_pass_records() noexcept;
|
||||
const std::vector<ResolvePassRecord>& resolve_pass_records() noexcept;
|
||||
void set_current_frame(uint32_t frame) noexcept;
|
||||
void set_framebuffer_sizes(uint32_t logicalWidth, uint32_t logicalHeight,
|
||||
uint32_t targetWidth, uint32_t targetHeight) noexcept;
|
||||
void reset_vertex_push_record() noexcept;
|
||||
const std::vector<uint8_t>& last_pushed_vertices() noexcept;
|
||||
const std::vector<uint16_t>& last_pushed_indices() noexcept;
|
||||
void reset_uniform_allocations() noexcept;
|
||||
const std::vector<uint8_t>& uniform_allocation(size_t index) noexcept;
|
||||
void use_draw_command_tracking(bool enabled) noexcept;
|
||||
void use_real_vertex_format_helpers(bool enabled) noexcept;
|
||||
} // namespace aurora::gfx::testing
|
||||
|
||||
class GXFifoTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
aurora::gfx::testing::set_current_frame(0);
|
||||
aurora::gfx::testing::set_framebuffer_sizes(640, 480, 640, 480);
|
||||
GXInit(nullptr, 0);
|
||||
aurora::gx::fifo::clear_buffer();
|
||||
aurora::gx::g_gxState = aurora::gx::GXState{};
|
||||
aurora::g_config.disableCopyFilter = false;
|
||||
aurora::gfx::depth_peek::testing::reset();
|
||||
aurora::gfx::testing::reset_resolve_pass_records();
|
||||
aurora::gfx::testing::reset_vertex_push_record();
|
||||
aurora::gfx::testing::use_real_vertex_format_helpers(false);
|
||||
}
|
||||
|
||||
// Copy the internal FIFO buffer contents and clear it
|
||||
std::vector<u8> capture_fifo() {
|
||||
auto size = aurora::gx::fifo::get_buffer_size();
|
||||
auto* data = aurora::gx::fifo::get_buffer_data();
|
||||
std::vector<u8> bytes(data, data + size);
|
||||
aurora::gx::fifo::clear_buffer();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// Reset g_gxState to default-constructed state
|
||||
void reset_gx_state() { aurora::gx::g_gxState = aurora::gx::GXState{}; }
|
||||
|
||||
// Decode a captured FIFO byte stream through the command processor
|
||||
void decode_fifo(const std::vector<u8>& bytes) {
|
||||
aurora::gx::fifo::process(bytes.data(), static_cast<u32>(bytes.size()), true);
|
||||
}
|
||||
|
||||
// Flush dirty state, then capture the FIFO buffer
|
||||
std::vector<u8> flush_and_capture() {
|
||||
__GXSetDirtyState();
|
||||
return capture_fifo();
|
||||
}
|
||||
|
||||
// Convenience reference to g_gxState
|
||||
aurora::gx::GXState& gxState() { return aurora::gx::g_gxState; }
|
||||
};
|
||||
@@ -0,0 +1,553 @@
|
||||
// Stubs for renderer symbols the GX/FIFO/command_processor code references but that live in the
|
||||
// full renderer, so the test binary links without the WebGPU runtime.
|
||||
|
||||
#include "gx/gx.hpp"
|
||||
#include "gfx/clear.hpp"
|
||||
#include "gfx/common.hpp"
|
||||
#include "gfx/depth_peek.hpp"
|
||||
#include "gfx/efb_ram_copy.hpp"
|
||||
#include "gfx/tex_copy_conv.hpp"
|
||||
#include "gfx/tex_palette_conv.hpp"
|
||||
#include "gfx/texture.hpp"
|
||||
#include "gx/pipeline.hpp"
|
||||
#include "gx/shader_info.hpp"
|
||||
#include "internal.hpp"
|
||||
#include "webgpu/gpu.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace {
|
||||
aurora::Vec2<uint32_t> s_logicalFbSize{640, 480};
|
||||
aurora::Vec2<uint32_t> s_renderTargetSize{640, 480};
|
||||
uint32_t s_currentFrame = 0;
|
||||
std::vector<uint8_t> s_lastPushedVertices;
|
||||
std::vector<uint16_t> s_lastPushedIndices;
|
||||
std::optional<aurora::gx::DrawData> s_lastGxDraw;
|
||||
bool s_trackDrawCommands = false;
|
||||
bool s_useRealVertexFormatHelpers = false;
|
||||
std::deque<std::vector<uint8_t>> s_uniformAllocations;
|
||||
} // namespace
|
||||
|
||||
// --- aurora::g_config ---
|
||||
namespace aurora {
|
||||
AuroraConfig g_config{};
|
||||
void wait_for_frame_worker() noexcept {}
|
||||
std::chrono::nanoseconds wait_for_frame_worker_sealed() noexcept { return {}; }
|
||||
bool wait_for_frame_worker_for(std::chrono::microseconds) noexcept { return true; }
|
||||
std::recursive_mutex& renderer_gpu_mutex() noexcept {
|
||||
static std::recursive_mutex mutex;
|
||||
return mutex;
|
||||
}
|
||||
} // namespace aurora
|
||||
|
||||
extern "C" bool aurora_wait_for_frame_worker_for(uint32_t) { return true; }
|
||||
|
||||
// --- aurora::log_internal ---
|
||||
namespace aurora {
|
||||
void log_internal(AuroraLogLevel level, const char* module, const char* message, unsigned int len) noexcept {
|
||||
fprintf(stderr, "[%d] %s: %.*s\n", static_cast<int>(level), module, len, message);
|
||||
}
|
||||
void Module::show_fatal_dialog(const char*, std::string_view) noexcept {}
|
||||
} // namespace aurora
|
||||
|
||||
namespace aurora::window {
|
||||
void set_present_surface_fill(bool) {}
|
||||
} // namespace aurora::window
|
||||
|
||||
// --- fmt::formatter<AuroraLogLevel> ---
|
||||
auto fmt::formatter<AuroraLogLevel>::format(AuroraLogLevel level, format_context& ctx) const
|
||||
-> format_context::iterator {
|
||||
return fmt::format_to(ctx.out(), "{}", static_cast<int>(level));
|
||||
}
|
||||
|
||||
// --- GPU buffers (default-constructed, not used in tests) ---
|
||||
namespace aurora::gfx {
|
||||
AuroraStats g_stats;
|
||||
wgpu::Buffer g_vertexBuffer;
|
||||
wgpu::Buffer g_uniformBuffer;
|
||||
wgpu::Buffer g_indexBuffer;
|
||||
wgpu::Buffer g_storageBuffer;
|
||||
uint32_t g_drawCallCount = 0;
|
||||
uint32_t g_mergedDrawCallCount = 0;
|
||||
} // namespace aurora::gfx
|
||||
|
||||
namespace aurora::webgpu {
|
||||
GraphicsConfig g_graphicsConfig{};
|
||||
} // namespace aurora::webgpu
|
||||
|
||||
// --- GXState (the real instance -- tests validate this) ---
|
||||
namespace aurora::gx {
|
||||
GXState g_gxState{};
|
||||
void notify_copy_texture_created() noexcept {}
|
||||
} // namespace aurora::gx
|
||||
|
||||
namespace aurora::vi {
|
||||
Vec2<uint32_t> configured_fb_size() noexcept { return s_logicalFbSize; }
|
||||
void configure(const GXRenderModeObj*) noexcept {}
|
||||
} // namespace aurora::vi
|
||||
|
||||
// --- Texture uploads ---
|
||||
namespace aurora::gfx {
|
||||
std::vector<TextureUpload> g_textureUploads;
|
||||
} // namespace aurora::gfx
|
||||
|
||||
namespace aurora::gfx::efb_ram {
|
||||
void schedule(void*, uint32_t, uint32_t, GXTexFmt, TextureHandle) noexcept {}
|
||||
} // namespace aurora::gfx::efb_ram
|
||||
|
||||
// --- get_texture ---
|
||||
namespace aurora::gx {
|
||||
const gfx::TextureBind& get_texture(GXTexMapID id) noexcept { return g_gxState.textures[id]; }
|
||||
void evict_texture_object(u32 texObjId) noexcept {
|
||||
for (auto& obj : g_gxState.loadedTextures) {
|
||||
if (obj.texObjId == texObjId) {
|
||||
obj.set_no_cache(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
void evict_tlut_object(u32 tlutObjId) noexcept {
|
||||
for (auto& obj : g_gxState.loadedTluts) {
|
||||
if (obj.tlutObjId == tlutObjId) {
|
||||
obj.set_no_cache(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
void invalidate_static_texture_cache() noexcept {
|
||||
for (auto& texture : g_gxState.textures) {
|
||||
texture.reset();
|
||||
}
|
||||
g_gxState.stateDirty = true;
|
||||
}
|
||||
void evict_copy_texture(const void* dest) noexcept {
|
||||
g_gxState.copyTextures.erase(dest);
|
||||
for (auto it = g_gxState.copyTextureCache.begin(); it != g_gxState.copyTextureCache.end();) {
|
||||
if (it->first.dest == dest) {
|
||||
g_gxState.copyTextureCache.erase(it++);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
void set_display_copy_present_source() noexcept {}
|
||||
void shutdown() noexcept {}
|
||||
Vec2<uint32_t> logical_fb_size() noexcept { return s_logicalFbSize; }
|
||||
gfx::Viewport map_logical_viewport(const gfx::Viewport& logicalViewport) noexcept {
|
||||
const float scaleX = static_cast<float>(s_renderTargetSize.x) / static_cast<float>(s_logicalFbSize.x);
|
||||
const float scaleY = static_cast<float>(s_renderTargetSize.y) / static_cast<float>(s_logicalFbSize.y);
|
||||
return {
|
||||
.left = logicalViewport.left * scaleX,
|
||||
.top = logicalViewport.top * scaleY,
|
||||
.width = logicalViewport.width * scaleX,
|
||||
.height = logicalViewport.height * scaleY,
|
||||
.znear = logicalViewport.znear,
|
||||
.zfar = logicalViewport.zfar,
|
||||
};
|
||||
}
|
||||
gfx::ClipRect map_logical_scissor(const gfx::ClipRect& logicalScissor) noexcept {
|
||||
const auto mapped = map_logical_viewport({
|
||||
.left = static_cast<float>(logicalScissor.x),
|
||||
.top = static_cast<float>(logicalScissor.y),
|
||||
.width = static_cast<float>(logicalScissor.width),
|
||||
.height = static_cast<float>(logicalScissor.height),
|
||||
.znear = 0.0f,
|
||||
.zfar = 1.0f,
|
||||
});
|
||||
const auto left = static_cast<int32_t>(std::floor(mapped.left));
|
||||
const auto top = static_cast<int32_t>(std::floor(mapped.top));
|
||||
const auto right = static_cast<int32_t>(std::ceil(mapped.left + mapped.width));
|
||||
const auto bottom = static_cast<int32_t>(std::ceil(mapped.top + mapped.height));
|
||||
return {left, top, right - left, bottom - top};
|
||||
}
|
||||
MappedRenderState map_logical_render_state() noexcept {
|
||||
return {
|
||||
.viewport = map_logical_viewport(g_gxState.logicalViewport),
|
||||
.scissor = map_logical_scissor(g_gxState.logicalScissor),
|
||||
};
|
||||
}
|
||||
void set_logical_viewport(const gfx::Viewport& viewport) noexcept {
|
||||
g_gxState.logicalViewport = viewport;
|
||||
set_render_viewport(map_logical_viewport(viewport));
|
||||
}
|
||||
void set_render_viewport(const gfx::Viewport& viewport) noexcept { g_gxState.renderViewport = viewport; }
|
||||
void set_logical_scissor(const gfx::ClipRect& scissor) noexcept {
|
||||
g_gxState.logicalScissor = scissor;
|
||||
set_render_scissor(map_logical_scissor(scissor));
|
||||
}
|
||||
void set_render_scissor(const gfx::ClipRect& scissor) noexcept { g_gxState.renderScissor = scissor; }
|
||||
} // namespace aurora::gx
|
||||
|
||||
// --- Shader/pipeline stubs ---
|
||||
namespace aurora::gx {
|
||||
void populate_pipeline_config(PipelineConfig& config, GXPrimitive primitive, GXVtxFmt fmt) noexcept {
|
||||
// No-op for tests
|
||||
}
|
||||
GXBindGroups build_bind_groups(const ShaderInfo& info) noexcept { return {}; }
|
||||
void resolve_sampled_textures(const ShaderInfo& info) noexcept {}
|
||||
u8 color_channel(GXChannelID id) noexcept { return 0; }
|
||||
u8 comp_type_size(GXAttr attr, GXCompType type) noexcept {
|
||||
if (!s_useRealVertexFormatHelpers) {
|
||||
return 0;
|
||||
}
|
||||
switch (attr) {
|
||||
case GX_VA_PNMTXIDX:
|
||||
case GX_VA_TEX0MTXIDX:
|
||||
case GX_VA_TEX1MTXIDX:
|
||||
case GX_VA_TEX2MTXIDX:
|
||||
case GX_VA_TEX3MTXIDX:
|
||||
case GX_VA_TEX4MTXIDX:
|
||||
case GX_VA_TEX5MTXIDX:
|
||||
case GX_VA_TEX6MTXIDX:
|
||||
case GX_VA_TEX7MTXIDX:
|
||||
return 1;
|
||||
case GX_VA_CLR0:
|
||||
case GX_VA_CLR1:
|
||||
switch (type) {
|
||||
case GX_RGB565:
|
||||
case GX_RGBA4:
|
||||
return 2;
|
||||
case GX_RGB8:
|
||||
case GX_RGBA6:
|
||||
return 3;
|
||||
case GX_RGBX8:
|
||||
case GX_RGBA8:
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
switch (type) {
|
||||
case GX_U8:
|
||||
case GX_S8:
|
||||
return 1;
|
||||
case GX_U16:
|
||||
case GX_S16:
|
||||
return 2;
|
||||
case GX_F32:
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
u8 comp_cnt_count(GXAttr attr, GXCompCnt cnt) noexcept {
|
||||
if (!s_useRealVertexFormatHelpers) {
|
||||
return 0;
|
||||
}
|
||||
switch (attr) {
|
||||
case GX_VA_PNMTXIDX:
|
||||
case GX_VA_TEX0MTXIDX:
|
||||
case GX_VA_TEX1MTXIDX:
|
||||
case GX_VA_TEX2MTXIDX:
|
||||
case GX_VA_TEX3MTXIDX:
|
||||
case GX_VA_TEX4MTXIDX:
|
||||
case GX_VA_TEX5MTXIDX:
|
||||
case GX_VA_TEX6MTXIDX:
|
||||
case GX_VA_TEX7MTXIDX:
|
||||
return 1;
|
||||
case GX_VA_POS:
|
||||
return cnt == GX_POS_XY ? 2 : cnt == GX_POS_XYZ ? 3 : 0;
|
||||
case GX_VA_NRM:
|
||||
return cnt == GX_NRM_XYZ ? 3 : (cnt == GX_NRM_NBT || cnt == GX_NRM_NBT3) ? 9 : 0;
|
||||
case GX_VA_CLR0:
|
||||
case GX_VA_CLR1:
|
||||
return 1;
|
||||
case GX_VA_TEX0:
|
||||
case GX_VA_TEX1:
|
||||
case GX_VA_TEX2:
|
||||
case GX_VA_TEX3:
|
||||
case GX_VA_TEX4:
|
||||
case GX_VA_TEX5:
|
||||
case GX_VA_TEX6:
|
||||
case GX_VA_TEX7:
|
||||
return cnt == GX_TEX_S ? 1 : cnt == GX_TEX_ST ? 2 : 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} // namespace aurora::gx
|
||||
|
||||
// --- Buffer push stubs ---
|
||||
namespace aurora::gfx {
|
||||
Range push_verts(const uint8_t* data, size_t length) {
|
||||
s_lastPushedVertices.assign(data, data + length);
|
||||
return {};
|
||||
}
|
||||
Range push_indices(const uint8_t* data, size_t length) {
|
||||
CHECK(length % sizeof(uint16_t) == 0, "unaligned test index upload");
|
||||
s_lastPushedIndices.resize(length / sizeof(uint16_t));
|
||||
std::memcpy(s_lastPushedIndices.data(), data, length);
|
||||
return {};
|
||||
}
|
||||
Range push_uniform(const uint8_t* data, size_t length) { return {}; }
|
||||
Range push_storage(const uint8_t* data, size_t length) { return {}; }
|
||||
std::pair<ByteBuffer, Range> map_uniform(size_t length) {
|
||||
s_uniformAllocations.emplace_back(length, 0);
|
||||
auto& uniformBytes = s_uniformAllocations.back();
|
||||
return {ByteBuffer{uniformBytes.data(), uniformBytes.size()},
|
||||
Range{static_cast<uint32_t>(s_uniformAllocations.size() - 1),
|
||||
static_cast<uint32_t>(length)}};
|
||||
}
|
||||
std::pair<ByteBuffer, Range> copy_uniform(Range source) {
|
||||
return map_uniform(source.size);
|
||||
}
|
||||
uint32_t align_uniform(uint32_t value) { return (value + 255u) & ~255u; }
|
||||
|
||||
Vec2<uint32_t> get_render_target_size() noexcept { return s_renderTargetSize; }
|
||||
Vec2<uint32_t> get_frame_buffer_size() noexcept { return s_renderTargetSize; }
|
||||
uint32_t current_frame() noexcept { return s_currentFrame; }
|
||||
// The command processor keys its resolved-pipeline memo on this. The test build never changes
|
||||
// render targets, so a fixed single-sample target matches what pipeline_ref would produce.
|
||||
uint32_t get_sample_count() noexcept { return 1; }
|
||||
void set_viewport(const Viewport& viewport) noexcept {}
|
||||
void set_scissor(uint32_t x, uint32_t y, uint32_t w, uint32_t h) noexcept {}
|
||||
} // namespace aurora::gfx
|
||||
|
||||
namespace aurora::gfx::testing {
|
||||
void set_current_frame(uint32_t frame) noexcept { s_currentFrame = frame; }
|
||||
|
||||
void reset_vertex_push_record() noexcept {
|
||||
s_lastPushedVertices.clear();
|
||||
s_lastPushedIndices.clear();
|
||||
s_lastGxDraw.reset();
|
||||
s_trackDrawCommands = false;
|
||||
g_mergedDrawCallCount = 0;
|
||||
}
|
||||
const std::vector<uint8_t>& last_pushed_vertices() noexcept {
|
||||
return s_lastPushedVertices;
|
||||
}
|
||||
const std::vector<uint16_t>& last_pushed_indices() noexcept {
|
||||
return s_lastPushedIndices;
|
||||
}
|
||||
void reset_uniform_allocations() noexcept {
|
||||
s_uniformAllocations.clear();
|
||||
}
|
||||
const std::vector<uint8_t>& uniform_allocation(size_t index) noexcept {
|
||||
CHECK(index < s_uniformAllocations.size(), "uniform test allocation {} out of range {}", index,
|
||||
s_uniformAllocations.size());
|
||||
return s_uniformAllocations[index];
|
||||
}
|
||||
void use_draw_command_tracking(bool enabled) noexcept {
|
||||
s_trackDrawCommands = enabled;
|
||||
s_lastGxDraw.reset();
|
||||
}
|
||||
void use_real_vertex_format_helpers(bool enabled) noexcept {
|
||||
s_useRealVertexFormatHelpers = enabled;
|
||||
}
|
||||
} // namespace aurora::gfx::testing
|
||||
|
||||
// --- Pipeline/draw command stubs ---
|
||||
namespace aurora::gfx {
|
||||
template <>
|
||||
PipelineRef pipeline_ref<clear::PipelineConfig>(const clear::PipelineConfig& config) {
|
||||
return 0;
|
||||
}
|
||||
template <>
|
||||
void push_draw_command<clear::DrawData>(clear::DrawData data) {
|
||||
// No-op
|
||||
}
|
||||
template <>
|
||||
PipelineRef pipeline_ref<gx::PipelineConfig>(const gx::PipelineConfig& config) {
|
||||
return 0;
|
||||
}
|
||||
template <>
|
||||
void push_draw_command<gx::DrawData>(gx::DrawData data) {
|
||||
if (s_trackDrawCommands) {
|
||||
s_lastGxDraw = std::move(data);
|
||||
}
|
||||
}
|
||||
template <>
|
||||
gx::DrawData* get_last_draw_command() {
|
||||
return s_trackDrawCommands && s_lastGxDraw.has_value() ? &*s_lastGxDraw : nullptr;
|
||||
}
|
||||
} // namespace aurora::gfx
|
||||
|
||||
// --- TextureBind::get_descriptor ---
|
||||
namespace aurora::gfx {
|
||||
wgpu::SamplerDescriptor TextureBind::get_descriptor() const noexcept { return wgpu::SamplerDescriptor{}; }
|
||||
} // namespace aurora::gfx
|
||||
|
||||
// --- Texture creation/write/replacement stubs ---
|
||||
namespace aurora::gfx {
|
||||
namespace testing {
|
||||
struct ResolvePassRecord {
|
||||
TextureHandle texture;
|
||||
ClipRect rect;
|
||||
bool clearColor = false;
|
||||
bool clearAlpha = false;
|
||||
bool clearDepth = false;
|
||||
Vec4<float> clearColorValue{0.f, 0.f, 0.f, 0.f};
|
||||
float clearDepthValue = 0.f;
|
||||
GXTexFmt resolveFormat = GX_TF_RGBA8;
|
||||
std::optional<Vec4<float>> sourceRectPixels;
|
||||
bool halfScale = false;
|
||||
std::array<u32, 3> copyFilterCoefficients{0, 64, 0};
|
||||
bool forceOpaqueAlpha = false;
|
||||
float copyFilterRowStride = 1.0f;
|
||||
bool clampTop = false;
|
||||
bool clampBottom = false;
|
||||
bool persistentCopy = false;
|
||||
};
|
||||
|
||||
namespace {
|
||||
std::vector<ResolvePassRecord> s_resolvePassRecords;
|
||||
} // namespace
|
||||
|
||||
void reset_resolve_pass_records() noexcept { s_resolvePassRecords.clear(); }
|
||||
|
||||
const std::vector<ResolvePassRecord>& resolve_pass_records() noexcept { return s_resolvePassRecords; }
|
||||
|
||||
void set_framebuffer_sizes(uint32_t logicalWidth, uint32_t logicalHeight,
|
||||
uint32_t targetWidth, uint32_t targetHeight) noexcept {
|
||||
s_logicalFbSize = {logicalWidth, logicalHeight};
|
||||
s_renderTargetSize = {targetWidth, targetHeight};
|
||||
}
|
||||
} // namespace testing
|
||||
|
||||
TextureHandle new_static_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 gxFormat,
|
||||
ArrayRef<uint8_t> data, bool tlut, const char* label) noexcept {
|
||||
return {};
|
||||
}
|
||||
TextureHandle new_dynamic_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 gxFormat,
|
||||
const char* label) noexcept {
|
||||
return {};
|
||||
}
|
||||
TextureHandle new_render_texture(uint32_t width, uint32_t height, u32 gxFormat, const char* label) noexcept {
|
||||
return std::make_shared<TextureRef>(wgpu::Texture{}, wgpu::TextureView{}, wgpu::TextureView{},
|
||||
wgpu::Extent3D{width, height, 1}, wgpu::TextureFormat::RGBA8Unorm, 1, gxFormat);
|
||||
}
|
||||
TextureHandle new_conv_texture(uint32_t width, uint32_t height, u32 gxFormat, const char* label) noexcept {
|
||||
return std::make_shared<TextureRef>(wgpu::Texture{}, wgpu::TextureView{}, wgpu::TextureView{},
|
||||
wgpu::Extent3D{width, height, 1}, wgpu::TextureFormat::RGBA8Unorm, 1, gxFormat);
|
||||
}
|
||||
void write_texture(const TextureRef& ref, ArrayRef<uint8_t> data) noexcept {}
|
||||
void resolve_pass(TextureHandle texture, ClipRect rect, bool clearColor, bool clearAlpha, bool clearDepth,
|
||||
Vec4<float> clearColorValue, float clearDepthValue, GXTexFmt resolveFormat,
|
||||
const Vec4<float>* sourceRectPixels, bool halfScale,
|
||||
const std::array<u32, 3>* copyFilterCoefficients, bool forceOpaqueAlpha,
|
||||
float copyFilterRowStride, bool clampTop, bool clampBottom, bool persistentCopy) {
|
||||
testing::ResolvePassRecord record;
|
||||
record.texture = std::move(texture);
|
||||
record.rect = rect;
|
||||
record.clearColor = clearColor;
|
||||
record.clearAlpha = clearAlpha;
|
||||
record.clearDepth = clearDepth;
|
||||
record.clearColorValue = clearColorValue;
|
||||
record.clearDepthValue = clearDepthValue;
|
||||
record.resolveFormat = resolveFormat;
|
||||
record.sourceRectPixels = sourceRectPixels != nullptr ? std::make_optional(*sourceRectPixels) : std::nullopt;
|
||||
record.halfScale = halfScale;
|
||||
record.copyFilterCoefficients =
|
||||
copyFilterCoefficients != nullptr ? *copyFilterCoefficients : std::array<u32, 3>{0, 64, 0};
|
||||
record.forceOpaqueAlpha = forceOpaqueAlpha;
|
||||
record.copyFilterRowStride = copyFilterRowStride;
|
||||
record.clampTop = clampTop;
|
||||
record.clampBottom = clampBottom;
|
||||
record.persistentCopy = persistentCopy;
|
||||
testing::s_resolvePassRecords.push_back(std::move(record));
|
||||
}
|
||||
void queue_palette_conv(tex_palette_conv::ConvRequest req) {}
|
||||
void begin_offscreen(uint32_t width, uint32_t height) {}
|
||||
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<uint32_t> 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<size_t>(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<uint32_t>& 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
|
||||
|
||||
namespace aurora::gfx::tex_palette_conv {
|
||||
void queue(ConvRequest req) {}
|
||||
} // namespace aurora::gfx::tex_palette_conv
|
||||
|
||||
namespace aurora::gfx::texture_replacement {
|
||||
u32 compute_texture_upload_size(const GXTexObj_& obj) noexcept { return 0; }
|
||||
void register_tlut(const GXTlutObj*, const void*, GXTlutFmt, u16) noexcept {}
|
||||
void load_tlut(const GXTlutObj*, u32) noexcept {}
|
||||
std::optional<TextureHandle> find_replacement(const GXTexObj_&) noexcept { return std::nullopt; }
|
||||
} // namespace aurora::gfx::texture_replacement
|
||||
|
||||
// --- Window stub ---
|
||||
#include "../lib/window.hpp"
|
||||
namespace aurora::window {
|
||||
AuroraWindowSize get_window_size() { return {640, 480, 640, 480, 640, 480, 1.0f}; }
|
||||
void set_frame_buffer_aspect_fit(bool) {}
|
||||
} // namespace aurora::window
|
||||
|
||||
// --- WebGPU C API stubs (prevent linker errors from wgpu:: destructors) ---
|
||||
extern "C" {
|
||||
void wgpuDeviceRelease(WGPUDevice) {}
|
||||
void wgpuQueueRelease(WGPUQueue) {}
|
||||
void wgpuSurfaceRelease(WGPUSurface) {}
|
||||
void wgpuBufferRelease(WGPUBuffer) {}
|
||||
void wgpuTextureRelease(WGPUTexture) {}
|
||||
void wgpuTextureViewRelease(WGPUTextureView) {}
|
||||
void wgpuSamplerRelease(WGPUSampler) {}
|
||||
void wgpuShaderModuleRelease(WGPUShaderModule) {}
|
||||
void wgpuRenderPipelineRelease(WGPURenderPipeline) {}
|
||||
void wgpuBindGroupRelease(WGPUBindGroup) {}
|
||||
void wgpuBindGroupLayoutRelease(WGPUBindGroupLayout) {}
|
||||
void wgpuPipelineLayoutRelease(WGPUPipelineLayout) {}
|
||||
void wgpuInstanceRelease(WGPUInstance) {}
|
||||
void wgpuDeviceAddRef(WGPUDevice) {}
|
||||
void wgpuQueueAddRef(WGPUQueue) {}
|
||||
void wgpuSurfaceAddRef(WGPUSurface) {}
|
||||
void wgpuBufferAddRef(WGPUBuffer) {}
|
||||
void wgpuTextureAddRef(WGPUTexture) {}
|
||||
void wgpuTextureViewAddRef(WGPUTextureView) {}
|
||||
void wgpuInstanceAddRef(WGPUInstance) {}
|
||||
}
|
||||
|
||||
void aurora::gfx::push_debug_group(std::string) {}
|
||||
void aurora_push_debug_group(const char*) {}
|
||||
void aurora_pop_debug_group() {}
|
||||
void aurora::gfx::insert_debug_marker(std::string) {}
|
||||
@@ -0,0 +1,106 @@
|
||||
#include <dolphin/os.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t kArenaBytes = 64 * 1024;
|
||||
|
||||
std::array<std::uint8_t, kArenaBytes> gArena{};
|
||||
|
||||
void resetAllocator(int maxHeaps = 8) {
|
||||
std::fill(gArena.begin(), gArena.end(), 0);
|
||||
void* start = gArena.data();
|
||||
void* end = gArena.data() + gArena.size();
|
||||
ASSERT_NE(OSInitAlloc(start, end, maxHeaps), nullptr);
|
||||
EXPECT_EQ(OSSetCurrentHeap(-1), -1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(OSAlloc, InitCreateAllocFreeCheck) {
|
||||
resetAllocator();
|
||||
|
||||
OSHeapHandle heap = OSCreateHeap(gArena.data() + 512, gArena.data() + 4096);
|
||||
ASSERT_GE(heap, 0);
|
||||
|
||||
EXPECT_EQ(OSSetCurrentHeap(heap), -1);
|
||||
|
||||
void* p = OSAllocFromHeap(heap, 96);
|
||||
ASSERT_NE(p, nullptr);
|
||||
EXPECT_EQ(reinterpret_cast<std::uintptr_t>(p) & 31u, 0u);
|
||||
EXPECT_GE(OSReferentSize(p), 96u);
|
||||
|
||||
s32 freeBefore = OSCheckHeap(heap);
|
||||
EXPECT_GE(freeBefore, 0);
|
||||
|
||||
OSFreeToHeap(heap, p);
|
||||
s32 freeAfter = OSCheckHeap(heap);
|
||||
EXPECT_GE(freeAfter, freeBefore);
|
||||
}
|
||||
|
||||
TEST(OSAlloc, AddToHeapMakesSpaceAvailable) {
|
||||
resetAllocator();
|
||||
|
||||
OSHeapHandle heap = OSCreateHeap(gArena.data() + 0x1000, gArena.data() + 0x2000);
|
||||
ASSERT_GE(heap, 0);
|
||||
|
||||
void* p1 = OSAllocFromHeap(heap, 0xD00);
|
||||
ASSERT_NE(p1, nullptr);
|
||||
|
||||
void* p2 = OSAllocFromHeap(heap, 0x400);
|
||||
EXPECT_EQ(p2, nullptr);
|
||||
|
||||
OSAddToHeap(heap, gArena.data() + 0x3000, gArena.data() + 0x3800);
|
||||
|
||||
p2 = OSAllocFromHeap(heap, 0x400);
|
||||
EXPECT_NE(p2, nullptr);
|
||||
}
|
||||
|
||||
TEST(OSAlloc, AllocFixedCarvesRangeFromHeap) {
|
||||
resetAllocator();
|
||||
|
||||
OSHeapHandle heap = OSCreateHeap(gArena.data() + 0x2000, gArena.data() + 0x5000);
|
||||
ASSERT_GE(heap, 0);
|
||||
|
||||
s32 freeBefore = OSCheckHeap(heap);
|
||||
ASSERT_GT(freeBefore, 0);
|
||||
|
||||
void* fixed = OSAllocFixed(gArena.data() + 0x3000, gArena.data() + 0x3400);
|
||||
ASSERT_NE(fixed, nullptr);
|
||||
|
||||
s32 freeAfter = OSCheckHeap(heap);
|
||||
EXPECT_GE(freeBefore, freeAfter);
|
||||
}
|
||||
|
||||
TEST(OSAlloc, SetCurrentHeapReturnsPrevious) {
|
||||
resetAllocator();
|
||||
|
||||
OSHeapHandle heapA = OSCreateHeap(gArena.data() + 0x1000, gArena.data() + 0x2000);
|
||||
OSHeapHandle heapB = OSCreateHeap(gArena.data() + 0x3000, gArena.data() + 0x4000);
|
||||
ASSERT_GE(heapA, 0);
|
||||
ASSERT_GE(heapB, 0);
|
||||
|
||||
EXPECT_EQ(OSSetCurrentHeap(heapA), -1);
|
||||
EXPECT_EQ(OSSetCurrentHeap(heapB), heapA);
|
||||
|
||||
void* p = OSAlloc(128);
|
||||
ASSERT_NE(p, nullptr);
|
||||
OSFree(p);
|
||||
|
||||
EXPECT_GE(OSCheckHeap(heapB), 0);
|
||||
}
|
||||
|
||||
TEST(OSAlloc, DestroyHeapInvalidatesHandle) {
|
||||
resetAllocator();
|
||||
|
||||
OSHeapHandle heap = OSCreateHeap(gArena.data() + 0x1000, gArena.data() + 0x2000);
|
||||
ASSERT_GE(heap, 0);
|
||||
|
||||
OSDestroyHeap(heap);
|
||||
EXPECT_LT(OSCheckHeap(heap), 0);
|
||||
EXPECT_EQ(OSAllocFromHeap(heap, 64), nullptr);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#include <aurora/aurora.h>
|
||||
|
||||
namespace aurora {
|
||||
AuroraConfig g_config{};
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
#include <dolphin/dvd.h>
|
||||
#include <aurora/dvd.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
// =============================================================================
|
||||
// Tests that do NOT require a disc image
|
||||
// =============================================================================
|
||||
|
||||
TEST(DVDStubs, Constants) {
|
||||
EXPECT_EQ(DVD_STATE_END, 0);
|
||||
EXPECT_EQ(DVD_STATE_BUSY, 1);
|
||||
EXPECT_EQ(DVD_STATE_CANCELED, 10);
|
||||
EXPECT_EQ(DVD_RESULT_GOOD, 0);
|
||||
EXPECT_EQ(DVD_RESULT_FATAL_ERROR, -1);
|
||||
EXPECT_EQ(DVD_RESULT_CANCELED, -6);
|
||||
}
|
||||
|
||||
TEST(DVDStubs, StructSizes) {
|
||||
EXPECT_GT(sizeof(DVDDiskID), 0u);
|
||||
EXPECT_GT(sizeof(DVDCommandBlock), 0u);
|
||||
EXPECT_GT(sizeof(DVDFileInfo), 0u);
|
||||
EXPECT_GT(sizeof(DVDDir), 0u);
|
||||
EXPECT_GT(sizeof(DVDDirEntry), 0u);
|
||||
EXPECT_GE(sizeof(DVDFileInfo), sizeof(DVDCommandBlock));
|
||||
}
|
||||
|
||||
TEST(DVDStubs, InitWithoutDisc) { DVDInit(); }
|
||||
|
||||
TEST(DVDStubs, GetDriveStatus) { EXPECT_EQ(DVDGetDriveStatus(), DVD_STATE_NO_DISK); }
|
||||
|
||||
TEST(DVDStubs, Reset) { DVDReset(); }
|
||||
|
||||
TEST(DVDStubs, ResetRequired) { EXPECT_EQ(DVDResetRequired(), FALSE); }
|
||||
|
||||
TEST(DVDStubs, PauseResume) {
|
||||
DVDPause();
|
||||
DVDResume();
|
||||
}
|
||||
|
||||
TEST(DVDStubs, AutoInvalidation) {
|
||||
BOOL prev = DVDSetAutoInvalidation(TRUE);
|
||||
EXPECT_EQ(prev, FALSE);
|
||||
prev = DVDSetAutoInvalidation(FALSE);
|
||||
EXPECT_EQ(prev, TRUE);
|
||||
prev = DVDSetAutoInvalidation(FALSE);
|
||||
EXPECT_EQ(prev, FALSE);
|
||||
}
|
||||
|
||||
TEST(DVDStubs, Cancel) {
|
||||
DVDCommandBlock block{};
|
||||
block.state = DVD_STATE_BUSY;
|
||||
s32 result = DVDCancel(&block);
|
||||
EXPECT_EQ(result, 0);
|
||||
EXPECT_EQ(block.state, DVD_STATE_CANCELED);
|
||||
}
|
||||
|
||||
TEST(DVDStubs, CancelAsync) {
|
||||
DVDCommandBlock block{};
|
||||
block.state = DVD_STATE_BUSY;
|
||||
DVDCancelAsync(&block, [](s32, DVDCommandBlock*) {});
|
||||
EXPECT_EQ(block.state, DVD_STATE_CANCELED);
|
||||
}
|
||||
|
||||
TEST(DVDStubs, CancelAll) { EXPECT_EQ(DVDCancelAll(), DVD_RESULT_GOOD); }
|
||||
|
||||
TEST(DVDStubs, SeekStubs) {
|
||||
DVDFileInfo fi{};
|
||||
fi.cb.state = DVD_STATE_BUSY;
|
||||
s32 result = DVDSeekPrio(&fi, 0, 2);
|
||||
EXPECT_EQ(result, DVD_RESULT_FATAL_ERROR);
|
||||
EXPECT_EQ(fi.cb.state, DVD_STATE_FATAL_ERROR);
|
||||
|
||||
fi.cb.state = DVD_STATE_BUSY;
|
||||
BOOL ok = DVDSeekAsyncPrio(&fi, 0, [](s32, DVDFileInfo*) {}, 2);
|
||||
EXPECT_EQ(ok, TRUE);
|
||||
EXPECT_EQ(fi.cb.state, DVD_STATE_FATAL_ERROR);
|
||||
}
|
||||
|
||||
TEST(DVDStubs, GetFSTLocation) { EXPECT_EQ(DVDGetFSTLocation(), nullptr); }
|
||||
|
||||
TEST(DVDStubs, GetCurrentDiskID) {
|
||||
DVDDiskID* id = DVDGetCurrentDiskID();
|
||||
EXPECT_NE(id, nullptr);
|
||||
}
|
||||
|
||||
TEST(DVDStubs, FileInfoStatus) {
|
||||
DVDFileInfo fi{};
|
||||
fi.cb.state = DVD_STATE_END;
|
||||
EXPECT_EQ(DVDGetFileInfoStatus(&fi), DVD_STATE_END);
|
||||
fi.cb.state = DVD_STATE_BUSY;
|
||||
EXPECT_EQ(DVDGetFileInfoStatus(&fi), DVD_STATE_BUSY);
|
||||
}
|
||||
|
||||
TEST(DVDStubs, TransferredSize) {
|
||||
DVDFileInfo fi{};
|
||||
fi.cb.transferredSize = 1234;
|
||||
EXPECT_EQ(DVDGetTransferredSize(&fi), 1234);
|
||||
}
|
||||
|
||||
TEST(DVDStubs, CommandBlockStatus) {
|
||||
DVDCommandBlock block{};
|
||||
block.state = DVD_STATE_WAITING;
|
||||
EXPECT_EQ(DVDGetCommandBlockStatus(&block), DVD_STATE_WAITING);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Without a disc: operations should fail gracefully
|
||||
// =============================================================================
|
||||
|
||||
TEST(DVDNoDisc, OpenFails) {
|
||||
DVDFileInfo fi{};
|
||||
EXPECT_EQ(DVDOpen("test.bin", &fi), FALSE);
|
||||
}
|
||||
|
||||
TEST(DVDNoDisc, FastOpenFails) {
|
||||
DVDFileInfo fi{};
|
||||
EXPECT_EQ(DVDFastOpen(0, &fi), FALSE);
|
||||
}
|
||||
|
||||
TEST(DVDNoDisc, CloseNullHandle) {
|
||||
DVDFileInfo fi{};
|
||||
fi.cb.userData = nullptr;
|
||||
fi.cb.state = DVD_STATE_BUSY;
|
||||
EXPECT_EQ(DVDClose(&fi), TRUE);
|
||||
EXPECT_EQ(fi.cb.state, DVD_STATE_END);
|
||||
}
|
||||
|
||||
TEST(DVDNoDisc, OpenDirFails) {
|
||||
DVDDir dir{};
|
||||
EXPECT_EQ(DVDOpenDir("/", &dir), FALSE);
|
||||
}
|
||||
|
||||
TEST(DVDNoDisc, CloseDir) {
|
||||
DVDDir dir{};
|
||||
EXPECT_EQ(DVDCloseDir(&dir), TRUE);
|
||||
}
|
||||
|
||||
TEST(DVDNoDisc, ChangeDirFails) { EXPECT_EQ(DVDChangeDir("/"), FALSE); }
|
||||
|
||||
TEST(DVDNoDisc, ConvertPathFails) { EXPECT_EQ(DVDConvertPathToEntrynum("/test"), -1); }
|
||||
|
||||
TEST(DVDNoDisc, GetCurrentDir) {
|
||||
char buf[256];
|
||||
EXPECT_EQ(DVDGetCurrentDir(buf, sizeof(buf)), TRUE);
|
||||
EXPECT_STREQ(buf, "/");
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Tests that require a disc image (conditionally compiled)
|
||||
// =============================================================================
|
||||
|
||||
#ifdef DVD_TEST_IMAGE
|
||||
|
||||
class DVDDiscTest : public ::testing::Test {
|
||||
protected:
|
||||
static void SetUpTestSuite() {
|
||||
ASSERT_TRUE(aurora_dvd_open(DVD_TEST_IMAGE));
|
||||
DVDInit();
|
||||
}
|
||||
|
||||
static void TearDownTestSuite() { aurora_dvd_close(); }
|
||||
};
|
||||
|
||||
// Helper: find the first file entry in root by iterating the directory.
|
||||
// Returns the entry number and fills fileName, or returns -1 if none found.
|
||||
static s32 findFirstRootFile(char* fileName, size_t fileNameSize) {
|
||||
DVDDir dir{};
|
||||
if (!DVDOpenDir("/", &dir))
|
||||
return -1;
|
||||
|
||||
DVDDirEntry dirent{};
|
||||
s32 fileEntry = -1;
|
||||
while (DVDReadDir(&dir, &dirent)) {
|
||||
if (!dirent.isDir) {
|
||||
fileEntry = static_cast<s32>(dirent.entryNum);
|
||||
std::snprintf(fileName, fileNameSize, "/%s", dirent.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
DVDCloseDir(&dir);
|
||||
return fileEntry;
|
||||
}
|
||||
|
||||
TEST_F(DVDDiscTest, ConvertPathRoot) { EXPECT_EQ(DVDConvertPathToEntrynum("/"), 0); }
|
||||
|
||||
TEST_F(DVDDiscTest, ConvertPathDotDotDot) {
|
||||
EXPECT_EQ(DVDConvertPathToEntrynum("."), 0);
|
||||
EXPECT_EQ(DVDConvertPathToEntrynum(".."), 0);
|
||||
}
|
||||
|
||||
TEST_F(DVDDiscTest, ConvertPathInvalid) {
|
||||
EXPECT_EQ(DVDConvertPathToEntrynum("/nonexistent_file_that_should_not_exist"), -1);
|
||||
}
|
||||
|
||||
TEST_F(DVDDiscTest, OpenDirRoot) {
|
||||
DVDDir dir{};
|
||||
EXPECT_EQ(DVDOpenDir("/", &dir), TRUE);
|
||||
EXPECT_EQ(dir.entryNum, 0u);
|
||||
EXPECT_EQ(dir.location, 1u);
|
||||
|
||||
DVDDirEntry dirent{};
|
||||
BOOL hasEntry = DVDReadDir(&dir, &dirent);
|
||||
if (hasEntry) {
|
||||
EXPECT_NE(dirent.name, nullptr);
|
||||
EXPECT_GT(std::strlen(dirent.name), 0u);
|
||||
}
|
||||
DVDCloseDir(&dir);
|
||||
}
|
||||
|
||||
TEST_F(DVDDiscTest, ChangeDirRoot) {
|
||||
EXPECT_EQ(DVDChangeDir("/"), TRUE);
|
||||
char buf[256];
|
||||
DVDGetCurrentDir(buf, sizeof(buf));
|
||||
EXPECT_STREQ(buf, "/");
|
||||
}
|
||||
|
||||
TEST_F(DVDDiscTest, OpenCloseFile) {
|
||||
char fileName[256] = {};
|
||||
s32 fileEntry = findFirstRootFile(fileName, sizeof(fileName));
|
||||
if (fileEntry < 0) {
|
||||
GTEST_SKIP() << "No files in root directory";
|
||||
}
|
||||
|
||||
DVDFileInfo fi{};
|
||||
EXPECT_EQ(DVDOpen(fileName, &fi), TRUE);
|
||||
EXPECT_GT(fi.length, 0u);
|
||||
EXPECT_EQ(fi.cb.state, DVD_STATE_END);
|
||||
EXPECT_EQ(DVDClose(&fi), TRUE);
|
||||
|
||||
EXPECT_EQ(DVDFastOpen(fileEntry, &fi), TRUE);
|
||||
EXPECT_EQ(DVDClose(&fi), TRUE);
|
||||
}
|
||||
|
||||
TEST_F(DVDDiscTest, ReadFile) {
|
||||
char fileName[256] = {};
|
||||
if (findFirstRootFile(fileName, sizeof(fileName)) < 0) {
|
||||
GTEST_SKIP() << "No files in root directory";
|
||||
}
|
||||
|
||||
DVDFileInfo fi{};
|
||||
ASSERT_EQ(DVDOpen(fileName, &fi), TRUE);
|
||||
|
||||
u32 readSize = fi.length < 32 ? fi.length : 32;
|
||||
std::vector<u8> buf(readSize);
|
||||
s32 bytesRead = DVDReadPrio(&fi, buf.data(), static_cast<s32>(readSize), 0, 2);
|
||||
EXPECT_EQ(bytesRead, static_cast<s32>(readSize));
|
||||
EXPECT_EQ(DVDGetTransferredSize(&fi), static_cast<s32>(readSize));
|
||||
|
||||
DVDClose(&fi);
|
||||
}
|
||||
|
||||
TEST_F(DVDDiscTest, ReadAsync) {
|
||||
char fileName[256] = {};
|
||||
if (findFirstRootFile(fileName, sizeof(fileName)) < 0) {
|
||||
GTEST_SKIP() << "No files in root directory";
|
||||
}
|
||||
|
||||
DVDFileInfo fi{};
|
||||
ASSERT_EQ(DVDOpen(fileName, &fi), TRUE);
|
||||
|
||||
u32 readSize = fi.length < 32 ? fi.length : 32;
|
||||
std::vector<u8> buf(readSize);
|
||||
BOOL ok = DVDReadAsyncPrio(&fi, buf.data(), static_cast<s32>(readSize), 0, [](s32, DVDFileInfo*) {}, 2);
|
||||
EXPECT_EQ(ok, TRUE);
|
||||
EXPECT_EQ(DVDGetTransferredSize(&fi), static_cast<s32>(readSize));
|
||||
|
||||
DVDClose(&fi);
|
||||
}
|
||||
|
||||
TEST_F(DVDDiscTest, DiskID) {
|
||||
DVDDiskID* id = DVDGetCurrentDiskID();
|
||||
ASSERT_NE(id, nullptr);
|
||||
bool hasGameName = false;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (id->gameName[i] != '\0') {
|
||||
hasGameName = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(hasGameName);
|
||||
}
|
||||
|
||||
#endif // DVD_TEST_IMAGE
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "gx/texture_bind_group_cache_key.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <new>
|
||||
|
||||
namespace aurora::gx {
|
||||
namespace {
|
||||
|
||||
TEST(TextureBindGroupCacheKeyTest, DistinctViewsDoNotAliasWhenWrapperStorageIsReused) {
|
||||
struct SimulatedTextureRef {
|
||||
const void* sampleTextureView;
|
||||
};
|
||||
|
||||
alignas(SimulatedTextureRef) std::array<std::byte, sizeof(SimulatedTextureRef)> wrapperStorage{};
|
||||
int firstView = 0;
|
||||
int secondView = 0;
|
||||
|
||||
TextureBindGroupCacheKey first{
|
||||
.sampledTextures = 1,
|
||||
};
|
||||
TextureBindGroupCacheKey second = first;
|
||||
auto* firstWrapper = new (wrapperStorage.data()) SimulatedTextureRef{&firstView};
|
||||
const void* const reusedWrapperAddress = firstWrapper;
|
||||
set_texture_bind_group_cache_slot(first, 0, firstWrapper->sampleTextureView, 0x1234, 0x5678, 0x9abc);
|
||||
firstWrapper->~SimulatedTextureRef();
|
||||
|
||||
auto* secondWrapper = new (wrapperStorage.data()) SimulatedTextureRef{&secondView};
|
||||
set_texture_bind_group_cache_slot(second, 0, secondWrapper->sampleTextureView, 0x1234, 0x5678, 0x9abc);
|
||||
|
||||
// A freed TextureRef wrapper can be reconstructed at the same allocator
|
||||
// address in one frame. The old key therefore saw these bindings as equal.
|
||||
EXPECT_EQ(reusedWrapperAddress, static_cast<const void*>(secondWrapper));
|
||||
EXPECT_NE(first, second);
|
||||
|
||||
TextureBindGroupCacheKey sameView = first;
|
||||
EXPECT_EQ(first, sameView);
|
||||
secondWrapper->~SimulatedTextureRef();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace aurora::gx
|
||||
Reference in new issue
Block a user