diff --git a/CMakeLists.txt b/CMakeLists.txt index 70c11cf..d77ff84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,7 +111,7 @@ add_library(aurora::shared ALIAS aurora_shared) target_compile_definitions(aurora_shared PUBLIC AURORA TARGET_PC) target_include_directories(aurora_shared PUBLIC include) target_link_libraries(aurora_shared PUBLIC ${AURORA_SDL3_TARGET} fmt::fmt xxhash) -target_link_libraries(aurora_shared PRIVATE absl::btree absl::flat_hash_map) +target_link_libraries(aurora_shared PRIVATE absl::btree absl::flat_hash_map TracyClient) if (AURORA_ENABLE_DVD) target_sources(aurora_shared PRIVATE lib/dolphin/dvd/dvd.cpp) diff --git a/cmake/aurora_core.cmake b/cmake/aurora_core.cmake index 3a9da55..9fc7bb2 100644 --- a/cmake/aurora_core.cmake +++ b/cmake/aurora_core.cmake @@ -9,7 +9,7 @@ add_library(aurora::core ALIAS aurora_core) target_compile_definitions(aurora_core PUBLIC AURORA TARGET_PC) target_include_directories(aurora_core PUBLIC include) target_link_libraries(aurora_core PUBLIC ${AURORA_SDL3_TARGET} fmt::fmt xxhash) -target_link_libraries(aurora_core PRIVATE absl::btree absl::flat_hash_map sqlite3) +target_link_libraries(aurora_core PRIVATE absl::btree absl::flat_hash_map sqlite3 TracyClient) if (AURORA_ENABLE_GX AND AURORA_CACHE_USE_ZSTD) target_compile_definitions(aurora_core PRIVATE AURORA_CACHE_USE_ZSTD) target_link_libraries(aurora_core PRIVATE libzstd_static) diff --git a/cmake/aurora_gx.cmake b/cmake/aurora_gx.cmake index 27bb716..12b8fa7 100644 --- a/cmake/aurora_gx.cmake +++ b/cmake/aurora_gx.cmake @@ -36,7 +36,7 @@ add_library(aurora_gx STATIC add_library(aurora::gx ALIAS aurora_gx) target_link_libraries(aurora_gx PUBLIC aurora::core xxhash) -target_link_libraries(aurora_gx PRIVATE absl::btree absl::flat_hash_map sqlite3) +target_link_libraries(aurora_gx PRIVATE absl::btree absl::flat_hash_map sqlite3 TracyClient) if (EMSCRIPTEN) target_link_options(aurora_gx PUBLIC -sUSE_WEBGPU=1 -sASYNCIFY -sEXIT_RUNTIME) target_compile_definitions(aurora_gx PRIVATE ENABLE_BACKEND_WEBGPU) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index b713452..a59bda3 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -161,3 +161,23 @@ if (AURORA_ENABLE_GX) endif () endif () endif () + +if (NOT TARGET TracyClient) + message(STATUS "aurora: Fetching Tracy") + + # Default to off, users can enable the cache variable if they want to use it. + set(TRACY_ENABLE OFF CACHE BOOL "Enable Tracy profiling") + + include(FetchContent) + FetchContent_Declare( + tracy + URL https://github.com/wolfpld/tracy/archive/refs/tags/v0.13.1.tar.gz + URL_HASH SHA256=d4efc50ebcb0bfcfdbba148995aeb75044c0d80f5d91223aebfaa8fa9e563d2b + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + ) + FetchContent_MakeAvailable(tracy) + + if (NOT TARGET TracyClient) + message(FATAL_ERROR "Failed to make TracyClient available") + endif () +endif () diff --git a/lib/aurora.cpp b/lib/aurora.cpp index fc7ea0a..adb7bd2 100644 --- a/lib/aurora.cpp +++ b/lib/aurora.cpp @@ -15,6 +15,8 @@ #include #include +#include "tracy/Tracy.hpp" + namespace aurora { AuroraConfig g_config; uint32_t g_sdlCustomEventsStart; @@ -167,6 +169,7 @@ void shutdown() noexcept { } const AuroraEvent* update() noexcept { + ZoneScoped; if (g_initialFrame) { g_initialFrame = false; input::initialize(); @@ -175,6 +178,7 @@ const AuroraEvent* update() noexcept { } bool begin_frame() noexcept { + ZoneScoped; #ifdef AURORA_ENABLE_GX if (!g_surface) { webgpu::refresh_surface(true); @@ -205,6 +209,7 @@ bool begin_frame() noexcept { Log.error("Failed to get surface texture: {}", magic_enum::enum_name(surfaceTexture.status)); return false; } + imgui::new_frame(window::get_window_size()); gfx::begin_frame(); #endif @@ -212,6 +217,7 @@ bool begin_frame() noexcept { } void end_frame() noexcept { + ZoneScoped; #ifdef AURORA_ENABLE_GX gx::fifo::drain(); const auto encoderDescriptor = wgpu::CommandEncoderDescriptor{ @@ -258,6 +264,23 @@ void end_frame() noexcept { Log.warn("Surface present failed: {}", static_cast(presentStatus)); } g_currentView = {}; + + TracyPlotConfig("aurora: lastVertSize", tracy::PlotFormatType::Memory, false, true, 0); + TracyPlotConfig("aurora: lastUniformSize", tracy::PlotFormatType::Memory, false, true, 0); + TracyPlotConfig("aurora: lastIndexSize", tracy::PlotFormatType::Memory, false, true, 0); + TracyPlotConfig("aurora: lastStorageSize", tracy::PlotFormatType::Memory, false, true, 0); + TracyPlotConfig("aurora: lastTextureUploadSize", tracy::PlotFormatType::Memory, false, true, 0); + + TracyPlot("aurora: queuedPipelines", static_cast(gfx::g_stats.queuedPipelines)); + TracyPlot("aurora: createdPipelines", static_cast(gfx::g_stats.createdPipelines)); + TracyPlot("aurora: drawCallCount", static_cast(gfx::g_stats.drawCallCount)); + TracyPlot("aurora: mergedDrawCallCount", static_cast(gfx::g_stats.mergedDrawCallCount)); + TracyPlot("aurora: lastVertSize", static_cast(gfx::g_stats.lastVertSize)); + TracyPlot("aurora: lastUniformSize", static_cast(gfx::g_stats.lastUniformSize)); + TracyPlot("aurora: lastIndexSize", static_cast(gfx::g_stats.lastIndexSize)); + TracyPlot("aurora: lastStorageSize", static_cast(gfx::g_stats.lastStorageSize)); + TracyPlot("aurora: lastTextureUploadSize", static_cast(gfx::g_stats.lastTextureUploadSize)); + #endif } } // namespace diff --git a/lib/dolphin/gx/GXManage.cpp b/lib/dolphin/gx/GXManage.cpp index f8114ba..475d6e2 100644 --- a/lib/dolphin/gx/GXManage.cpp +++ b/lib/dolphin/gx/GXManage.cpp @@ -281,10 +281,6 @@ void GXFlush() { if (__gx->dirtyState) { __GXSetDirtyState(); } - for (u32 i = 32; i > 0; i--) { - GX_WRITE_U8(0); - } - // PPCSync(); } void GXPixModeSync() { diff --git a/lib/dolphin/gx/GXTexture.cpp b/lib/dolphin/gx/GXTexture.cpp index 1c88874..c9ae65c 100644 --- a/lib/dolphin/gx/GXTexture.cpp +++ b/lib/dolphin/gx/GXTexture.cpp @@ -8,11 +8,14 @@ #include +#include "tracy/Tracy.hpp" + using namespace aurora::gfx; extern "C" { void GXInitTexObj(GXTexObj* obj_, const void* data, u16 width, u16 height, GXTexFmt format, GXTexWrapMode wrapS, GXTexWrapMode wrapT, GXBool mipmap) { + ZoneScopedS(3); if (reinterpret_cast(obj_)->ref) Log.fatal("Texture object already has a reference"); memset(obj_, 0, sizeof(GXTexObj)); @@ -40,6 +43,7 @@ void GXInitTexObj(GXTexObj* obj_, const void* data, u16 width, u16 height, GXTex void GXInitTexObjCI(GXTexObj* obj_, const void* data, u16 width, u16 height, GXCITexFmt format, GXTexWrapMode wrapS, GXTexWrapMode wrapT, GXBool mipmap, u32 tlut) { + ZoneScopedS(3); if (reinterpret_cast(obj_)->ref) Log.fatal("Texture object already has a reference"); memset(obj_, 0, sizeof(GXTexObj)); diff --git a/lib/gfx/clear.cpp b/lib/gfx/clear.cpp index 2799a5c..d0b4c78 100644 --- a/lib/gfx/clear.cpp +++ b/lib/gfx/clear.cpp @@ -1,6 +1,7 @@ #include "clear.hpp" #include "../webgpu/gpu.hpp" +#include "tracy/Tracy.hpp" namespace { wgpu::ColorWriteMask clear_write_mask(bool clearColor, bool clearAlpha) { @@ -21,6 +22,7 @@ using webgpu::g_device; using webgpu::g_graphicsConfig; wgpu::RenderPipeline create_pipeline(const PipelineConfig& config) { + ZoneScoped; wgpu::ShaderSourceWGSL sourceDescriptor{}; sourceDescriptor.code = R"""( struct VertexOutput { diff --git a/lib/gfx/common.cpp b/lib/gfx/common.cpp index faa8036..c4106b6 100644 --- a/lib/gfx/common.cpp +++ b/lib/gfx/common.cpp @@ -16,6 +16,8 @@ #include #include +#include "tracy/Tracy.hpp" + namespace aurora::gfx { static Module Log("aurora::gfx"); @@ -72,11 +74,11 @@ namespace aurora { // we create specialized methods to handle them. Note that these are highly dependent on // the structure definition, which could easily change with Dawn updates. template <> -inline HashType xxh3_hash(const wgpu::BindGroupDescriptor& input, HashType seed) { - constexpr auto offset = offsetof(wgpu::BindGroupDescriptor, layout); // skip nextInChain, label +inline HashType xxh3_hash(const WGPUBindGroupDescriptor& input, HashType seed) { + constexpr auto offset = offsetof(WGPUBindGroupDescriptor, layout); // skip nextInChain, label const auto hash = xxh3_hash_s(reinterpret_cast(&input) + offset, - sizeof(wgpu::BindGroupDescriptor) - offset - sizeof(void*) /* skip entries */, seed); - return xxh3_hash_s(input.entries, sizeof(wgpu::BindGroupEntry) * input.entryCount, hash); + sizeof(WGPUBindGroupDescriptor) - offset - sizeof(void*) /* skip entries */, seed); + return xxh3_hash_s(input.entries, sizeof(WGPUBindGroupEntry) * input.entryCount, hash); } template <> inline HashType xxh3_hash(const wgpu::SamplerDescriptor& input, HashType seed) { @@ -222,7 +224,7 @@ void push_draw_command(clear::DrawData data) { } template <> -PipelineRef pipeline_ref(clear::PipelineConfig config) { +PipelineRef pipeline_ref(const clear::PipelineConfig& config) { return find_pipeline(ShaderType::Clear, config, [=] { return create_pipeline(config); }); } @@ -349,6 +351,7 @@ static OffscreenCacheEntry get_offscreen_textures(uint32_t width, uint32_t heigh } void begin_offscreen(uint32_t width, uint32_t height) { + ZoneScoped; CHECK(g_currentRenderPass != UINT32_MAX, "begin_offscreen called outside of a frame"); // If the current EFB pass has no resolve target, its output is unobservable. @@ -391,6 +394,7 @@ void begin_offscreen(uint32_t width, uint32_t height) { } void end_offscreen() { + ZoneScoped; CHECK(g_inOffscreen, "end_offscreen called without begin_offscreen"); g_inOffscreen = false; @@ -419,7 +423,7 @@ void push_draw_command(gx::DrawData data) { } template <> -PipelineRef pipeline_ref(gx::PipelineConfig config) { +PipelineRef pipeline_ref(const gx::PipelineConfig& config) { return find_pipeline(ShaderType::GX, config, [=] { return create_pipeline(config); }); } @@ -504,8 +508,12 @@ void map_staging_buffer() { } void begin_frame() { - while (!bufferMapped) { - g_instance.ProcessEvents(); + ZoneScoped; + { + ZoneScopedN("Wait for buffer map"); + while (!bufferMapped) { + g_instance.ProcessEvents(); + } } size_t bufferOffset = 0; const auto& stagingBuf = g_stagingBuffers[currentStagingBuffer]; @@ -537,13 +545,14 @@ void begin_frame() { } void end_frame(const wgpu::CommandEncoder& cmd) { + ZoneScoped; ASSERT(!g_inOffscreen, "end_frame called while offscreen rendering is active"); uint64_t bufferOffset = 0; const auto writeBuffer = [&](ByteBuffer& buf, wgpu::Buffer& out, uint64_t size, std::string_view label) { const auto writeSize = buf.size(); // Only need to copy this many bytes if (writeSize > 0) { - cmd.CopyBufferToBuffer(g_stagingBuffers[currentStagingBuffer], bufferOffset, out, 0, ALIGN(writeSize, 4)); - buf.clear(); + cmd.CopyBufferToBuffer(g_stagingBuffers[currentStagingBuffer], bufferOffset, out, 0, AURORA_ALIGN(writeSize, 4)); + buf.release(); } bufferOffset += size; return writeSize; @@ -561,7 +570,7 @@ void end_frame(const wgpu::CommandEncoder& cmd) { .layout = wgpu::TexelCopyBufferLayout{ .offset = item.layout.offset + bufferOffset, - .bytesPerRow = ALIGN(item.layout.bytesPerRow, 256), + .bytesPerRow = AURORA_ALIGN(item.layout.bytesPerRow, 256), .rowsPerImage = item.layout.rowsPerImage, }, .buffer = g_stagingBuffers[currentStagingBuffer], @@ -569,7 +578,7 @@ void end_frame(const wgpu::CommandEncoder& cmd) { cmd.CopyBufferToTexture(&buf, &item.tex, &item.size); } g_textureUploads.clear(); - g_textureUpload.clear(); + g_textureUpload.release(); } currentStagingBuffer = (currentStagingBuffer + 1) % g_stagingBuffers.size(); map_staging_buffer(); @@ -584,6 +593,7 @@ void end_frame(const wgpu::CommandEncoder& cmd) { uint32_t current_frame() noexcept { return g_frameIndex; } void render(wgpu::CommandEncoder& cmd) { + ZoneScoped; for (u32 i = 0; i < g_renderPasses.size(); ++i) { const auto& passInfo = g_renderPasses[i]; for (const auto& conv : passInfo.paletteConvs) { @@ -807,7 +817,7 @@ Range push_storage(const uint8_t* data, size_t length) { } Range push_texture_data(const uint8_t* data, size_t length, u32 bytesPerRow, u32 rowsPerImage) { // For CopyBufferToTexture, we need an alignment of 256 per row (see Dawn kTextureBytesPerRowAlignment) - const auto copyBytesPerRow = ALIGN(bytesPerRow, 256); + const auto copyBytesPerRow = AURORA_ALIGN(bytesPerRow, 256); const auto range = map(g_textureUpload, copyBytesPerRow * rowsPerImage, 0); u8* dst = g_textureUpload.data() + range.offset; for (u32 i = 0; i < rowsPerImage; ++i) { @@ -835,21 +845,21 @@ std::pair map_storage(size_t length) { } // TODO: should we avoid caching bind groups altogether? -BindGroupRef bind_group_ref(const wgpu::BindGroupDescriptor& descriptor) { +BindGroupRef bind_group_ref(const WGPUBindGroupDescriptor& descriptor) { #ifdef EMSCRIPTEN - const auto bg = g_device.CreateBindGroup(&descriptor); + const auto bg = wgpuDeviceCreateBindGroup(g_device.Get(), &descriptor); BindGroupRef id = reinterpret_cast(bg.Get()); g_cachedBindGroups.try_emplace(id, bg); #else const auto id = xxh3_hash(descriptor); if (!g_cachedBindGroups.contains(id)) { - g_cachedBindGroups.try_emplace(id, g_device.CreateBindGroup(&descriptor)); + g_cachedBindGroups.try_emplace(id, wgpuDeviceCreateBindGroup(g_device.Get(), &descriptor)); } #endif return id; } -wgpu::BindGroup find_bind_group(BindGroupRef id) { +wgpu::BindGroup& find_bind_group(BindGroupRef id) { #ifdef EMSCRIPTEN return g_cachedBindGroups[id]; #else @@ -859,7 +869,7 @@ wgpu::BindGroup find_bind_group(BindGroupRef id) { #endif } -wgpu::Sampler sampler_ref(const wgpu::SamplerDescriptor& descriptor) { +wgpu::Sampler& sampler_ref(const wgpu::SamplerDescriptor& descriptor) { const auto id = xxh3_hash(descriptor); auto it = g_cachedSamplers.find(id); if (it == g_cachedSamplers.end()) { @@ -868,7 +878,7 @@ wgpu::Sampler sampler_ref(const wgpu::SamplerDescriptor& descriptor) { return it->second; } -uint32_t align_uniform(uint32_t value) { return ALIGN(value, g_cachedLimits.minUniformBufferOffsetAlignment); } +uint32_t align_uniform(uint32_t value) { return AURORA_ALIGN(value, g_cachedLimits.minUniformBufferOffsetAlignment); } void insert_debug_marker(std::string label) { #if defined(AURORA_GFX_DEBUG_GROUPS) diff --git a/lib/gfx/common.hpp b/lib/gfx/common.hpp index 3516e15..a10f975 100644 --- a/lib/gfx/common.hpp +++ b/lib/gfx/common.hpp @@ -109,7 +109,7 @@ public: m_length += size; } - void clear() { + void release() { if (m_data != nullptr && m_owned) { free(m_data); } @@ -119,6 +119,10 @@ public: m_owned = true; } + void clear() { + m_length = 0; + } + void reserve_extra(size_t size) { resize(m_length + size, true); } private: @@ -141,6 +145,10 @@ private: if (!m_owned) { abort(); } + // Exponential expansion to avoid O(n^2) time complexity. + if (size < m_capacity * 2) { + size = m_capacity * 2; + } m_data = static_cast(realloc(m_data, size)); if (zeroed) { memset(m_data + m_capacity, 0, size - m_capacity); @@ -272,13 +280,13 @@ template DrawData* get_last_draw_command(); template -PipelineRef pipeline_ref(PipelineConfig config); +PipelineRef pipeline_ref(const PipelineConfig& config); bool bind_pipeline(PipelineRef ref, const wgpu::RenderPassEncoder& pass); -BindGroupRef bind_group_ref(const wgpu::BindGroupDescriptor& descriptor); -wgpu::BindGroup find_bind_group(BindGroupRef id); +BindGroupRef bind_group_ref(const WGPUBindGroupDescriptor& descriptor); +wgpu::BindGroup& find_bind_group(BindGroupRef id); -wgpu::Sampler sampler_ref(const wgpu::SamplerDescriptor& descriptor); +wgpu::Sampler& sampler_ref(const wgpu::SamplerDescriptor& descriptor); uint32_t align_uniform(uint32_t value); diff --git a/lib/gfx/texture.cpp b/lib/gfx/texture.cpp index c1c2d86..8336e39 100644 --- a/lib/gfx/texture.cpp +++ b/lib/gfx/texture.cpp @@ -16,6 +16,8 @@ #include #include +#include "tracy/Tracy.hpp" + namespace aurora::gfx { using webgpu::g_device; using webgpu::g_queue; @@ -141,6 +143,7 @@ static bool setup_swizzle(wgpu::TextureComponentSwizzleDescriptor& swizzle, u32 TextureHandle new_dynamic_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 gxFormat, const char* label) noexcept { + ZoneScopedS(3); const auto wgpuFormat = to_wgpu(gxFormat); const wgpu::Extent3D size{ .width = width, diff --git a/lib/gx/command_processor.cpp b/lib/gx/command_processor.cpp index a6fc9e5..0a8e51d 100644 --- a/lib/gx/command_processor.cpp +++ b/lib/gx/command_processor.cpp @@ -14,6 +14,8 @@ #include "dolphin/gx/GXAurora.h" #include +#include "tracy/Tracy.hpp" + namespace aurora::gx::fifo { static Module Log("aurora::gx::fifo"); @@ -104,20 +106,35 @@ static constexpr u8 CP_OPCODE_MASK = GX_OPCODE_MASK; static constexpr u8 CP_VAT_MASK = GX_VAT_MASK; // Read helpers for big/little endian +#if _MSC_VER +template +__forceinline // Yes, this was necessary. +inline T unaligned_load(const T* ptr) { + return *static_cast(ptr); +} +#else +template +inline T unaligned_load(const T* ptr) { + T copy; + memcpy(©, ptr, sizeof(T)); + return copy; +} +#endif + static inline u16 read_u16(const u8* ptr, bool bigEndian) { + const u16 val = unaligned_load(reinterpret_cast(ptr)); if (bigEndian) { - return static_cast(ptr[0] << 8 | ptr[1]); + return bswap(val); } - return static_cast(ptr[1] << 8 | ptr[0]); + return val; } static inline u32 read_u32(const u8* ptr, bool bigEndian) { + const u32 val = unaligned_load(reinterpret_cast(ptr)); if (bigEndian) { - return static_cast(ptr[0]) << 24 | static_cast(ptr[1]) << 16 | static_cast(ptr[2]) << 8 | - static_cast(ptr[3]); + return bswap(val); } - return static_cast(ptr[3]) << 24 | static_cast(ptr[2]) << 16 | static_cast(ptr[1]) << 8 | - static_cast(ptr[0]); + return val; } static u32 bp_get(u32 reg, u32 size, u32 shift); @@ -317,6 +334,7 @@ static void handle_draw(u8 cmd, const u8* data, u32& pos, u32 size, bool bigEndi static void handle_aurora(const u8* data, u32& pos, u32 size, bool bigEndian); void process(const u8* data, u32 size, bool bigEndian) { + ZoneScoped; u32 pos = 0; while (pos < size) { @@ -354,6 +372,7 @@ void process(const u8* data, u32 size, bool bigEndian) { case CP_CMD_LOAD_INDX_B: case CP_CMD_LOAD_INDX_C: case CP_CMD_LOAD_INDX_D: { + ZoneScopedN("LOAD_INDX"); // Indexed XF load: 4 bytes of data CHECK(pos + 4 <= size, "indexed XF read overrun"); u32 arrayType = GX_POS_MTX_ARRAY + (opcode - (CP_CMD_LOAD_INDX_A / 0x08)); @@ -426,10 +445,11 @@ void process(const u8* data, u32 size, bool bigEndian) { } // Helper to extract bit fields from a 32-bit register -static u32 bp_get(u32 reg, u32 size, u32 shift) { return reg >> shift & (1u << size) - 1; } +inline static u32 bp_get(u32 reg, u32 size, u32 shift) { return reg >> shift & (1u << size) - 1; } // BP register handler - decodes BP (RAS/pixel engine) register writes and updates g_gxState static void handle_bp(u32 value, bool bigEndian) { + ZoneScoped; u32 regId = (value >> 24) & 0xFF; // Mask off the register ID from the value for field extraction // (the regId is stored in bits 24-31, data is in bits 0-23) @@ -1012,6 +1032,7 @@ static void handle_bp(u32 value, bool bigEndian) { // CP register handler - decodes CP register writes and updates g_gxState static void handle_cp(u8 addr, u32 value, bool bigEndian) { + ZoneScoped; switch (addr) { // VCD low (0x50) case 0x50: { @@ -1030,6 +1051,7 @@ static void handle_cp(u8 addr, u32 value, bool bigEndian) { vd[GX_VA_CLR0] = static_cast(bp_get(value, 2, 13)); vd[GX_VA_CLR1] = static_cast(bp_get(value, 2, 15)); g_gxState.stateDirty = true; + g_gxState.clearVtxSizeCache(); break; } @@ -1045,6 +1067,7 @@ static void handle_cp(u8 addr, u32 value, bool bigEndian) { vd[GX_VA_TEX6] = static_cast(bp_get(value, 2, 12)); vd[GX_VA_TEX7] = static_cast(bp_get(value, 2, 14)); g_gxState.stateDirty = true; + g_gxState.clearVtxSizeCache(); break; } @@ -1086,6 +1109,7 @@ static void handle_cp(u8 addr, u32 value, bool bigEndian) { vf.attrs[GX_VA_TEX0].type = static_cast(bp_get(value, 3, 22)); vf.attrs[GX_VA_TEX0].frac = static_cast(bp_get(value, 5, 25)); g_gxState.stateDirty = true; + g_gxState.clearVtxSizeCache(); } // VAT B registers (0x80-0x87) else if (addr >= 0x80 && addr <= 0x87) { @@ -1104,6 +1128,7 @@ static void handle_cp(u8 addr, u32 value, bool bigEndian) { vf.attrs[GX_VA_TEX4].type = static_cast(bp_get(value, 3, 28)); // TEX4 frac is in VAT C g_gxState.stateDirty = true; + g_gxState.clearVtxSizeCache(); } // VAT C registers (0x90-0x97) else if (addr >= 0x90 && addr <= 0x97) { @@ -1120,6 +1145,7 @@ static void handle_cp(u8 addr, u32 value, bool bigEndian) { vf.attrs[GX_VA_TEX7].type = static_cast(bp_get(value, 3, 24)); vf.attrs[GX_VA_TEX7].frac = static_cast(bp_get(value, 5, 27)); g_gxState.stateDirty = true; + g_gxState.clearVtxSizeCache(); } // Array base addresses (0xA0-0xAF) else if (addr >= 0xA0 && addr <= 0xAF) { @@ -1143,6 +1169,7 @@ static void handle_cp(u8 addr, u32 value, bool bigEndian) { // XF register handler - decodes XF (transform unit) register writes and updates g_gxState static void handle_xf(const u8* data, u32& pos, u32 size, bool bigEndian) { + ZoneScoped; CHECK(pos + 4 <= size, "XF header read overrun"); u32 header = read_u32(data + pos, bigEndian); pos += 4; @@ -1362,16 +1389,24 @@ static void handle_xf(const u8* data, u32& pos, u32 size, bool bigEndian) { pos += dataBytes; } +static void handle_draw_overrun [[noreturn]] (u32 totalVtxBytes, const u8* data, const u32& pos, u32 size) { + // Hex dump around the draw command for debugging + u32 cmdPos = pos - 2 - 1; // opcode byte position (before vtxCount and pos++) + u32 dumpStart = (cmdPos > 16) ? cmdPos - 16 : 0; + u32 dumpEnd = (cmdPos + 32 < size) ? cmdPos + 32 : size; + std::string hex; + for (u32 i = dumpStart; i < dumpEnd; i++) { + if (i == cmdPos) + hex += fmt::format("[{:02x}]", data[i]); + else + hex += fmt::format(" {:02x}", data[i]); + } + Log.error(" hex dump around draw cmd (pos {}-{}):{}", dumpStart, dumpEnd - 1, hex); + FATAL("draw vertex data overrun: need {} bytes at pos {}, have {}", totalVtxBytes, pos, size); +} + // Draw command handler - parses vertices inline and caches results -static void handle_draw(u8 cmd, const u8* data, u32& pos, u32 size, bool bigEndian) { - u8 opcode = cmd & CP_OPCODE_MASK; - GXVtxFmt fmt = static_cast(cmd & CP_VAT_MASK); - GXPrimitive prim = static_cast(opcode); - - CHECK(pos + 2 <= size, "draw vtxCount read overrun"); - u16 vtxCount = read_u16(data + pos, bigEndian); - pos += 2; - +static u32 calculate_last_vtx_size(GXVtxFmt fmt) { u32 vtxSize = 0; const auto& vtxFmt = g_gxState.vtxFmts[fmt]; for (int i = GX_VA_PNMTXIDX; i <= GX_VA_TEX7; ++i) { @@ -1392,38 +1427,53 @@ static void handle_draw(u8 cmd, const u8* data, u32& pos, u32 size, bool bigEndi break; } } + + g_gxState.lastVtxFmt = fmt; + g_gxState.lastVtxSize = vtxSize; + + return vtxSize; +} + +static void handle_draw_unmerged(GXPrimitive prim, GXVtxFmt fmt, u16 vtxCount, gfx::Range vertRange); + +// Draw command handler - parses vertices inline and caches results +static ByteBuffer handle_draw_idx_buf; + +static void handle_draw(u8 cmd, const u8* data, u32& pos, u32 size, bool bigEndian) { + ZoneScoped; + u8 opcode = cmd & CP_OPCODE_MASK; + GXVtxFmt fmt = static_cast(cmd & CP_VAT_MASK); + GXPrimitive prim = static_cast(opcode); + + CHECK(pos + 2 <= size, "draw vtxCount read overrun"); + u16 vtxCount = read_u16(data + pos, bigEndian); + pos += 2; + + u32 vtxSize; + if (g_gxState.lastVtxFmt == fmt) LIKELY { + vtxSize = g_gxState.lastVtxSize; + } else UNLIKELY { + vtxSize = calculate_last_vtx_size(fmt); + } + u32 totalVtxBytes = vtxCount * vtxSize; - if (pos + totalVtxBytes > size) { - // Hex dump around the draw command for debugging - u32 cmdPos = pos - 2 - 1; // opcode byte position (before vtxCount and pos++) - u32 dumpStart = (cmdPos > 16) ? cmdPos - 16 : 0; - u32 dumpEnd = (cmdPos + 32 < size) ? cmdPos + 32 : size; - std::string hex; - for (u32 i = dumpStart; i < dumpEnd; i++) { - if (i == cmdPos) - hex += fmt::format("[{:02x}]", data[i]); - else - hex += fmt::format(" {:02x}", data[i]); - } - Log.error(" hex dump around draw cmd (pos {}-{}):{}", dumpStart, dumpEnd - 1, hex); - FATAL("draw vertex data overrun: need {} bytes at pos {}, have {}", totalVtxBytes, pos, size); + if (pos + totalVtxBytes > size) UNLIKELY { + handle_draw_overrun(totalVtxBytes, data, pos, size); } // Push raw vertex data to buffer gfx::Range vertRange = gfx::push_verts(data + pos, totalVtxBytes); pos += totalVtxBytes; - u32 numIndices = 0; - gfx::Range idxRange; // Try to merge with previous draw call - if (!g_gxState.stateDirty) { + if (!g_gxState.stateDirty) LIKELY { auto* lastDraw = gfx::get_last_draw_command(); // Only if the previous draw call was a single instance draw (no lines/points handling) if (lastDraw != nullptr && prim != GX_LINES && prim != GX_LINESTRIP && prim != GX_POINTS && - lastDraw->instanceCount == 1) { - ByteBuffer idxBuf; - numIndices = prepare_idx_buffer(idxBuf, prim, lastDraw->vtxCount, vtxCount); - idxRange = gfx::push_indices(idxBuf.data(), idxBuf.size()); + lastDraw->instanceCount == 1) LIKELY { + u32 numIndices = prepare_idx_buffer(handle_draw_idx_buf, prim, lastDraw->vtxCount, vtxCount); + gfx::Range idxRange = gfx::push_indices(handle_draw_idx_buf.data(), handle_draw_idx_buf.size()); + handle_draw_idx_buf.clear(); CHECK(lastDraw->vertRange.offset + lastDraw->vertRange.size == vertRange.offset, "Non-consecutive vertex ranges ({} < {})", lastDraw->vertRange.offset + lastDraw->vertRange.size, vertRange.offset); @@ -1439,10 +1489,22 @@ static void handle_draw(u8 cmd, const u8* data, u32& pos, u32 size, bool bigEndi } } + handle_draw_unmerged(prim, fmt, vtxCount, vertRange); +} + +static ByteBuffer handle_draw_unmerged_idxBuf; + +static void handle_draw_unmerged(GXPrimitive prim, GXVtxFmt fmt, u16 vtxCount, gfx::Range vertRange) { + ZoneScoped; + u32 numIndices = 0; + gfx::Range idxRange; + { ByteBuffer idxBuf; - numIndices = prepare_idx_buffer(idxBuf, prim, 0, vtxCount); - idxRange = gfx::push_indices(idxBuf.data(), idxBuf.size()); + auto& realBuf = vtxCount < 1000 ? handle_draw_unmerged_idxBuf : idxBuf; + numIndices = prepare_idx_buffer(realBuf, prim, 0, vtxCount); + idxRange = gfx::push_indices(realBuf.data(), realBuf.size()); + realBuf.clear(); } // Build pipeline, bind groups, and push draw command @@ -1501,6 +1563,7 @@ std::string read_string(const u8* data, u32& pos, u32 size, bool bigEndian) { } void handle_aurora(const u8* data, u32& pos, u32 size, bool bigEndian) { + ZoneScoped; CHECK(pos + 2 <= size, "Aurora cmd read overrun"); u16 subCmd = read_u16(data + pos, bigEndian); pos += 2; diff --git a/lib/gx/gx.cpp b/lib/gx/gx.cpp index 46c94f4..bc81678 100644 --- a/lib/gx/gx.cpp +++ b/lib/gx/gx.cpp @@ -12,6 +12,8 @@ #include #include +#include "tracy/Tracy.hpp" + static aurora::Module Log("aurora::gx"); namespace aurora::gx { @@ -186,6 +188,7 @@ 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 wgpu::DepthStencilState depthStencil{ .format = g_graphicsConfig.depthFormat, .depthWriteEnabled = config.depthCompare && config.depthUpdate, @@ -439,11 +442,13 @@ GXBindGroups build_bind_groups(const ShaderInfo& info, const ShaderConfig& confi const BindGroupRanges& ranges) noexcept { const auto layouts = build_bind_group_layouts(config); - std::array uniformEntries; + // Using C WGPU types instead of C++ wrappers to avoid destructor overhead + std::array uniformEntries{}; uniformEntries[0].binding = 0; - uniformEntries[0].buffer = gfx::g_vertexBuffer; + uniformEntries[0].buffer = gfx::g_vertexBuffer.Get(); + uniformEntries[0].size = WGPU_WHOLE_SIZE; uniformEntries[1].binding = 1; - uniformEntries[1].buffer = gfx::g_uniformBuffer; + uniformEntries[1].buffer = gfx::g_uniformBuffer.Get(); uniformEntries[1].size = info.uniformSize; u32 uniformBindIdx = 2; for (u32 i = 0; i < MaxIndexAttr; ++i) { @@ -451,46 +456,46 @@ GXBindGroups build_bind_groups(const ShaderInfo& info, const ShaderConfig& confi if (range.size <= 0) { continue; } - wgpu::BindGroupEntry& entry = uniformEntries[uniformBindIdx]; + WGPUBindGroupEntry& entry = uniformEntries[uniformBindIdx]; entry.binding = uniformBindIdx; - entry.buffer = gfx::g_storageBuffer; + entry.buffer = gfx::g_storageBuffer.Get(); entry.size = range.size; ++uniformBindIdx; } - std::array samplerEntries; - std::array textureEntries; + std::array samplerEntries{}; + std::array textureEntries{}; u32 textureCount = 0; for (u32 i = 0; i < MaxTextures; ++i) { const auto& tex = g_gxState.textures[i]; - wgpu::BindGroupEntry& samplerEntry = samplerEntries[textureCount]; - wgpu::BindGroupEntry& textureEntry = textureEntries[textureCount]; + WGPUBindGroupEntry& samplerEntry = samplerEntries[textureCount]; + WGPUBindGroupEntry& textureEntry = textureEntries[textureCount]; samplerEntry.binding = textureCount; textureEntry.binding = textureCount; - if (tex) { - samplerEntry.sampler = gfx::sampler_ref(tex.get_descriptor()); - textureEntry.textureView = tex.texObj.ref->sampleTextureView; + if (tex && (info.sampledTextures[i] || info.sampledIndTextures[i])) { + samplerEntry.sampler = gfx::sampler_ref(tex.get_descriptor()).Get(); + textureEntry.textureView = tex.texObj.ref->sampleTextureView.Get(); } else { - samplerEntry.sampler = sEmptySampler; - textureEntry.textureView = sEmptyTextureView; + samplerEntry.sampler = sEmptySampler.Get(); + textureEntry.textureView = sEmptyTextureView.Get(); } ++textureCount; } - const wgpu::BindGroupDescriptor uniformBindGroupDescriptor{ - .label = "GX Uniform Bind Group", - .layout = layouts.uniformLayout, + const WGPUBindGroupDescriptor uniformBindGroupDescriptor{ + .label = {"GX Uniform Bind Group", WGPU_STRLEN}, + .layout = layouts.uniformLayout.Get(), .entryCount = uniformBindIdx, .entries = uniformEntries.data(), }; - const wgpu::BindGroupDescriptor samplerBindGroupDescriptor{ - .label = "GX Sampler Bind Group", - .layout = layouts.samplerLayout, + const WGPUBindGroupDescriptor samplerBindGroupDescriptor{ + .label = {"GX Sampler Bind Group", WGPU_STRLEN}, + .layout = layouts.samplerLayout.Get(), .entryCount = textureCount, .entries = samplerEntries.data(), }; - const wgpu::BindGroupDescriptor textureBindGroupDescriptor{ - .label = "GX Texture Bind Group", - .layout = layouts.textureLayout, + const WGPUBindGroupDescriptor textureBindGroupDescriptor{ + .label = {"GX Texture Bind Group", WGPU_STRLEN}, + .layout = layouts.textureLayout.Get(), .entryCount = textureCount, .entries = textureEntries.data(), }; diff --git a/lib/gx/gx.hpp b/lib/gx/gx.hpp index 78d2c80..82f800f 100644 --- a/lib/gx/gx.hpp +++ b/lib/gx/gx.hpp @@ -315,6 +315,8 @@ struct GXState { std::array, MaxPTTexMtx> ptTexMtxs; std::array tcgs; std::array texCoordScales; + u16 lastVtxSize = 0; + GXVtxFmt lastVtxFmt = GX_MAX_VTXFMT; std::array vtxDesc; std::array vtxFmts; std::array tevSwapTable{ @@ -358,6 +360,10 @@ struct GXState { bool stateDirty = true; std::array bpRegCache; GXState() { bpRegCache[0xFE] = 0x00FFFFFF; } + + void clearVtxSizeCache() { + lastVtxFmt = GX_MAX_VTXFMT; + } }; extern GXState g_gxState; diff --git a/lib/gx/pipeline.cpp b/lib/gx/pipeline.cpp index b851175..0ec6f36 100644 --- a/lib/gx/pipeline.cpp +++ b/lib/gx/pipeline.cpp @@ -3,11 +3,13 @@ #include "../webgpu/gpu.hpp" #include "gx_fmt.hpp" #include "shader_info.hpp" +#include "tracy/Tracy.hpp" namespace aurora::gx { static Module Log("aurora::gx"); wgpu::RenderPipeline create_pipeline(const PipelineConfig& config) { + ZoneScoped; const auto shader = build_shader(config.shaderConfig); return build_pipeline(config, {}, shader, "GX Pipeline"); } diff --git a/lib/gx/shader.cpp b/lib/gx/shader.cpp index a7bfe43..c8fedcf 100644 --- a/lib/gx/shader.cpp +++ b/lib/gx/shader.cpp @@ -13,6 +13,8 @@ #include #include +#include "tracy/Tracy.hpp" + namespace aurora::gx { using namespace fmt::literals; using namespace std::string_literals; @@ -680,6 +682,7 @@ auto lighting_func(const ShaderConfig& config, const ColorChannelConfig& cc, u8 } wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept { + ZoneScoped; const auto hash = xxh3_hash(config); { std::lock_guard lock{g_gxCachedShadersMutex}; diff --git a/lib/imgui.cpp b/lib/imgui.cpp index 1e9ad52..34911b4 100644 --- a/lib/imgui.cpp +++ b/lib/imgui.cpp @@ -15,6 +15,7 @@ #include "backends/imgui_impl_sdl3.h" #include "backends/imgui_impl_sdlrenderer3.h" #include "backends/imgui_impl_wgpu.h" +#include "tracy/Tracy.hpp" namespace aurora::imgui { static float g_scale; @@ -36,6 +37,7 @@ void create_context() noexcept { } void initialize() noexcept { + ZoneScoped; SDL_Renderer* renderer = window::get_sdl_renderer(); ImGui_ImplSDL3_InitForSDLRenderer(window::get_sdl_window(), renderer); g_useSdlRenderer = renderer != nullptr; @@ -50,6 +52,7 @@ void initialize() noexcept { } void shutdown() noexcept { + ZoneScoped; if (g_useSdlRenderer) { ImGui_ImplSDLRenderer3_Shutdown(); } else { @@ -69,6 +72,7 @@ void process_event(const SDL_Event& event) noexcept { } void new_frame(const AuroraWindowSize& size) noexcept { + ZoneScoped; const float framebufferScaleX = size.width > 0 ? static_cast(size.native_fb_width) / static_cast(size.width) : 1.0f; const float framebufferScaleY = @@ -98,6 +102,7 @@ void new_frame(const AuroraWindowSize& size) noexcept { } void render(const wgpu::RenderPassEncoder& pass) noexcept { + ZoneScoped; ImGui::Render(); auto* data = ImGui::GetDrawData(); diff --git a/lib/internal.hpp b/lib/internal.hpp index da2a683..8c5df12 100644 --- a/lib/internal.hpp +++ b/lib/internal.hpp @@ -80,9 +80,7 @@ auto underlying(T value) -> std::underlying_type_t { return static_cast>(value); } -#ifndef ALIGN -#define ALIGN(x, a) (((x) + ((a) - 1)) & ~((a) - 1)) -#endif +#define AURORA_ALIGN(x, a) (((x) + ((a) - 1)) & ~((a) - 1)) #define POINTER_ADD_TYPE(type_, ptr_, offset_) ((type_)((uintptr_t)(ptr_) + (uintptr_t)(offset_))) #define POINTER_ADD(ptr_, offset_) POINTER_ADD_TYPE(decltype(ptr_), ptr_, offset_) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index df1fcec..988a50b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -56,6 +56,7 @@ if (AURORA_ENABLE_GX) absl::flat_hash_map absl::btree dawn::dawncpp_headers + TracyClient ${AURORA_SDL3_TARGET} ) diff --git a/tests/gx_test_stubs.cpp b/tests/gx_test_stubs.cpp index 999edd1..bfa5bb7 100644 --- a/tests/gx_test_stubs.cpp +++ b/tests/gx_test_stubs.cpp @@ -97,7 +97,7 @@ void set_scissor(uint32_t x, uint32_t y, uint32_t w, uint32_t h) noexcept {} // --- Pipeline/draw command stubs --- namespace aurora::gfx { template <> -PipelineRef pipeline_ref(clear::PipelineConfig config) { +PipelineRef pipeline_ref(const clear::PipelineConfig& config) { return 0; } template <> @@ -105,7 +105,7 @@ void push_draw_command(clear::DrawData data) { // No-op } template <> -PipelineRef pipeline_ref(gx::PipelineConfig config) { +PipelineRef pipeline_ref(const gx::PipelineConfig& config) { return 0; } template <>