2022-07-27 11:25:25 -04:00
|
|
|
#include "common.hpp"
|
|
|
|
|
|
2026-04-01 00:49:28 -06:00
|
|
|
#include "clear.hpp"
|
2026-04-24 21:55:17 -06:00
|
|
|
#include "depth_peek.hpp"
|
2022-07-27 11:25:25 -04:00
|
|
|
#include "../internal.hpp"
|
|
|
|
|
#include "../webgpu/gpu.hpp"
|
2026-06-12 10:28:52 -06:00
|
|
|
#include "../webgpu/gpu_prof.hpp"
|
2026-03-05 20:31:51 -07:00
|
|
|
#include "../gx/pipeline.hpp"
|
2026-06-04 23:43:13 -06:00
|
|
|
#ifdef AURORA_ENABLE_RMLUI
|
|
|
|
|
#include "../rmlui/pipeline.hpp"
|
|
|
|
|
#endif
|
2026-04-08 20:03:25 -06:00
|
|
|
#include "pipeline_cache.hpp"
|
2026-06-04 23:43:13 -06:00
|
|
|
#include "render_worker.hpp"
|
2026-04-01 00:49:28 -06:00
|
|
|
#include "tex_copy_conv.hpp"
|
|
|
|
|
#include "tex_palette_conv.hpp"
|
2026-04-06 23:04:01 -04:00
|
|
|
#include "texture_replacement.hpp"
|
2022-07-27 11:25:25 -04:00
|
|
|
#include "texture.hpp"
|
2026-04-18 14:10:32 -06:00
|
|
|
#include "../window.hpp"
|
2022-07-27 11:25:25 -04:00
|
|
|
|
2026-05-09 09:18:05 -06:00
|
|
|
#include <atomic>
|
2026-06-04 23:43:13 -06:00
|
|
|
#include <chrono>
|
|
|
|
|
#include <deque>
|
|
|
|
|
#include <mutex>
|
2026-04-02 23:01:14 -06:00
|
|
|
#include <optional>
|
2026-03-11 01:38:35 +01:00
|
|
|
#include <ranges>
|
2026-06-04 23:43:13 -06:00
|
|
|
#include <string>
|
|
|
|
|
#include <thread>
|
2025-04-04 01:59:30 -06:00
|
|
|
|
|
|
|
|
#include <absl/container/flat_hash_map.h>
|
2022-07-27 11:25:25 -04:00
|
|
|
#include <magic_enum.hpp>
|
|
|
|
|
|
2026-04-09 03:19:58 +02:00
|
|
|
#include "tracy/Tracy.hpp"
|
|
|
|
|
|
2022-07-27 11:25:25 -04:00
|
|
|
namespace aurora::gfx {
|
|
|
|
|
static Module Log("aurora::gfx");
|
|
|
|
|
|
|
|
|
|
using webgpu::g_device;
|
2022-08-02 16:37:56 -04:00
|
|
|
using webgpu::g_instance;
|
2022-07-27 11:25:25 -04:00
|
|
|
using webgpu::g_queue;
|
|
|
|
|
|
|
|
|
|
#ifdef AURORA_GFX_DEBUG_GROUPS
|
|
|
|
|
std::vector<std::string> g_debugGroupStack;
|
2026-03-11 01:38:35 +01:00
|
|
|
std::vector<std::string> g_debugMarkers;
|
2022-07-27 11:25:25 -04:00
|
|
|
#endif
|
|
|
|
|
|
2026-06-12 10:28:52 -06:00
|
|
|
// Label for a newly created render pass; tagged with the innermost debug
|
|
|
|
|
// group the application has open, so application-level labels show up on
|
|
|
|
|
// pass names in graphics debuggers and GPU profiles.
|
|
|
|
|
static std::string pass_label(std::string_view kind) {
|
|
|
|
|
#ifdef AURORA_GFX_DEBUG_GROUPS
|
|
|
|
|
if (!g_debugGroupStack.empty()) {
|
|
|
|
|
return fmt::format("{} ({})", kind, g_debugGroupStack.back());
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
return std::string{kind};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 18:18:29 -06:00
|
|
|
constexpr uint64_t StagingBufferSize = UniformBufferSize + VertexBufferSize + IndexBufferSize + StorageBufferSize +
|
|
|
|
|
(UseTextureBuffer ? TextureUploadSize : 0);
|
2026-06-04 23:43:13 -06:00
|
|
|
constexpr size_t FrameSlotCount = 2;
|
|
|
|
|
constexpr size_t StagingBufferCount = FrameSlotCount + 2;
|
|
|
|
|
|
|
|
|
|
struct StagingHighWater {
|
|
|
|
|
uint32_t verts = 0;
|
|
|
|
|
uint32_t uniforms = 0;
|
|
|
|
|
uint32_t indices = 0;
|
|
|
|
|
uint32_t storage = 0;
|
|
|
|
|
uint32_t textureUpload = 0;
|
|
|
|
|
size_t textureUploadCount = 0;
|
|
|
|
|
};
|
2022-07-27 11:25:25 -04:00
|
|
|
|
|
|
|
|
struct ShaderDrawCommand {
|
|
|
|
|
ShaderType type;
|
|
|
|
|
union {
|
2026-04-01 00:49:28 -06:00
|
|
|
clear::DrawData clear;
|
2026-03-05 20:31:51 -07:00
|
|
|
gx::DrawData gx;
|
2026-06-04 23:43:13 -06:00
|
|
|
#ifdef AURORA_ENABLE_RMLUI
|
|
|
|
|
rmlui::DrawData rml;
|
|
|
|
|
#endif
|
2022-07-27 11:25:25 -04:00
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
enum class CommandType {
|
|
|
|
|
SetViewport,
|
|
|
|
|
SetScissor,
|
|
|
|
|
Draw,
|
2026-03-11 01:38:35 +01:00
|
|
|
DebugMarker,
|
2022-07-27 11:25:25 -04:00
|
|
|
};
|
|
|
|
|
struct Command {
|
|
|
|
|
CommandType type;
|
|
|
|
|
#ifdef AURORA_GFX_DEBUG_GROUPS
|
|
|
|
|
std::vector<std::string> debugGroupStack;
|
|
|
|
|
#endif
|
|
|
|
|
union Data {
|
2026-03-05 10:17:03 -07:00
|
|
|
Viewport setViewport;
|
2026-04-18 14:10:32 -06:00
|
|
|
ClipRect setScissor;
|
2022-07-27 11:25:25 -04:00
|
|
|
ShaderDrawCommand draw;
|
2026-03-11 01:38:35 +01:00
|
|
|
size_t debugMarkerIndex;
|
2022-07-27 11:25:25 -04:00
|
|
|
} data;
|
|
|
|
|
};
|
|
|
|
|
} // namespace aurora::gfx
|
|
|
|
|
|
|
|
|
|
namespace aurora {
|
|
|
|
|
// For types that we can't ensure are safe to hash with has_unique_object_representations,
|
|
|
|
|
// 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 <>
|
2026-04-09 03:51:06 +02:00
|
|
|
inline HashType xxh3_hash(const WGPUBindGroupDescriptor& input, HashType seed) {
|
|
|
|
|
constexpr auto offset = offsetof(WGPUBindGroupDescriptor, layout); // skip nextInChain, label
|
2022-07-27 11:25:25 -04:00
|
|
|
const auto hash = xxh3_hash_s(reinterpret_cast<const u8*>(&input) + offset,
|
2026-04-09 03:51:06 +02:00
|
|
|
sizeof(WGPUBindGroupDescriptor) - offset - sizeof(void*) /* skip entries */, seed);
|
|
|
|
|
return xxh3_hash_s(input.entries, sizeof(WGPUBindGroupEntry) * input.entryCount, hash);
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
template <>
|
2022-08-02 16:37:56 -04:00
|
|
|
inline HashType xxh3_hash(const wgpu::SamplerDescriptor& input, HashType seed) {
|
2025-04-04 01:59:30 -06:00
|
|
|
constexpr auto offset = offsetof(wgpu::SamplerDescriptor, addressModeU); // skip nextInChain, label
|
2022-07-27 11:25:25 -04:00
|
|
|
return xxh3_hash_s(reinterpret_cast<const u8*>(&input) + offset,
|
2022-08-02 16:37:56 -04:00
|
|
|
sizeof(wgpu::SamplerDescriptor) - offset - 2 /* skip padding */, seed);
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
} // namespace aurora
|
|
|
|
|
|
|
|
|
|
namespace aurora::gfx {
|
2026-04-12 18:55:35 -06:00
|
|
|
namespace {
|
|
|
|
|
struct CachedBindGroup {
|
|
|
|
|
wgpu::BindGroup bindGroup;
|
|
|
|
|
uint32_t lastUsedFrame = 0;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-21 01:06:16 -06:00
|
|
|
constexpr uint32_t BindGroupCacheRetainFrames = 32;
|
|
|
|
|
constexpr uint32_t BindGroupCacheSweepPeriod = 16;
|
2026-04-12 18:55:35 -06:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
static absl::flat_hash_map<BindGroupRef, CachedBindGroup> g_cachedBindGroups;
|
2022-08-02 16:37:56 -04:00
|
|
|
static absl::flat_hash_map<SamplerRef, wgpu::Sampler> g_cachedSamplers;
|
2026-06-04 23:43:13 -06:00
|
|
|
static std::mutex g_bindGroupCacheMutex;
|
|
|
|
|
static std::mutex g_samplerCacheMutex;
|
2022-07-27 11:25:25 -04:00
|
|
|
|
2022-08-02 16:37:56 -04:00
|
|
|
wgpu::Buffer g_vertexBuffer;
|
|
|
|
|
wgpu::Buffer g_uniformBuffer;
|
|
|
|
|
wgpu::Buffer g_indexBuffer;
|
|
|
|
|
wgpu::Buffer g_storageBuffer;
|
2026-05-09 09:18:05 -06:00
|
|
|
enum class BufferMapState {
|
|
|
|
|
Unmapped,
|
|
|
|
|
Mapping,
|
|
|
|
|
Mapped,
|
|
|
|
|
};
|
2026-06-04 23:43:13 -06:00
|
|
|
static std::array<wgpu::Buffer, StagingBufferCount> g_stagingBuffers;
|
|
|
|
|
static std::array<std::atomic<BufferMapState>, StagingBufferCount> s_mappingStates;
|
2025-04-03 00:12:22 -06:00
|
|
|
static wgpu::Limits g_cachedLimits;
|
2026-04-08 20:03:25 -06:00
|
|
|
static uint32_t g_frameIndex = UINT32_MAX;
|
2022-07-27 11:25:25 -04:00
|
|
|
static PipelineRef g_currentPipeline;
|
2026-04-12 18:55:35 -06:00
|
|
|
wgpu::BindGroupLayout g_staticBindGroupLayout;
|
|
|
|
|
wgpu::BindGroup g_staticBindGroup;
|
|
|
|
|
wgpu::BindGroupLayout g_uniformBindGroupLayout;
|
|
|
|
|
wgpu::BindGroup g_uniformBindGroup;
|
2022-07-27 11:25:25 -04:00
|
|
|
|
2022-07-28 19:48:02 -04:00
|
|
|
// for imgui debug
|
2026-03-05 20:31:51 -07:00
|
|
|
AuroraStats g_stats{};
|
2026-05-14 16:29:45 -06:00
|
|
|
uint32_t g_drawCallCount = 0;
|
|
|
|
|
uint32_t g_mergedDrawCallCount = 0;
|
2022-07-28 19:48:02 -04:00
|
|
|
|
2022-07-27 11:25:25 -04:00
|
|
|
using CommandList = std::vector<Command>;
|
|
|
|
|
struct RenderPass {
|
2026-06-04 23:43:13 -06:00
|
|
|
std::string label;
|
2026-04-02 23:01:14 -06:00
|
|
|
wgpu::TextureView colorView;
|
|
|
|
|
wgpu::TextureView resolveView; // MSAA resolve target; null if msaaSamples == 1
|
2026-06-04 23:43:13 -06:00
|
|
|
wgpu::TextureView depthStencilView;
|
2026-04-02 23:01:14 -06:00
|
|
|
wgpu::Texture copySourceTexture;
|
|
|
|
|
wgpu::TextureView copySourceView;
|
2026-04-21 14:14:02 -06:00
|
|
|
wgpu::TextureView copySourceDepthView;
|
2026-04-02 23:01:14 -06:00
|
|
|
wgpu::Extent3D targetSize;
|
|
|
|
|
uint32_t msaaSamples = 1;
|
|
|
|
|
|
2022-08-09 02:05:33 -04:00
|
|
|
TextureHandle resolveTarget;
|
2026-04-02 23:01:14 -06:00
|
|
|
GXTexFmt resolveFormat = GX_TF_RGBA8;
|
2022-07-27 11:25:25 -04:00
|
|
|
ClipRect resolveRect;
|
2026-04-02 23:01:14 -06:00
|
|
|
Range resolveUniformRange;
|
2026-04-01 00:49:28 -06:00
|
|
|
Vec4<float> clearColorValue{0.f, 0.f, 0.f, 0.f};
|
|
|
|
|
float clearDepthValue = gx::UseReversedZ ? 0.f : 1.f;
|
2026-06-04 23:43:13 -06:00
|
|
|
wgpu::LoadOp colorLoadOp = wgpu::LoadOp::Undefined;
|
|
|
|
|
wgpu::StoreOp colorStoreOp = wgpu::StoreOp::Store;
|
|
|
|
|
wgpu::LoadOp depthLoadOp = wgpu::LoadOp::Undefined;
|
|
|
|
|
wgpu::StoreOp depthStoreOp = wgpu::StoreOp::Store;
|
|
|
|
|
wgpu::LoadOp stencilLoadOp = wgpu::LoadOp::Undefined;
|
|
|
|
|
wgpu::StoreOp stencilStoreOp = wgpu::StoreOp::Undefined;
|
|
|
|
|
uint32_t stencilClearValue = 0;
|
2022-07-27 11:25:25 -04:00
|
|
|
CommandList commands;
|
2026-04-01 00:49:28 -06:00
|
|
|
bool clearColor = true;
|
|
|
|
|
bool clearDepth = true;
|
2026-06-04 23:43:13 -06:00
|
|
|
bool hasDepth = true;
|
|
|
|
|
bool hasStencil = false;
|
|
|
|
|
bool offscreen = false;
|
|
|
|
|
bool observable = false;
|
|
|
|
|
bool captureDepthSnapshot = false;
|
|
|
|
|
bool sealed = false;
|
2026-04-01 00:49:28 -06:00
|
|
|
std::vector<tex_palette_conv::ConvRequest> paletteConvs;
|
2022-07-27 11:25:25 -04:00
|
|
|
};
|
2026-06-04 23:43:13 -06:00
|
|
|
|
|
|
|
|
struct TextureCopy {
|
|
|
|
|
wgpu::TexelCopyTextureInfo src;
|
|
|
|
|
wgpu::TexelCopyTextureInfo dst;
|
|
|
|
|
wgpu::Extent3D size;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum class FrameOpType : uint8_t {
|
|
|
|
|
RenderPass,
|
|
|
|
|
TextureCopy,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct FrameOp {
|
|
|
|
|
FrameOpType type = FrameOpType::RenderPass;
|
|
|
|
|
uint32_t index = 0;
|
|
|
|
|
RenderPass* renderPass = nullptr;
|
|
|
|
|
TextureCopy* textureCopy = nullptr;
|
|
|
|
|
StagingHighWater highWater;
|
|
|
|
|
std::vector<const TextureUpload*> textureUploads;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
using RenderPassList = std::deque<RenderPass>;
|
|
|
|
|
struct FramePacket {
|
|
|
|
|
RenderPassList renderPasses;
|
|
|
|
|
std::deque<TextureCopy> textureCopies;
|
|
|
|
|
std::deque<FrameOp> ops;
|
|
|
|
|
std::deque<TextureUpload> textureUploads;
|
|
|
|
|
ByteBuffer verts;
|
|
|
|
|
ByteBuffer uniforms;
|
|
|
|
|
ByteBuffer indices;
|
|
|
|
|
ByteBuffer storage;
|
|
|
|
|
ByteBuffer textureUpload;
|
|
|
|
|
wgpu::CommandEncoder encoder;
|
|
|
|
|
uint64_t frameId = 0;
|
|
|
|
|
uint32_t frameIndex = 0;
|
|
|
|
|
size_t stagingBuffer = 0;
|
|
|
|
|
StagingHighWater copied;
|
|
|
|
|
AuroraStats stats{};
|
|
|
|
|
uint32_t drawCallCount = 0;
|
|
|
|
|
uint32_t mergedDrawCallCount = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static std::array<FramePacket, FrameSlotCount> g_framePackets;
|
|
|
|
|
static FramePacket* g_recordingFrame = nullptr;
|
|
|
|
|
static size_t g_recordingFrameSlot = 0;
|
|
|
|
|
static uint64_t g_nextFrameId = 1;
|
|
|
|
|
static render_worker::FrameSlotPool g_frameSlots{FrameSlotCount};
|
|
|
|
|
static render_worker::FrameSlotPool g_stagingSlots{StagingBufferCount};
|
2022-07-27 11:25:25 -04:00
|
|
|
static u32 g_currentRenderPass = UINT32_MAX;
|
2026-04-02 23:01:14 -06:00
|
|
|
static bool g_inOffscreen = false;
|
|
|
|
|
static std::optional<RenderPass> g_suspendedEfbPass;
|
2026-04-15 22:35:48 -06:00
|
|
|
static Viewport g_suspendedEfbViewport;
|
2026-04-18 14:10:32 -06:00
|
|
|
static ClipRect g_suspendedEfbScissor;
|
2026-04-02 23:01:14 -06:00
|
|
|
static webgpu::TextureWithSampler g_offscreenColor;
|
|
|
|
|
static webgpu::TextureWithSampler g_offscreenDepth;
|
2026-06-04 23:43:13 -06:00
|
|
|
static Viewport g_cachedViewport;
|
|
|
|
|
static ClipRect g_cachedScissor;
|
|
|
|
|
|
|
|
|
|
static void map_staging_buffer(size_t slot, bool releaseSlotOnCompletion = false) {
|
|
|
|
|
auto expected = BufferMapState::Unmapped;
|
|
|
|
|
if (!s_mappingStates[slot].compare_exchange_strong(expected, BufferMapState::Mapping, std::memory_order_acq_rel,
|
|
|
|
|
std::memory_order_acquire)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_stagingBuffers[slot].MapAsync(
|
|
|
|
|
wgpu::MapMode::Write, 0, StagingBufferSize, wgpu::CallbackMode::AllowSpontaneous,
|
|
|
|
|
[slot, releaseSlotOnCompletion](wgpu::MapAsyncStatus status, wgpu::StringView message) {
|
|
|
|
|
if (status == wgpu::MapAsyncStatus::CallbackCancelled || status == wgpu::MapAsyncStatus::Aborted) {
|
|
|
|
|
Log.warn("Buffer mapping {}: {}", magic_enum::enum_name(status), message);
|
|
|
|
|
s_mappingStates[slot].store(BufferMapState::Unmapped, std::memory_order_release);
|
|
|
|
|
if (releaseSlotOnCompletion) {
|
|
|
|
|
g_stagingSlots.release(slot);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
ASSERT(status == wgpu::MapAsyncStatus::Success, "Buffer mapping failed: {} {}", magic_enum::enum_name(status),
|
|
|
|
|
message);
|
|
|
|
|
s_mappingStates[slot].store(BufferMapState::Mapped, std::memory_order_release);
|
|
|
|
|
if (releaseSlotOnCompletion) {
|
|
|
|
|
g_stagingSlots.release(slot);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-04-02 23:01:14 -06:00
|
|
|
|
|
|
|
|
static void set_efb_targets(RenderPass& pass) {
|
|
|
|
|
pass.colorView = webgpu::g_frameBuffer.view;
|
|
|
|
|
pass.resolveView = webgpu::g_graphicsConfig.msaaSamples > 1 ? webgpu::g_frameBufferResolved.view : nullptr;
|
2026-06-04 23:43:13 -06:00
|
|
|
pass.depthStencilView = webgpu::g_depthBuffer.view;
|
2026-04-02 23:01:14 -06:00
|
|
|
pass.copySourceTexture =
|
|
|
|
|
webgpu::g_graphicsConfig.msaaSamples > 1 ? webgpu::g_frameBufferResolved.texture : webgpu::g_frameBuffer.texture;
|
|
|
|
|
pass.copySourceView =
|
|
|
|
|
webgpu::g_graphicsConfig.msaaSamples > 1 ? webgpu::g_frameBufferResolved.view : webgpu::g_frameBuffer.view;
|
2026-04-21 14:14:02 -06:00
|
|
|
pass.copySourceDepthView = webgpu::g_depthBuffer.view;
|
2026-04-02 23:01:14 -06:00
|
|
|
pass.targetSize = webgpu::g_frameBuffer.size;
|
|
|
|
|
pass.msaaSamples = webgpu::g_graphicsConfig.msaaSamples;
|
2026-06-04 23:43:13 -06:00
|
|
|
pass.hasDepth = true;
|
|
|
|
|
pass.hasStencil = false;
|
2026-04-02 23:01:14 -06:00
|
|
|
}
|
|
|
|
|
|
2026-04-07 19:01:41 -06:00
|
|
|
struct OffscreenCacheKey {
|
2026-04-02 23:01:14 -06:00
|
|
|
uint32_t width;
|
|
|
|
|
uint32_t height;
|
|
|
|
|
|
2026-04-07 19:01:41 -06:00
|
|
|
bool operator==(const OffscreenCacheKey& rhs) const { return width == rhs.width && height == rhs.height; }
|
2026-04-02 23:01:14 -06:00
|
|
|
template <typename H>
|
2026-04-07 19:01:41 -06:00
|
|
|
friend H AbslHashValue(H h, const OffscreenCacheKey& key) {
|
2026-04-02 23:01:14 -06:00
|
|
|
return H::combine(std::move(h), key.width, key.height);
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-04-07 19:01:41 -06:00
|
|
|
struct OffscreenCacheEntry {
|
|
|
|
|
webgpu::TextureWithSampler color;
|
|
|
|
|
webgpu::TextureWithSampler depth;
|
|
|
|
|
};
|
|
|
|
|
static absl::flat_hash_map<OffscreenCacheKey, OffscreenCacheEntry> g_offscreenCache;
|
2026-06-04 23:43:13 -06:00
|
|
|
|
|
|
|
|
static FramePacket& current_frame_packet() {
|
|
|
|
|
CHECK(g_recordingFrame != nullptr, "No active frame packet");
|
|
|
|
|
return *g_recordingFrame;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static RenderPassList& current_render_passes() { return current_frame_packet().renderPasses; }
|
|
|
|
|
|
|
|
|
|
static StagingHighWater current_high_water(const FramePacket& frame) noexcept {
|
|
|
|
|
return {
|
|
|
|
|
.verts = static_cast<uint32_t>(frame.verts.size()),
|
|
|
|
|
.uniforms = static_cast<uint32_t>(frame.uniforms.size()),
|
|
|
|
|
.indices = static_cast<uint32_t>(frame.indices.size()),
|
|
|
|
|
.storage = static_cast<uint32_t>(frame.storage.size()),
|
|
|
|
|
.textureUpload = static_cast<uint32_t>(frame.textureUpload.size()),
|
|
|
|
|
.textureUploadCount = frame.textureUploads.size(),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static FrameOp capture_frame_op(FramePacket& frame, FrameOpType type, uint32_t index) {
|
|
|
|
|
FrameOp op{
|
|
|
|
|
.type = type,
|
|
|
|
|
.index = index,
|
|
|
|
|
.renderPass =
|
|
|
|
|
type == FrameOpType::RenderPass && index < frame.renderPasses.size() ? &frame.renderPasses[index] : nullptr,
|
|
|
|
|
.textureCopy = type == FrameOpType::TextureCopy && index < frame.textureCopies.size()
|
|
|
|
|
? &frame.textureCopies[index]
|
|
|
|
|
: nullptr,
|
|
|
|
|
.highWater = current_high_water(frame),
|
|
|
|
|
};
|
|
|
|
|
op.textureUploads.reserve(op.highWater.textureUploadCount);
|
|
|
|
|
for (size_t i = 0; i < op.highWater.textureUploadCount; ++i) {
|
|
|
|
|
op.textureUploads.push_back(&frame.textureUploads[i]);
|
|
|
|
|
}
|
|
|
|
|
return op;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void seal_pass(FramePacket& frame, uint32_t passIndex) {
|
|
|
|
|
if (passIndex >= frame.renderPasses.size()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
auto& pass = frame.renderPasses[passIndex];
|
|
|
|
|
if (pass.sealed) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
pass.sealed = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void encode_op(wgpu::CommandEncoder& cmd, FramePacket& frame, const FrameOp& op);
|
|
|
|
|
static void render(wgpu::CommandEncoder& cmd, FramePacket& frame, RenderPass& passInfo, uint32_t passIndex);
|
|
|
|
|
static void render_pass(const wgpu::RenderPassEncoder& pass, FramePacket& frame, const RenderPass& passInfo);
|
|
|
|
|
static void expire_cached_bind_groups();
|
|
|
|
|
static void push_command(CommandType type, const Command::Data& data);
|
|
|
|
|
|
|
|
|
|
static void enqueue_op(FramePacket& frame, size_t frameSlot, uint32_t opIndex) {
|
|
|
|
|
if (opIndex >= frame.ops.size()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
auto op = frame.ops[opIndex];
|
|
|
|
|
render_worker::enqueue_encode_pass(frame.frameId, opIndex, [frameSlot, op = std::move(op)] {
|
|
|
|
|
if (op.renderPass == nullptr && op.textureCopy == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
auto& packet = g_framePackets[frameSlot];
|
|
|
|
|
encode_op(packet.encoder, packet, op);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void enqueue_pass(FramePacket& frame, size_t frameSlot, uint32_t passIndex) {
|
|
|
|
|
seal_pass(frame, passIndex);
|
|
|
|
|
const auto opIndex = static_cast<uint32_t>(frame.ops.size());
|
|
|
|
|
frame.ops.emplace_back(capture_frame_op(frame, FrameOpType::RenderPass, passIndex));
|
|
|
|
|
enqueue_op(frame, frameSlot, opIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void queue_texture_upload(TextureUpload upload) {
|
|
|
|
|
if (g_currentRenderPass != UINT32_MAX) {
|
|
|
|
|
ASSERT(!current_render_passes()[g_currentRenderPass].sealed,
|
|
|
|
|
"Attempted to append texture upload to sealed render pass {}", g_currentRenderPass);
|
|
|
|
|
}
|
|
|
|
|
current_frame_packet().textureUploads.emplace_back(std::move(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) {
|
|
|
|
|
const auto copyBytesPerRow = AURORA_ALIGN(bytesPerRow, 256);
|
|
|
|
|
auto& frame = current_frame_packet();
|
|
|
|
|
if (frame.textureUpload.size() + copyBytesPerRow * rowsPerImage <= TextureUploadSize) {
|
|
|
|
|
const auto range = push_texture_data(data, length, bytesPerRow, rowsPerImage);
|
|
|
|
|
const wgpu::TexelCopyBufferLayout layout{
|
|
|
|
|
.offset = range.offset,
|
|
|
|
|
.bytesPerRow = bytesPerRow,
|
|
|
|
|
.rowsPerImage = rowsPerImage,
|
|
|
|
|
};
|
|
|
|
|
queue_texture_upload(TextureUpload{layout, std::move(tex), size});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const uint64_t uploadSize = copyBytesPerRow * rowsPerImage;
|
|
|
|
|
const wgpu::BufferDescriptor descriptor{
|
|
|
|
|
.label = "Overflow Texture Upload Buffer",
|
|
|
|
|
.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc,
|
|
|
|
|
.size = uploadSize,
|
|
|
|
|
.mappedAtCreation = true,
|
|
|
|
|
};
|
|
|
|
|
auto buffer = g_device.CreateBuffer(&descriptor);
|
|
|
|
|
auto* dst = static_cast<uint8_t*>(buffer.GetMappedRange(0, uploadSize));
|
|
|
|
|
for (uint32_t row = 0; row < rowsPerImage; ++row) {
|
|
|
|
|
memcpy(dst, data, bytesPerRow);
|
|
|
|
|
data += bytesPerRow;
|
|
|
|
|
dst += copyBytesPerRow;
|
|
|
|
|
}
|
|
|
|
|
buffer.Unmap();
|
|
|
|
|
|
|
|
|
|
const wgpu::TexelCopyBufferLayout layout{
|
|
|
|
|
.offset = 0,
|
|
|
|
|
.bytesPerRow = bytesPerRow,
|
|
|
|
|
.rowsPerImage = rowsPerImage,
|
|
|
|
|
};
|
|
|
|
|
queue_texture_upload(TextureUpload{layout, std::move(tex), size, std::move(buffer)});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void queue_texture_copy(wgpu::TexelCopyTextureInfo src, wgpu::TexelCopyTextureInfo dst, wgpu::Extent3D size) {
|
|
|
|
|
ZoneScoped;
|
|
|
|
|
auto& frame = current_frame_packet();
|
|
|
|
|
if (g_currentRenderPass != UINT32_MAX) {
|
|
|
|
|
enqueue_pass(frame, g_recordingFrameSlot, g_currentRenderPass);
|
|
|
|
|
g_currentRenderPass = UINT32_MAX;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto copyIndex = static_cast<uint32_t>(frame.textureCopies.size());
|
|
|
|
|
frame.textureCopies.emplace_back(TextureCopy{
|
|
|
|
|
.src = std::move(src),
|
|
|
|
|
.dst = std::move(dst),
|
|
|
|
|
.size = size,
|
|
|
|
|
});
|
|
|
|
|
const auto opIndex = static_cast<uint32_t>(frame.ops.size());
|
|
|
|
|
frame.ops.emplace_back(capture_frame_op(frame, FrameOpType::TextureCopy, copyIndex));
|
|
|
|
|
enqueue_op(frame, g_recordingFrameSlot, opIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void begin_color_pass(const ColorPassDescriptor& desc) {
|
|
|
|
|
ZoneScoped;
|
|
|
|
|
auto& frame = current_frame_packet();
|
|
|
|
|
if (g_currentRenderPass != UINT32_MAX) {
|
|
|
|
|
enqueue_pass(frame, g_recordingFrameSlot, g_currentRenderPass);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RenderPass pass{
|
|
|
|
|
.label = desc.label != nullptr ? desc.label : "",
|
|
|
|
|
.colorView = desc.colorView,
|
|
|
|
|
.resolveView = desc.resolveView,
|
|
|
|
|
.depthStencilView = desc.depthStencilView,
|
|
|
|
|
.targetSize = desc.targetSize,
|
|
|
|
|
.msaaSamples = desc.sampleCount,
|
|
|
|
|
.clearColorValue =
|
|
|
|
|
{
|
|
|
|
|
static_cast<float>(desc.clearColor.r),
|
|
|
|
|
static_cast<float>(desc.clearColor.g),
|
|
|
|
|
static_cast<float>(desc.clearColor.b),
|
|
|
|
|
static_cast<float>(desc.clearColor.a),
|
|
|
|
|
},
|
|
|
|
|
.clearDepthValue = desc.depthClearValue,
|
|
|
|
|
.colorLoadOp = desc.colorLoadOp,
|
|
|
|
|
.colorStoreOp = desc.colorStoreOp,
|
|
|
|
|
.depthLoadOp = desc.depthLoadOp,
|
|
|
|
|
.depthStoreOp = desc.depthStoreOp,
|
|
|
|
|
.stencilLoadOp = desc.stencilLoadOp,
|
|
|
|
|
.stencilStoreOp = desc.stencilStoreOp,
|
|
|
|
|
.stencilClearValue = desc.stencilClearValue,
|
|
|
|
|
.clearColor = desc.colorLoadOp == wgpu::LoadOp::Clear,
|
|
|
|
|
.clearDepth = desc.depthLoadOp == wgpu::LoadOp::Clear,
|
|
|
|
|
.hasDepth = desc.hasDepth,
|
|
|
|
|
.hasStencil = desc.hasStencil,
|
|
|
|
|
.observable = desc.observable,
|
|
|
|
|
};
|
|
|
|
|
pass.commands.reserve(128);
|
|
|
|
|
frame.renderPasses.emplace_back(std::move(pass));
|
|
|
|
|
g_currentRenderPass = static_cast<uint32_t>(frame.renderPasses.size() - 1);
|
|
|
|
|
|
|
|
|
|
g_cachedViewport = {0.f, 0.f, static_cast<float>(desc.targetSize.width), static_cast<float>(desc.targetSize.height),
|
|
|
|
|
0.f, 1.f};
|
|
|
|
|
g_cachedScissor = {0, 0, static_cast<int32_t>(desc.targetSize.width), static_cast<int32_t>(desc.targetSize.height)};
|
|
|
|
|
push_command(CommandType::SetViewport, Command::Data{.setViewport = g_cachedViewport});
|
|
|
|
|
push_command(CommandType::SetScissor, Command::Data{.setScissor = g_cachedScissor});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void end_color_pass() {
|
|
|
|
|
ZoneScoped;
|
|
|
|
|
if (g_currentRenderPass == UINT32_MAX) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
enqueue_pass(current_frame_packet(), g_recordingFrameSlot, g_currentRenderPass);
|
|
|
|
|
g_currentRenderPass = UINT32_MAX;
|
|
|
|
|
}
|
2022-07-27 11:25:25 -04:00
|
|
|
|
|
|
|
|
static inline void push_command(CommandType type, const Command::Data& data) {
|
2022-08-09 02:05:33 -04:00
|
|
|
if (g_currentRenderPass == UINT32_MAX)
|
|
|
|
|
UNLIKELY {
|
2025-04-06 16:37:05 -06:00
|
|
|
Log.warn("Dropping command {}", magic_enum::enum_name(type));
|
2022-08-09 02:05:33 -04:00
|
|
|
return;
|
|
|
|
|
}
|
2026-06-04 23:43:13 -06:00
|
|
|
auto& renderPass = current_render_passes()[g_currentRenderPass];
|
|
|
|
|
ASSERT(!renderPass.sealed, "Attempted to append command {} to sealed render pass {}", magic_enum::enum_name(type),
|
|
|
|
|
g_currentRenderPass);
|
|
|
|
|
renderPass.commands.push_back({
|
2022-07-27 11:25:25 -04:00
|
|
|
.type = type,
|
|
|
|
|
#ifdef AURORA_GFX_DEBUG_GROUPS
|
|
|
|
|
.debugGroupStack = g_debugGroupStack,
|
|
|
|
|
#endif
|
|
|
|
|
.data = data,
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-04-04 01:59:30 -06:00
|
|
|
|
2026-03-05 20:31:51 -07:00
|
|
|
template <>
|
|
|
|
|
gx::DrawData* get_last_draw_command() {
|
2026-06-04 23:43:13 -06:00
|
|
|
if (g_currentRenderPass >= current_render_passes().size()) {
|
2026-03-05 20:31:51 -07:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
2026-06-04 23:43:13 -06:00
|
|
|
auto& last = current_render_passes()[g_currentRenderPass].commands.back();
|
2026-03-05 20:31:51 -07:00
|
|
|
if (last.type != CommandType::Draw || last.data.draw.type != ShaderType::GX) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
return &last.data.draw.gx;
|
2022-07-28 19:48:02 -04:00
|
|
|
}
|
2022-07-27 11:25:25 -04:00
|
|
|
|
2022-07-28 19:48:02 -04:00
|
|
|
static void push_draw_command(ShaderDrawCommand data) {
|
|
|
|
|
push_command(CommandType::Draw, Command::Data{.draw = data});
|
2026-05-14 16:29:45 -06:00
|
|
|
++g_drawCallCount;
|
2022-07-28 19:48:02 -04:00
|
|
|
}
|
2022-07-27 11:25:25 -04:00
|
|
|
|
2026-04-18 14:10:32 -06:00
|
|
|
Vec2<uint32_t> get_render_target_size() noexcept {
|
2026-06-04 23:43:13 -06:00
|
|
|
if (g_currentRenderPass < current_render_passes().size()) {
|
|
|
|
|
const auto& size = current_render_passes()[g_currentRenderPass].targetSize;
|
2026-04-18 14:10:32 -06:00
|
|
|
return {size.width, size.height};
|
|
|
|
|
}
|
|
|
|
|
const auto windowSize = window::get_window_size();
|
|
|
|
|
return {windowSize.fb_width, windowSize.fb_height};
|
|
|
|
|
}
|
2026-03-05 18:09:26 +01:00
|
|
|
|
2026-04-18 14:10:32 -06:00
|
|
|
void set_viewport(const Viewport& cmd) noexcept {
|
2022-07-27 11:25:25 -04:00
|
|
|
if (cmd != g_cachedViewport) {
|
|
|
|
|
push_command(CommandType::SetViewport, Command::Data{.setViewport = cmd});
|
|
|
|
|
g_cachedViewport = cmd;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-04-04 01:59:30 -06:00
|
|
|
|
2026-04-18 14:10:32 -06:00
|
|
|
void set_scissor(const ClipRect& cmd) noexcept {
|
2022-07-27 11:25:25 -04:00
|
|
|
if (cmd != g_cachedScissor) {
|
|
|
|
|
push_command(CommandType::SetScissor, Command::Data{.setScissor = cmd});
|
|
|
|
|
g_cachedScissor = cmd;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 00:49:28 -06:00
|
|
|
template <>
|
|
|
|
|
void push_draw_command(clear::DrawData data) {
|
|
|
|
|
push_draw_command(ShaderDrawCommand{.type = ShaderType::Clear, .clear = data});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <>
|
2026-04-09 03:51:06 +02:00
|
|
|
PipelineRef pipeline_ref(const clear::PipelineConfig& config) {
|
2026-04-01 00:49:28 -06:00
|
|
|
return find_pipeline(ShaderType::Clear, config, [=] { return create_pipeline(config); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void resolve_pass(TextureHandle texture, ClipRect rect, bool clearColor, bool clearAlpha, bool clearDepth,
|
2026-04-02 23:01:14 -06:00
|
|
|
Vec4<float> clearColorValue, float clearDepthValue, GXTexFmt resolveFormat) {
|
|
|
|
|
// Resolve current render pass
|
2026-06-04 23:43:13 -06:00
|
|
|
auto& prevPass = current_render_passes()[g_currentRenderPass];
|
2026-04-02 23:01:14 -06:00
|
|
|
prevPass.resolveTarget = std::move(texture);
|
|
|
|
|
prevPass.resolveRect = rect;
|
|
|
|
|
prevPass.resolveFormat = resolveFormat;
|
|
|
|
|
// Push UV transform uniform for tex_copy_conv (crop region in UV space)
|
|
|
|
|
const auto srcW = static_cast<float>(prevPass.targetSize.width);
|
|
|
|
|
const auto srcH = static_cast<float>(prevPass.targetSize.height);
|
|
|
|
|
const std::array uvTransform{
|
|
|
|
|
static_cast<float>(rect.x) / srcW,
|
|
|
|
|
static_cast<float>(rect.y) / srcH,
|
|
|
|
|
static_cast<float>(rect.width) / srcW,
|
|
|
|
|
static_cast<float>(rect.height) / srcH,
|
|
|
|
|
};
|
|
|
|
|
prevPass.resolveUniformRange = push_uniform(uvTransform);
|
2026-06-04 23:43:13 -06:00
|
|
|
enqueue_pass(current_frame_packet(), g_recordingFrameSlot, g_currentRenderPass);
|
2026-04-02 23:01:14 -06:00
|
|
|
|
|
|
|
|
// Populate new render pass from previous
|
|
|
|
|
const auto msaaSamples = prevPass.msaaSamples;
|
|
|
|
|
RenderPass newPass{
|
2026-06-12 10:28:52 -06:00
|
|
|
.label = pass_label("EFB"),
|
2026-04-02 23:01:14 -06:00
|
|
|
.colorView = prevPass.colorView,
|
|
|
|
|
.resolveView = prevPass.resolveView,
|
2026-06-04 23:43:13 -06:00
|
|
|
.depthStencilView = prevPass.depthStencilView,
|
2026-04-02 23:01:14 -06:00
|
|
|
.copySourceTexture = prevPass.copySourceTexture,
|
|
|
|
|
.copySourceView = prevPass.copySourceView,
|
2026-04-21 14:14:02 -06:00
|
|
|
.copySourceDepthView = prevPass.copySourceDepthView,
|
2026-04-02 23:01:14 -06:00
|
|
|
.targetSize = prevPass.targetSize,
|
|
|
|
|
.msaaSamples = msaaSamples,
|
|
|
|
|
.clearColorValue = clearColorValue,
|
|
|
|
|
.clearDepthValue = clearDepthValue,
|
|
|
|
|
.clearColor = clearColor && clearAlpha,
|
|
|
|
|
.clearDepth = clearDepth,
|
2026-06-04 23:43:13 -06:00
|
|
|
.hasDepth = prevPass.hasDepth,
|
|
|
|
|
.hasStencil = prevPass.hasStencil,
|
2026-04-02 23:01:14 -06:00
|
|
|
};
|
2026-06-04 23:43:13 -06:00
|
|
|
newPass.commands.reserve(2048);
|
|
|
|
|
current_render_passes().emplace_back(std::move(newPass));
|
2022-07-27 11:25:25 -04:00
|
|
|
++g_currentRenderPass;
|
2026-04-02 23:01:14 -06:00
|
|
|
|
2026-04-01 00:49:28 -06:00
|
|
|
if (!newPass.clearColor && (clearColor || clearAlpha)) {
|
|
|
|
|
// If we're only clearing color _or_ alpha, perform a clear draw
|
|
|
|
|
push_draw_command(clear::DrawData{
|
|
|
|
|
.pipeline = pipeline_ref(clear::PipelineConfig{
|
2026-04-02 23:01:14 -06:00
|
|
|
.msaaSamples = msaaSamples,
|
2026-04-01 00:49:28 -06:00
|
|
|
.clearColor = clearColor,
|
|
|
|
|
.clearAlpha = clearAlpha,
|
|
|
|
|
.clearDepth = false, // Depth cleared via render attachment
|
|
|
|
|
}),
|
|
|
|
|
.color =
|
|
|
|
|
wgpu::Color{
|
|
|
|
|
.r = clearColorValue.x(),
|
|
|
|
|
.g = clearColorValue.y(),
|
|
|
|
|
.b = clearColorValue.z(),
|
|
|
|
|
.a = clearColorValue.w(),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-03-07 20:35:33 -08:00
|
|
|
push_command(CommandType::SetViewport, Command::Data{.setViewport = g_cachedViewport});
|
|
|
|
|
push_command(CommandType::SetScissor, Command::Data{.setScissor = g_cachedScissor});
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-01 00:49:28 -06:00
|
|
|
void queue_palette_conv(tex_palette_conv::ConvRequest req) {
|
2026-06-04 23:43:13 -06:00
|
|
|
auto& renderPass = current_render_passes()[g_currentRenderPass];
|
|
|
|
|
ASSERT(!renderPass.sealed, "Attempted to append palette conversion to sealed render pass {}", g_currentRenderPass);
|
|
|
|
|
renderPass.paletteConvs.push_back(std::move(req));
|
2026-04-01 00:49:28 -06:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 23:01:14 -06:00
|
|
|
bool is_offscreen() noexcept { return g_inOffscreen; }
|
|
|
|
|
|
|
|
|
|
uint32_t get_sample_count() noexcept {
|
|
|
|
|
CHECK(g_currentRenderPass != UINT32_MAX, "get_sample_count called outside of a frame");
|
2026-06-04 23:43:13 -06:00
|
|
|
return current_render_passes()[g_currentRenderPass].msaaSamples;
|
2026-04-02 23:01:14 -06:00
|
|
|
}
|
|
|
|
|
|
2026-04-21 01:06:16 -06:00
|
|
|
void clear_caches() noexcept {
|
|
|
|
|
g_offscreenCache.clear();
|
2026-06-04 23:43:13 -06:00
|
|
|
std::lock_guard lock{g_bindGroupCacheMutex};
|
2026-04-21 01:06:16 -06:00
|
|
|
g_cachedBindGroups.clear();
|
|
|
|
|
}
|
2026-04-02 23:01:14 -06:00
|
|
|
|
2026-04-07 19:01:41 -06:00
|
|
|
static OffscreenCacheEntry get_offscreen_textures(uint32_t width, uint32_t height) {
|
|
|
|
|
OffscreenCacheKey key{width, height};
|
|
|
|
|
if (const auto it = g_offscreenCache.find(key); it != g_offscreenCache.end()) {
|
2026-04-02 23:01:14 -06:00
|
|
|
return it->second;
|
|
|
|
|
}
|
2026-04-07 22:48:29 -06:00
|
|
|
const auto colorFormat = webgpu::g_graphicsConfig.surfaceConfiguration.format;
|
2026-04-02 23:01:14 -06:00
|
|
|
const wgpu::Extent3D size{width, height, 1};
|
2026-04-07 19:01:41 -06:00
|
|
|
const wgpu::TextureDescriptor colorDesc{
|
|
|
|
|
.label = "Offscreen Color",
|
|
|
|
|
.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc |
|
|
|
|
|
wgpu::TextureUsage::CopyDst,
|
|
|
|
|
.dimension = wgpu::TextureDimension::e2D,
|
|
|
|
|
.size = size,
|
2026-04-07 22:48:29 -06:00
|
|
|
.format = colorFormat,
|
2026-04-07 19:01:41 -06:00
|
|
|
.mipLevelCount = 1,
|
|
|
|
|
.sampleCount = 1,
|
|
|
|
|
};
|
|
|
|
|
auto colorTexture = g_device.CreateTexture(&colorDesc);
|
|
|
|
|
auto colorView = colorTexture.CreateView();
|
|
|
|
|
webgpu::TextureWithSampler color{
|
|
|
|
|
.texture = std::move(colorTexture),
|
|
|
|
|
.view = std::move(colorView),
|
|
|
|
|
.size = size,
|
2026-04-07 22:48:29 -06:00
|
|
|
.format = colorFormat,
|
2026-04-07 19:01:41 -06:00
|
|
|
};
|
2026-04-07 22:48:29 -06:00
|
|
|
const auto depthFormat = webgpu::g_graphicsConfig.depthFormat;
|
2026-04-07 19:01:41 -06:00
|
|
|
const wgpu::TextureDescriptor depthDesc{
|
2026-04-02 23:01:14 -06:00
|
|
|
.label = "Offscreen Depth",
|
|
|
|
|
.usage = wgpu::TextureUsage::RenderAttachment,
|
|
|
|
|
.dimension = wgpu::TextureDimension::e2D,
|
|
|
|
|
.size = size,
|
2026-04-07 22:48:29 -06:00
|
|
|
.format = depthFormat,
|
2026-04-02 23:01:14 -06:00
|
|
|
.mipLevelCount = 1,
|
|
|
|
|
.sampleCount = 1,
|
|
|
|
|
};
|
2026-04-07 19:01:41 -06:00
|
|
|
auto depthTexture = g_device.CreateTexture(&depthDesc);
|
|
|
|
|
auto depthView = depthTexture.CreateView();
|
|
|
|
|
webgpu::TextureWithSampler depth{
|
|
|
|
|
.texture = std::move(depthTexture),
|
|
|
|
|
.view = std::move(depthView),
|
2026-04-02 23:01:14 -06:00
|
|
|
.size = size,
|
2026-04-07 22:48:29 -06:00
|
|
|
.format = depthFormat,
|
2026-04-02 23:01:14 -06:00
|
|
|
};
|
2026-04-07 19:01:41 -06:00
|
|
|
OffscreenCacheEntry entry{
|
|
|
|
|
.color = std::move(color),
|
|
|
|
|
.depth = std::move(depth),
|
|
|
|
|
};
|
|
|
|
|
auto [insertIt, _] = g_offscreenCache.emplace(key, std::move(entry));
|
2026-04-02 23:01:14 -06:00
|
|
|
return insertIt->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void begin_offscreen(uint32_t width, uint32_t height) {
|
2026-04-09 03:19:58 +02:00
|
|
|
ZoneScoped;
|
2026-04-02 23:01:14 -06:00
|
|
|
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.
|
|
|
|
|
// Suspend it so that we can resume it after the offscreen pass.
|
|
|
|
|
if (!g_inOffscreen) {
|
2026-06-04 23:43:13 -06:00
|
|
|
auto& currentPass = current_render_passes()[g_currentRenderPass];
|
2026-04-02 23:01:14 -06:00
|
|
|
if (!currentPass.resolveTarget) {
|
|
|
|
|
g_suspendedEfbPass = std::move(currentPass);
|
2026-06-04 23:43:13 -06:00
|
|
|
current_render_passes().pop_back();
|
2026-04-02 23:01:14 -06:00
|
|
|
--g_currentRenderPass;
|
2026-06-04 23:43:13 -06:00
|
|
|
} else {
|
|
|
|
|
enqueue_pass(current_frame_packet(), g_recordingFrameSlot, g_currentRenderPass);
|
2026-04-02 23:01:14 -06:00
|
|
|
}
|
2026-04-15 22:35:48 -06:00
|
|
|
g_suspendedEfbViewport = g_cachedViewport;
|
|
|
|
|
g_suspendedEfbScissor = g_cachedScissor;
|
2026-04-02 23:01:14 -06:00
|
|
|
}
|
|
|
|
|
|
2026-04-07 19:01:41 -06:00
|
|
|
// Create offscreen textures
|
|
|
|
|
auto offscreenEntry = get_offscreen_textures(width, height);
|
|
|
|
|
g_offscreenColor = std::move(offscreenEntry.color);
|
|
|
|
|
g_offscreenDepth = std::move(offscreenEntry.depth);
|
2026-04-02 23:01:14 -06:00
|
|
|
|
|
|
|
|
// Start a new pass with offscreen targets
|
|
|
|
|
RenderPass newPass{
|
2026-06-12 10:28:52 -06:00
|
|
|
.label = pass_label("Offscreen"),
|
2026-04-02 23:01:14 -06:00
|
|
|
.colorView = g_offscreenColor.view,
|
2026-06-04 23:43:13 -06:00
|
|
|
.depthStencilView = g_offscreenDepth.view,
|
2026-04-02 23:01:14 -06:00
|
|
|
.copySourceTexture = g_offscreenColor.texture,
|
|
|
|
|
.copySourceView = g_offscreenColor.view,
|
2026-04-21 14:14:02 -06:00
|
|
|
.copySourceDepthView = g_offscreenDepth.view,
|
2026-04-07 19:01:41 -06:00
|
|
|
.targetSize = {width, height, 1},
|
2026-04-02 23:01:14 -06:00
|
|
|
.msaaSamples = 1,
|
|
|
|
|
.clearColorValue = {0.f, 0.f, 0.f, 0.f},
|
|
|
|
|
.clearDepthValue = gx::UseReversedZ ? 0.f : 1.f,
|
|
|
|
|
.clearColor = true,
|
|
|
|
|
.clearDepth = true,
|
2026-06-04 23:43:13 -06:00
|
|
|
.hasDepth = true,
|
|
|
|
|
.hasStencil = false,
|
|
|
|
|
.offscreen = true,
|
2026-04-02 23:01:14 -06:00
|
|
|
};
|
2026-06-04 23:43:13 -06:00
|
|
|
current_render_passes().emplace_back(std::move(newPass));
|
2026-04-02 23:01:14 -06:00
|
|
|
++g_currentRenderPass;
|
|
|
|
|
|
|
|
|
|
g_inOffscreen = true;
|
|
|
|
|
|
2026-04-15 22:35:48 -06:00
|
|
|
g_cachedViewport = {0.f, 0.f, static_cast<float>(width), static_cast<float>(height), 0.f, 1.f};
|
2026-04-18 14:10:32 -06:00
|
|
|
g_cachedScissor = {0, 0, static_cast<int32_t>(width), static_cast<int32_t>(height)};
|
2026-04-15 22:35:48 -06:00
|
|
|
push_command(CommandType::SetViewport, Command::Data{.setViewport = g_cachedViewport});
|
|
|
|
|
push_command(CommandType::SetScissor, Command::Data{.setScissor = g_cachedScissor});
|
2026-04-02 23:01:14 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void end_offscreen() {
|
2026-04-09 03:19:58 +02:00
|
|
|
ZoneScoped;
|
2026-04-02 23:01:14 -06:00
|
|
|
CHECK(g_inOffscreen, "end_offscreen called without begin_offscreen");
|
|
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
enqueue_pass(current_frame_packet(), g_recordingFrameSlot, g_currentRenderPass);
|
|
|
|
|
|
2026-04-02 23:01:14 -06:00
|
|
|
g_inOffscreen = false;
|
|
|
|
|
g_offscreenColor = {};
|
|
|
|
|
g_offscreenDepth = {};
|
|
|
|
|
|
|
|
|
|
// Resume suspended EFB pass, or start a new one (load existing content)
|
|
|
|
|
if (g_suspendedEfbPass) {
|
2026-06-04 23:43:13 -06:00
|
|
|
current_render_passes().emplace_back(std::move(*g_suspendedEfbPass));
|
2026-04-02 23:01:14 -06:00
|
|
|
g_suspendedEfbPass.reset();
|
|
|
|
|
} else {
|
2026-06-04 23:43:13 -06:00
|
|
|
auto& pass = current_render_passes().emplace_back();
|
2026-06-12 10:28:52 -06:00
|
|
|
pass.label = pass_label("EFB");
|
2026-04-02 23:01:14 -06:00
|
|
|
pass.clearColor = false;
|
|
|
|
|
pass.clearDepth = false;
|
|
|
|
|
}
|
|
|
|
|
++g_currentRenderPass;
|
2026-06-04 23:43:13 -06:00
|
|
|
set_efb_targets(current_render_passes()[g_currentRenderPass]);
|
2026-04-02 23:01:14 -06:00
|
|
|
|
2026-04-15 22:35:48 -06:00
|
|
|
g_cachedViewport = g_suspendedEfbViewport;
|
|
|
|
|
g_cachedScissor = g_suspendedEfbScissor;
|
2026-04-02 23:01:14 -06:00
|
|
|
push_command(CommandType::SetViewport, Command::Data{.setViewport = g_cachedViewport});
|
|
|
|
|
push_command(CommandType::SetScissor, Command::Data{.setScissor = g_cachedScissor});
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-27 11:25:25 -04:00
|
|
|
template <>
|
2026-03-05 20:31:51 -07:00
|
|
|
void push_draw_command(gx::DrawData data) {
|
|
|
|
|
push_draw_command(ShaderDrawCommand{.type = ShaderType::GX, .gx = data});
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
2025-04-04 01:59:30 -06:00
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
#ifdef AURORA_ENABLE_RMLUI
|
|
|
|
|
template <>
|
|
|
|
|
void push_draw_command(rmlui::DrawData data) {
|
|
|
|
|
push_draw_command(ShaderDrawCommand{.type = ShaderType::Rml, .rml = data});
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-07-27 11:25:25 -04:00
|
|
|
template <>
|
2026-04-09 03:51:06 +02:00
|
|
|
PipelineRef pipeline_ref(const gx::PipelineConfig& config) {
|
2026-04-01 00:49:28 -06:00
|
|
|
return find_pipeline(ShaderType::GX, config, [=] { return create_pipeline(config); });
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
#ifdef AURORA_ENABLE_RMLUI
|
|
|
|
|
template <>
|
|
|
|
|
PipelineRef pipeline_ref(const rmlui::PipelineConfig& config) {
|
|
|
|
|
return find_pipeline(ShaderType::Rml, config, [=] { return rmlui::create_pipeline(config); });
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-07-27 11:25:25 -04:00
|
|
|
void initialize() {
|
2026-04-08 20:03:25 -06:00
|
|
|
g_frameIndex = 0;
|
2026-06-04 23:43:13 -06:00
|
|
|
render_worker::initialize();
|
|
|
|
|
// render_worker::set_event_pump([] {
|
|
|
|
|
// if (g_instance) {
|
|
|
|
|
// g_instance.ProcessEvents();
|
|
|
|
|
// }
|
|
|
|
|
// });
|
2026-04-24 21:55:17 -06:00
|
|
|
depth_peek::initialize();
|
2026-04-01 00:49:28 -06:00
|
|
|
tex_copy_conv::initialize();
|
|
|
|
|
tex_palette_conv::initialize();
|
2026-04-06 23:04:01 -04:00
|
|
|
texture_replacement::initialize();
|
2026-04-01 00:49:28 -06:00
|
|
|
|
2022-07-27 11:25:25 -04:00
|
|
|
// For uniform & storage buffer offset alignments
|
2022-08-02 16:37:56 -04:00
|
|
|
g_device.GetLimits(&g_cachedLimits);
|
2022-07-27 11:25:25 -04:00
|
|
|
|
2022-08-02 16:37:56 -04:00
|
|
|
const auto createBuffer = [](wgpu::Buffer& out, wgpu::BufferUsage usage, uint64_t size, const char* label) {
|
2022-07-27 11:25:25 -04:00
|
|
|
if (size <= 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-08-02 16:37:56 -04:00
|
|
|
const wgpu::BufferDescriptor descriptor{
|
2022-07-27 11:25:25 -04:00
|
|
|
.label = label,
|
|
|
|
|
.usage = usage,
|
|
|
|
|
.size = size,
|
|
|
|
|
};
|
2022-08-02 16:37:56 -04:00
|
|
|
out = g_device.CreateBuffer(&descriptor);
|
2022-07-27 11:25:25 -04:00
|
|
|
};
|
2022-08-02 16:37:56 -04:00
|
|
|
createBuffer(g_uniformBuffer, wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst, UniformBufferSize,
|
2022-07-27 11:25:25 -04:00
|
|
|
"Shared Uniform Buffer");
|
2026-06-04 23:43:13 -06:00
|
|
|
createBuffer(g_vertexBuffer, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Vertex | wgpu::BufferUsage::CopyDst,
|
|
|
|
|
VertexBufferSize, "Shared Vertex Buffer");
|
2022-08-02 16:37:56 -04:00
|
|
|
createBuffer(g_indexBuffer, wgpu::BufferUsage::Index | wgpu::BufferUsage::CopyDst, IndexBufferSize,
|
|
|
|
|
"Shared Index Buffer");
|
|
|
|
|
createBuffer(g_storageBuffer, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst, StorageBufferSize,
|
2022-07-27 11:25:25 -04:00
|
|
|
"Shared Storage Buffer");
|
2026-06-04 23:43:13 -06:00
|
|
|
for (size_t i = 0; i < g_stagingBuffers.size(); ++i) {
|
2025-04-03 21:03:08 -06:00
|
|
|
const auto label = fmt::format("Staging Buffer {}", i);
|
2022-08-02 16:37:56 -04:00
|
|
|
createBuffer(g_stagingBuffers[i], wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc, StagingBufferSize,
|
2022-07-27 11:25:25 -04:00
|
|
|
label.c_str());
|
|
|
|
|
}
|
2026-06-04 23:43:13 -06:00
|
|
|
for (auto& state : s_mappingStates) {
|
|
|
|
|
state.store(BufferMapState::Unmapped, std::memory_order_release);
|
|
|
|
|
}
|
|
|
|
|
for (size_t slot = 0; slot < g_stagingBuffers.size(); ++slot) {
|
|
|
|
|
map_staging_buffer(slot);
|
|
|
|
|
}
|
2022-07-27 11:25:25 -04:00
|
|
|
|
2026-04-12 18:55:35 -06:00
|
|
|
{
|
|
|
|
|
constexpr std::array layoutEntries{
|
|
|
|
|
// Vertex data buffer
|
|
|
|
|
wgpu::BindGroupLayoutEntry{
|
|
|
|
|
.binding = 0,
|
|
|
|
|
.visibility = wgpu::ShaderStage::Vertex,
|
|
|
|
|
.buffer =
|
|
|
|
|
wgpu::BufferBindingLayout{
|
|
|
|
|
.type = wgpu::BufferBindingType::ReadOnlyStorage,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
// Storage data buffer
|
|
|
|
|
wgpu::BindGroupLayoutEntry{
|
|
|
|
|
.binding = 1,
|
|
|
|
|
.visibility = wgpu::ShaderStage::Vertex,
|
|
|
|
|
.buffer =
|
|
|
|
|
wgpu::BufferBindingLayout{
|
|
|
|
|
.type = wgpu::BufferBindingType::ReadOnlyStorage,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
const wgpu::BindGroupLayoutDescriptor layoutDesc{
|
|
|
|
|
.label = "Static bind group layout",
|
|
|
|
|
.entryCount = layoutEntries.size(),
|
|
|
|
|
.entries = layoutEntries.data(),
|
|
|
|
|
};
|
|
|
|
|
g_staticBindGroupLayout = g_device.CreateBindGroupLayout(&layoutDesc);
|
|
|
|
|
const std::array entries{
|
|
|
|
|
wgpu::BindGroupEntry{
|
|
|
|
|
.binding = 0,
|
|
|
|
|
.buffer = g_vertexBuffer,
|
|
|
|
|
},
|
|
|
|
|
wgpu::BindGroupEntry{
|
|
|
|
|
.binding = 1,
|
|
|
|
|
.buffer = g_storageBuffer,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
const wgpu::BindGroupDescriptor bindGroupDescriptor{
|
|
|
|
|
.label = "Static bind group",
|
|
|
|
|
.layout = g_staticBindGroupLayout,
|
|
|
|
|
.entryCount = entries.size(),
|
|
|
|
|
.entries = entries.data(),
|
|
|
|
|
};
|
|
|
|
|
g_staticBindGroup = g_device.CreateBindGroup(&bindGroupDescriptor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
constexpr std::array layoutEntries{
|
|
|
|
|
// Uniform buffer (dynamic offset)
|
|
|
|
|
wgpu::BindGroupLayoutEntry{
|
|
|
|
|
.binding = 0,
|
|
|
|
|
.visibility = wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment,
|
|
|
|
|
.buffer =
|
|
|
|
|
wgpu::BufferBindingLayout{
|
|
|
|
|
.type = wgpu::BufferBindingType::Uniform,
|
|
|
|
|
.hasDynamicOffset = true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
const wgpu::BindGroupLayoutDescriptor layoutDesc{
|
|
|
|
|
.label = "Uniform bind group layout",
|
|
|
|
|
.entryCount = layoutEntries.size(),
|
|
|
|
|
.entries = layoutEntries.data(),
|
|
|
|
|
};
|
|
|
|
|
g_uniformBindGroupLayout = g_device.CreateBindGroupLayout(&layoutDesc);
|
|
|
|
|
const std::array entries{
|
|
|
|
|
wgpu::BindGroupEntry{
|
|
|
|
|
.binding = 0,
|
|
|
|
|
.buffer = g_uniformBuffer,
|
|
|
|
|
.size = gx::MaxUniformSize,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
const wgpu::BindGroupDescriptor bindGroupDescriptor{
|
|
|
|
|
.label = "Uniform bind group",
|
|
|
|
|
.layout = g_uniformBindGroupLayout,
|
|
|
|
|
.entryCount = entries.size(),
|
|
|
|
|
.entries = entries.data(),
|
|
|
|
|
};
|
|
|
|
|
g_uniformBindGroup = g_device.CreateBindGroup(&bindGroupDescriptor);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 00:49:28 -06:00
|
|
|
gx::initialize();
|
2026-06-04 23:43:13 -06:00
|
|
|
#ifdef AURORA_ENABLE_RMLUI
|
|
|
|
|
rmlui::initialize_pipeline();
|
|
|
|
|
#endif
|
2026-04-08 20:03:25 -06:00
|
|
|
initialize_pipeline_cache();
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void shutdown() {
|
2026-06-04 23:43:13 -06:00
|
|
|
render_worker::synchronize();
|
|
|
|
|
render_worker::shutdown();
|
2026-04-08 20:03:25 -06:00
|
|
|
shutdown_pipeline_cache();
|
2026-04-24 21:55:17 -06:00
|
|
|
depth_peek::shutdown();
|
2026-04-01 00:49:28 -06:00
|
|
|
tex_copy_conv::shutdown();
|
|
|
|
|
tex_palette_conv::shutdown();
|
2026-04-06 23:04:01 -04:00
|
|
|
texture_replacement::shutdown();
|
2022-07-27 11:25:25 -04:00
|
|
|
gx::shutdown();
|
2026-06-04 23:43:13 -06:00
|
|
|
#ifdef AURORA_ENABLE_RMLUI
|
|
|
|
|
rmlui::shutdown_pipeline();
|
|
|
|
|
#endif
|
2022-07-27 11:25:25 -04:00
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
{
|
|
|
|
|
std::lock_guard lock{g_bindGroupCacheMutex};
|
|
|
|
|
g_cachedBindGroups.clear();
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard lock{g_samplerCacheMutex};
|
|
|
|
|
g_cachedSamplers.clear();
|
|
|
|
|
}
|
2022-08-02 16:37:56 -04:00
|
|
|
g_vertexBuffer = {};
|
|
|
|
|
g_uniformBuffer = {};
|
|
|
|
|
g_indexBuffer = {};
|
|
|
|
|
g_storageBuffer = {};
|
|
|
|
|
g_stagingBuffers.fill({});
|
2026-06-04 23:43:13 -06:00
|
|
|
for (auto& packet : g_framePackets) {
|
|
|
|
|
packet = {};
|
|
|
|
|
}
|
|
|
|
|
g_recordingFrame = nullptr;
|
2022-07-27 11:25:25 -04:00
|
|
|
g_currentRenderPass = UINT32_MAX;
|
2026-04-07 19:01:41 -06:00
|
|
|
g_offscreenCache.clear();
|
2026-04-02 23:01:14 -06:00
|
|
|
g_offscreenColor = {};
|
|
|
|
|
g_offscreenDepth = {};
|
2026-04-12 18:55:35 -06:00
|
|
|
g_staticBindGroup = {};
|
|
|
|
|
g_staticBindGroupLayout = {};
|
|
|
|
|
g_uniformBindGroup = {};
|
|
|
|
|
g_uniformBindGroupLayout = {};
|
2026-04-02 23:01:14 -06:00
|
|
|
g_inOffscreen = false;
|
2026-04-08 20:03:25 -06:00
|
|
|
g_frameIndex = UINT32_MAX;
|
2026-06-04 23:43:13 -06:00
|
|
|
g_frameSlots.reset();
|
|
|
|
|
g_stagingSlots.reset();
|
|
|
|
|
for (auto& state : s_mappingStates) {
|
|
|
|
|
state.store(BufferMapState::Unmapped, std::memory_order_release);
|
|
|
|
|
}
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
static bool wait_for_staging_buffer(size_t slot) {
|
|
|
|
|
ZoneScopedN("Wait for buffer map");
|
|
|
|
|
map_staging_buffer(slot);
|
|
|
|
|
while (true) {
|
|
|
|
|
const auto mappingState = s_mappingStates[slot].load(std::memory_order_acquire);
|
|
|
|
|
if (mappingState == BufferMapState::Mapped) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (mappingState == BufferMapState::Unmapped) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (render_worker::is_idle()) {
|
|
|
|
|
g_instance.ProcessEvents();
|
|
|
|
|
} else {
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds{1});
|
|
|
|
|
}
|
2026-05-09 09:18:05 -06:00
|
|
|
}
|
2026-06-04 23:43:13 -06:00
|
|
|
}
|
2026-05-09 09:18:05 -06:00
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
static size_t acquire_frame_slot() {
|
|
|
|
|
ZoneScopedN("Acquire frame slot");
|
|
|
|
|
while (true) {
|
|
|
|
|
if (auto slot = g_frameSlots.try_acquire()) {
|
|
|
|
|
return *slot;
|
|
|
|
|
}
|
|
|
|
|
if (render_worker::is_idle()) {
|
|
|
|
|
g_instance.ProcessEvents();
|
|
|
|
|
} else {
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::microseconds{100});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::optional<size_t> acquire_mapped_staging_buffer() {
|
|
|
|
|
ZoneScopedN("Acquire mapped staging buffer");
|
|
|
|
|
while (true) {
|
|
|
|
|
if (auto slot = g_stagingSlots.try_acquire()) {
|
|
|
|
|
if (wait_for_staging_buffer(*slot)) {
|
|
|
|
|
return *slot;
|
|
|
|
|
}
|
|
|
|
|
g_stagingSlots.release(*slot);
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
if (render_worker::is_idle()) {
|
|
|
|
|
g_instance.ProcessEvents();
|
|
|
|
|
} else {
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::microseconds{100});
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
|
2026-05-09 09:18:05 -06:00
|
|
|
bool begin_frame() {
|
2026-04-09 03:19:58 +02:00
|
|
|
ZoneScoped;
|
2026-06-04 23:43:13 -06:00
|
|
|
const size_t frameSlot = acquire_frame_slot();
|
|
|
|
|
const auto stagingSlot = acquire_mapped_staging_buffer();
|
|
|
|
|
if (!stagingSlot) {
|
|
|
|
|
g_frameSlots.release(frameSlot);
|
|
|
|
|
return false;
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
2026-06-04 23:43:13 -06:00
|
|
|
|
|
|
|
|
auto& frame = g_framePackets[frameSlot];
|
|
|
|
|
frame = {};
|
|
|
|
|
frame.frameId = g_nextFrameId++;
|
|
|
|
|
frame.frameIndex = g_frameIndex;
|
|
|
|
|
frame.stagingBuffer = *stagingSlot;
|
|
|
|
|
g_recordingFrame = &frame;
|
|
|
|
|
g_recordingFrameSlot = frameSlot;
|
|
|
|
|
|
2022-07-27 11:25:25 -04:00
|
|
|
size_t bufferOffset = 0;
|
2026-06-04 23:43:13 -06:00
|
|
|
const auto& stagingBuf = g_stagingBuffers[*stagingSlot];
|
2022-07-27 11:25:25 -04:00
|
|
|
const auto mapBuffer = [&](ByteBuffer& buf, uint64_t size) {
|
|
|
|
|
if (size <= 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-08-02 16:37:56 -04:00
|
|
|
buf = ByteBuffer{static_cast<u8*>(stagingBuf.GetMappedRange(bufferOffset, size)), static_cast<size_t>(size)};
|
2022-07-27 11:25:25 -04:00
|
|
|
bufferOffset += size;
|
|
|
|
|
};
|
2026-06-04 23:43:13 -06:00
|
|
|
mapBuffer(frame.verts, VertexBufferSize);
|
|
|
|
|
mapBuffer(frame.uniforms, UniformBufferSize);
|
|
|
|
|
mapBuffer(frame.indices, IndexBufferSize);
|
|
|
|
|
mapBuffer(frame.storage, StorageBufferSize);
|
2026-04-10 18:18:29 -06:00
|
|
|
if constexpr (UseTextureBuffer) {
|
2026-06-04 23:43:13 -06:00
|
|
|
mapBuffer(frame.textureUpload, TextureUploadSize);
|
2026-04-10 18:18:29 -06:00
|
|
|
}
|
2022-07-27 11:25:25 -04:00
|
|
|
|
2026-05-14 16:29:45 -06:00
|
|
|
g_drawCallCount = 0;
|
|
|
|
|
g_mergedDrawCallCount = 0;
|
2026-04-02 23:01:14 -06:00
|
|
|
g_suspendedEfbPass.reset();
|
2022-07-28 19:48:02 -04:00
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
current_render_passes().emplace_back();
|
|
|
|
|
auto& pass = current_render_passes()[0];
|
2026-06-12 10:28:52 -06:00
|
|
|
pass.label = pass_label("EFB");
|
2026-06-04 23:43:13 -06:00
|
|
|
set_efb_targets(pass);
|
|
|
|
|
pass.clearColorValue = gx::g_gxState.clearColor;
|
|
|
|
|
pass.clearDepthValue = gx::clear_depth_value();
|
2022-07-27 11:25:25 -04:00
|
|
|
g_currentRenderPass = 0;
|
2026-04-18 14:10:32 -06:00
|
|
|
// Refresh render viewport/scissor from logical in case FB size changed
|
|
|
|
|
g_cachedViewport = gx::map_logical_viewport(gx::g_gxState.logicalViewport);
|
|
|
|
|
g_cachedScissor = gx::map_logical_scissor(gx::g_gxState.logicalScissor);
|
2026-03-07 20:35:33 -08:00
|
|
|
push_command(CommandType::SetViewport, Command::Data{.setViewport = g_cachedViewport});
|
|
|
|
|
push_command(CommandType::SetScissor, Command::Data{.setScissor = g_cachedScissor});
|
2026-04-08 20:03:25 -06:00
|
|
|
begin_pipeline_frame();
|
2026-06-04 23:43:13 -06:00
|
|
|
render_worker::enqueue_begin_frame(frame.frameId, [frameSlot] {
|
|
|
|
|
const auto encoderDescriptor = wgpu::CommandEncoderDescriptor{
|
|
|
|
|
.label = "Redraw encoder",
|
|
|
|
|
};
|
|
|
|
|
g_framePackets[frameSlot].encoder = g_device.CreateCommandEncoder(&encoderDescriptor);
|
2026-06-12 10:28:52 -06:00
|
|
|
webgpu::gpu_prof::frame_begin(g_framePackets[frameSlot].encoder);
|
2026-06-04 23:43:13 -06:00
|
|
|
});
|
2026-05-09 09:18:05 -06:00
|
|
|
return true;
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
void finish() {
|
|
|
|
|
ZoneScoped;
|
|
|
|
|
if (g_recordingFrame == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
ASSERT(!g_inOffscreen, "finish called while offscreen rendering is active");
|
|
|
|
|
if (g_currentRenderPass != UINT32_MAX) {
|
|
|
|
|
auto& frame = current_frame_packet();
|
|
|
|
|
frame.uniforms.append_zeroes(gx::MaxUniformSize); // Pad the end of the GX uniform buffer slice.
|
|
|
|
|
auto& pass = frame.renderPasses[g_currentRenderPass];
|
|
|
|
|
pass.observable = true;
|
|
|
|
|
pass.captureDepthSnapshot = true;
|
|
|
|
|
enqueue_pass(frame, g_recordingFrameSlot, g_currentRenderPass);
|
|
|
|
|
g_currentRenderPass = UINT32_MAX;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void end_frame(EndFrameCallback callback) {
|
2026-04-09 03:19:58 +02:00
|
|
|
ZoneScoped;
|
2026-04-02 23:01:14 -06:00
|
|
|
ASSERT(!g_inOffscreen, "end_frame called while offscreen rendering is active");
|
2026-06-04 23:43:13 -06:00
|
|
|
ASSERT(g_currentRenderPass == UINT32_MAX, "end_frame called before finish finalized the current render pass");
|
|
|
|
|
auto& frame = current_frame_packet();
|
|
|
|
|
frame.stats.drawCallCount = g_drawCallCount;
|
|
|
|
|
frame.stats.mergedDrawCallCount = g_mergedDrawCallCount;
|
|
|
|
|
frame.stats.lastVertSize = frame.verts.size();
|
|
|
|
|
frame.stats.lastUniformSize = frame.uniforms.size();
|
|
|
|
|
frame.stats.lastIndexSize = frame.indices.size();
|
|
|
|
|
frame.stats.lastStorageSize = frame.storage.size();
|
|
|
|
|
frame.stats.lastTextureUploadSize = frame.textureUpload.size();
|
|
|
|
|
|
|
|
|
|
const size_t frameSlot = g_recordingFrameSlot;
|
|
|
|
|
const uint64_t frameId = frame.frameId;
|
2022-07-27 11:25:25 -04:00
|
|
|
g_currentRenderPass = UINT32_MAX;
|
2025-04-14 17:16:13 -06:00
|
|
|
for (auto& array : gx::g_gxState.arrays) {
|
|
|
|
|
array.cachedRange = {};
|
|
|
|
|
}
|
2026-04-08 20:03:25 -06:00
|
|
|
end_pipeline_frame();
|
|
|
|
|
++g_frameIndex;
|
2026-06-04 23:43:13 -06:00
|
|
|
g_recordingFrame = nullptr;
|
|
|
|
|
|
|
|
|
|
#if defined(AURORA_GFX_DEBUG_GROUPS)
|
|
|
|
|
if (!g_debugGroupStack.empty()) {
|
|
|
|
|
for (auto& it : std::ranges::reverse_view(g_debugGroupStack)) {
|
|
|
|
|
Log.warn("Debug group was not popped at end of frame: {}", it);
|
|
|
|
|
}
|
|
|
|
|
g_debugGroupStack.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (g_debugMarkers.size() > 0) {
|
|
|
|
|
g_debugMarkers.clear();
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
const size_t stagingSlot = frame.stagingBuffer;
|
|
|
|
|
render_worker::enqueue_end_frame(frameId, [frameSlot, stagingSlot, callback = std::move(callback)]() mutable {
|
|
|
|
|
auto& packet = g_framePackets[frameSlot];
|
|
|
|
|
g_stagingBuffers[stagingSlot].Unmap();
|
|
|
|
|
s_mappingStates[stagingSlot].store(BufferMapState::Unmapped, std::memory_order_release);
|
|
|
|
|
auto encoder = std::move(packet.encoder);
|
|
|
|
|
const auto stats = packet.stats;
|
|
|
|
|
packet = {};
|
|
|
|
|
g_frameSlots.release(frameSlot);
|
|
|
|
|
g_stats.drawCallCount = stats.drawCallCount;
|
|
|
|
|
g_stats.mergedDrawCallCount = stats.mergedDrawCallCount;
|
|
|
|
|
g_stats.lastVertSize = stats.lastVertSize;
|
|
|
|
|
g_stats.lastUniformSize = stats.lastUniformSize;
|
|
|
|
|
g_stats.lastIndexSize = stats.lastIndexSize;
|
|
|
|
|
g_stats.lastStorageSize = stats.lastStorageSize;
|
|
|
|
|
g_stats.lastTextureUploadSize = stats.lastTextureUploadSize;
|
|
|
|
|
if (callback) {
|
|
|
|
|
callback(encoder);
|
|
|
|
|
}
|
|
|
|
|
expire_cached_bind_groups();
|
|
|
|
|
map_staging_buffer(stagingSlot, true);
|
|
|
|
|
});
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-08 20:03:25 -06:00
|
|
|
uint32_t current_frame() noexcept { return g_frameIndex; }
|
|
|
|
|
|
2026-04-12 18:55:35 -06:00
|
|
|
static void expire_cached_bind_groups() {
|
2026-06-04 23:43:13 -06:00
|
|
|
std::lock_guard lock{g_bindGroupCacheMutex};
|
2026-04-12 18:55:35 -06:00
|
|
|
if (g_cachedBindGroups.empty() || g_frameIndex == UINT32_MAX || g_frameIndex % BindGroupCacheSweepPeriod != 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 16:29:45 -06:00
|
|
|
ZoneScoped;
|
2026-04-12 18:55:35 -06:00
|
|
|
for (auto it = g_cachedBindGroups.begin(); it != g_cachedBindGroups.end();) {
|
|
|
|
|
if (g_frameIndex - it->second.lastUsedFrame > BindGroupCacheRetainFrames) {
|
|
|
|
|
g_cachedBindGroups.erase(it++);
|
|
|
|
|
} else {
|
|
|
|
|
++it;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
static constexpr uint64_t VertexStagingOffset = 0;
|
|
|
|
|
static constexpr uint64_t UniformStagingOffset = VertexStagingOffset + VertexBufferSize;
|
|
|
|
|
static constexpr uint64_t IndexStagingOffset = UniformStagingOffset + UniformBufferSize;
|
|
|
|
|
static constexpr uint64_t StorageStagingOffset = IndexStagingOffset + IndexBufferSize;
|
|
|
|
|
static constexpr uint64_t TextureUploadStagingOffset = StorageStagingOffset + StorageBufferSize;
|
2026-04-02 23:01:14 -06:00
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
static constexpr uint32_t align_down_copy_offset(uint32_t value) noexcept { return value & ~3u; }
|
2026-04-01 00:49:28 -06:00
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
static void copy_staging_buffer_range(wgpu::CommandEncoder& cmd, const FramePacket& frame, uint32_t& copied,
|
|
|
|
|
uint32_t highWater, uint64_t stagingOffset, const wgpu::Buffer& dst) {
|
|
|
|
|
if (highWater <= copied) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const uint32_t copyStart = align_down_copy_offset(copied);
|
|
|
|
|
const uint32_t copyEnd = AURORA_ALIGN(highWater, 4);
|
|
|
|
|
cmd.CopyBufferToBuffer(g_stagingBuffers[frame.stagingBuffer], stagingOffset + copyStart, dst, copyStart,
|
|
|
|
|
copyEnd - copyStart);
|
|
|
|
|
copied = highWater;
|
|
|
|
|
}
|
2022-07-27 11:25:25 -04:00
|
|
|
|
2026-06-12 10:28:52 -06:00
|
|
|
static bool needs_staging_copy(const FramePacket& frame, const FrameOp& op) {
|
|
|
|
|
const auto& highWater = op.highWater;
|
|
|
|
|
if (highWater.verts > frame.copied.verts || highWater.uniforms > frame.copied.uniforms ||
|
|
|
|
|
highWater.indices > frame.copied.indices || highWater.storage > frame.copied.storage) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if constexpr (UseTextureBuffer) {
|
|
|
|
|
return op.textureUploads.size() > frame.copied.textureUploadCount;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
static void copy_staging_to_high_water(wgpu::CommandEncoder& cmd, FramePacket& frame, const FrameOp& op) {
|
2026-06-12 10:28:52 -06:00
|
|
|
if (!needs_staging_copy(frame, op)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const webgpu::gpu_prof::Zone profZone{cmd, "Staging copies"};
|
2026-06-04 23:43:13 -06:00
|
|
|
const auto& highWater = op.highWater;
|
|
|
|
|
copy_staging_buffer_range(cmd, frame, frame.copied.verts, highWater.verts, VertexStagingOffset, g_vertexBuffer);
|
|
|
|
|
copy_staging_buffer_range(cmd, frame, frame.copied.uniforms, highWater.uniforms, UniformStagingOffset,
|
|
|
|
|
g_uniformBuffer);
|
|
|
|
|
copy_staging_buffer_range(cmd, frame, frame.copied.indices, highWater.indices, IndexStagingOffset, g_indexBuffer);
|
|
|
|
|
copy_staging_buffer_range(cmd, frame, frame.copied.storage, highWater.storage, StorageStagingOffset, g_storageBuffer);
|
2026-04-24 21:55:17 -06:00
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
if constexpr (UseTextureBuffer) {
|
|
|
|
|
for (size_t i = frame.copied.textureUploadCount; i < op.textureUploads.size(); ++i) {
|
|
|
|
|
const auto& item = *op.textureUploads[i];
|
|
|
|
|
const wgpu::TexelCopyBufferInfo buf{
|
|
|
|
|
.layout =
|
|
|
|
|
wgpu::TexelCopyBufferLayout{
|
|
|
|
|
.offset = item.buffer ? item.layout.offset : item.layout.offset + TextureUploadStagingOffset,
|
|
|
|
|
.bytesPerRow = AURORA_ALIGN(item.layout.bytesPerRow, 256),
|
|
|
|
|
.rowsPerImage = item.layout.rowsPerImage,
|
|
|
|
|
},
|
|
|
|
|
.buffer = item.buffer ? item.buffer : g_stagingBuffers[frame.stagingBuffer],
|
2022-07-27 11:25:25 -04:00
|
|
|
};
|
2026-06-04 23:43:13 -06:00
|
|
|
cmd.CopyBufferToTexture(&buf, &item.tex, &item.size);
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
2026-06-04 23:43:13 -06:00
|
|
|
frame.copied.textureUpload = highWater.textureUpload;
|
|
|
|
|
frame.copied.textureUploadCount = op.textureUploads.size();
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
2026-06-04 23:43:13 -06:00
|
|
|
}
|
2026-03-11 01:38:35 +01:00
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
static void encode_op(wgpu::CommandEncoder& cmd, FramePacket& frame, const FrameOp& op) {
|
|
|
|
|
copy_staging_to_high_water(cmd, frame, op);
|
|
|
|
|
switch (op.type) {
|
|
|
|
|
case FrameOpType::RenderPass:
|
|
|
|
|
if (op.renderPass != nullptr) {
|
|
|
|
|
render(cmd, frame, *op.renderPass, op.index);
|
2026-03-11 01:38:35 +01:00
|
|
|
}
|
2026-06-04 23:43:13 -06:00
|
|
|
break;
|
|
|
|
|
case FrameOpType::TextureCopy:
|
|
|
|
|
if (op.textureCopy != nullptr) {
|
2026-06-12 10:28:52 -06:00
|
|
|
const webgpu::gpu_prof::Zone profZone{cmd, "Texture copy"};
|
2026-06-04 23:43:13 -06:00
|
|
|
cmd.CopyTextureToTexture(&op.textureCopy->src, &op.textureCopy->dst, &op.textureCopy->size);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void render(wgpu::CommandEncoder& cmd, FramePacket& frame, RenderPass& passInfo, uint32_t passIndex) {
|
|
|
|
|
ZoneScoped;
|
|
|
|
|
if (!passInfo.sealed) {
|
|
|
|
|
return;
|
2026-03-11 01:38:35 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
for (const auto& conv : passInfo.paletteConvs) {
|
|
|
|
|
tex_palette_conv::run(cmd, conv);
|
|
|
|
|
}
|
|
|
|
|
if (!passInfo.observable && !passInfo.resolveTarget && !passInfo.offscreen) {
|
|
|
|
|
// Skip intermediate EFB render passes without observable output.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::array attachments{
|
|
|
|
|
wgpu::RenderPassColorAttachment{
|
|
|
|
|
.view = passInfo.colorView,
|
|
|
|
|
.resolveTarget = passInfo.resolveView,
|
|
|
|
|
.loadOp = passInfo.colorLoadOp != wgpu::LoadOp::Undefined
|
|
|
|
|
? passInfo.colorLoadOp
|
|
|
|
|
: (passInfo.clearColor ? wgpu::LoadOp::Clear : wgpu::LoadOp::Load),
|
|
|
|
|
.storeOp = passInfo.colorStoreOp,
|
|
|
|
|
.clearValue =
|
|
|
|
|
{
|
|
|
|
|
.r = passInfo.clearColorValue.x(),
|
|
|
|
|
.g = passInfo.clearColorValue.y(),
|
|
|
|
|
.b = passInfo.clearColorValue.z(),
|
|
|
|
|
.a = passInfo.clearColorValue.w(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
wgpu::RenderPassDepthStencilAttachment depthStencilAttachment{};
|
|
|
|
|
const wgpu::RenderPassDepthStencilAttachment* depthStencilAttachmentPtr = nullptr;
|
|
|
|
|
if (passInfo.depthStencilView) {
|
|
|
|
|
depthStencilAttachment = {
|
|
|
|
|
.view = passInfo.depthStencilView,
|
|
|
|
|
.depthLoadOp = passInfo.hasDepth ? (passInfo.depthLoadOp != wgpu::LoadOp::Undefined
|
|
|
|
|
? passInfo.depthLoadOp
|
|
|
|
|
: (passInfo.clearDepth ? wgpu::LoadOp::Clear : wgpu::LoadOp::Load))
|
|
|
|
|
: wgpu::LoadOp::Undefined,
|
|
|
|
|
.depthStoreOp = passInfo.hasDepth ? passInfo.depthStoreOp : wgpu::StoreOp::Undefined,
|
|
|
|
|
.depthClearValue = passInfo.clearDepthValue,
|
|
|
|
|
.stencilLoadOp = passInfo.hasStencil ? passInfo.stencilLoadOp : wgpu::LoadOp::Undefined,
|
|
|
|
|
.stencilStoreOp = passInfo.hasStencil ? passInfo.stencilStoreOp : wgpu::StoreOp::Undefined,
|
|
|
|
|
.stencilClearValue = passInfo.stencilClearValue,
|
|
|
|
|
};
|
|
|
|
|
depthStencilAttachmentPtr = &depthStencilAttachment;
|
|
|
|
|
}
|
2026-06-12 10:28:52 -06:00
|
|
|
const auto label =
|
|
|
|
|
passInfo.label.empty() ? fmt::format("Render pass {}", passIndex) : fmt::format("{} {}", passInfo.label, passIndex);
|
2026-06-04 23:43:13 -06:00
|
|
|
const wgpu::RenderPassDescriptor renderPassDescriptor{
|
2026-06-12 10:28:52 -06:00
|
|
|
.label = label.c_str(),
|
2026-06-04 23:43:13 -06:00
|
|
|
.colorAttachmentCount = attachments.size(),
|
|
|
|
|
.colorAttachments = attachments.data(),
|
|
|
|
|
.depthStencilAttachment = depthStencilAttachmentPtr,
|
2026-06-12 10:28:52 -06:00
|
|
|
.timestampWrites = webgpu::gpu_prof::pass_writes(label),
|
2026-06-04 23:43:13 -06:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto pass = cmd.BeginRenderPass(&renderPassDescriptor);
|
|
|
|
|
render_pass(pass, frame, passInfo);
|
|
|
|
|
pass.End();
|
|
|
|
|
|
|
|
|
|
if (passInfo.captureDepthSnapshot) {
|
|
|
|
|
depth_peek::encode_frame_snapshot(cmd, passInfo.copySourceDepthView, passInfo.targetSize, passInfo.msaaSamples);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (passInfo.resolveTarget) {
|
|
|
|
|
const auto& dstSize = passInfo.resolveTarget->size;
|
|
|
|
|
const bool needsConversion = tex_copy_conv::needs_conversion(passInfo.resolveFormat);
|
|
|
|
|
const bool needsScaling = dstSize.width != static_cast<uint32_t>(passInfo.resolveRect.width) ||
|
|
|
|
|
dstSize.height != static_cast<uint32_t>(passInfo.resolveRect.height);
|
|
|
|
|
const bool isDepth = gx::is_depth_format(passInfo.resolveFormat);
|
|
|
|
|
if (isDepth && passInfo.msaaSamples > 1) {
|
|
|
|
|
Log.fatal("Depth tex copies from multisampled EFB targets are not supported");
|
|
|
|
|
}
|
|
|
|
|
const tex_copy_conv::ConvRequest convReq{
|
|
|
|
|
.fmt = passInfo.resolveFormat,
|
|
|
|
|
.srcView = isDepth ? passInfo.copySourceDepthView : passInfo.copySourceView,
|
|
|
|
|
.uniformRange = passInfo.resolveUniformRange,
|
|
|
|
|
.dst = passInfo.resolveTarget,
|
|
|
|
|
.sampleFilter = needsScaling ? tex_copy_conv::SampleFilter::Linear : tex_copy_conv::SampleFilter::Nearest,
|
|
|
|
|
};
|
|
|
|
|
if (needsConversion) {
|
|
|
|
|
tex_copy_conv::run(cmd, convReq);
|
|
|
|
|
} else if (needsScaling) {
|
|
|
|
|
tex_copy_conv::blit(cmd, convReq);
|
|
|
|
|
} else {
|
2026-06-12 10:28:52 -06:00
|
|
|
const webgpu::gpu_prof::Zone profZone{cmd, "EFB copy"};
|
2026-06-04 23:43:13 -06:00
|
|
|
const wgpu::TexelCopyTextureInfo src{
|
|
|
|
|
.texture = passInfo.copySourceTexture,
|
|
|
|
|
.origin =
|
|
|
|
|
wgpu::Origin3D{
|
|
|
|
|
.x = static_cast<uint32_t>(passInfo.resolveRect.x),
|
|
|
|
|
.y = static_cast<uint32_t>(passInfo.resolveRect.y),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
const wgpu::TexelCopyTextureInfo dst{
|
|
|
|
|
.texture = passInfo.resolveTarget->texture,
|
|
|
|
|
};
|
|
|
|
|
const wgpu::Extent3D size{
|
|
|
|
|
.width = static_cast<uint32_t>(passInfo.resolveRect.width),
|
|
|
|
|
.height = static_cast<uint32_t>(passInfo.resolveRect.height),
|
|
|
|
|
.depthOrArrayLayers = 1,
|
|
|
|
|
};
|
|
|
|
|
cmd.CopyTextureToTexture(&src, &dst, &size);
|
|
|
|
|
}
|
2026-03-11 01:38:35 +01:00
|
|
|
}
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-24 21:55:17 -06:00
|
|
|
void after_submit() noexcept { depth_peek::after_submit(); }
|
|
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
void gpu_synchronize() { render_worker::synchronize(); }
|
|
|
|
|
|
|
|
|
|
static void render_pass(const wgpu::RenderPassEncoder& pass, FramePacket& frame, const RenderPass& passInfo) {
|
|
|
|
|
ZoneScoped;
|
2022-07-27 11:25:25 -04:00
|
|
|
g_currentPipeline = UINTPTR_MAX;
|
|
|
|
|
#ifdef AURORA_GFX_DEBUG_GROUPS
|
|
|
|
|
std::vector<std::string> lastDebugGroupStack;
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-04-12 18:55:35 -06:00
|
|
|
// Bind static bind group for the whole pass
|
|
|
|
|
pass.SetBindGroup(0, g_staticBindGroup);
|
|
|
|
|
pass.SetBindGroup(2, gx::g_emptyTextureBindGroup);
|
|
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
for (const auto& cmd : passInfo.commands) {
|
2022-07-27 11:25:25 -04:00
|
|
|
#ifdef AURORA_GFX_DEBUG_GROUPS
|
|
|
|
|
{
|
|
|
|
|
size_t firstDiff = lastDebugGroupStack.size();
|
|
|
|
|
for (size_t i = 0; i < lastDebugGroupStack.size(); ++i) {
|
|
|
|
|
if (i >= cmd.debugGroupStack.size() || cmd.debugGroupStack[i] != lastDebugGroupStack[i]) {
|
|
|
|
|
firstDiff = i;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (size_t i = firstDiff; i < lastDebugGroupStack.size(); ++i) {
|
2022-08-02 16:37:56 -04:00
|
|
|
pass.PopDebugGroup();
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
for (size_t i = firstDiff; i < cmd.debugGroupStack.size(); ++i) {
|
2022-08-02 16:37:56 -04:00
|
|
|
pass.PushDebugGroup(cmd.debugGroupStack[i].c_str());
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
lastDebugGroupStack = cmd.debugGroupStack;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
switch (cmd.type) {
|
|
|
|
|
case CommandType::SetViewport: {
|
|
|
|
|
const auto& vp = cmd.data.setViewport;
|
2025-04-14 17:16:13 -06:00
|
|
|
const float minDepth = gx::UseReversedZ ? 1.f - vp.zfar : vp.znear;
|
|
|
|
|
const float maxDepth = gx::UseReversedZ ? 1.f - vp.znear : vp.zfar;
|
|
|
|
|
pass.SetViewport(vp.left, vp.top, vp.width, vp.height, minDepth, maxDepth);
|
2022-07-27 11:25:25 -04:00
|
|
|
} break;
|
|
|
|
|
case CommandType::SetScissor: {
|
|
|
|
|
const auto& sc = cmd.data.setScissor;
|
2026-06-04 23:43:13 -06:00
|
|
|
const auto& size = passInfo.targetSize;
|
2026-04-18 14:10:32 -06:00
|
|
|
const auto x = std::clamp(static_cast<uint32_t>(sc.x), 0u, size.width);
|
|
|
|
|
const auto y = std::clamp(static_cast<uint32_t>(sc.y), 0u, size.height);
|
|
|
|
|
const auto w = std::clamp(static_cast<uint32_t>(sc.width), 0u, size.width - x);
|
|
|
|
|
const auto h = std::clamp(static_cast<uint32_t>(sc.height), 0u, size.height - y);
|
2025-04-07 20:12:31 -06:00
|
|
|
pass.SetScissorRect(x, y, w, h);
|
2022-07-27 11:25:25 -04:00
|
|
|
} break;
|
|
|
|
|
case CommandType::Draw: {
|
|
|
|
|
const auto& draw = cmd.data.draw;
|
|
|
|
|
switch (draw.type) {
|
2026-04-01 00:49:28 -06:00
|
|
|
case ShaderType::Clear:
|
2026-06-04 23:43:13 -06:00
|
|
|
clear::render(draw.clear, pass, passInfo.targetSize);
|
2026-04-01 00:49:28 -06:00
|
|
|
break;
|
2026-03-05 20:31:51 -07:00
|
|
|
case ShaderType::GX:
|
2026-04-01 00:49:28 -06:00
|
|
|
gx::render(draw.gx, pass);
|
2022-07-27 11:25:25 -04:00
|
|
|
break;
|
2026-06-04 23:43:13 -06:00
|
|
|
#ifdef AURORA_ENABLE_RMLUI
|
|
|
|
|
case ShaderType::Rml:
|
|
|
|
|
rmlui::render(draw.rml, pass);
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
} break;
|
2026-03-11 01:38:35 +01:00
|
|
|
case CommandType::DebugMarker: {
|
|
|
|
|
#if defined(AURORA_GFX_DEBUG_GROUPS)
|
|
|
|
|
pass.InsertDebugMarker(wgpu::StringView(g_debugMarkers[cmd.data.debugMarkerIndex]));
|
|
|
|
|
#endif
|
|
|
|
|
} break;
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef AURORA_GFX_DEBUG_GROUPS
|
|
|
|
|
for (size_t i = 0; i < lastDebugGroupStack.size(); ++i) {
|
2022-08-02 16:37:56 -04:00
|
|
|
pass.PopDebugGroup();
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
void render_pass(const wgpu::RenderPassEncoder& pass, u32 idx) {
|
|
|
|
|
auto& frame = current_frame_packet();
|
|
|
|
|
render_pass(pass, frame, frame.renderPasses[idx]);
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-02 16:37:56 -04:00
|
|
|
bool bind_pipeline(PipelineRef ref, const wgpu::RenderPassEncoder& pass) {
|
2022-07-27 11:25:25 -04:00
|
|
|
if (ref == g_currentPipeline) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2026-04-08 20:03:25 -06:00
|
|
|
wgpu::RenderPipeline pipeline;
|
|
|
|
|
if (!get_pipeline(ref, pipeline)) {
|
2022-07-27 11:25:25 -04:00
|
|
|
return false;
|
|
|
|
|
}
|
2026-04-08 20:03:25 -06:00
|
|
|
pass.SetPipeline(pipeline);
|
2022-07-27 11:25:25 -04:00
|
|
|
g_currentPipeline = ref;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
static Range push(ByteBuffer& target, const uint8_t* data, size_t length, size_t alignment) {
|
2022-07-27 11:25:25 -04:00
|
|
|
if (alignment != 0) {
|
2026-06-04 23:43:13 -06:00
|
|
|
const size_t begin = target.size();
|
|
|
|
|
const size_t alignedBegin = AURORA_ALIGN(begin, alignment);
|
|
|
|
|
if (alignedBegin > begin) {
|
|
|
|
|
target.append_zeroes(alignedBegin - begin);
|
2026-03-13 19:46:11 -07:00
|
|
|
}
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
2026-06-04 23:43:13 -06:00
|
|
|
const auto begin = target.size();
|
|
|
|
|
if (length > 0) {
|
2022-07-27 11:25:25 -04:00
|
|
|
target.append(data, length);
|
|
|
|
|
}
|
2026-06-04 23:43:13 -06:00
|
|
|
return {static_cast<uint32_t>(begin), static_cast<uint32_t>(length)};
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
2026-06-04 23:43:13 -06:00
|
|
|
static Range map(ByteBuffer& target, size_t length, size_t alignment) {
|
2022-07-27 11:25:25 -04:00
|
|
|
if (alignment != 0) {
|
2026-06-04 23:43:13 -06:00
|
|
|
const size_t begin = target.size();
|
|
|
|
|
const size_t alignedBegin = AURORA_ALIGN(begin, alignment);
|
|
|
|
|
if (alignedBegin > begin) {
|
|
|
|
|
target.append_zeroes(alignedBegin - begin);
|
2026-03-13 19:46:11 -07:00
|
|
|
}
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
auto begin = target.size();
|
2026-06-04 23:43:13 -06:00
|
|
|
if (length > 0) {
|
|
|
|
|
target.append_zeroes(length);
|
|
|
|
|
}
|
|
|
|
|
return {static_cast<uint32_t>(begin), static_cast<uint32_t>(length)};
|
|
|
|
|
}
|
|
|
|
|
Range push_verts(const uint8_t* data, size_t length, size_t alignment) {
|
|
|
|
|
ZoneScoped;
|
|
|
|
|
return push(current_frame_packet().verts, data, length, alignment);
|
|
|
|
|
}
|
|
|
|
|
Range push_indices(const uint8_t* data, size_t length, size_t alignment) {
|
|
|
|
|
ZoneScoped;
|
|
|
|
|
return push(current_frame_packet().indices, data, length, alignment);
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
Range push_uniform(const uint8_t* data, size_t length) {
|
2026-06-04 23:43:13 -06:00
|
|
|
ZoneScoped;
|
|
|
|
|
return push(current_frame_packet().uniforms, data, length, g_cachedLimits.minUniformBufferOffsetAlignment);
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
Range push_storage(const uint8_t* data, size_t length) {
|
2026-06-04 23:43:13 -06:00
|
|
|
ZoneScoped;
|
|
|
|
|
return push(current_frame_packet().storage, data, length, g_cachedLimits.minStorageBufferOffsetAlignment);
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
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)
|
2026-04-09 03:19:58 +02:00
|
|
|
const auto copyBytesPerRow = AURORA_ALIGN(bytesPerRow, 256);
|
2026-06-04 23:43:13 -06:00
|
|
|
const auto range = map(current_frame_packet().textureUpload, copyBytesPerRow * rowsPerImage, 0);
|
|
|
|
|
u8* dst = current_frame_packet().textureUpload.data() + range.offset;
|
2022-07-27 11:25:25 -04:00
|
|
|
for (u32 i = 0; i < rowsPerImage; ++i) {
|
|
|
|
|
memcpy(dst, data, bytesPerRow);
|
|
|
|
|
data += bytesPerRow;
|
|
|
|
|
dst += copyBytesPerRow;
|
|
|
|
|
}
|
|
|
|
|
return range;
|
|
|
|
|
}
|
|
|
|
|
std::pair<ByteBuffer, Range> map_verts(size_t length) {
|
2026-06-04 23:43:13 -06:00
|
|
|
const auto range = map(current_frame_packet().verts, length, 4);
|
|
|
|
|
return {ByteBuffer{current_frame_packet().verts.data() + range.offset, range.size}, range};
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
std::pair<ByteBuffer, Range> map_indices(size_t length) {
|
2026-06-04 23:43:13 -06:00
|
|
|
const auto range = map(current_frame_packet().indices, length, 4);
|
|
|
|
|
return {ByteBuffer{current_frame_packet().indices.data() + range.offset, range.size}, range};
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
std::pair<ByteBuffer, Range> map_uniform(size_t length) {
|
2026-06-04 23:43:13 -06:00
|
|
|
const auto range = map(current_frame_packet().uniforms, length, g_cachedLimits.minUniformBufferOffsetAlignment);
|
|
|
|
|
return {ByteBuffer{current_frame_packet().uniforms.data() + range.offset, range.size}, range};
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
std::pair<ByteBuffer, Range> map_storage(size_t length) {
|
2026-06-04 23:43:13 -06:00
|
|
|
const auto range = map(current_frame_packet().storage, length, g_cachedLimits.minStorageBufferOffsetAlignment);
|
|
|
|
|
return {ByteBuffer{current_frame_packet().storage.data() + range.offset, range.size}, range};
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
|
2026-04-09 03:51:06 +02:00
|
|
|
BindGroupRef bind_group_ref(const WGPUBindGroupDescriptor& descriptor) {
|
2022-07-27 11:25:25 -04:00
|
|
|
const auto id = xxh3_hash(descriptor);
|
2026-06-04 23:43:13 -06:00
|
|
|
std::lock_guard lock{g_bindGroupCacheMutex};
|
2026-04-12 18:55:35 -06:00
|
|
|
const auto it = g_cachedBindGroups.find(id);
|
|
|
|
|
if (it == g_cachedBindGroups.end()) {
|
|
|
|
|
auto bg = wgpu::BindGroup::Acquire(wgpuDeviceCreateBindGroup(g_device.Get(), &descriptor));
|
|
|
|
|
g_cachedBindGroups.emplace(id, CachedBindGroup{
|
|
|
|
|
.bindGroup = std::move(bg),
|
|
|
|
|
.lastUsedFrame = g_frameIndex,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
it->second.lastUsedFrame = g_frameIndex;
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
return id;
|
|
|
|
|
}
|
2025-04-04 01:59:30 -06:00
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
wgpu::BindGroup find_bind_group(BindGroupRef id) {
|
|
|
|
|
std::lock_guard lock{g_bindGroupCacheMutex};
|
2022-07-27 11:25:25 -04:00
|
|
|
const auto it = g_cachedBindGroups.find(id);
|
2022-08-09 02:05:33 -04:00
|
|
|
CHECK(it != g_cachedBindGroups.end(), "get_bind_group: failed to locate {:x}", id);
|
2026-04-12 18:55:35 -06:00
|
|
|
return it->second.bindGroup;
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
|
2026-06-04 23:43:13 -06:00
|
|
|
wgpu::Sampler sampler_ref(const wgpu::SamplerDescriptor& descriptor) {
|
2022-07-27 11:25:25 -04:00
|
|
|
const auto id = xxh3_hash(descriptor);
|
2026-06-04 23:43:13 -06:00
|
|
|
std::lock_guard lock{g_samplerCacheMutex};
|
2022-07-27 11:25:25 -04:00
|
|
|
auto it = g_cachedSamplers.find(id);
|
|
|
|
|
if (it == g_cachedSamplers.end()) {
|
2022-08-02 16:37:56 -04:00
|
|
|
it = g_cachedSamplers.try_emplace(id, g_device.CreateSampler(&descriptor)).first;
|
2022-07-27 11:25:25 -04:00
|
|
|
}
|
|
|
|
|
return it->second;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 03:19:58 +02:00
|
|
|
uint32_t align_uniform(uint32_t value) { return AURORA_ALIGN(value, g_cachedLimits.minUniformBufferOffsetAlignment); }
|
2026-03-11 01:38:35 +01:00
|
|
|
|
|
|
|
|
void insert_debug_marker(std::string label) {
|
|
|
|
|
#if defined(AURORA_GFX_DEBUG_GROUPS)
|
|
|
|
|
auto idx = g_debugMarkers.size();
|
|
|
|
|
g_debugMarkers.emplace_back(std::move(label));
|
2026-03-29 22:33:28 -06:00
|
|
|
push_command(CommandType::DebugMarker, {.debugMarkerIndex = idx});
|
2026-03-11 01:38:35 +01:00
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-27 11:25:25 -04:00
|
|
|
} // namespace aurora::gfx
|
|
|
|
|
|
2026-03-11 01:38:35 +01:00
|
|
|
void aurora::gfx::push_debug_group(std::string label) {
|
|
|
|
|
#if defined(AURORA_GFX_DEBUG_GROUPS)
|
|
|
|
|
g_debugGroupStack.push_back(std::move(label));
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2022-07-27 11:25:25 -04:00
|
|
|
void push_debug_group(const char* label) {
|
|
|
|
|
#ifdef AURORA_GFX_DEBUG_GROUPS
|
|
|
|
|
aurora::gfx::g_debugGroupStack.emplace_back(label);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
void pop_debug_group() {
|
|
|
|
|
#ifdef AURORA_GFX_DEBUG_GROUPS
|
2026-03-11 01:38:35 +01:00
|
|
|
if (aurora::gfx::g_debugGroupStack.empty()) {
|
|
|
|
|
aurora::gfx::Log.error("Debug group stack underflowed!");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-27 11:25:25 -04:00
|
|
|
aurora::gfx::g_debugGroupStack.pop_back();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2026-03-05 20:31:51 -07:00
|
|
|
|
|
|
|
|
const AuroraStats* aurora_get_stats() { return &aurora::gfx::g_stats; }
|