Add offscreen rendering support & GXSetTexCopyDst (#89)

* Add offscreen rendering support & GXSetTexCopyDst

* Try to fix alignment for MSVC
This commit is contained in:
Luke Street
2026-04-02 23:01:14 -06:00
committed by GitHub
parent c177334a9f
commit 33ec54347d
16 changed files with 386 additions and 123 deletions
+15
View File
@@ -1,6 +1,8 @@
#ifndef DOLPHIN_GXAURORA_H
#define DOLPHIN_GXAURORA_H
#include <dolphin/types.h>
#if __cplusplus
extern "C" {
#endif
@@ -58,6 +60,19 @@ void GXPopDebugGroup();
*/
void GXInsertDebugMarker(const char* label);
/**
* Create an offscreen framebuffer and switch rendering to it.
* All subsequent GX rendering will target this framebuffer until GXRestoreFrameBuffer() is called.
* Use GXCopyTex to resolve the offscreen content into a texture.
*/
void GXCreateFrameBuffer(u32 width, u32 height);
/**
* Restore rendering to the main EFB framebuffer.
* Must be called after GXCreateFrameBuffer() to resume normal rendering.
*/
void GXRestoreFrameBuffer(void);
#if __cplusplus
}
#endif
+13
View File
@@ -5,6 +5,9 @@
#include "__gx.h"
#include "gx.hpp"
#include "../../gfx/common.hpp"
#include "../../gx/fifo.hpp"
static void GXWriteString(const char* label) {
auto length = strlen(label);
@@ -30,3 +33,13 @@ void GXInsertDebugMarker(const char* label) {
GX_WRITE_AURORA(GX_LOAD_AURORA_DEBUG_MARKER_INSERT);
GXWriteString(label);
}
void GXCreateFrameBuffer(u32 width, u32 height) {
aurora::gx::fifo::drain();
aurora::gfx::begin_offscreen(width, height);
}
void GXRestoreFrameBuffer() {
aurora::gx::fifo::drain();
aurora::gfx::end_offscreen();
}
+22 -32
View File
@@ -85,8 +85,9 @@ void GXSetTexCopySrc(u16 left, u16 top, u16 wd, u16 ht) { g_gxState.texCopySrc =
void GXSetDispCopyDst(u16 wd, u16 ht) {}
void GXSetTexCopyDst(u16 wd, u16 ht, GXTexFmt fmt, GXBool mipmap) {
// TODO texture copy scaling (mipmap)
g_gxState.texCopyFmt = fmt;
g_gxState.texCopyDstWidth = wd;
g_gxState.texCopyDstHeight = ht;
}
// TODO GXSetDispCopyFrame2Field
@@ -125,25 +126,29 @@ void GXCopyDisp(void* dest, GXBool clear) {}
void GXCopyTex(void* dest, GXBool clear) {
const auto& rect = g_gxState.texCopySrc;
const wgpu::Extent3D size{
.width = static_cast<uint32_t>(rect.width),
.height = static_cast<uint32_t>(rect.height),
.depthOrArrayLayers = 1,
};
const u32 dstWidth = g_gxState.texCopyDstWidth;
const u32 dstHeight = g_gxState.texCopyDstHeight;
const auto texCopyFmt = g_gxState.texCopyFmt;
const aurora::gx::GXState::CopyTextureKey key{
.dest = dest,
.width = size.width,
.height = size.height,
.format = g_gxState.texCopyFmt,
.width = dstWidth,
.height = dstHeight,
.format = texCopyFmt,
};
auto it = g_gxState.copyTextureCache.find(key);
if (it == g_gxState.copyTextureCache.end()) {
// Configure the texture swizzle to use alpha 1.0 if targeting RGB565 or EFB doesn't have alpha
const auto fmt = g_gxState.texCopyFmt == GX_TF_RGB565 || g_gxState.pixelFmt == GX_PF_RGB8_Z24 ||
g_gxState.pixelFmt == GX_PF_RGB565_Z16
? GX_TF_RGB565
: GX_TF_RGBA8;
auto handle = aurora::gfx::new_render_texture(rect.width, rect.height, fmt, "Resolved Texture");
aurora::gfx::TextureHandle handle;
if (aurora::gfx::tex_copy_conv::needs_conversion(texCopyFmt)) {
handle = aurora::gfx::new_conv_texture(dstWidth, dstHeight, texCopyFmt, "Copy Conv Texture");
} else {
// Configure the texture swizzle to use alpha 1.0 if targeting RGB565 or EFB doesn't have alpha
const auto fmt = texCopyFmt == GX_TF_RGB565 || g_gxState.pixelFmt == GX_PF_RGB8_Z24 ||
g_gxState.pixelFmt == GX_PF_RGB565_Z16
? GX_TF_RGB565
: GX_TF_RGBA8;
handle = aurora::gfx::new_render_texture(dstWidth, dstHeight, fmt, "Resolved Texture");
}
it = g_gxState.copyTextureCache.emplace(key, handle).first;
}
const auto& handle = it->second;
@@ -167,24 +172,9 @@ void GXCopyTex(void* dest, GXBool clear) {
const auto clearAlpha = clear && g_gxState.alphaUpdate;
const auto clearDepth = clear && g_gxState.depthUpdate;
aurora::gfx::resolve_pass(handle, rect, clearColor, clearAlpha, clearDepth, g_gxState.clearColor,
aurora::gx::clear_depth_value());
aurora::gx::clear_depth_value(), texCopyFmt);
if (aurora::gfx::tex_copy_conv::needs_conversion(g_gxState.texCopyFmt)) {
auto convIt = g_gxState.convTextureCache.find(key);
if (convIt == g_gxState.convTextureCache.end()) {
auto convHandle =
aurora::gfx::new_conv_texture(rect.width, rect.height, g_gxState.texCopyFmt, "Copy Conv Texture");
convIt = g_gxState.convTextureCache.emplace(key, convHandle).first;
}
aurora::gfx::queue_copy_conv({
.fmt = g_gxState.texCopyFmt,
.src = handle,
.dst = convIt->second,
});
g_gxState.copyTextures[dest] = convIt->second;
} else {
g_gxState.copyTextures[dest] = handle;
}
g_gxState.copyTextures[dest] = handle;
}
// TODO GXGetYScaleFactor
+6 -8
View File
@@ -18,7 +18,6 @@ wgpu::ColorWriteMask clear_write_mask(bool clearColor, bool clearAlpha) {
namespace aurora::gfx::clear {
using webgpu::g_device;
using webgpu::g_frameBuffer;
using webgpu::g_graphicsConfig;
wgpu::RenderPipeline create_pipeline(const PipelineConfig& config) {
@@ -103,23 +102,22 @@ fn fs_main() -> @location(0) vec4<f32> {
.depthStencil = &depthStencil,
.multisample =
wgpu::MultisampleState{
.count = g_graphicsConfig.msaaSamples,
.mask = UINT32_MAX,
.count = config.msaaSamples,
},
.fragment = &fragmentState,
};
return g_device.CreateRenderPipeline(&pipelineDescriptor);
}
void render(const DrawData& data, const wgpu::RenderPassEncoder& pass) {
void render(const DrawData& data, const wgpu::RenderPassEncoder& pass, const wgpu::Extent3D& targetSize) {
if (!bind_pipeline(data.pipeline, pass)) {
return;
}
const auto& size = g_frameBuffer.size;
pass.SetBlendConstant(&data.color);
pass.SetViewport(0.f, 0.f, static_cast<float>(size.width), static_cast<float>(size.height), data.depth, data.depth);
pass.SetScissorRect(0, 0, size.width, size.height);
pass.SetViewport(0.f, 0.f, static_cast<float>(targetSize.width), static_cast<float>(targetSize.height), data.depth,
data.depth);
pass.SetScissorRect(0, 0, targetSize.width, targetSize.height);
pass.Draw(3);
}
} // namespace aurora::gfx::clear
} // namespace aurora::gfx::clear
+9 -8
View File
@@ -11,16 +11,17 @@ struct DrawData {
float depth = 0.f;
};
constexpr uint8_t ClearPipelineConfigVersion = 1;
constexpr uint32_t ClearPipelineConfigVersion = 2;
struct PipelineConfig {
uint8_t version = ClearPipelineConfigVersion;
bool clearColor : 1 = true;
bool clearAlpha : 1 = true;
bool clearDepth : 1 = true;
uint8_t _pad : 5 = 0;
uint32_t version = ClearPipelineConfigVersion;
uint32_t msaaSamples = 1;
bool clearColor = true;
bool clearAlpha = true;
bool clearDepth = true;
uint8_t _pad = 0;
};
static_assert(std::has_unique_object_representations_v<PipelineConfig>);
wgpu::RenderPipeline create_pipeline(const PipelineConfig& config);
void render(const DrawData& data, const wgpu::RenderPassEncoder& pass);
} // namespace aurora::gfx::clear
void render(const DrawData& data, const wgpu::RenderPassEncoder& pass, const wgpu::Extent3D& targetSize);
} // namespace aurora::gfx::clear
+244 -44
View File
@@ -13,6 +13,7 @@
#include <deque>
#include <fstream>
#include <mutex>
#include <optional>
#include <thread>
#include <ranges>
@@ -137,18 +138,55 @@ std::atomic_ref createdPipelines{g_stats.createdPipelines};
using CommandList = std::vector<Command>;
struct RenderPass {
wgpu::TextureView colorView;
wgpu::TextureView resolveView; // MSAA resolve target; null if msaaSamples == 1
wgpu::TextureView depthView;
wgpu::Texture copySourceTexture;
wgpu::TextureView copySourceView;
wgpu::Extent3D targetSize;
uint32_t msaaSamples = 1;
TextureHandle resolveTarget;
GXTexFmt resolveFormat = GX_TF_RGBA8;
ClipRect resolveRect;
Range resolveUniformRange;
Vec4<float> clearColorValue{0.f, 0.f, 0.f, 0.f};
float clearDepthValue = gx::UseReversedZ ? 0.f : 1.f;
CommandList commands;
bool clearColor = true;
bool clearDepth = true;
std::vector<tex_copy_conv::ConvRequest> copyConvs;
std::vector<tex_palette_conv::ConvRequest> paletteConvs;
};
static std::vector<RenderPass> g_renderPasses;
static u32 g_currentRenderPass = UINT32_MAX;
static bool g_inOffscreen = false;
static std::optional<RenderPass> g_suspendedEfbPass;
static webgpu::TextureWithSampler g_offscreenColor;
static webgpu::TextureWithSampler g_offscreenDepth;
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;
pass.depthView = webgpu::g_depthBuffer.view;
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;
pass.targetSize = webgpu::g_frameBuffer.size;
pass.msaaSamples = webgpu::g_graphicsConfig.msaaSamples;
}
struct OffscreenDepthKey {
uint32_t width;
uint32_t height;
bool operator==(const OffscreenDepthKey& rhs) const { return width == rhs.width && height == rhs.height; }
template <typename H>
friend H AbslHashValue(H h, const OffscreenDepthKey& key) {
return H::combine(std::move(h), key.width, key.height);
}
};
static absl::flat_hash_map<OffscreenDepthKey, webgpu::TextureWithSampler> g_offscreenDepthCache;
std::vector<TextureUpload> g_textureUploads;
static ByteBuffer g_serializedPipelines{};
@@ -273,20 +311,46 @@ PipelineRef pipeline_ref(clear::PipelineConfig config) {
}
void resolve_pass(TextureHandle texture, ClipRect rect, bool clearColor, bool clearAlpha, bool clearDepth,
Vec4<float> clearColorValue, float clearDepthValue) {
auto& currentPass = g_renderPasses[g_currentRenderPass];
currentPass.resolveTarget = std::move(texture);
currentPass.resolveRect = rect;
auto& newPass = g_renderPasses.emplace_back();
newPass.clearColorValue = clearColorValue;
newPass.clearDepthValue = clearDepthValue;
newPass.clearColor = clearColor && clearAlpha;
newPass.clearDepth = clearDepth;
Vec4<float> clearColorValue, float clearDepthValue, GXTexFmt resolveFormat) {
// Resolve current render pass
auto& prevPass = g_renderPasses[g_currentRenderPass];
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);
// Populate new render pass from previous
const auto msaaSamples = prevPass.msaaSamples;
RenderPass newPass{
.colorView = prevPass.colorView,
.resolveView = prevPass.resolveView,
.depthView = prevPass.depthView,
.copySourceTexture = prevPass.copySourceTexture,
.copySourceView = prevPass.copySourceView,
.targetSize = prevPass.targetSize,
.msaaSamples = msaaSamples,
.clearColorValue = clearColorValue,
.clearDepthValue = clearDepthValue,
.clearColor = clearColor && clearAlpha,
.clearDepth = clearDepth,
};
g_renderPasses.emplace_back(std::move(newPass));
++g_currentRenderPass;
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{
.msaaSamples = msaaSamples,
.clearColor = clearColor,
.clearAlpha = clearAlpha,
.clearDepth = false, // Depth cleared via render attachment
@@ -304,15 +368,131 @@ void resolve_pass(TextureHandle texture, ClipRect rect, bool clearColor, bool cl
push_command(CommandType::SetScissor, Command::Data{.setScissor = g_cachedScissor});
}
void queue_copy_conv(tex_copy_conv::ConvRequest req) {
CHECK(g_currentRenderPass > 0, "queue_copy_conv called without a prior resolve_pass");
g_renderPasses[g_currentRenderPass - 1].copyConvs.push_back(std::move(req));
}
void queue_palette_conv(tex_palette_conv::ConvRequest req) {
g_renderPasses[g_currentRenderPass].paletteConvs.push_back(std::move(req));
}
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");
return g_renderPasses[g_currentRenderPass].msaaSamples;
}
void clear_offscreen_cache() { g_offscreenDepthCache.clear(); }
static webgpu::TextureWithSampler get_offscreen_depth(uint32_t width, uint32_t height) {
OffscreenDepthKey key{width, height};
auto it = g_offscreenDepthCache.find(key);
if (it != g_offscreenDepthCache.end()) {
return it->second;
}
const auto format = webgpu::g_graphicsConfig.depthFormat;
const wgpu::Extent3D size{width, height, 1};
const wgpu::TextureDescriptor desc{
.label = "Offscreen Depth",
.usage = wgpu::TextureUsage::RenderAttachment,
.dimension = wgpu::TextureDimension::e2D,
.size = size,
.format = format,
.mipLevelCount = 1,
.sampleCount = 1,
};
auto texture = g_device.CreateTexture(&desc);
auto view = texture.CreateView();
webgpu::TextureWithSampler result{
.texture = std::move(texture),
.view = std::move(view),
.size = size,
.format = format,
};
auto [insertIt, _] = g_offscreenDepthCache.emplace(key, result);
return insertIt->second;
}
void begin_offscreen(uint32_t width, uint32_t height) {
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) {
auto& currentPass = g_renderPasses[g_currentRenderPass];
if (!currentPass.resolveTarget) {
g_suspendedEfbPass = std::move(currentPass);
g_renderPasses.pop_back();
--g_currentRenderPass;
}
}
// Create offscreen color target
const wgpu::Extent3D size{width, height, 1};
const auto colorFormat = webgpu::g_graphicsConfig.surfaceConfiguration.format;
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,
.format = colorFormat,
.mipLevelCount = 1,
.sampleCount = 1,
};
auto colorTexture = g_device.CreateTexture(&colorDesc);
auto colorView = colorTexture.CreateView();
g_offscreenColor = {
.texture = std::move(colorTexture),
.view = std::move(colorView),
.size = size,
.format = colorFormat,
};
g_offscreenDepth = get_offscreen_depth(width, height);
// Start a new pass with offscreen targets
RenderPass newPass{
.colorView = g_offscreenColor.view,
.depthView = g_offscreenDepth.view,
.copySourceTexture = g_offscreenColor.texture,
.copySourceView = g_offscreenColor.view,
.targetSize = size,
.msaaSamples = 1,
.clearColorValue = {0.f, 0.f, 0.f, 0.f},
.clearDepthValue = gx::UseReversedZ ? 0.f : 1.f,
.clearColor = true,
.clearDepth = true,
};
g_renderPasses.emplace_back(std::move(newPass));
++g_currentRenderPass;
g_inOffscreen = true;
push_command(CommandType::SetViewport, Command::Data{.setViewport = {0.f, 0.f, static_cast<float>(width),
static_cast<float>(height), 0.f, 1.f}});
push_command(CommandType::SetScissor, Command::Data{.setScissor = {0, 0, width, height}});
}
void end_offscreen() {
CHECK(g_inOffscreen, "end_offscreen called without begin_offscreen");
g_inOffscreen = false;
g_offscreenColor = {};
g_offscreenDepth = {};
// Resume suspended EFB pass, or start a new one (load existing content)
if (g_suspendedEfbPass) {
g_renderPasses.emplace_back(std::move(*g_suspendedEfbPass));
g_suspendedEfbPass.reset();
} else {
auto& pass = g_renderPasses.emplace_back();
pass.clearColor = false;
pass.clearDepth = false;
}
++g_currentRenderPass;
set_efb_targets(g_renderPasses[g_currentRenderPass]);
push_command(CommandType::SetViewport, Command::Data{.setViewport = g_cachedViewport});
push_command(CommandType::SetScissor, Command::Data{.setScissor = g_cachedScissor});
}
template <>
void push_draw_command(gx::DrawData data) {
push_draw_command(ShaderDrawCommand{.type = ShaderType::GX, .gx = data});
@@ -506,6 +686,10 @@ void shutdown() {
g_stagingBuffers.fill({});
g_renderPasses.clear();
g_currentRenderPass = UINT32_MAX;
g_offscreenDepthCache.clear();
g_offscreenColor = {};
g_offscreenDepth = {};
g_inOffscreen = false;
queuedPipelines = 0;
createdPipelines = 0;
@@ -549,8 +733,10 @@ void begin_frame() {
g_stats.drawCallCount = 0;
g_stats.mergedDrawCallCount = 0;
g_suspendedEfbPass.reset();
g_renderPasses.emplace_back();
set_efb_targets(g_renderPasses[0]);
g_renderPasses[0].clearColorValue = gx::g_gxState.clearColor;
g_renderPasses[0].clearDepthValue = gx::clear_depth_value();
g_currentRenderPass = 0;
@@ -563,6 +749,7 @@ void begin_frame() {
}
void end_frame(const wgpu::CommandEncoder& cmd) {
ASSERT(!g_inOffscreen, "end_frame called while offscreen rendering is active");
uint64_t bufferOffset = 0;
const auto writeBuffer = [&](ByteBuffer& buf, wgpu::Buffer& out, uint64_t size, std::string_view label) {
const auto writeSize = buf.size(); // Only need to copy this many bytes
@@ -611,13 +798,20 @@ void end_frame(const wgpu::CommandEncoder& cmd) {
void render(wgpu::CommandEncoder& cmd) {
for (u32 i = 0; i < g_renderPasses.size(); ++i) {
const auto& passInfo = g_renderPasses[i];
for (const auto& conv : passInfo.paletteConvs) {
tex_palette_conv::run(cmd, conv);
}
if (i == g_renderPasses.size() - 1) {
ASSERT(!passInfo.resolveTarget, "Final render pass must not have resolve target");
} else if (!passInfo.resolveTarget) {
// Skip intermediate render passes without resolve target
continue;
}
const std::array attachments{
wgpu::RenderPassColorAttachment{
.view = webgpu::g_frameBuffer.view,
.resolveTarget = webgpu::g_graphicsConfig.msaaSamples > 1 ? webgpu::g_frameBufferResolved.view : nullptr,
.view = passInfo.colorView,
.resolveTarget = passInfo.resolveView,
.loadOp = passInfo.clearColor ? wgpu::LoadOp::Clear : wgpu::LoadOp::Load,
.storeOp = wgpu::StoreOp::Store,
.clearValue =
@@ -630,7 +824,7 @@ void render(wgpu::CommandEncoder& cmd) {
},
};
const wgpu::RenderPassDepthStencilAttachment depthStencilAttachment{
.view = webgpu::g_depthBuffer.view,
.view = passInfo.depthView,
.depthLoadOp = passInfo.clearDepth ? wgpu::LoadOp::Clear : wgpu::LoadOp::Load,
.depthStoreOp = wgpu::StoreOp::Store,
.depthClearValue = passInfo.clearDepthValue,
@@ -643,37 +837,43 @@ void render(wgpu::CommandEncoder& cmd) {
.depthStencilAttachment = &depthStencilAttachment,
};
for (const auto& conv : passInfo.paletteConvs) {
tex_palette_conv::run(cmd, conv);
}
auto pass = cmd.BeginRenderPass(&renderPassDescriptor);
render_pass(pass, i);
pass.End();
if (passInfo.resolveTarget) {
wgpu::TexelCopyTextureInfo src{
.origin =
wgpu::Origin3D{
.x = static_cast<uint32_t>(passInfo.resolveRect.x),
.y = static_cast<uint32_t>(passInfo.resolveRect.y),
},
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 tex_copy_conv::ConvRequest convReq{
.fmt = passInfo.resolveFormat,
.srcView = passInfo.copySourceView,
.uniformRange = passInfo.resolveUniformRange,
.dst = passInfo.resolveTarget,
};
if (webgpu::g_graphicsConfig.msaaSamples > 1) {
src.texture = webgpu::g_frameBufferResolved.texture;
if (needsConversion) {
tex_copy_conv::run(cmd, convReq);
} else if (needsScaling) {
tex_copy_conv::blit(cmd, convReq);
} else {
src.texture = webgpu::g_frameBuffer.texture;
}
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);
for (const auto& conv : passInfo.copyConvs) {
tex_copy_conv::run(cmd, conv);
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);
}
}
}
@@ -728,7 +928,7 @@ void render_pass(const wgpu::RenderPassEncoder& pass, u32 idx) {
} break;
case CommandType::SetScissor: {
const auto& sc = cmd.data.setScissor;
const auto& size = webgpu::g_frameBuffer.size;
const auto& size = g_renderPasses[idx].targetSize;
const auto x = std::clamp(sc.x, 0u, size.width);
const auto y = std::clamp(sc.y, 0u, size.height);
const auto w = std::clamp(sc.w, 0u, size.width - x);
@@ -739,7 +939,7 @@ void render_pass(const wgpu::RenderPassEncoder& pass, u32 idx) {
const auto& draw = cmd.data.draw;
switch (draw.type) {
case ShaderType::Clear:
clear::render(draw.clear, pass);
clear::render(draw.clear, pass, g_renderPasses[idx].targetSize);
break;
case ShaderType::GX:
gx::render(draw.gx, pass);
+8 -5
View File
@@ -10,6 +10,7 @@
#include <aurora/gfx.h>
#include <aurora/math.hpp>
#include <dolphin/gx/GXEnum.h>
#include <webgpu/webgpu_cpp.h>
#define XXH_STATIC_LINKING_ONLY
#include <xxhash.h>
@@ -213,15 +214,17 @@ void render(wgpu::CommandEncoder& cmd);
void render_pass(const wgpu::RenderPassEncoder& pass, uint32_t idx);
void map_staging_buffer();
void resolve_pass(TextureHandle texture, ClipRect rect, bool clearColor, bool clearAlpha, bool clearDepth,
Vec4<float> clearColorValue, float clearDepthValue);
Vec4<float> clearColorValue, float clearDepthValue, GXTexFmt resolveFormat = GX_TF_RGBA8);
void begin_offscreen(uint32_t width, uint32_t height);
void end_offscreen();
bool is_offscreen() noexcept;
uint32_t get_sample_count() noexcept;
void clear_offscreen_cache();
namespace tex_copy_conv {
struct ConvRequest;
} // namespace tex_copy_conv
namespace tex_palette_conv {
struct ConvRequest;
} // namespace tex_palette_conv
void queue_copy_conv(tex_copy_conv::ConvRequest req);
void queue_palette_conv(tex_palette_conv::ConvRequest req);
Range push_verts(const uint8_t* data, size_t length);
+45 -9
View File
@@ -20,6 +20,12 @@ static constexpr std::string_view ShaderPreamble = R"(
@group(0) @binding(0) var src_samp: sampler;
@group(0) @binding(1) var src: texture_2d<f32>;
struct UVTransform {
offset: vec2f,
scale: vec2f,
};
@group(0) @binding(2) var<uniform> uv_xf: UVTransform;
struct VertexOutput {
@builtin(position) pos: vec4f,
@location(0) uv: vec2f,
@@ -39,7 +45,7 @@ var<private> uvs: array<vec2f, 3> = array(
@vertex fn vs_main(@builtin(vertex_index) vi: u32) -> VertexOutput {
var out: VertexOutput;
out.pos = vec4f(positions[vi], 0.0, 1.0);
out.uv = uvs[vi];
out.uv = uvs[vi] * uv_xf.scale + uv_xf.offset;
return out;
}
@@ -53,6 +59,13 @@ fn quantize4(v: f32) -> f32 {
}
)"sv;
// Passthrough blit (for scaling)
static constexpr std::string_view FragPassthrough = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
return textureSample(src, src_samp, in.uv);
}
)"sv;
// GX_TF_I4: 4-bit intensity -> R8Unorm (quantized)
static constexpr std::string_view FragI4 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
@@ -197,6 +210,7 @@ static constexpr std::array ConvPipelines{
static wgpu::BindGroupLayout g_bindGroupLayout;
static wgpu::Sampler g_sampler;
static absl::flat_hash_map<GXTexFmt, wgpu::RenderPipeline> g_pipelines;
static wgpu::RenderPipeline g_blitPipeline;
static wgpu::RenderPipeline create_pipeline(const ConvPipeline& conv) {
std::string shaderSource;
@@ -267,6 +281,14 @@ void initialize() {
.viewDimension = wgpu::TextureViewDimension::e2D,
},
},
wgpu::BindGroupLayoutEntry{
.binding = 2,
.visibility = wgpu::ShaderStage::Vertex,
.buffer =
wgpu::BufferBindingLayout{
.type = wgpu::BufferBindingType::Uniform,
},
},
};
const wgpu::BindGroupLayoutDescriptor bindGroupLayoutDescriptor{
.label = "TexCopyConv Bind Group Layout",
@@ -275,6 +297,8 @@ void initialize() {
};
g_bindGroupLayout = g_device.CreateBindGroupLayout(&bindGroupLayoutDescriptor);
g_blitPipeline = create_pipeline(
{GX_TF_RGBA8, FragPassthrough, webgpu::g_graphicsConfig.surfaceConfiguration.format, "TexCopyConv Blit"});
for (const auto& conv : ConvPipelines) {
g_pipelines[conv.fmt] = create_pipeline(conv);
if (conv.outputFormat != to_wgpu(conv.fmt)) {
@@ -292,16 +316,12 @@ void initialize() {
void shutdown() {
g_pipelines.clear();
g_blitPipeline = {};
g_bindGroupLayout = {};
g_sampler = {};
}
void run(const wgpu::CommandEncoder& cmd, const ConvRequest& req) {
const auto it = g_pipelines.find(req.fmt);
if (it == g_pipelines.end()) {
Log.fatal("No copy conversion pipeline for format {}", static_cast<int>(req.fmt));
}
static void execute(const wgpu::CommandEncoder& cmd, const ConvRequest& req, const wgpu::RenderPipeline& pipeline) {
const std::array bindGroupEntries{
wgpu::BindGroupEntry{
.binding = 0,
@@ -309,7 +329,13 @@ void run(const wgpu::CommandEncoder& cmd, const ConvRequest& req) {
},
wgpu::BindGroupEntry{
.binding = 1,
.textureView = req.src->sampleTextureView,
.textureView = req.srcView,
},
wgpu::BindGroupEntry{
.binding = 2,
.buffer = g_uniformBuffer,
.offset = req.uniformRange.offset,
.size = req.uniformRange.size,
},
};
const wgpu::BindGroupDescriptor bindGroupDescriptor{
@@ -333,10 +359,20 @@ void run(const wgpu::CommandEncoder& cmd, const ConvRequest& req) {
.colorAttachments = colorAttachments.data(),
};
const auto pass = cmd.BeginRenderPass(&renderPassDescriptor);
pass.SetPipeline(it->second);
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup);
pass.Draw(3);
pass.End();
}
void run(const wgpu::CommandEncoder& cmd, const ConvRequest& req) {
const auto it = g_pipelines.find(req.fmt);
if (it == g_pipelines.end()) {
Log.fatal("No copy conversion pipeline for format {}", static_cast<int>(req.fmt));
}
execute(cmd, req, it->second);
}
void blit(const wgpu::CommandEncoder& cmd, const ConvRequest& req) { execute(cmd, req, g_blitPipeline); }
} // namespace aurora::gfx::tex_copy_conv
+4 -2
View File
@@ -8,8 +8,9 @@ namespace aurora::gfx::tex_copy_conv {
struct ConvRequest {
GXTexFmt fmt;
TextureHandle src;
TextureHandle dst;
wgpu::TextureView srcView; // View of resolved EFB / offscreen color
Range uniformRange; // UV transform uniform (offset + scale)
TextureHandle dst; // Destination texture
};
bool needs_conversion(GXTexFmt fmt);
@@ -17,5 +18,6 @@ bool needs_conversion(GXTexFmt fmt);
void initialize();
void shutdown();
void run(const wgpu::CommandEncoder& cmd, const ConvRequest& req);
void blit(const wgpu::CommandEncoder& cmd, const ConvRequest& req);
} // namespace aurora::gfx::tex_copy_conv
+2 -3
View File
@@ -29,7 +29,6 @@ const gfx::TextureBind& get_texture(GXTexMapID id) noexcept { return g_gxState.t
void clear_copy_texture_cache() noexcept {
g_gxState.copyTextures.clear();
g_gxState.copyTextureCache.clear();
g_gxState.convTextureCache.clear();
}
static inline wgpu::BlendFactor to_blend_factor(GXBlendFactor fac, bool isDst) {
@@ -231,8 +230,7 @@ wgpu::RenderPipeline build_pipeline(const PipelineConfig& config, ArrayRef<wgpu:
.depthStencil = &depthStencil,
.multisample =
wgpu::MultisampleState{
.count = g_graphicsConfig.msaaSamples,
.mask = UINT32_MAX,
.count = config.msaaSamples,
},
.fragment = &fragmentState,
};
@@ -415,6 +413,7 @@ void populate_pipeline_config(PipelineConfig& config, GXPrimitive primitive, GXV
config.shaderConfig.alphaCompare = g_gxState.alphaCompare;
}
config = {
.msaaSamples = gfx::get_sample_count(),
.shaderConfig = config.shaderConfig,
.depthFunc = g_gxState.depthFunc,
.cullMode = config.shaderConfig.lineMode == 0 ? g_gxState.cullMode : GX_CULL_NONE,
+2 -1
View File
@@ -328,6 +328,8 @@ struct GXState {
std::array<AttrArray, MaxVtxAttr> arrays;
gfx::ClipRect texCopySrc;
GXTexFmt texCopyFmt;
u16 texCopyDstWidth = 0;
u16 texCopyDstHeight = 0;
struct CopyTextureKey {
const void* dest = nullptr;
u32 width = 0;
@@ -345,7 +347,6 @@ struct GXState {
};
absl::flat_hash_map<const void*, gfx::TextureHandle> copyTextures;
absl::flat_hash_map<CopyTextureKey, gfx::TextureHandle> copyTextureCache;
absl::flat_hash_map<CopyTextureKey, gfx::TextureHandle> convTextureCache;
bool depthCompare = true;
bool depthUpdate = true;
bool colorUpdate = true;
+2
View File
@@ -933,5 +933,7 @@ inline std::string format_as(const GXTexFmt& fmt) {
return "GX_TF_R8_PC";
case GX_TF_RGBA8_PC:
return "GX_TF_RGBA8_PC";
default:
return fmt::format("GXTexFmt({})", underlying(fmt));
}
}
+6 -5
View File
@@ -14,19 +14,20 @@ struct DrawData {
uint32_t indexCount;
uint32_t instanceCount;
GXBindGroups bindGroups;
u32 dstAlpha;
uint32_t dstAlpha;
};
constexpr u32 GXPipelineConfigVersion = 10;
constexpr uint32_t GXPipelineConfigVersion = 11;
struct PipelineConfig {
u32 version = GXPipelineConfigVersion;
uint32_t version = GXPipelineConfigVersion;
uint32_t msaaSamples = 1;
ShaderConfig shaderConfig;
GXCompare depthFunc;
GXCullMode cullMode;
GXBlendMode blendMode;
GXBlendFactor blendFacSrc, blendFacDst;
GXLogicOp blendOp;
u32 dstAlpha;
uint32_t dstAlpha;
bool depthCompare, depthUpdate, alphaUpdate, colorUpdate;
};
static_assert(std::has_unique_object_representations_v<PipelineConfig>);
@@ -34,5 +35,5 @@ static_assert(std::has_unique_object_representations_v<PipelineConfig>);
wgpu::RenderPipeline create_pipeline([[maybe_unused]] const PipelineConfig& config);
void render(const DrawData& data, const wgpu::RenderPassEncoder& pass);
void queue_surface(const u8* dlStart, u32 dlSize, bool bigEndian) noexcept;
void queue_surface(const u8* dlStart, uint32_t dlSize, bool bigEndian) noexcept;
} // namespace aurora::gx
+2 -1
View File
@@ -23,7 +23,8 @@ void log_internal(const AuroraLogLevel level, const char* module, const char* me
}
} // namespace aurora
auto fmt::formatter<AuroraLogLevel>::format(const AuroraLogLevel level, format_context& ctx) const -> format_context::iterator {
auto fmt::formatter<AuroraLogLevel>::format(const AuroraLogLevel level, format_context& ctx) const
-> format_context::iterator {
std::string_view name = "unknown";
switch (level) {
case LOG_DEBUG:
+5 -1
View File
@@ -21,7 +21,10 @@
namespace aurora::gx {
void clear_copy_texture_cache() noexcept;
}
} // namespace aurora::gx
namespace aurora::gfx {
void clear_offscreen_cache();
} // namespace aurora::gfx
namespace aurora::webgpu {
static Module Log("aurora::gpu");
@@ -604,6 +607,7 @@ void resize_swapchain(uint32_t width, uint32_t height, bool force) {
}
if (sizeChanged) {
gx::clear_copy_texture_cache();
gfx::clear_offscreen_cache();
}
g_graphicsConfig.surfaceConfiguration.width = width;
g_graphicsConfig.surfaceConfiguration.height = height;
+1 -4
View File
@@ -139,15 +139,12 @@ TextureHandle new_render_texture(uint32_t width, uint32_t height, u32 gxFormat,
TextureHandle new_conv_texture(uint32_t width, uint32_t height, u32 gxFormat, const char* label) noexcept { return {}; }
void write_texture(const TextureRef& ref, ArrayRef<uint8_t> data) noexcept {}
void resolve_pass(TextureHandle texture, ClipRect rect, bool clearColor, bool clearAlpha, bool clearDepth,
Vec4<float> clearColorValue, float clearDepthValue) {}
void queue_copy_conv(tex_copy_conv::ConvRequest req) {}
Vec4<float> clearColorValue, float clearDepthValue, GXTexFmt resolveFormat) {}
void queue_palette_conv(tex_palette_conv::ConvRequest req) {}
} // namespace aurora::gfx
namespace aurora::gfx::tex_copy_conv {
bool needs_conversion(GXTexFmt fmt) { return false; }
wgpu::TextureFormat output_format(GXTexFmt fmt) { return wgpu::TextureFormat::Undefined; }
void queue(ConvRequest req) {}
} // namespace aurora::gfx::tex_copy_conv
namespace aurora::gfx::tex_palette_conv {