Merge branch 'main' into pipeline-cache

# Conflicts:
#	cmake/aurora_gx.cmake
#	lib/gfx/common.cpp
This commit is contained in:
Luke Street
2026-04-08 19:52:26 -06:00
20 changed files with 247 additions and 98 deletions
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+20
View File
@@ -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 ()
+23
View File
@@ -15,6 +15,8 @@
#include <SDL3/SDL_filesystem.h>
#include <magic_enum.hpp>
#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<int>(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<int64_t>(gfx::g_stats.queuedPipelines));
TracyPlot("aurora: createdPipelines", static_cast<int64_t>(gfx::g_stats.createdPipelines));
TracyPlot("aurora: drawCallCount", static_cast<int64_t>(gfx::g_stats.drawCallCount));
TracyPlot("aurora: mergedDrawCallCount", static_cast<int64_t>(gfx::g_stats.mergedDrawCallCount));
TracyPlot("aurora: lastVertSize", static_cast<int64_t>(gfx::g_stats.lastVertSize));
TracyPlot("aurora: lastUniformSize", static_cast<int64_t>(gfx::g_stats.lastUniformSize));
TracyPlot("aurora: lastIndexSize", static_cast<int64_t>(gfx::g_stats.lastIndexSize));
TracyPlot("aurora: lastStorageSize", static_cast<int64_t>(gfx::g_stats.lastStorageSize));
TracyPlot("aurora: lastTextureUploadSize", static_cast<int64_t>(gfx::g_stats.lastTextureUploadSize));
#endif
}
} // namespace
-4
View File
@@ -281,10 +281,6 @@ void GXFlush() {
if (__gx->dirtyState) {
__GXSetDirtyState();
}
for (u32 i = 32; i > 0; i--) {
GX_WRITE_U8(0);
}
// PPCSync();
}
void GXPixModeSync() {
+4
View File
@@ -8,11 +8,14 @@
#include <absl/container/flat_hash_map.h>
#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<GXTexObj_*>(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<GXTexObj_*>(obj_)->ref)
Log.fatal("Texture object already has a reference");
memset(obj_, 0, sizeof(GXTexObj));
+2
View File
@@ -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 {
+29 -19
View File
@@ -16,6 +16,8 @@
#include <absl/container/flat_hash_map.h>
#include <magic_enum.hpp>
#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<const u8*>(&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<ByteBuffer, Range> 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<BindGroupRef>(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)
+13 -5
View File
@@ -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<uint8_t*>(realloc(m_data, size));
if (zeroed) {
memset(m_data + m_capacity, 0, size - m_capacity);
@@ -272,13 +280,13 @@ template <typename DrawData>
DrawData* get_last_draw_command();
template <typename PipelineConfig>
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);
+3
View File
@@ -16,6 +16,8 @@
#include <magic_enum.hpp>
#include <webgpu/webgpu_cpp.h>
#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,
+102 -39
View File
@@ -14,6 +14,8 @@
#include "dolphin/gx/GXAurora.h"
#include <limits>
#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<typename T>
__forceinline // Yes, this was necessary.
inline T unaligned_load(const T* ptr) {
return *static_cast<const __unaligned T*>(ptr);
}
#else
template<typename T>
inline T unaligned_load(const T* ptr) {
T copy;
memcpy(&copy, ptr, sizeof(T));
return copy;
}
#endif
static inline u16 read_u16(const u8* ptr, bool bigEndian) {
const u16 val = unaligned_load(reinterpret_cast<const u16*>(ptr));
if (bigEndian) {
return static_cast<u16>(ptr[0] << 8 | ptr[1]);
return bswap(val);
}
return static_cast<u16>(ptr[1] << 8 | ptr[0]);
return val;
}
static inline u32 read_u32(const u8* ptr, bool bigEndian) {
const u32 val = unaligned_load(reinterpret_cast<const u32*>(ptr));
if (bigEndian) {
return static_cast<u32>(ptr[0]) << 24 | static_cast<u32>(ptr[1]) << 16 | static_cast<u32>(ptr[2]) << 8 |
static_cast<u32>(ptr[3]);
return bswap(val);
}
return static_cast<u32>(ptr[3]) << 24 | static_cast<u32>(ptr[2]) << 16 | static_cast<u32>(ptr[1]) << 8 |
static_cast<u32>(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<GXAttrType>(bp_get(value, 2, 13));
vd[GX_VA_CLR1] = static_cast<GXAttrType>(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<GXAttrType>(bp_get(value, 2, 12));
vd[GX_VA_TEX7] = static_cast<GXAttrType>(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<GXCompType>(bp_get(value, 3, 22));
vf.attrs[GX_VA_TEX0].frac = static_cast<u8>(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<GXCompType>(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<GXCompType>(bp_get(value, 3, 24));
vf.attrs[GX_VA_TEX7].frac = static_cast<u8>(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<GXVtxFmt>(cmd & CP_VAT_MASK);
GXPrimitive prim = static_cast<GXPrimitive>(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<GXVtxFmt>(cmd & CP_VAT_MASK);
GXPrimitive prim = static_cast<GXPrimitive>(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<DrawData>();
// 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;
+28 -23
View File
@@ -12,6 +12,8 @@
#include <cfloat>
#include <mutex>
#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<wgpu::VertexBufferLayout> 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<wgpu::BindGroupEntry, MaxIndexAttr + 3> uniformEntries;
// Using C WGPU types instead of C++ wrappers to avoid destructor overhead
std::array<WGPUBindGroupEntry, MaxIndexAttr + 3> 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<wgpu::BindGroupEntry, MaxTextures> samplerEntries;
std::array<wgpu::BindGroupEntry, MaxTextures> textureEntries;
std::array<WGPUBindGroupEntry, MaxTextures> samplerEntries{};
std::array<WGPUBindGroupEntry, MaxTextures> 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(),
};
+6
View File
@@ -315,6 +315,8 @@ struct GXState {
std::array<Mat3x4<float>, MaxPTTexMtx> ptTexMtxs;
std::array<TcgConfig, MaxTexCoord> tcgs;
std::array<TexCoordScale, MaxTexCoord> texCoordScales;
u16 lastVtxSize = 0;
GXVtxFmt lastVtxFmt = GX_MAX_VTXFMT;
std::array<GXAttrType, MaxVtxAttr> vtxDesc;
std::array<VtxFmt, MaxVtxFmt> vtxFmts;
std::array<TevSwap, MaxTevSwap> tevSwapTable{
@@ -358,6 +360,10 @@ struct GXState {
bool stateDirty = true;
std::array<u32, 0x100> bpRegCache;
GXState() { bpRegCache[0xFE] = 0x00FFFFFF; }
void clearVtxSizeCache() {
lastVtxFmt = GX_MAX_VTXFMT;
}
};
extern GXState g_gxState;
+2
View File
@@ -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");
}
+3
View File
@@ -13,6 +13,8 @@
#include <string_view>
#include <utility>
#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};
+5
View File
@@ -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<float>(size.native_fb_width) / static_cast<float>(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();
+1 -3
View File
@@ -80,9 +80,7 @@ auto underlying(T value) -> std::underlying_type_t<T> {
return static_cast<std::underlying_type_t<T>>(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_)
+1
View File
@@ -56,6 +56,7 @@ if (AURORA_ENABLE_GX)
absl::flat_hash_map
absl::btree
dawn::dawncpp_headers
TracyClient
${AURORA_SDL3_TARGET}
)
+2 -2
View File
@@ -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>(clear::PipelineConfig config) {
PipelineRef pipeline_ref<clear::PipelineConfig>(const clear::PipelineConfig& config) {
return 0;
}
template <>
@@ -105,7 +105,7 @@ void push_draw_command<clear::DrawData>(clear::DrawData data) {
// No-op
}
template <>
PipelineRef pipeline_ref<gx::PipelineConfig>(gx::PipelineConfig config) {
PipelineRef pipeline_ref<gx::PipelineConfig>(const gx::PipelineConfig& config) {
return 0;
}
template <>