Introduce render worker & revamp RmlUi backend

This commit is contained in:
Luke Street
2026-06-04 23:43:13 -06:00
parent 04cd3b3ac7
commit 71d8462557
33 changed files with 2979 additions and 1828 deletions
+8
View File
@@ -3,6 +3,7 @@ add_library(aurora_gx STATIC
lib/gfx/common.cpp
lib/gfx/depth_peek.cpp
lib/gfx/pipeline_cache.cpp
lib/gfx/render_worker.cpp
lib/gfx/dds_io.cpp
lib/gfx/tex_copy_conv.cpp
lib/gfx/tex_palette_conv.cpp
@@ -43,3 +44,10 @@ set_target_properties(aurora_gx PROPERTIES FOLDER "aurora")
target_link_libraries(aurora_gx PUBLIC aurora::core dawn::webgpu_dawn xxhash)
target_link_libraries(aurora_gx PRIVATE absl::btree absl::flat_hash_map sqlite3 TracyClient PNG::PNG)
target_compile_definitions(aurora_gx PRIVATE WEBGPU_DAWN)
if (AURORA_ENABLE_RMLUI)
target_sources(aurora_gx PRIVATE
lib/rmlui/pipeline.cpp
lib/rmlui/pipeline.hpp
)
endif ()
+89 -76
View File
@@ -176,16 +176,11 @@ AuroraInfo initialize(int argc, char* argv[], const AuroraConfig& config) noexce
};
}
#ifdef AURORA_ENABLE_GX
wgpu::TextureView g_currentView;
#endif
void shutdown() noexcept {
#ifdef AURORA_ENABLE_RMLUI
rmlui::shutdown();
#endif
#ifdef AURORA_ENABLE_GX
g_currentView = {};
imgui::shutdown();
gfx::shutdown();
webgpu::shutdown();
@@ -221,36 +216,10 @@ bool begin_frame() noexcept {
return false;
}
}
wgpu::SurfaceTexture surfaceTexture;
g_surface.GetCurrentTexture(&surfaceTexture);
switch (surfaceTexture.status) {
case wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal:
g_currentView = surfaceTexture.texture.CreateView();
break;
case wgpu::SurfaceGetCurrentTextureStatus::Timeout:
Log.warn("Surface texture acquisition timed out");
return false;
case wgpu::SurfaceGetCurrentTextureStatus::SuccessSuboptimal:
case wgpu::SurfaceGetCurrentTextureStatus::Outdated:
Log.info("Surface texture is {}, reconfiguring swapchain", magic_enum::enum_name(surfaceTexture.status));
webgpu::refresh_surface(false);
return false;
case wgpu::SurfaceGetCurrentTextureStatus::Lost:
Log.warn("Surface texture is {}, releasing surface", magic_enum::enum_name(surfaceTexture.status));
webgpu::release_surface();
case wgpu::SurfaceGetCurrentTextureStatus::Error:
Log.warn("Surface texture is {}, dropping surface", magic_enum::enum_name(surfaceTexture.status));
g_surface = {};
return false;
default:
Log.error("Failed to get surface texture: {}", magic_enum::enum_name(surfaceTexture.status));
return false;
}
}
imgui::new_frame(window::get_window_size());
if (!gfx::begin_frame()) {
g_currentView = {};
return false;
}
#endif
@@ -261,33 +230,51 @@ void end_frame() noexcept {
ZoneScoped;
#ifdef AURORA_ENABLE_GX
gx::fifo::drain();
const auto encoderDescriptor = wgpu::CommandEncoderDescriptor{
.label = "Redraw encoder",
};
auto encoder = g_device.CreateCommandEncoder(&encoderDescriptor);
gfx::end_frame(encoder);
gfx::render(encoder);
{
gfx::finish();
auto imguiDrawData = imgui::freeze();
const auto& presentSource = webgpu::present_source();
const auto viewport = webgpu::calculate_present_viewport(webgpu::g_graphicsConfig.surfaceConfiguration.width,
webgpu::g_graphicsConfig.surfaceConfiguration.height,
presentSource.size.width, presentSource.size.height);
wgpu::BindGroup rmlBindGroup;
#if AURORA_ENABLE_RMLUI
if (rmlui::is_initialized()) {
rmlBindGroup = rmlui::record_frame(viewport);
}
#endif
gfx::end_frame([rmlBindGroup = std::move(rmlBindGroup), viewport,
imguiDrawData = std::move(imguiDrawData)](wgpu::CommandEncoder& encoder) {
window::SurfaceLock surfaceLock;
if (window::is_presentable() && g_surface && g_currentView) {
const auto& presentSource = webgpu::present_source();
auto viewport = webgpu::calculate_present_viewport(webgpu::g_graphicsConfig.surfaceConfiguration.width,
webgpu::g_graphicsConfig.surfaceConfiguration.height,
presentSource.size.width, presentSource.size.height);
const auto& resampledSource = webgpu::resample_present_source(encoder, viewport);
wgpu::BindGroup presentBindGroup = webgpu::create_copy_bind_group(resampledSource);
#if AURORA_ENABLE_RMLUI
if (rmlui::is_initialized()) {
const auto rmlOutput = rmlui::render(encoder, viewport, resampledSource);
if (rmlOutput.texture != nullptr) {
presentBindGroup = rmlOutput.copyBindGroup;
}
wgpu::Texture currentTexture;
wgpu::TextureView currentView;
auto surfaceStatus = wgpu::SurfaceGetCurrentTextureStatus::Error;
if (window::is_presentable() && g_surface) {
ZoneScopedN("Acquire texture");
wgpu::SurfaceTexture surfaceTexture;
g_surface.GetCurrentTexture(&surfaceTexture);
surfaceStatus = surfaceTexture.status;
if (surfaceStatus == wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal) {
currentTexture = std::move(surfaceTexture.texture);
currentView = currentTexture.CreateView();
}
}
const bool canPresent = currentTexture && currentView;
if (canPresent) {
wgpu::BindGroup presentBindGroup;
if (rmlBindGroup) {
presentBindGroup = rmlBindGroup;
} else {
const auto& resampledSource = webgpu::resample_present_source(encoder, viewport);
presentBindGroup = webgpu::create_copy_bind_group(resampledSource);
}
#endif
{
const std::array attachments{
wgpu::RenderPassColorAttachment{
.view = g_currentView,
.view = currentView,
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
},
@@ -309,7 +296,7 @@ void end_frame() noexcept {
{
const std::array attachments{
wgpu::RenderPassColorAttachment{
.view = g_currentView,
.view = currentView,
.loadOp = wgpu::LoadOp::Load,
.storeOp = wgpu::StoreOp::Store,
},
@@ -322,44 +309,70 @@ void end_frame() noexcept {
const auto pass = encoder.BeginRenderPass(&renderPassDescriptor);
pass.SetViewport(0.f, 0.f, static_cast<float>(webgpu::g_graphicsConfig.surfaceConfiguration.width),
static_cast<float>(webgpu::g_graphicsConfig.surfaceConfiguration.height), 0.f, 1.f);
imgui::render(pass);
imgui::render(pass, imguiDrawData);
pass.End();
}
} else {
Log.info("Skipping present; window not presentable");
webgpu::release_surface();
}
const wgpu::CommandBufferDescriptor cmdBufDescriptor{.label = "Redraw command buffer"};
const auto buffer = encoder.Finish(&cmdBufDescriptor);
g_queue.Submit(1, &buffer);
gfx::after_submit();
if (window::is_presentable() && g_surface) {
{
ZoneScopedN("Queue Submit");
g_queue.Submit(1, &buffer);
}
if (canPresent && g_surface) {
ZoneScopedN("Present");
auto presentStatus = g_surface.Present();
if (presentStatus != wgpu::Status::Success) {
Log.warn("Surface present failed: {}", static_cast<int>(presentStatus));
webgpu::release_surface();
}
} else if (g_surface) {
webgpu::release_surface();
switch (surfaceStatus) {
case wgpu::SurfaceGetCurrentTextureStatus::Timeout:
Log.warn("Surface texture acquisition timed out");
break;
case wgpu::SurfaceGetCurrentTextureStatus::SuccessSuboptimal:
case wgpu::SurfaceGetCurrentTextureStatus::Outdated:
Log.info("Surface texture is {}, reconfiguring swapchain", magic_enum::enum_name(surfaceStatus));
webgpu::refresh_surface(false);
break;
case wgpu::SurfaceGetCurrentTextureStatus::Lost:
Log.warn("Surface texture is {}, releasing surface", magic_enum::enum_name(surfaceStatus));
webgpu::release_surface();
break;
case wgpu::SurfaceGetCurrentTextureStatus::Error:
Log.warn("Surface texture is {}, dropping surface", magic_enum::enum_name(surfaceStatus));
g_surface = {};
break;
default:
if (!window::is_presentable()) {
webgpu::release_surface();
} else {
Log.error("Failed to get surface texture: {}", magic_enum::enum_name(surfaceStatus));
}
break;
}
}
g_currentView = {};
}
gfx::after_submit();
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);
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));
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
}
+1 -1
View File
@@ -198,7 +198,7 @@ void shutdown_rumble() noexcept {
}
}
} // namespace detail
#elif !defined(SDL_PLATFORM_IOS)
#elif !defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS)
bool rumble_available() noexcept { return false; }
void rumble(const uint16_t lowFreq, const uint16_t highFreq, const uint16_t durationMs) noexcept {
-2
View File
@@ -5,8 +5,6 @@
#include <dolphin/gx/GXCpu2Efb.h>
void GXPeekZ(u16 x, u16 y, u32* z) {
aurora::gfx::depth_peek::poll();
if (z != nullptr) {
u32 value = 0;
if (aurora::gfx::depth_peek::read_latest(x, y, value)) {
+5 -6
View File
@@ -58,13 +58,12 @@ void GXCallDisplayList(const void* data, u32 nbytes) {
__GXSendFlushPrim();
}
// Drain the internal FIFO so that any pending CP register writes
// (VCD, VAT, etc.) are processed into g_gxState before the display
// list's draw commands reference them.
aurora::gx::fifo::drain();
// Write display list contents to the FIFO
aurora::gx::fifo::write_data(data, nbytes);
// Process the display list through the command processor
aurora::gx::fifo::process(static_cast<const u8*>(data), nbytes, true);
// TEMP: debugging aid
// aurora::gx::fifo::drain();
// aurora::gx::fifo::process(static_cast<const u8*>(data), nbytes, true);
}
void GXCallDisplayListLE(const void* data, u32 nbytes) {
+2
View File
@@ -148,6 +148,8 @@ void GXSetDispCopyGamma(GXGamma gamma) {}
void GXCopyDisp(void* dest, GXBool clear) {}
void GXCopyTex(void* dest, GXBool clear) {
aurora::gx::fifo::drain();
const auto rect = aurora::gx::map_logical_scissor(g_gxState.texCopySrc);
const auto [dstWidth, dstHeight] = scale_copy_dst(g_gxState.texCopyDstWidth, g_gxState.texCopyDstHeight);
const auto texCopyFmt = g_gxState.texCopyFmt;
+4 -3
View File
@@ -48,9 +48,10 @@ void GXEnd() {
}
sInBegin = false;
}
if (!aurora::gx::fifo::in_display_list()) {
aurora::gx::fifo::drain();
}
// TEMP: debugging aid
// if (!aurora::gx::fifo::in_display_list()) {
// aurora::gx::fifo::drain();
// }
}
void GXPosition3f32(f32 x, f32 y, f32 z) {
+767 -264
View File
File diff suppressed because it is too large Load Diff
+40 -12
View File
@@ -6,6 +6,7 @@
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <type_traits>
#include <utility>
@@ -170,7 +171,7 @@ private:
} // namespace aurora
namespace aurora::gfx {
inline constexpr bool UseTextureBuffer = false;
inline constexpr bool UseTextureBuffer = true;
inline constexpr uint64_t UniformBufferSize = 25165824; // 24mb
inline constexpr uint64_t VertexBufferSize = 3145728; // 3mb
inline constexpr uint64_t IndexBufferSize = 1048576; // 1mb
@@ -215,25 +216,52 @@ using webgpu::Viewport;
struct TextureRef;
using TextureHandle = std::shared_ptr<TextureRef>;
using EndFrameCallback = std::function<void(wgpu::CommandEncoder&)>;
enum class ShaderType : uint8_t {
Clear = 0,
GX = 1,
Rml = 2,
};
void initialize();
void shutdown();
bool begin_frame();
void end_frame(const wgpu::CommandEncoder& cmd);
void finish();
void end_frame(EndFrameCallback callback);
uint32_t current_frame() noexcept;
void render(wgpu::CommandEncoder& cmd);
void render_pass(const wgpu::RenderPassEncoder& pass, uint32_t idx);
void after_submit() noexcept;
void map_staging_buffer();
void gpu_synchronize();
void resolve_pass(TextureHandle texture, ClipRect rect, bool clearColor, bool clearAlpha, bool clearDepth,
Vec4<float> clearColorValue, float clearDepthValue, GXTexFmt resolveFormat = GX_TF_RGBA8);
struct ColorPassDescriptor {
const char* label = nullptr;
wgpu::TextureView colorView;
wgpu::TextureView resolveView;
wgpu::TextureView depthStencilView;
wgpu::Extent3D targetSize;
uint32_t sampleCount = 1;
wgpu::LoadOp colorLoadOp = wgpu::LoadOp::Clear;
wgpu::StoreOp colorStoreOp = wgpu::StoreOp::Store;
wgpu::Color clearColor{0.f, 0.f, 0.f, 0.f};
bool hasDepth = false;
wgpu::LoadOp depthLoadOp = wgpu::LoadOp::Undefined;
wgpu::StoreOp depthStoreOp = wgpu::StoreOp::Undefined;
float depthClearValue = 0.f;
bool hasStencil = false;
wgpu::LoadOp stencilLoadOp = wgpu::LoadOp::Undefined;
wgpu::StoreOp stencilStoreOp = wgpu::StoreOp::Undefined;
uint32_t stencilClearValue = 0;
bool observable = true;
};
void begin_color_pass(const ColorPassDescriptor& desc);
void end_color_pass();
void queue_texture_copy(wgpu::TexelCopyTextureInfo src, wgpu::TexelCopyTextureInfo dst, wgpu::Extent3D size);
void begin_offscreen(uint32_t width, uint32_t height);
void end_offscreen();
bool is_offscreen() noexcept;
@@ -245,15 +273,15 @@ struct ConvRequest;
} // namespace tex_palette_conv
void queue_palette_conv(tex_palette_conv::ConvRequest req);
Range push_verts(const uint8_t* data, size_t length);
Range push_verts(const uint8_t* data, size_t length, size_t alignment);
template <typename T>
static Range push_verts(ArrayRef<T> data) {
return push_verts(reinterpret_cast<const uint8_t*>(data.data()), data.size() * sizeof(T));
static Range push_verts(ArrayRef<T> data, size_t alignment) {
return push_verts(reinterpret_cast<const uint8_t*>(data.data()), data.size() * sizeof(T), alignment);
}
Range push_indices(const uint8_t* data, size_t length);
Range push_indices(const uint8_t* data, size_t length, size_t alignment);
template <typename T>
static Range push_indices(ArrayRef<T> data) {
return push_indices(reinterpret_cast<const uint8_t*>(data.data()), data.size() * sizeof(T));
static Range push_indices(ArrayRef<T> data, size_t alignment) {
return push_indices(reinterpret_cast<const uint8_t*>(data.data()), data.size() * sizeof(T), alignment);
}
Range push_uniform(const uint8_t* data, size_t length);
template <typename T>
@@ -287,9 +315,9 @@ PipelineRef pipeline_ref(const PipelineConfig& config);
bool bind_pipeline(PipelineRef ref, const wgpu::RenderPassEncoder& pass);
BindGroupRef bind_group_ref(const WGPUBindGroupDescriptor& descriptor);
wgpu::BindGroup& find_bind_group(BindGroupRef id);
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);
+2 -6
View File
@@ -2,6 +2,7 @@
#include "../dolphin/vi/vi_internal.hpp"
#include "../gx/gx.hpp"
#include "../gfx/render_worker.hpp"
#include "../webgpu/gpu.hpp"
#include <algorithm>
@@ -330,12 +331,6 @@ bool read_latest(uint16_t x, uint16_t y, uint32_t& z) noexcept {
return true;
}
void poll() noexcept {
if (g_instance) {
g_instance.ProcessEvents();
}
}
void encode_frame_snapshot(const wgpu::CommandEncoder& cmd, const wgpu::TextureView& depthView,
wgpu::Extent3D sourceSize, uint32_t msaaSamples) noexcept {
ZoneScoped;
@@ -375,6 +370,7 @@ void encode_frame_snapshot(const wgpu::CommandEncoder& cmd, const wgpu::TextureV
byteSize = slot->byteSize;
}
ASSERT(render_worker::is_worker_thread(), "Depth peek queue write must run on the render worker");
g_queue.WriteBuffer(paramsBuffer, 0, &params, sizeof(params));
const std::array bindGroupEntries{
-1
View File
@@ -11,7 +11,6 @@ void shutdown();
void request_snapshot() noexcept;
bool read_latest(uint16_t x, uint16_t y, uint32_t& z) noexcept;
void poll() noexcept;
void encode_frame_snapshot(const wgpu::CommandEncoder& cmd, const wgpu::TextureView& depthView,
wgpu::Extent3D sourceSize, uint32_t msaaSamples) noexcept;
+34
View File
@@ -3,6 +3,9 @@
#include "clear.hpp"
#include "../fs_helper.hpp"
#include "../gx/pipeline.hpp"
#ifdef AURORA_ENABLE_RMLUI
#include "../rmlui/pipeline.hpp"
#endif
#include "../sqlite_utils.hpp"
#include "../webgpu/gpu.hpp"
@@ -140,12 +143,18 @@ static PendingPipeline* touch_pending_pipeline(PipelineRef hash, bool prioritize
return &g_priorityPipelines.back();
}
static PipelineRef g_lastPipelineRef = std::numeric_limits<PipelineRef>::max();
template <typename PipelineConfig>
static PipelineRef find_pipeline_impl(ShaderType type, const PipelineConfig& config, NewPipelineCallback&& cb,
bool persist, std::optional<uint32_t> firstFrameUsedOverride) {
ZoneScoped;
const PipelineRef hash = xxh3_hash(config, static_cast<HashType>(type));
if (hash == g_lastPipelineRef) {
return g_lastPipelineRef;
}
g_lastPipelineRef = hash;
const uint32_t firstFrameUsed = firstFrameUsedOverride.value_or(current_frame());
bool notifyWorker = false;
std::optional<PipelineCacheWrite> cacheWrite;
@@ -352,7 +361,18 @@ static void prune_old_pipeline_cache_versions() {
if (ret != SQLITE_OK) {
Log.error("Failed to prune GX pipeline cache rows: {}", sqlite3_errmsg(g_pipelineCacheDb));
pipeline_cache_abort();
return;
}
#ifdef AURORA_ENABLE_RMLUI
const auto rmlDelete = fmt::format("DELETE FROM pipeline_cache WHERE type = {} AND config_version < {}",
underlying(ShaderType::Rml), rmlui::RmlPipelineConfigVersion);
ret = sqlite::exec(g_pipelineCacheDb, rmlDelete.c_str());
if (ret != SQLITE_OK) {
Log.error("Failed to prune RmlUi pipeline cache rows: {}", sqlite3_errmsg(g_pipelineCacheDb));
pipeline_cache_abort();
}
#endif
}
static bool write_pipeline_cache_record(const PipelineCacheWrite& write) {
@@ -552,6 +572,13 @@ static void load_pipeline_cache() {
return;
}
load_pipeline_cache_entries<gx::PipelineConfig>(ShaderType::GX, gx::GXPipelineConfigVersion, gx::create_pipeline);
#ifdef AURORA_ENABLE_RMLUI
if (g_pipelineCacheBroken) {
return;
}
load_pipeline_cache_entries<rmlui::PipelineConfig>(ShaderType::Rml, rmlui::RmlPipelineConfigVersion,
rmlui::create_pipeline);
#endif
}
static void start_pipeline_cache_writer() {
@@ -588,6 +615,13 @@ PipelineRef find_pipeline(ShaderType type, const gx::PipelineConfig& config, New
return find_pipeline_impl(type, config, std::move(cb), true, std::nullopt);
}
#ifdef AURORA_ENABLE_RMLUI
template <>
PipelineRef find_pipeline(ShaderType type, const rmlui::PipelineConfig& config, NewPipelineCallback&& cb) {
return find_pipeline_impl(type, config, std::move(cb), true, std::nullopt);
}
#endif
void initialize_pipeline_cache() {
g_pipelineCacheBroken = false;
g_pipelineCacheWriterStop = false;
+4
View File
@@ -12,6 +12,10 @@ namespace aurora::gx {
struct PipelineConfig;
} // namespace aurora::gx
namespace aurora::rmlui {
struct PipelineConfig;
} // namespace aurora::rmlui
namespace aurora::gfx {
using NewPipelineCallback = std::function<wgpu::RenderPipeline()>;
+270
View File
@@ -0,0 +1,270 @@
#include "render_worker.hpp"
#include <algorithm>
#include <atomic>
#include <chrono>
#include <tracy/Tracy.hpp>
namespace aurora::gfx::render_worker {
namespace {
constexpr size_t QueueCapacity = 256;
constexpr auto IdlePumpInterval = std::chrono::milliseconds{1};
BoundedQueue g_queue{QueueCapacity};
std::thread g_thread;
std::atomic_bool g_running = false;
std::atomic_size_t g_pendingItems = 0;
std::thread::id g_workerThreadId;
void complete_sync(const std::shared_ptr<SyncState>& sync) {
if (!sync) {
return;
}
ZoneScoped;
{
std::lock_guard lock{sync->mutex};
sync->complete = true;
}
sync->cv.notify_all();
}
void worker_main() {
#ifdef TRACY_ENABLE
tracy::SetThreadName("Aurora render worker");
#endif
g_workerThreadId = std::this_thread::get_id();
while (true) {
bool closed = false;
auto item = g_queue.pop_for(IdlePumpInterval, closed);
if (!item) {
if (closed) {
break;
}
continue;
}
if (item->work) {
ZoneScopedN("QueueItem work");
item->work();
}
complete_sync(item->sync);
g_pendingItems.fetch_sub(1, std::memory_order_acq_rel);
if (item->type == ItemType::Shutdown) {
break;
}
}
g_workerThreadId = {};
}
void enqueue(QueueItem item) {
ZoneScoped;
if (is_worker_thread()) {
if (item.work) {
item.work();
}
complete_sync(item.sync);
return;
}
if (!g_running.load(std::memory_order_acquire)) {
if (item.work) {
item.work();
}
complete_sync(item.sync);
return;
}
g_pendingItems.fetch_add(1, std::memory_order_acq_rel);
if (!g_queue.push(std::move(item))) {
g_pendingItems.fetch_sub(1, std::memory_order_acq_rel);
}
}
} // namespace
BoundedQueue::BoundedQueue(size_t capacity) : m_capacity(capacity) {}
bool BoundedQueue::push(QueueItem item) {
ZoneScoped;
std::unique_lock lock{m_mutex};
m_notFull.wait(lock, [&] { return m_closed || m_items.size() < m_capacity; });
if (m_closed) {
return false;
}
m_items.emplace_back(std::move(item));
lock.unlock();
m_notEmpty.notify_one();
return true;
}
std::optional<QueueItem> BoundedQueue::pop_for(std::chrono::milliseconds timeout, bool& closed) {
std::unique_lock lock{m_mutex};
m_notEmpty.wait_for(lock, timeout, [&] { return m_closed || !m_items.empty(); });
closed = m_closed && m_items.empty();
if (m_items.empty()) {
return std::nullopt;
}
auto item = std::move(m_items.front());
m_items.pop_front();
lock.unlock();
m_notFull.notify_one();
return item;
}
void BoundedQueue::close() {
{
std::lock_guard lock{m_mutex};
m_closed = true;
}
m_notEmpty.notify_all();
m_notFull.notify_all();
}
void BoundedQueue::reset() {
{
std::lock_guard lock{m_mutex};
m_items.clear();
m_closed = false;
}
m_notFull.notify_all();
}
size_t BoundedQueue::size() const {
std::lock_guard lock{m_mutex};
return m_items.size();
}
FrameSlotPool::FrameSlotPool(size_t slotCount) : m_freeSlots(slotCount, true) {}
size_t FrameSlotPool::acquire() {
std::unique_lock lock{m_mutex};
m_cv.wait(lock, [&] {
for (const bool free : m_freeSlots) {
if (free) {
return true;
}
}
return false;
});
for (size_t i = 0; i < m_freeSlots.size(); ++i) {
if (m_freeSlots[i]) {
m_freeSlots[i] = false;
return i;
}
}
return 0;
}
std::optional<size_t> FrameSlotPool::try_acquire() {
std::lock_guard lock{m_mutex};
for (size_t i = 0; i < m_freeSlots.size(); ++i) {
if (m_freeSlots[i]) {
m_freeSlots[i] = false;
return i;
}
}
return std::nullopt;
}
void FrameSlotPool::release(size_t slot) {
{
std::lock_guard lock{m_mutex};
if (slot < m_freeSlots.size()) {
m_freeSlots[slot] = true;
}
}
m_cv.notify_one();
}
void FrameSlotPool::reset() {
{
std::lock_guard lock{m_mutex};
std::fill(m_freeSlots.begin(), m_freeSlots.end(), true);
}
m_cv.notify_all();
}
size_t FrameSlotPool::free_count() const {
std::lock_guard lock{m_mutex};
return static_cast<size_t>(std::ranges::count(m_freeSlots, true));
}
void initialize() {
if (g_running.exchange(true, std::memory_order_acq_rel)) {
return;
}
g_queue.reset();
g_pendingItems.store(0, std::memory_order_release);
g_thread = std::thread(worker_main);
}
void shutdown() {
if (!g_running.load(std::memory_order_acquire)) {
return;
}
enqueue({
.type = ItemType::Shutdown,
});
if (g_thread.joinable()) {
g_thread.join();
}
g_running.store(false, std::memory_order_release);
g_queue.close();
g_queue.reset();
g_pendingItems.store(0, std::memory_order_release);
}
void enqueue_begin_frame(uint64_t frameId, WorkCallback work) {
enqueue({
.type = ItemType::BeginFrame,
.frameId = frameId,
.work = std::move(work),
});
}
void enqueue_encode_pass(uint64_t frameId, uint32_t passIndex, WorkCallback work) {
enqueue({
.type = ItemType::EncodePass,
.frameId = frameId,
.passIndex = passIndex,
.work = std::move(work),
});
}
void enqueue_end_frame(uint64_t frameId, WorkCallback work) {
enqueue({
.type = ItemType::EndFrame,
.frameId = frameId,
.work = std::move(work),
});
}
void enqueue_work(WorkCallback work) {
enqueue({
.type = ItemType::Sync,
.work = std::move(work),
});
}
void synchronize() {
if (is_worker_thread()) {
return;
}
ZoneScoped;
auto sync = std::make_shared<SyncState>();
enqueue({
.type = ItemType::Sync,
.sync = sync,
});
std::unique_lock lock{sync->mutex};
sync->cv.wait(lock, [&] { return sync->complete; });
}
bool is_worker_thread() noexcept { return g_workerThreadId == std::this_thread::get_id(); }
bool is_idle() noexcept { return g_pendingItems.load(std::memory_order_acquire) == 0; }
} // namespace aurora::gfx::render_worker
+88
View File
@@ -0,0 +1,88 @@
#pragma once
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <chrono>
#include <deque>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <thread>
#include <vector>
namespace aurora::gfx::render_worker {
enum class ItemType : uint8_t {
BeginFrame,
EncodePass,
EndFrame,
Sync,
Shutdown,
};
using WorkCallback = std::function<void()>;
struct SyncState {
std::mutex mutex;
std::condition_variable cv;
bool complete = false;
};
struct QueueItem {
ItemType type = ItemType::Sync;
uint64_t frameId = 0;
uint32_t passIndex = 0;
WorkCallback work;
std::shared_ptr<SyncState> sync;
};
class BoundedQueue {
public:
explicit BoundedQueue(size_t capacity);
bool push(QueueItem item);
std::optional<QueueItem> pop_for(std::chrono::milliseconds timeout, bool& closed);
void close();
void reset();
[[nodiscard]] size_t size() const;
private:
size_t m_capacity = 0;
mutable std::mutex m_mutex;
std::condition_variable m_notEmpty;
std::condition_variable m_notFull;
std::deque<QueueItem> m_items;
bool m_closed = false;
};
class FrameSlotPool {
public:
explicit FrameSlotPool(size_t slotCount);
size_t acquire();
std::optional<size_t> try_acquire();
void release(size_t slot);
void reset();
[[nodiscard]] size_t free_count() const;
private:
mutable std::mutex m_mutex;
std::condition_variable m_cv;
std::vector<bool> m_freeSlots;
};
void initialize();
void shutdown();
void enqueue_begin_frame(uint64_t frameId, WorkCallback work);
void enqueue_encode_pass(uint64_t frameId, uint32_t passIndex, WorkCallback work);
void enqueue_end_frame(uint64_t frameId, WorkCallback work);
void enqueue_work(WorkCallback work);
void synchronize();
bool is_worker_thread() noexcept;
bool is_idle() noexcept;
} // namespace aurora::gfx::render_worker
+4 -14
View File
@@ -133,13 +133,8 @@ TextureHandle new_static_texture_2d(uint32_t width, uint32_t height, uint32_t mi
.mipLevel = mip,
};
if constexpr (UseTextureBuffer) {
const auto range = push_texture_data(data.data() + offset, dataSize, bytesPerRow, heightBlocks);
const wgpu::TexelCopyBufferLayout dataLayout{
.offset = range.offset,
.bytesPerRow = bytesPerRow,
.rowsPerImage = heightBlocks,
};
g_textureUploads.emplace_back(dataLayout, std::move(dstView), physicalSize);
queue_texture_upload_data(data.data() + offset, dataSize, bytesPerRow, heightBlocks, std::move(dstView),
physicalSize);
} else {
const wgpu::TexelCopyBufferLayout dataLayout{
.bytesPerRow = bytesPerRow,
@@ -288,13 +283,8 @@ void write_texture(TextureRef& ref, ArrayRef<uint8_t> data) noexcept {
.mipLevel = mip,
};
if constexpr (UseTextureBuffer) {
const auto range = push_texture_data(data.data() + offset, dataSize, bytesPerRow, heightBlocks);
const wgpu::TexelCopyBufferLayout dataLayout{
.offset = range.offset,
.bytesPerRow = bytesPerRow,
.rowsPerImage = heightBlocks,
};
g_textureUploads.emplace_back(dataLayout, std::move(dstView), physicalSize);
queue_texture_upload_data(data.data() + offset, dataSize, bytesPerRow, heightBlocks, std::move(dstView),
physicalSize);
} else {
const wgpu::TexelCopyBufferLayout dataLayout{
.bytesPerRow = bytesPerRow,
+7 -1
View File
@@ -10,11 +10,17 @@ struct TextureUpload {
wgpu::TexelCopyBufferLayout layout;
wgpu::TexelCopyTextureInfo tex;
wgpu::Extent3D size;
wgpu::Buffer buffer;
TextureUpload(wgpu::TexelCopyBufferLayout layout, wgpu::TexelCopyTextureInfo tex, wgpu::Extent3D size) noexcept
: layout(layout), tex(std::move(tex)), size(size) {}
TextureUpload(wgpu::TexelCopyBufferLayout layout, wgpu::TexelCopyTextureInfo tex, wgpu::Extent3D size,
wgpu::Buffer buffer) noexcept
: layout(layout), tex(std::move(tex)), size(size), buffer(std::move(buffer)) {}
};
extern std::vector<TextureUpload> g_textureUploads;
void queue_texture_upload(TextureUpload upload);
void queue_texture_upload_data(const uint8_t* data, size_t length, uint32_t bytesPerRow, uint32_t rowsPerImage,
wgpu::TexelCopyTextureInfo tex, wgpu::Extent3D size);
struct TextureFormatInfo {
uint8_t blockWidth;
+1
View File
@@ -558,6 +558,7 @@ static ByteBuffer BuildRGBA8FromBC1(uint32_t width, uint32_t height, uint32_t mi
}
ConvertedTexture convert_texture(u32 format, uint32_t width, uint32_t height, uint32_t mips, ArrayRef<uint8_t> data) {
ZoneScoped;
ByteBuffer converted;
switch (format) {
DEFAULT_FATAL("convert_texture: unknown texture format {}", format);
+36 -21
View File
@@ -1562,32 +1562,47 @@ static void handle_draw(u8 cmd, const u8* data, u32& pos, u32 size, bool bigEndi
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);
auto* lastDraw = !g_gxState.stateDirty ? gfx::get_last_draw_command<DrawData>() : nullptr;
const bool canMerge = lastDraw != nullptr && prim != GX_LINES && prim != GX_LINESTRIP && prim != GX_POINTS &&
lastDraw->instanceCount == 1;
// Push raw vertex data to buffer. Merged draws must remain byte-contiguous with the previous range.
gfx::Range vertRange = gfx::push_verts(data + pos, totalVtxBytes, canMerge ? 0 : 4);
pos += totalVtxBytes;
// Try to merge with previous draw call
if (!g_gxState.stateDirty) LIKELY {
auto* lastDraw = gfx::get_last_draw_command<DrawData>();
if (canMerge) {
// 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) 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());
u32 numIndices = 0;
gfx::Range idxRange;
const bool hadIndexRange = lastDraw->idxRange.size != 0;
if (lastDraw->indexCount == 0 && prim != GX_TRIANGLES) {
// Generate triangle index buffer for previous draw
lastDraw->indexCount = prepare_idx_buffer(handle_draw_idx_buf, GX_TRIANGLES, 0, lastDraw->vtxCount);
}
if (lastDraw->indexCount != 0) {
numIndices += prepare_idx_buffer(handle_draw_idx_buf, prim, lastDraw->vtxCount, vtxCount);
idxRange = gfx::push_indices(handle_draw_idx_buf.data(), handle_draw_idx_buf.size(), hadIndexRange ? 0 : 4);
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);
}
CHECK(lastDraw->vertRange.offset + lastDraw->vertRange.size == vertRange.offset,
"Non-consecutive vertex ranges ({} < {})", lastDraw->vertRange.offset + lastDraw->vertRange.size,
vertRange.offset);
if (hadIndexRange) {
CHECK(lastDraw->idxRange.offset + lastDraw->idxRange.size == idxRange.offset,
"Non-consecutive index ranges ({} < {})", lastDraw->idxRange.offset + lastDraw->idxRange.size,
idxRange.offset);
lastDraw->vertRange.size += vertRange.size;
lastDraw->idxRange.size += idxRange.size;
lastDraw->vtxCount += vtxCount;
lastDraw->indexCount += numIndices;
++gfx::g_mergedDrawCallCount;
return;
}
lastDraw->vertRange.size += vertRange.size;
if (lastDraw->idxRange.size == 0) {
lastDraw->idxRange = idxRange;
} else {
lastDraw->idxRange.size += idxRange.size;
}
lastDraw->vtxCount += vtxCount;
lastDraw->indexCount += numIndices;
++gfx::g_mergedDrawCallCount;
return;
}
handle_draw_unmerged(prim, fmt, vtxCount, vertRange);
@@ -1600,11 +1615,11 @@ static void handle_draw_unmerged(GXPrimitive prim, GXVtxFmt fmt, u16 vtxCount, g
u32 numIndices = 0;
gfx::Range idxRange;
{
ByteBuffer idxBuf;
auto& realBuf = vtxCount < 1000 ? handle_draw_unmerged_idxBuf : idxBuf;
if (prim != GX_TRIANGLES) {
ZoneScopedN("build idx buffer");
auto& realBuf = handle_draw_unmerged_idxBuf;
numIndices = prepare_idx_buffer(realBuf, prim, 0, vtxCount);
idxRange = gfx::push_indices(realBuf.data(), realBuf.size());
idxRange = gfx::push_indices(realBuf.data(), realBuf.size(), 4);
realBuf.clear();
}
+5 -1
View File
@@ -29,6 +29,10 @@ void render(const DrawData& data, const wgpu::RenderPassEncoder& pass) {
const wgpu::Color color{0.f, 0.f, 0.f, data.dstAlpha / 255.f};
pass.SetBlendConstant(&color);
}
pass.DrawIndexed(data.indexCount, data.instanceCount);
if (data.indexCount == 0) {
pass.Draw(data.vtxCount, data.instanceCount);
} else {
pass.DrawIndexed(data.indexCount, data.instanceCount);
}
}
} // namespace aurora::gx

Some files were not shown because too many files have changed in this diff Show More