Texture refactor (#78)

- Removes special sampling logic from the GX shader in favor of performing conversions using separate draw calls and swizzling
- Simplifies texture bindings
- Much more accurate EFB copy handling (still incomplete but better)
  - Handle copy clears appropriately (color, alpha, depth) + GXSetDstAlpha copies
- Re-enables code for buffered texture uploads
- Adds `lastTextureUploadSize` to AuroraStats
- Use RG8 + swizzle for IA4/IA8
This commit is contained in:
Luke Street
2026-04-01 00:49:28 -06:00
committed by GitHub
parent 9c3d4665ce
commit 6b9f614d32
31 changed files with 1465 additions and 464 deletions
+3
View File
@@ -123,7 +123,10 @@ if (AURORA_ENABLE_GX)
lib/imgui.cpp
lib/webgpu/gpu.cpp
# aurora_gx sources
lib/gfx/clear.cpp
lib/gfx/common.cpp
lib/gfx/tex_copy_conv.cpp
lib/gfx/tex_palette_conv.cpp
lib/gfx/texture.cpp
lib/gfx/texture_convert.cpp
lib/gx/command_processor.cpp
+3
View File
@@ -1,5 +1,8 @@
add_library(aurora_gx STATIC
lib/gfx/clear.cpp
lib/gfx/common.cpp
lib/gfx/tex_copy_conv.cpp
lib/gfx/tex_palette_conv.cpp
lib/gfx/texture.cpp
lib/gfx/texture_convert.cpp
lib/gx/command_processor.cpp
+1
View File
@@ -25,6 +25,7 @@ typedef struct {
uint32_t lastUniformSize;
uint32_t lastIndexSize;
uint32_t lastStorageSize;
uint32_t lastTextureUploadSize;
} AuroraStats;
const AuroraStats* aurora_get_stats();
+47 -5
View File
@@ -1,8 +1,12 @@
#include "gx.hpp"
#include "__gx.h"
#include "../../gfx/tex_copy_conv.hpp"
#include "../../gfx/texture.hpp"
#include "../../window.hpp"
#include "../../gfx/clear.hpp"
#include "../../webgpu/wgpu.hpp"
#include "../../webgpu/gpu.hpp"
extern "C" {
GXRenderModeObj GXNtsc480IntDf = {
@@ -134,15 +138,53 @@ void GXCopyTex(void* dest, GXBool clear) {
};
auto it = g_gxState.copyTextureCache.find(key);
if (it == g_gxState.copyTextureCache.end()) {
auto handle = aurora::gfx::new_render_texture(rect.width, rect.height, g_gxState.texCopyFmt, "Resolved Texture");
// 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");
it = g_gxState.copyTextureCache.emplace(key, handle).first;
}
auto& handle = it->second;
auto currentIt = g_gxState.copyTextures.find(dest);
if (currentIt == g_gxState.copyTextures.end() || currentIt->second != handle) {
const auto& handle = it->second;
if (g_gxState.alphaUpdate && g_gxState.dstAlpha != UINT32_MAX) {
if (!clear) {
// TODO: figure out the right behavior here.
// should the copy have a specific alpha value but the EFB remains untouched?
}
// Overwrite alpha before resolving
aurora::gfx::push_draw_command(aurora::gfx::clear::DrawData{
.pipeline = aurora::gfx::pipeline_ref(aurora::gfx::clear::PipelineConfig{
.clearColor = false,
.clearAlpha = true,
.clearDepth = false,
}),
.color = wgpu::Color{0.f, 0.f, 0.f, g_gxState.dstAlpha / 255.f},
});
}
const auto clearColor = clear && g_gxState.colorUpdate;
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());
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;
}
aurora::gfx::resolve_pass(handle, rect, clear, g_gxState.clearColor);
}
// TODO GXGetYScaleFactor
+42 -1
View File
@@ -149,7 +149,48 @@ void GXSetZCompLoc(GXBool before_tex) {
}
void GXSetPixelFmt(GXPixelFmt pix_fmt, GXZFmt16 z_fmt) {
// Stub - pixel format changes require more complex handling
u32 oldPeCtrl = __gx->peCtrl;
u8 hwPixelFmt = 0;
switch (pix_fmt) {
case GX_PF_RGB8_Z24:
hwPixelFmt = 0;
break;
case GX_PF_RGBA6_Z24:
hwPixelFmt = 1;
break;
case GX_PF_RGB565_Z16:
hwPixelFmt = 2;
break;
case GX_PF_Z24:
hwPixelFmt = 3;
break;
case GX_PF_Y8:
case GX_PF_U8:
case GX_PF_V8:
hwPixelFmt = 4;
break;
case GX_PF_YUV420:
hwPixelFmt = 5;
break;
default:
UNLIKELY FATAL("GXSetPixelFmt: unsupported pixel format {}", static_cast<u32>(pix_fmt));
}
SET_REG_FIELD(0, __gx->peCtrl, 3, 0, hwPixelFmt);
SET_REG_FIELD(0, __gx->peCtrl, 3, 3, z_fmt);
if (oldPeCtrl != __gx->peCtrl) {
GX_WRITE_RAS_REG(__gx->peCtrl);
SET_REG_FIELD(0, __gx->genMode, 1, 9, pix_fmt == GX_PF_RGB565_Z16);
__gx->dirtyState |= 4;
}
if (hwPixelFmt == 4) {
SET_REG_FIELD(0, __gx->cmode1, 2, 9, (static_cast<u32>(pix_fmt) - GX_PF_Y8) & 0x3);
GX_WRITE_RAS_REG(__gx->cmode1);
}
__gx->bpSent = 1;
}
void GXSetDither(GXBool dither) {
+31
View File
@@ -1,7 +1,9 @@
#include "gx.hpp"
#include "__gx.h"
#include "../../gfx/tex_palette_conv.hpp"
#include "../../gfx/texture.hpp"
#include "../../webgpu/gpu.hpp"
#include <absl/container/flat_hash_map.h>
@@ -114,6 +116,35 @@ void GXLoadTexObj(GXTexObj* obj_, GXTexMapID id) {
obj->dataInvalidated = false;
}
g_gxState.textures[id] = {*obj};
// Perform palette conversion if necessary
if (aurora::gx::is_palette_format(obj->fmt)) {
const auto& tlutObj = g_gxState.tluts[obj->tlut];
CHECK(tlutObj.ref, "TLUT {} not loaded for palette texture", static_cast<int>(obj->tlut));
using aurora::gfx::tex_palette_conv::Variant;
Variant variant;
if (obj->ref->format == wgpu::TextureFormat::R16Sint) {
// CPU-decoded static texture
variant = Variant::Direct;
} else {
// Float texture (copy-converted)
variant = obj->fmt == GX_TF_C4 ? Variant::FromFloat4 : Variant::FromFloat8;
}
const auto label = fmt::format("PaletteConv_{}", static_cast<int>(id));
auto dst = aurora::gfx::new_conv_texture(obj->width, obj->height, GX_TF_RGBA8, label.c_str());
aurora::gfx::queue_palette_conv({
.variant = variant,
.src = obj->ref,
.dst = dst,
.tlut = tlutObj.ref,
});
auto& out = g_gxState.textures[id].texObj;
out.ref = std::move(dst);
out.fmt = GX_TF_RGBA8;
}
g_gxState.stateDirty = true; // TODO only if changed?
}
+125
View File
@@ -0,0 +1,125 @@
#include "clear.hpp"
#include "../webgpu/gpu.hpp"
namespace {
wgpu::ColorWriteMask clear_write_mask(bool clearColor, bool clearAlpha) {
auto writeMask = wgpu::ColorWriteMask::None;
if (clearColor) {
writeMask |= wgpu::ColorWriteMask::Red | wgpu::ColorWriteMask::Green | wgpu::ColorWriteMask::Blue;
}
if (clearAlpha) {
writeMask |= wgpu::ColorWriteMask::Alpha;
}
return writeMask;
}
} // namespace
namespace aurora::gfx::clear {
using webgpu::g_device;
using webgpu::g_frameBuffer;
using webgpu::g_graphicsConfig;
wgpu::RenderPipeline create_pipeline(const PipelineConfig& config) {
wgpu::ShaderSourceWGSL sourceDescriptor{};
sourceDescriptor.code = R"""(
struct VertexOutput {
@builtin(position) pos: vec4<f32>,
};
var<private> pos: array<vec2<f32>, 3> = array<vec2<f32>, 3>(
vec2(-1.0, 1.0),
vec2(-1.0, -3.0),
vec2(3.0, 1.0),
);
@vertex
fn vs_main(@builtin(vertex_index) vtxIdx: u32) -> VertexOutput {
var out: VertexOutput;
out.pos = vec4<f32>(pos[vtxIdx], 0.0, 1.0);
return out;
}
@fragment
fn fs_main() -> @location(0) vec4<f32> {
return vec4<f32>(1.0);
}
)""";
const wgpu::ShaderModuleDescriptor moduleDescriptor{
.nextInChain = &sourceDescriptor,
.label = "EFB Clear Module",
};
auto module = g_device.CreateShaderModule(&moduleDescriptor);
constexpr wgpu::PipelineLayoutDescriptor layoutDescriptor{
.bindGroupLayoutCount = 0,
.bindGroupLayouts = nullptr,
};
auto pipelineLayout = g_device.CreatePipelineLayout(&layoutDescriptor);
constexpr wgpu::BlendState blendState{
.color =
wgpu::BlendComponent{
.operation = wgpu::BlendOperation::Add,
.srcFactor = wgpu::BlendFactor::Constant,
.dstFactor = wgpu::BlendFactor::Zero,
},
.alpha =
wgpu::BlendComponent{
.operation = wgpu::BlendOperation::Add,
.srcFactor = wgpu::BlendFactor::Constant,
.dstFactor = wgpu::BlendFactor::Zero,
},
};
const wgpu::ColorTargetState colorTarget{
.format = g_graphicsConfig.surfaceConfiguration.format,
.blend = &blendState,
.writeMask = clear_write_mask(config.clearColor, config.clearAlpha),
};
const wgpu::FragmentState fragmentState{
.module = module,
.entryPoint = "fs_main",
.targetCount = 1,
.targets = &colorTarget,
};
const wgpu::DepthStencilState depthStencil{
.format = g_graphicsConfig.depthFormat,
.depthWriteEnabled = config.clearDepth,
.depthCompare = wgpu::CompareFunction::Always,
};
const auto label = fmt::format("EFB Clear Pipeline (color {}, alpha {}, depth {})", config.clearColor,
config.clearAlpha, config.clearDepth);
const wgpu::RenderPipelineDescriptor pipelineDescriptor{
.label = label.c_str(),
.layout = pipelineLayout,
.vertex =
wgpu::VertexState{
.module = module,
.entryPoint = "vs_main",
},
.primitive =
wgpu::PrimitiveState{
.topology = wgpu::PrimitiveTopology::TriangleList,
},
.depthStencil = &depthStencil,
.multisample =
wgpu::MultisampleState{
.count = g_graphicsConfig.msaaSamples,
.mask = UINT32_MAX,
},
.fragment = &fragmentState,
};
return g_device.CreateRenderPipeline(&pipelineDescriptor);
}
void render(const DrawData& data, const wgpu::RenderPassEncoder& pass) {
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.Draw(3);
}
} // namespace aurora::gfx::clear
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include "common.hpp"
#include <webgpu/webgpu_cpp.h>
namespace aurora::gfx::clear {
struct DrawData {
PipelineRef pipeline;
wgpu::Color color;
float depth = 0.f;
};
constexpr uint8_t ClearPipelineConfigVersion = 1;
struct PipelineConfig {
uint8_t version = ClearPipelineConfigVersion;
bool clearColor : 1 = true;
bool clearAlpha : 1 = true;
bool clearDepth : 1 = true;
uint8_t _pad : 5 = 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
+93 -30
View File
@@ -1,8 +1,11 @@
#include "common.hpp"
#include "clear.hpp"
#include "../internal.hpp"
#include "../webgpu/gpu.hpp"
#include "../gx/pipeline.hpp"
#include "tex_copy_conv.hpp"
#include "tex_palette_conv.hpp"
#include "texture.hpp"
#include <atomic>
@@ -38,12 +41,10 @@ constexpr uint64_t TextureUploadSize = 25165824; // 24mb
constexpr uint64_t StagingBufferSize =
UniformBufferSize + VertexBufferSize + IndexBufferSize + StorageBufferSize + TextureUploadSize;
struct ShaderState {
gx::State gx;
};
struct ShaderDrawCommand {
ShaderType type;
union {
clear::DrawData clear;
gx::DrawData gx;
};
};
@@ -127,7 +128,6 @@ wgpu::Buffer g_storageBuffer;
static std::array<wgpu::Buffer, 3> g_stagingBuffers;
static wgpu::Limits g_cachedLimits;
static ShaderState g_state;
static PipelineRef g_currentPipeline;
// for imgui debug
@@ -139,10 +139,13 @@ using CommandList = std::vector<Command>;
struct RenderPass {
TextureHandle resolveTarget;
ClipRect resolveRect;
Vec4<float> clearColor{0.f, 0.f, 0.f, 0.f};
float clearDepth = gx::UseReversedZ ? 0.f : 1.f;
Vec4<float> clearColorValue{0.f, 0.f, 0.f, 0.f};
float clearDepthValue = gx::UseReversedZ ? 0.f : 1.f;
CommandList commands;
bool clear = true;
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;
@@ -259,19 +262,57 @@ void set_scissor(uint32_t x, uint32_t y, uint32_t w, uint32_t h) noexcept {
}
}
void resolve_pass(TextureHandle texture, ClipRect rect, bool clear, Vec4<float> clearColor) {
auto& currentPass = aurora::gfx::g_renderPasses[g_currentRenderPass];
template <>
void push_draw_command(clear::DrawData data) {
push_draw_command(ShaderDrawCommand{.type = ShaderType::Clear, .clear = data});
}
template <>
PipelineRef pipeline_ref(clear::PipelineConfig config) {
return find_pipeline(ShaderType::Clear, config, [=] { return create_pipeline(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.clearColor = clearColor;
newPass.clearDepth = g_renderPasses[g_currentRenderPass].clearDepth;
newPass.clear = clear;
newPass.clearColorValue = clearColorValue;
newPass.clearDepthValue = clearDepthValue;
newPass.clearColor = clearColor && clearAlpha;
newPass.clearDepth = clearDepth;
++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{
.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(),
},
});
}
push_command(CommandType::SetViewport, Command::Data{.setViewport = g_cachedViewport});
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));
}
template <>
void push_draw_command(gx::DrawData data) {
push_draw_command(ShaderDrawCommand{.type = ShaderType::GX, .gx = data});
@@ -279,7 +320,7 @@ void push_draw_command(gx::DrawData data) {
template <>
PipelineRef pipeline_ref(gx::PipelineConfig config) {
return find_pipeline(ShaderType::GX, config, [=]() { return create_pipeline(g_state.gx, config); });
return find_pipeline(ShaderType::GX, config, [=] { return create_pipeline(config); });
}
static void pipeline_worker() {
@@ -347,6 +388,17 @@ void load_pipeline_cache() {
u32 size = *reinterpret_cast<const u32*>(pipelineCache.data() + offset);
offset += sizeof(u32);
switch (type) {
case ShaderType::Clear: {
if (size != sizeof(clear::PipelineConfig)) {
break;
}
const auto config = *reinterpret_cast<const clear::PipelineConfig*>(pipelineCache.data() + offset);
if (config.version != clear::ClearPipelineConfigVersion) {
break;
}
find_pipeline(type, config, [=] { return clear::create_pipeline(config); }, true);
break;
}
case ShaderType::GX: {
if (size != sizeof(gx::PipelineConfig)) {
break;
@@ -355,7 +407,7 @@ void load_pipeline_cache() {
if (config.version != gx::GXPipelineConfigVersion) {
break;
}
find_pipeline(type, config, [=]() { return gx::create_pipeline(g_state.gx, config); }, true);
find_pipeline(type, config, [=] { return gx::create_pipeline(config); }, true);
break;
}
default:
@@ -380,6 +432,9 @@ void save_pipeline_cache() {
}
void initialize() {
tex_copy_conv::initialize();
tex_palette_conv::initialize();
// No async pipelines for OpenGL (ES)
if (webgpu::g_backendType == wgpu::BackendType::OpenGL || webgpu::g_backendType == wgpu::BackendType::OpenGLES ||
webgpu::g_backendType == wgpu::BackendType::WebGPU) {
@@ -419,7 +474,7 @@ void initialize() {
}
map_staging_buffer();
g_state.gx = gx::construct_state();
gx::initialize();
load_pipeline_cache();
}
@@ -433,6 +488,8 @@ void shutdown() {
save_pipeline_cache();
tex_copy_conv::shutdown();
tex_palette_conv::shutdown();
gx::shutdown();
g_textureUploads.clear();
@@ -450,8 +507,6 @@ void shutdown() {
g_renderPasses.clear();
g_currentRenderPass = UINT32_MAX;
g_state = {};
queuedPipelines = 0;
createdPipelines = 0;
}
@@ -496,11 +551,8 @@ void begin_frame() {
g_stats.mergedDrawCallCount = 0;
g_renderPasses.emplace_back();
g_renderPasses[0].clearColor = gx::g_gxState.clearColor;
{
float normalizedDepth = static_cast<float>(gx::g_gxState.clearDepth) / 16777215.f;
g_renderPasses[0].clearDepth = gx::UseReversedZ ? (1.f - normalizedDepth) : normalizedDepth;
}
g_renderPasses[0].clearColorValue = gx::g_gxState.clearColor;
g_renderPasses[0].clearDepthValue = gx::clear_depth_value();
g_currentRenderPass = 0;
push_command(CommandType::SetViewport, Command::Data{.setViewport = g_cachedViewport});
push_command(CommandType::SetScissor, Command::Data{.setScissor = g_cachedScissor});
@@ -526,6 +578,7 @@ void end_frame(const wgpu::CommandEncoder& cmd) {
g_stats.lastUniformSize = writeBuffer(g_uniforms, g_uniformBuffer, UniformBufferSize, "Uniform");
g_stats.lastIndexSize = writeBuffer(g_indices, g_indexBuffer, IndexBufferSize, "Index");
g_stats.lastStorageSize = writeBuffer(g_storage, g_storageBuffer, StorageBufferSize, "Storage");
g_stats.lastTextureUploadSize = g_textureUpload.size();
{
// Perform texture copies
for (const auto& item : g_textureUploads) {
@@ -565,22 +618,22 @@ void render(wgpu::CommandEncoder& cmd) {
wgpu::RenderPassColorAttachment{
.view = webgpu::g_frameBuffer.view,
.resolveTarget = webgpu::g_graphicsConfig.msaaSamples > 1 ? webgpu::g_frameBufferResolved.view : nullptr,
.loadOp = passInfo.clear ? wgpu::LoadOp::Clear : wgpu::LoadOp::Load,
.loadOp = passInfo.clearColor ? wgpu::LoadOp::Clear : wgpu::LoadOp::Load,
.storeOp = wgpu::StoreOp::Store,
.clearValue =
{
.r = passInfo.clearColor.x(),
.g = passInfo.clearColor.y(),
.b = passInfo.clearColor.z(),
.a = passInfo.clearColor.w(),
.r = passInfo.clearColorValue.x(),
.g = passInfo.clearColorValue.y(),
.b = passInfo.clearColorValue.z(),
.a = passInfo.clearColorValue.w(),
},
},
};
const wgpu::RenderPassDepthStencilAttachment depthStencilAttachment{
.view = webgpu::g_depthBuffer.view,
.depthLoadOp = passInfo.clear ? wgpu::LoadOp::Clear : wgpu::LoadOp::Load,
.depthLoadOp = passInfo.clearDepth ? wgpu::LoadOp::Clear : wgpu::LoadOp::Load,
.depthStoreOp = wgpu::StoreOp::Store,
.depthClearValue = passInfo.clearDepth,
.depthClearValue = passInfo.clearDepthValue,
};
const auto label = fmt::format("Render pass {}", i);
const wgpu::RenderPassDescriptor renderPassDescriptor{
@@ -589,6 +642,10 @@ void render(wgpu::CommandEncoder& cmd) {
.colorAttachments = attachments.data(),
.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();
@@ -615,6 +672,9 @@ void render(wgpu::CommandEncoder& cmd) {
.depthOrArrayLayers = 1,
};
cmd.CopyTextureToTexture(&src, &dst, &size);
for (const auto& conv : passInfo.copyConvs) {
tex_copy_conv::run(cmd, conv);
}
}
}
g_renderPasses.clear();
@@ -678,8 +738,11 @@ void render_pass(const wgpu::RenderPassEncoder& pass, u32 idx) {
case CommandType::Draw: {
const auto& draw = cmd.data.draw;
switch (draw.type) {
case ShaderType::Clear:
clear::render(draw.clear, pass);
break;
case ShaderType::GX:
gx::render(g_state.gx, draw.gx, pass);
gx::render(draw.gx, pass);
break;
}
} break;
+12 -1
View File
@@ -200,6 +200,7 @@ struct TextureRef;
using TextureHandle = std::shared_ptr<TextureRef>;
enum class ShaderType : uint8_t {
Clear = 0,
GX = 1,
};
@@ -211,7 +212,17 @@ void end_frame(const wgpu::CommandEncoder& cmd);
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 clear, Vec4<float> clearColor);
void resolve_pass(TextureHandle texture, ClipRect rect, bool clearColor, bool clearAlpha, bool clearDepth,
Vec4<float> clearColorValue, float clearDepthValue);
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);
template <typename T>
+342
View File
@@ -0,0 +1,342 @@
#include "tex_copy_conv.hpp"
#include "../internal.hpp"
#include "../webgpu/gpu.hpp"
#include "texture.hpp"
#include "../gx/gx_fmt.hpp"
#include <vector>
#include <absl/container/flat_hash_map.h>
#include "texture_convert.hpp"
namespace aurora::gfx::tex_copy_conv {
static Module Log("aurora::gfx::tex_copy_conv");
using webgpu::g_device;
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 VertexOutput {
@builtin(position) pos: vec4f,
@location(0) uv: vec2f,
};
var<private> positions: array<vec2f, 3> = array(
vec2f(-1.0, 1.0),
vec2f(-1.0, -3.0),
vec2f(3.0, 1.0),
);
var<private> uvs: array<vec2f, 3> = array(
vec2f(0.0, 0.0),
vec2f(0.0, 2.0),
vec2f(2.0, 0.0),
);
@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];
return out;
}
fn intensity(rgb: vec3f) -> f32 {
// ITU-R BT.601 luma coefficients
return dot(rgb, vec3f(0.257, 0.504, 0.098)) + 16.0 / 255.0;
}
fn quantize4(v: f32) -> f32 {
return floor(v * 16.0) / 15.0;
}
)"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 {
let rgb = textureSample(src, src_samp, in.uv).rgb;
let i = quantize4(intensity(rgb));
return vec4f(i, 0.0, 0.0, 1.0);
}
)"sv;
// GX_TF_I8: 8-bit intensity -> R8Unorm
static constexpr std::string_view FragI8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let rgb = textureSample(src, src_samp, in.uv).rgb;
let i = intensity(rgb);
return vec4f(i, 0.0, 0.0, 1.0);
}
)"sv;
// GX_TF_IA4: 4-bit intensity + 4-bit alpha -> RG8Unorm
static constexpr std::string_view FragIA4 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let c = textureSample(src, src_samp, in.uv);
let i = quantize4(intensity(c.rgb));
let a = quantize4(c.a);
return vec4f(i, a, 0.0, 1.0);
}
)"sv;
// GX_TF_IA8: 8-bit intensity + 8-bit alpha -> RG8Unorm
static constexpr std::string_view FragIA8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let c = textureSample(src, src_samp, in.uv);
let i = intensity(c.rgb);
return vec4f(i, c.a, 0.0, 1.0);
}
)"sv;
// GX_TF_RGB565: Blit alpha to 1.0
static constexpr std::string_view FragRGB565 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let c = textureSample(src, src_samp, in.uv);
return vec4f(c.rgb, 1.0);
}
)"sv;
// GX_CTF_R4: 4-bit red -> R8Unorm
static constexpr std::string_view FragR4 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let r = quantize4(textureSample(src, src_samp, in.uv).r);
return vec4f(r, 0.0, 0.0, 1.0);
}
)"sv;
// GX_CTF_RA4: 4-bit red + 4-bit alpha -> RG8Unorm
static constexpr std::string_view FragRA4 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let c = textureSample(src, src_samp, in.uv);
return vec4f(quantize4(c.r), quantize4(c.a), 0.0, 1.0);
}
)"sv;
// GX_CTF_RA8: 8-bit red + 8-bit alpha -> RG8Unorm
static constexpr std::string_view FragRA8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let c = textureSample(src, src_samp, in.uv);
return vec4f(c.ra, 0.0, 1.0);
}
)"sv;
// GX_CTF_A8: 8-bit alpha -> R8Unorm
static constexpr std::string_view FragA8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let a = textureSample(src, src_samp, in.uv).a;
return vec4f(a, 0.0, 0.0, 1.0);
}
)"sv;
// GX_CTF_R8: 8-bit red -> R8Unorm
static constexpr std::string_view FragR8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let r = textureSample(src, src_samp, in.uv).r;
return vec4f(r, 0.0, 0.0, 1.0);
}
)"sv;
// GX_CTF_G8: 8-bit green -> R8Unorm
static constexpr std::string_view FragG8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let g = textureSample(src, src_samp, in.uv).g;
return vec4f(g, 0.0, 0.0, 1.0);
}
)"sv;
// GX_CTF_B8: 8-bit blue -> R8Unorm
static constexpr std::string_view FragB8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let b = textureSample(src, src_samp, in.uv).b;
return vec4f(b, 0.0, 0.0, 1.0);
}
)"sv;
// GX_CTF_RG8: 8-bit red + 8-bit green -> RG8Unorm
static constexpr std::string_view FragRG8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let c = textureSample(src, src_samp, in.uv);
return vec4f(c.rg, 0.0, 1.0);
}
)"sv;
// GX_CTF_GB8: 8-bit green + 8-bit blue -> RG8Unorm
static constexpr std::string_view FragGB8 = R"(
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let c = textureSample(src, src_samp, in.uv);
return vec4f(c.gb, 0.0, 1.0);
}
)"sv;
struct ConvPipeline {
GXTexFmt fmt;
std::string_view fragShader;
wgpu::TextureFormat outputFormat;
const char* label;
};
static constexpr std::array ConvPipelines{
ConvPipeline{GX_TF_I4, FragI4, wgpu::TextureFormat::R8Unorm, "TexCopyConv I4"},
ConvPipeline{GX_TF_I8, FragI8, wgpu::TextureFormat::R8Unorm, "TexCopyConv I8"},
ConvPipeline{GX_TF_IA4, FragIA4, wgpu::TextureFormat::RG8Unorm, "TexCopyConv IA4"},
ConvPipeline{GX_TF_IA8, FragIA8, wgpu::TextureFormat::RG8Unorm, "TexCopyConv IA8"},
// ConvPipeline{GX_TF_RGB565, FragRGB565, wgpu::TextureFormat::RGBA8Unorm, "TexCopyConv RGB565"},
ConvPipeline{GX_CTF_R4, FragR4, wgpu::TextureFormat::R8Unorm, "TexCopyConv R4"},
ConvPipeline{GX_CTF_RA4, FragRA4, wgpu::TextureFormat::RG8Unorm, "TexCopyConv RA4"},
ConvPipeline{GX_CTF_RA8, FragRA8, wgpu::TextureFormat::RG8Unorm, "TexCopyConv RA8"},
ConvPipeline{GX_CTF_A8, FragA8, wgpu::TextureFormat::R8Unorm, "TexCopyConv A8"},
ConvPipeline{GX_CTF_R8, FragR8, wgpu::TextureFormat::R8Unorm, "TexCopyConv R8"},
ConvPipeline{GX_CTF_G8, FragG8, wgpu::TextureFormat::R8Unorm, "TexCopyConv G8"},
ConvPipeline{GX_CTF_B8, FragB8, wgpu::TextureFormat::R8Unorm, "TexCopyConv B8"},
ConvPipeline{GX_CTF_RG8, FragRG8, wgpu::TextureFormat::RG8Unorm, "TexCopyConv RG8"},
ConvPipeline{GX_CTF_GB8, FragGB8, wgpu::TextureFormat::RG8Unorm, "TexCopyConv GB8"},
};
static wgpu::BindGroupLayout g_bindGroupLayout;
static wgpu::Sampler g_sampler;
static absl::flat_hash_map<GXTexFmt, wgpu::RenderPipeline> g_pipelines;
static wgpu::RenderPipeline create_pipeline(const ConvPipeline& conv) {
std::string shaderSource;
shaderSource.reserve(ShaderPreamble.size() + conv.fragShader.size());
shaderSource += ShaderPreamble;
shaderSource += conv.fragShader;
const wgpu::ShaderSourceWGSL wgslSource{wgpu::ShaderSourceWGSL::Init{
.code = shaderSource.c_str(),
}};
const wgpu::ShaderModuleDescriptor moduleDescriptor{
.nextInChain = &wgslSource,
.label = conv.label,
};
const auto module = g_device.CreateShaderModule(&moduleDescriptor);
const std::array colorTargets{wgpu::ColorTargetState{
.format = conv.outputFormat,
}};
const wgpu::FragmentState fragmentState{
.module = module,
.entryPoint = "fs_main",
.targetCount = colorTargets.size(),
.targets = colorTargets.data(),
};
constexpr wgpu::PipelineLayoutDescriptor layoutDescriptor{
.bindGroupLayoutCount = 1,
.bindGroupLayouts = &g_bindGroupLayout,
};
const auto pipelineLayout = g_device.CreatePipelineLayout(&layoutDescriptor);
const wgpu::RenderPipelineDescriptor pipelineDescriptor{
.label = conv.label,
.layout = pipelineLayout,
.vertex =
wgpu::VertexState{
.module = module,
.entryPoint = "vs_main",
},
.primitive =
wgpu::PrimitiveState{
.topology = wgpu::PrimitiveTopology::TriangleList,
},
.fragment = &fragmentState,
};
return g_device.CreateRenderPipeline(&pipelineDescriptor);
}
bool needs_conversion(const GXTexFmt fmt) { return g_pipelines.contains(fmt); }
void initialize() {
constexpr std::array bindGroupLayoutEntries{
wgpu::BindGroupLayoutEntry{
.binding = 0,
.visibility = wgpu::ShaderStage::Fragment,
.sampler =
wgpu::SamplerBindingLayout{
.type = wgpu::SamplerBindingType::Filtering,
},
},
wgpu::BindGroupLayoutEntry{
.binding = 1,
.visibility = wgpu::ShaderStage::Fragment,
.texture =
wgpu::TextureBindingLayout{
.sampleType = wgpu::TextureSampleType::Float,
.viewDimension = wgpu::TextureViewDimension::e2D,
},
},
};
const wgpu::BindGroupLayoutDescriptor bindGroupLayoutDescriptor{
.label = "TexCopyConv Bind Group Layout",
.entryCount = bindGroupLayoutEntries.size(),
.entries = bindGroupLayoutEntries.data(),
};
g_bindGroupLayout = g_device.CreateBindGroupLayout(&bindGroupLayoutDescriptor);
for (const auto& conv : ConvPipelines) {
g_pipelines[conv.fmt] = create_pipeline(conv);
if (conv.outputFormat != to_wgpu(conv.fmt)) {
Log.fatal("Output format mismatch for {}", conv.fmt);
}
}
constexpr wgpu::SamplerDescriptor samplerDescriptor{
.label = "TexCopyConv Sampler",
.magFilter = wgpu::FilterMode::Nearest,
.minFilter = wgpu::FilterMode::Nearest,
};
g_sampler = g_device.CreateSampler(&samplerDescriptor);
}
void shutdown() {
g_pipelines.clear();
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));
}
const std::array bindGroupEntries{
wgpu::BindGroupEntry{
.binding = 0,
.sampler = g_sampler,
},
wgpu::BindGroupEntry{
.binding = 1,
.textureView = req.src->sampleTextureView,
},
};
const wgpu::BindGroupDescriptor bindGroupDescriptor{
.layout = g_bindGroupLayout,
.entryCount = bindGroupEntries.size(),
.entries = bindGroupEntries.data(),
};
const auto bindGroup = g_device.CreateBindGroup(&bindGroupDescriptor);
const std::array colorAttachments{
wgpu::RenderPassColorAttachment{
.view = req.dst->attachmentTextureView,
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
.clearValue = {0.0, 0.0, 0.0, 0.0},
},
};
const wgpu::RenderPassDescriptor renderPassDescriptor{
.label = "TexCopyConv Pass",
.colorAttachmentCount = colorAttachments.size(),
.colorAttachments = colorAttachments.data(),
};
const auto pass = cmd.BeginRenderPass(&renderPassDescriptor);
pass.SetPipeline(it->second);
pass.SetBindGroup(0, bindGroup);
pass.Draw(3);
pass.End();
}
} // namespace aurora::gfx::tex_copy_conv
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "common.hpp"
#include <dolphin/gx/GXEnum.h>
namespace aurora::gfx::tex_copy_conv {
struct ConvRequest {
GXTexFmt fmt;
TextureHandle src;
TextureHandle dst;
};
bool needs_conversion(GXTexFmt fmt);
void initialize();
void shutdown();
void run(const wgpu::CommandEncoder& cmd, const ConvRequest& req);
} // namespace aurora::gfx::tex_copy_conv
+260
View File
@@ -0,0 +1,260 @@
#include "tex_palette_conv.hpp"
#include "../internal.hpp"
#include "../webgpu/gpu.hpp"
#include "texture.hpp"
#include <vector>
namespace aurora::gfx::tex_palette_conv {
static Module Log("aurora::gfx::tex_palette_conv");
using webgpu::g_device;
static constexpr std::string_view ShaderPreambleVtx = R"(
struct VertexOutput {
@builtin(position) pos: vec4f,
@location(0) uv: vec2f,
};
var<private> positions: array<vec2f, 3> = array(
vec2f(-1.0, 1.0),
vec2f(-1.0, -3.0),
vec2f(3.0, 1.0),
);
var<private> uvs: array<vec2f, 3> = array(
vec2f(0.0, 0.0),
vec2f(0.0, 2.0),
vec2f(2.0, 0.0),
);
@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];
return out;
}
fn intensity(rgb: vec3f) -> f32 {
// ITU-R BT.601 luma coefficients
return dot(rgb, vec3f(0.257, 0.504, 0.098)) + 16.0 / 255.0;
}
)"sv;
// Direct: R16Sint index texture + TLUT -> RGBA8
static constexpr std::string_view ShaderDirect = R"(
@group(0) @binding(0) var src_samp: sampler;
@group(0) @binding(1) var src: texture_2d<i32>;
@group(0) @binding(2) var tlut: texture_2d<f32>;
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let texSize = vec2f(textureDimensions(src));
let coord = vec2i(floor(in.uv * texSize));
let idx = textureLoad(src, coord, 0).r;
return textureLoad(tlut, vec2i(idx, 0), 0);
}
)"sv;
// FromFloat8: f32 texture (R8Unorm) -> 8-bit index -> TLUT -> RGBA8
static constexpr std::string_view ShaderFromFloat8 = R"(
@group(0) @binding(0) var src_samp: sampler;
@group(0) @binding(1) var src: texture_2d<f32>;
@group(0) @binding(2) var tlut: texture_2d<f32>;
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let texSize = vec2f(textureDimensions(src));
let coord = vec2i(floor(in.uv * texSize));
let r = textureLoad(src, coord, 0).r;
return textureLoad(tlut, vec2i(i32(r * 255.0), 0), 0);
}
)"sv;
// FromFloat4: f32 texture (R8Unorm) -> 4-bit index -> TLUT -> RGBA8
static constexpr std::string_view ShaderFromFloat4 = R"(
@group(0) @binding(0) var src_samp: sampler;
@group(0) @binding(1) var src: texture_2d<f32>;
@group(0) @binding(2) var tlut: texture_2d<f32>;
@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let texSize = vec2f(textureDimensions(src));
let coord = vec2i(floor(in.uv * texSize));
let r = textureLoad(src, coord, 0).r;
return textureLoad(tlut, vec2i(i32(r * 15.0), 0), 0);
}
)"sv;
struct PipelineInfo {
wgpu::RenderPipeline pipeline;
wgpu::BindGroupLayout bindGroupLayout;
};
static PipelineInfo g_directPipeline;
static PipelineInfo g_fromFloat8Pipeline;
static PipelineInfo g_fromFloat4Pipeline;
static wgpu::Sampler g_sampler;
static PipelineInfo create_pipeline(std::string_view fragBindingsAndShader, wgpu::TextureSampleType srcSampleType,
const char* label) {
std::string shaderSource;
shaderSource.reserve(ShaderPreambleVtx.size() + fragBindingsAndShader.size());
shaderSource += ShaderPreambleVtx;
shaderSource += fragBindingsAndShader;
wgpu::ShaderSourceWGSL wgslSource{};
wgslSource.code = shaderSource.c_str();
const wgpu::ShaderModuleDescriptor moduleDescriptor{
.nextInChain = &wgslSource,
.label = label,
};
auto module = g_device.CreateShaderModule(&moduleDescriptor);
const std::array bindGroupLayoutEntries{
wgpu::BindGroupLayoutEntry{
.binding = 0,
.visibility = wgpu::ShaderStage::Fragment,
.sampler =
wgpu::SamplerBindingLayout{
.type = wgpu::SamplerBindingType::NonFiltering,
},
},
wgpu::BindGroupLayoutEntry{
.binding = 1,
.visibility = wgpu::ShaderStage::Fragment,
.texture =
wgpu::TextureBindingLayout{
.sampleType = srcSampleType,
.viewDimension = wgpu::TextureViewDimension::e2D,
},
},
wgpu::BindGroupLayoutEntry{
.binding = 2,
.visibility = wgpu::ShaderStage::Fragment,
.texture =
wgpu::TextureBindingLayout{
.sampleType = wgpu::TextureSampleType::Float,
.viewDimension = wgpu::TextureViewDimension::e2D,
},
},
};
const auto bindGroupLayoutLabel = fmt::format("{} Bind Group Layout", label);
const wgpu::BindGroupLayoutDescriptor bindGroupLayoutDescriptor{
.label = bindGroupLayoutLabel.c_str(),
.entryCount = bindGroupLayoutEntries.size(),
.entries = bindGroupLayoutEntries.data(),
};
auto bindGroupLayout = g_device.CreateBindGroupLayout(&bindGroupLayoutDescriptor);
const wgpu::PipelineLayoutDescriptor layoutDescriptor{
.bindGroupLayoutCount = 1,
.bindGroupLayouts = &bindGroupLayout,
};
auto pipelineLayout = g_device.CreatePipelineLayout(&layoutDescriptor);
constexpr std::array colorTargets{wgpu::ColorTargetState{
.format = wgpu::TextureFormat::RGBA8Unorm,
}};
const wgpu::FragmentState fragmentState{
.module = module,
.entryPoint = "fs_main",
.targetCount = colorTargets.size(),
.targets = colorTargets.data(),
};
const wgpu::RenderPipelineDescriptor pipelineDescriptor{
.label = label,
.layout = pipelineLayout,
.vertex =
wgpu::VertexState{
.module = module,
.entryPoint = "vs_main",
},
.primitive =
wgpu::PrimitiveState{
.topology = wgpu::PrimitiveTopology::TriangleList,
},
.fragment = &fragmentState,
};
return PipelineInfo{
.pipeline = g_device.CreateRenderPipeline(&pipelineDescriptor),
.bindGroupLayout = std::move(bindGroupLayout),
};
}
static const PipelineInfo& pipeline_for_variant(Variant variant) {
switch (variant) {
case Variant::Direct:
return g_directPipeline;
case Variant::FromFloat8:
return g_fromFloat8Pipeline;
case Variant::FromFloat4:
return g_fromFloat4Pipeline;
}
FATAL("invalid palette conv variant {}", static_cast<int>(variant));
}
void initialize() {
g_directPipeline = create_pipeline(ShaderDirect, wgpu::TextureSampleType::Sint, "TexPaletteConv Direct");
g_fromFloat8Pipeline =
create_pipeline(ShaderFromFloat8, wgpu::TextureSampleType::UnfilterableFloat, "TexPaletteConv FromFloat8");
g_fromFloat4Pipeline =
create_pipeline(ShaderFromFloat4, wgpu::TextureSampleType::UnfilterableFloat, "TexPaletteConv FromFloat4");
constexpr wgpu::SamplerDescriptor samplerDesc{
.label = "TexPaletteConv Sampler",
.magFilter = wgpu::FilterMode::Nearest,
.minFilter = wgpu::FilterMode::Nearest,
};
g_sampler = g_device.CreateSampler(&samplerDesc);
}
void shutdown() {
g_directPipeline = {};
g_fromFloat8Pipeline = {};
g_fromFloat4Pipeline = {};
g_sampler = {};
}
void run(const wgpu::CommandEncoder& cmd, const ConvRequest& req) {
const auto& [pipeline, bindGroupLayout] = pipeline_for_variant(req.variant);
const std::array bindGroupEntries{
wgpu::BindGroupEntry{
.binding = 0,
.sampler = g_sampler,
},
wgpu::BindGroupEntry{
.binding = 1,
.textureView = req.src->sampleTextureView,
},
wgpu::BindGroupEntry{
.binding = 2,
.textureView = req.tlut->sampleTextureView,
},
};
const wgpu::BindGroupDescriptor bindGroupDescriptor{
.layout = bindGroupLayout,
.entryCount = bindGroupEntries.size(),
.entries = bindGroupEntries.data(),
};
const auto bindGroup = g_device.CreateBindGroup(&bindGroupDescriptor);
const std::array colorAttachments{
wgpu::RenderPassColorAttachment{
.view = req.dst->attachmentTextureView,
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
.clearValue = {0.0, 0.0, 0.0, 0.0},
},
};
const wgpu::RenderPassDescriptor renderPassDescriptor{
.label = "TexPaletteConv Pass",
.colorAttachmentCount = colorAttachments.size(),
.colorAttachments = colorAttachments.data(),
};
const auto pass = cmd.BeginRenderPass(&renderPassDescriptor);
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup);
pass.Draw(3);
pass.End();
}
} // namespace aurora::gfx::tex_palette_conv
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "common.hpp"
namespace aurora::gfx::tex_palette_conv {
enum class Variant : uint8_t {
Direct, // R16Sint index texture -> TLUT -> RGBA8
FromFloat8, // f32 texture, R channel * 255 -> 8-bit index -> TLUT -> RGBA8
FromFloat4, // f32 texture, R channel * 15 -> 4-bit index -> TLUT -> RGBA8
};
struct ConvRequest {
Variant variant;
TextureHandle src;
TextureHandle dst;
TextureHandle tlut;
};
void initialize();
void shutdown();
void run(const wgpu::CommandEncoder& cmd, const ConvRequest& req);
} // namespace aurora::gfx::tex_palette_conv
+90 -33
View File
@@ -5,6 +5,7 @@
#include "aurora/aurora.h"
#include "texture.hpp"
#include "texture_convert.hpp"
#include "../gx/gx_fmt.hpp"
#include <algorithm>
#include <cstdint>
@@ -34,6 +35,7 @@ TextureFormatInfo format_info(wgpu::TextureFormat format) {
DEFAULT_FATAL("unimplemented texture format {}", magic_enum::enum_name(format));
case wgpu::TextureFormat::R8Unorm:
return {1, 1, 1, false};
case wgpu::TextureFormat::RG8Unorm:
case wgpu::TextureFormat::R16Sint:
return {1, 1, 2, false};
case wgpu::TextureFormat::RGBA8Unorm:
@@ -90,15 +92,13 @@ TextureHandle new_static_texture_2d(uint32_t width, uint32_t height, uint32_t mi
.texture = ref.texture,
.mipLevel = mip,
};
// const auto range = push_texture_data(data.data() + offset, dataSize, bytesPerRow, heightBlocks);
const auto range = push_texture_data(data.data() + offset, dataSize, bytesPerRow, heightBlocks);
const wgpu::TexelCopyBufferLayout dataLayout{
// .offset = range.offset,
.offset = range.offset,
.bytesPerRow = bytesPerRow,
.rowsPerImage = heightBlocks,
};
// TODO
// g_textureUploads.emplace_back(dataLayout, std::move(dstView), physicalSize);
g_queue.WriteTexture(&dstView, data.data() + offset, dataSize, &dataLayout, &physicalSize);
g_textureUploads.emplace_back(dataLayout, std::move(dstView), physicalSize);
offset += dataSize;
}
if (data.size() != UINT32_MAX && offset < data.size()) {
@@ -107,24 +107,37 @@ TextureHandle new_static_texture_2d(uint32_t width, uint32_t height, uint32_t mi
return handle;
}
static bool setup_swizzle(wgpu::TextureComponentSwizzleDescriptor & swizzle, u32 format) {
static bool setup_swizzle(wgpu::TextureComponentSwizzleDescriptor& swizzle, u32 format) {
switch (format) {
case GX_TF_I4:
case GX_TF_I8:
case GX_TF_R8_PC:
swizzle.swizzle.r = wgpu::ComponentSwizzle::R;
swizzle.swizzle.g = wgpu::ComponentSwizzle::R;
swizzle.swizzle.b = wgpu::ComponentSwizzle::R;
swizzle.swizzle.a = wgpu::ComponentSwizzle::R;
return true;
// TODO: IA4/IA8 can be RG8Unorm with swizzle RRRG.
case GX_TF_IA4:
case GX_TF_IA8:
swizzle.swizzle.r = wgpu::ComponentSwizzle::R;
swizzle.swizzle.g = wgpu::ComponentSwizzle::R;
swizzle.swizzle.b = wgpu::ComponentSwizzle::R;
swizzle.swizzle.a = wgpu::ComponentSwizzle::G;
return true;
case GX_TF_RGB565:
swizzle.swizzle.r = wgpu::ComponentSwizzle::R;
swizzle.swizzle.g = wgpu::ComponentSwizzle::G;
swizzle.swizzle.b = wgpu::ComponentSwizzle::B;
swizzle.swizzle.a = wgpu::ComponentSwizzle::One;
return true;
default:
return false;
}
}
TextureHandle new_dynamic_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 format,
TextureHandle new_dynamic_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 gxFormat,
const char* label) noexcept {
const auto wgpuFormat = to_wgpu(format);
const auto wgpuFormat = to_wgpu(gxFormat);
const wgpu::Extent3D size{
.width = width,
.height = height,
@@ -139,6 +152,7 @@ TextureHandle new_dynamic_texture_2d(uint32_t width, uint32_t height, uint32_t m
.mipLevelCount = mips,
.sampleCount = 1,
};
auto texture = g_device.CreateTexture(&textureDescriptor);
const auto viewLabel = fmt::format("{} view", label);
wgpu::TextureViewDescriptor textureViewDescriptor{
.label = viewLabel.c_str(),
@@ -146,19 +160,16 @@ TextureHandle new_dynamic_texture_2d(uint32_t width, uint32_t height, uint32_t m
.dimension = wgpu::TextureViewDimension::e2D,
.mipLevelCount = mips,
};
wgpu::TextureComponentSwizzleDescriptor swizzle{};
if (setup_swizzle(swizzle, format)) {
wgpu::TextureComponentSwizzleDescriptor swizzle;
if (setup_swizzle(swizzle, gxFormat)) {
textureViewDescriptor.nextInChain = &swizzle;
}
auto texture = g_device.CreateTexture(&textureDescriptor);
auto textureView = texture.CreateView(&textureViewDescriptor);
return std::make_shared<TextureRef>(std::move(texture), std::move(textureView), size, wgpuFormat, mips, format,
false);
return std::make_shared<TextureRef>(std::move(texture), std::move(textureView), wgpu::TextureView{}, size, wgpuFormat,
mips, gxFormat, false);
}
TextureHandle new_render_texture(uint32_t width, uint32_t height, u32 fmt, const char* label) noexcept {
TextureHandle new_render_texture(uint32_t width, uint32_t height, u32 gxFormat, const char* label) noexcept {
const auto wgpuFormat = webgpu::g_graphicsConfig.surfaceConfiguration.format;
const wgpu::Extent3D size{
.width = width,
@@ -167,22 +178,77 @@ TextureHandle new_render_texture(uint32_t width, uint32_t height, u32 fmt, const
};
const wgpu::TextureDescriptor textureDescriptor{
.label = label,
.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst,
.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment,
.dimension = wgpu::TextureDimension::e2D,
.size = size,
.format = wgpuFormat,
.mipLevelCount = 1,
.sampleCount = 1,
};
auto texture = g_device.CreateTexture(&textureDescriptor);
// Create texture view for color attachments
const auto viewLabel = fmt::format("{} view", label);
const wgpu::TextureViewDescriptor textureViewDescriptor{
wgpu::TextureViewDescriptor textureViewDescriptor{
.label = viewLabel.c_str(),
.format = wgpuFormat,
.dimension = wgpu::TextureViewDimension::e2D,
};
auto attachmentTextureView = texture.CreateView(&textureViewDescriptor);
// Create texture view for sampling, with swizzle if needed
wgpu::TextureView sampleTextureView;
wgpu::TextureComponentSwizzleDescriptor swizzle;
if (setup_swizzle(swizzle, gxFormat)) {
textureViewDescriptor.nextInChain = &swizzle;
sampleTextureView = texture.CreateView(&textureViewDescriptor);
} else {
sampleTextureView = attachmentTextureView;
}
return std::make_shared<TextureRef>(std::move(texture), std::move(sampleTextureView),
std::move(attachmentTextureView), size, wgpuFormat, 1, gxFormat, true);
}
TextureHandle new_conv_texture(uint32_t width, uint32_t height, u32 gxFormat, const char* label) noexcept {
const auto wgpuFormat = to_wgpu(gxFormat);
const wgpu::Extent3D size{
.width = width,
.height = height,
.depthOrArrayLayers = 1,
};
const wgpu::TextureDescriptor textureDescriptor{
.label = label,
.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment,
.dimension = wgpu::TextureDimension::e2D,
.size = size,
.format = wgpuFormat,
.mipLevelCount = 1,
.sampleCount = 1,
};
auto texture = g_device.CreateTexture(&textureDescriptor);
auto textureView = texture.CreateView(&textureViewDescriptor);
return std::make_shared<TextureRef>(std::move(texture), std::move(textureView), size, wgpuFormat, 1, fmt, true);
// Create texture view for color attachments
const auto viewLabel = fmt::format("{} view", label);
wgpu::TextureViewDescriptor textureViewDescriptor{
.label = viewLabel.c_str(),
.format = wgpuFormat,
.dimension = wgpu::TextureViewDimension::e2D,
};
auto attachmentTextureView = texture.CreateView(&textureViewDescriptor);
// Create texture view for sampling, with swizzle if needed
wgpu::TextureView sampleTextureView;
wgpu::TextureComponentSwizzleDescriptor swizzle;
if (setup_swizzle(swizzle, gxFormat)) {
textureViewDescriptor.nextInChain = &swizzle;
sampleTextureView = texture.CreateView(&textureViewDescriptor);
} else {
sampleTextureView = attachmentTextureView;
}
return std::make_shared<TextureRef>(std::move(texture), std::move(sampleTextureView),
std::move(attachmentTextureView), size, wgpuFormat, 1, gxFormat, false);
}
void write_texture(const TextureRef& ref, ArrayRef<uint8_t> data) noexcept {
@@ -209,26 +275,17 @@ void write_texture(const TextureRef& ref, ArrayRef<uint8_t> data) noexcept {
const uint32_t dataSize = bytesPerRow * heightBlocks * mipSize.depthOrArrayLayers;
CHECK(offset + dataSize <= data.size(), "write_texture: expected at least {} bytes, got {}", offset + dataSize,
data.size());
// auto dstView = wgpu::ImageCopyTexture{
// .texture = ref.texture,
// .mipLevel = mip,
// };
// const auto range = push_texture_data(data.data() + offset, dataSize, bytesPerRow, heightBlocks);
// const auto dataLayout = wgpu::TextureDataLayout{
// .offset = range.offset,
// .bytesPerRow = bytesPerRow,
// .rowsPerImage = heightBlocks,
// };
// g_textureUploads.emplace_back(dataLayout, std::move(dstView), physicalSize);
const wgpu::TexelCopyTextureInfo dstView{
.texture = ref.texture,
.mipLevel = mip,
};
const auto range = push_texture_data(data.data() + offset, dataSize, bytesPerRow, heightBlocks);
const wgpu::TexelCopyBufferLayout dataLayout{
.offset = range.offset,
.bytesPerRow = bytesPerRow,
.rowsPerImage = heightBlocks,
};
g_queue.WriteTexture(&dstView, data.data() + offset, dataSize, &dataLayout, &physicalSize);
g_textureUploads.emplace_back(dataLayout, std::move(dstView), physicalSize);
offset += dataSize;
}
if (data.size() != UINT32_MAX && offset < data.size()) {
+11 -8
View File
@@ -17,17 +17,19 @@ extern std::vector<TextureUpload> g_textureUploads;
constexpr u32 InvalidTextureFormat = -1;
struct TextureRef {
wgpu::Texture texture;
wgpu::TextureView view;
wgpu::TextureView sampleTextureView;
wgpu::TextureView attachmentTextureView;
wgpu::Extent3D size;
wgpu::TextureFormat format;
uint32_t mipCount;
u32 gxFormat;
bool isRenderTexture; // :shrug: for now
TextureRef(wgpu::Texture texture, wgpu::TextureView view, wgpu::Extent3D size, wgpu::TextureFormat format,
uint32_t mipCount, u32 gxFormat, bool isRenderTexture)
TextureRef(wgpu::Texture texture, wgpu::TextureView sampleTextureView, wgpu::TextureView attachmentTextureView,
wgpu::Extent3D size, wgpu::TextureFormat format, uint32_t mipCount, u32 gxFormat, bool isRenderTexture)
: texture(std::move(texture))
, view(std::move(view))
, sampleTextureView(std::move(sampleTextureView))
, attachmentTextureView(std::move(attachmentTextureView))
, size(size)
, format(format)
, mipCount(mipCount)
@@ -35,11 +37,12 @@ struct TextureRef {
, isRenderTexture(isRenderTexture) {}
};
TextureHandle new_static_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 format, ArrayRef<uint8_t> data,
bool tlut, const char* label) noexcept;
TextureHandle new_dynamic_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 format,
TextureHandle new_static_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 gxFormat,
ArrayRef<uint8_t> data, bool tlut, const char* label) noexcept;
TextureHandle new_dynamic_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 gxFormat,
const char* label) noexcept;
TextureHandle new_render_texture(uint32_t width, uint32_t height, u32 fmt, const char* label) noexcept;
TextureHandle new_render_texture(uint32_t width, uint32_t height, u32 gxFormat, const char* label) noexcept;
TextureHandle new_conv_texture(uint32_t width, uint32_t height, u32 gxFormat, const char* label) noexcept;
void write_texture(const TextureRef& ref, ArrayRef<uint8_t> data) noexcept;
}; // namespace aurora::gfx
+11 -12
View File
@@ -12,6 +12,11 @@ struct RGBA8 {
uint8_t a;
};
struct RG8 {
uint8_t r;
uint8_t g;
};
// http://www.mindcontrol.org/~hplus/graphics/expand-bits.html
template <uint8_t v>
constexpr uint8_t ExpandTo8(uint8_t n) {
@@ -132,24 +137,21 @@ struct TextureDecoderI8 {
struct TextureDecoderIA4 {
using Source = uint8_t;
using Target = RGBA8;
using Target = RG8;
static constexpr uint32_t Frac = 1;
static constexpr uint32_t BlockWidth = 8;
static constexpr uint32_t BlockHeight = 4;
static void decode_texel(Target* target, const Source* in, const uint32_t x) {
const uint8_t intensity = ExpandTo8<4>(in[x] & 0xf);
target[x].r = intensity;
target[x].g = intensity;
target[x].b = intensity;
target[x].a = ExpandTo8<4>(in[x] >> 4);
target[x].r = ExpandTo8<4>(in[x] & 0xf);
target[x].g = ExpandTo8<4>(in[x] >> 4);
}
};
struct TextureDecoderIA8 {
using Source = uint16_t;
using Target = RGBA8;
using Target = RG8;
static constexpr uint32_t Frac = 1;
static constexpr uint32_t BlockWidth = 4;
@@ -157,11 +159,8 @@ struct TextureDecoderIA8 {
static void decode_texel(Target* target, const Source* in, const uint32_t x) {
const auto texel = in[x];
const uint8_t intensity = texel >> 8;
target[x].r = intensity;
target[x].g = intensity;
target[x].b = intensity;
target[x].a = texel & 0xff;
target[x].r = texel >> 8;
target[x].g = texel & 0xff;
}
};
+14 -2
View File
@@ -5,12 +5,24 @@
#include "../webgpu/gpu.hpp"
namespace aurora::gfx {
static wgpu::TextureFormat to_wgpu(u32 format) {
switch (format) {
static constexpr wgpu::TextureFormat to_wgpu(u32 gxFormat) {
switch (gxFormat) {
case GX_TF_I4:
case GX_TF_I8:
case GX_TF_R8_PC:
case GX_CTF_R4:
case GX_CTF_A8:
case GX_CTF_R8:
case GX_CTF_B8:
case GX_CTF_G8:
return wgpu::TextureFormat::R8Unorm;
case GX_TF_IA4:
case GX_TF_IA8:
case GX_CTF_RA4:
case GX_CTF_RA8:
case GX_CTF_RG8:
case GX_CTF_GB8:
return wgpu::TextureFormat::RG8Unorm;
case GX_TF_C4:
case GX_TF_C8:
case GX_TF_C14X2:
+39 -2
View File
@@ -120,6 +120,38 @@ static inline u32 read_u32(const u8* ptr, bool bigEndian) {
static_cast<u32>(ptr[0]);
}
static u32 bp_get(u32 reg, u32 size, u32 shift);
static GXPixelFmt decode_pixel_fmt(u32 peCtrl, u32 cmode1) {
switch (bp_get(peCtrl, 3, 0)) {
case 0:
return GX_PF_RGB8_Z24;
case 1:
return GX_PF_RGBA6_Z24;
case 2:
return GX_PF_RGB565_Z16;
case 3:
return GX_PF_Z24;
case 4:
switch (bp_get(cmode1, 2, 9)) {
case 0:
return GX_PF_Y8;
case 1:
return GX_PF_U8;
case 2:
return GX_PF_V8;
default:
Log.warn("command_processor: unsupported cmode1 pixel subtype {}", bp_get(cmode1, 2, 9));
return GX_PF_Y8;
}
case 5:
return GX_PF_YUV420;
default:
Log.warn("command_processor: unsupported PE pixel format {}", bp_get(peCtrl, 3, 0));
return GX_PF_RGB8_Z24;
}
}
static inline u64 read_u64(const u8* ptr, bool bigEndian) {
u64 loaded;
// Unaligned-safe load
@@ -667,12 +699,17 @@ static void handle_bp(u32 value, bool bigEndian) {
u8 alpha = bp_get(value, 8, 0);
bool enabled = bp_get(value, 1, 8) != 0;
g_gxState.dstAlpha = enabled ? alpha : UINT32_MAX;
g_gxState.pixelFmt = decode_pixel_fmt(g_gxState.bpRegCache[0x43], value);
g_gxState.stateDirty = true;
break;
}
// PE control (0x43) - zcomp location
// PE control (0x43) - pixel format, z format, zcomp location
case 0x43: {
// Log.warn("Unimplemented: BP register {:x} (zcomp loc)", regId);
g_gxState.pixelFmt = decode_pixel_fmt(value, g_gxState.bpRegCache[0x42]);
g_gxState.zFmt = static_cast<GXZFmt16>(bp_get(value, 3, 3));
g_gxState.zCompLocBeforeTex = bp_get(value, 1, 6) != 0;
g_gxState.stateDirty = true;
break;
}
+66 -133
View File
@@ -20,11 +20,16 @@ using webgpu::g_graphicsConfig;
GXState g_gxState{};
static wgpu::Sampler sEmptySampler;
static wgpu::Texture sEmptyTexture;
static wgpu::TextureView sEmptyTextureView;
const gfx::TextureBind& get_texture(GXTexMapID id) noexcept { return g_gxState.textures[static_cast<size_t>(id)]; }
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) {
@@ -182,9 +187,8 @@ static inline wgpu::PrimitiveState to_primitive_state(GXCullMode gx_cullMode) {
};
}
wgpu::RenderPipeline build_pipeline(const PipelineConfig& config, const ShaderInfo& info,
ArrayRef<wgpu::VertexBufferLayout> vtxBuffers, wgpu::ShaderModule shader,
const char* label) noexcept {
wgpu::RenderPipeline build_pipeline(const PipelineConfig& config, ArrayRef<wgpu::VertexBufferLayout> vtxBuffers,
wgpu::ShaderModule shader, const char* label) noexcept {
const wgpu::DepthStencilState depthStencil{
.format = g_graphicsConfig.depthFormat,
.depthWriteEnabled = config.depthCompare && config.depthUpdate,
@@ -203,7 +207,7 @@ wgpu::RenderPipeline build_pipeline(const PipelineConfig& config, const ShaderIn
.targetCount = colorTargets.size(),
.targets = colorTargets.data(),
};
auto layouts = build_bind_group_layouts(info, config.shaderConfig);
auto layouts = build_bind_group_layouts(config.shaderConfig);
const std::array bindGroupLayouts{
layouts.uniformLayout,
layouts.samplerLayout,
@@ -211,7 +215,7 @@ wgpu::RenderPipeline build_pipeline(const PipelineConfig& config, const ShaderIn
};
const wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor{
.label = "GX Pipeline Layout",
.bindGroupLayoutCount = static_cast<uint32_t>(info.sampledTextures.any() ? bindGroupLayouts.size() : 1),
.bindGroupLayoutCount = bindGroupLayouts.size(),
.bindGroupLayouts = bindGroupLayouts.data(),
};
auto pipelineLayout = g_device.CreatePipelineLayout(&pipelineLayoutDescriptor);
@@ -412,20 +416,6 @@ void populate_pipeline_config(PipelineConfig& config, GXPrimitive primitive, GXV
if (g_gxState.alphaCompare) {
config.shaderConfig.alphaCompare = g_gxState.alphaCompare;
}
for (u8 i = 0; i < MaxTextures; ++i) {
const auto& bind = g_gxState.textures[i];
TextureConfig texConfig{};
if (bind.texObj.ref) {
if (requires_copy_conversion(bind.texObj)) {
texConfig.copyFmt = bind.texObj.ref->gxFormat;
}
if (requires_load_conversion(bind.texObj)) {
texConfig.loadFmt = bind.texObj.fmt;
}
texConfig.renderTex = bind.texObj.ref->isRenderTexture;
}
config.shaderConfig.textureConfig[i] = texConfig;
}
config = {
.shaderConfig = config.shaderConfig,
.depthFunc = g_gxState.depthFunc,
@@ -445,16 +435,16 @@ void populate_pipeline_config(PipelineConfig& config, GXPrimitive primitive, GXV
static std::mutex sBindGroupLayoutMutex;
static absl::flat_hash_map<u32, wgpu::BindGroupLayout> sUniformBindGroupLayouts;
static absl::flat_hash_map<u32, std::pair<wgpu::BindGroupLayout, wgpu::BindGroupLayout>> sTextureBindGroupLayouts;
static wgpu::BindGroupLayout sTextureBindGroupLayout;
static wgpu::BindGroupLayout sSamplerBindGroupLayout;
GXBindGroups build_bind_groups(const ShaderInfo& info, const ShaderConfig& config,
const BindGroupRanges& ranges) noexcept {
const auto layouts = build_bind_group_layouts(info, config);
const auto layouts = build_bind_group_layouts(config);
std::array<wgpu::BindGroupEntry, MaxIndexAttr + 3> uniformEntries;
memset(&uniformEntries, 0, sizeof(uniformEntries));
uniformEntries[0].binding = 0;
uniformEntries[0].buffer = gfx::g_vertexBuffer;
uniformEntries[0].size = wgpu::kWholeSize;
uniformEntries[1].binding = 1;
uniformEntries[1].buffer = gfx::g_uniformBuffer;
uniformEntries[1].size = info.uniformSize;
@@ -472,39 +462,22 @@ GXBindGroups build_bind_groups(const ShaderInfo& info, const ShaderConfig& confi
}
std::array<wgpu::BindGroupEntry, MaxTextures> samplerEntries;
std::array<wgpu::BindGroupEntry, MaxTextures * 2> textureEntries;
memset(&samplerEntries, 0, sizeof(samplerEntries));
memset(&textureEntries, 0, sizeof(textureEntries));
u32 samplerCount = 0;
std::array<wgpu::BindGroupEntry, MaxTextures> textureEntries;
u32 textureCount = 0;
for (u32 i = 0; i < info.sampledTextures.size(); ++i) {
if (!info.sampledTextures.test(i)) {
continue;
}
for (u32 i = 0; i < MaxTextures; ++i) {
const auto& tex = g_gxState.textures[i];
CHECK(tex, "unbound texture {}", i);
wgpu::BindGroupEntry& samplerEntry = samplerEntries[samplerCount];
samplerEntry.binding = samplerCount;
samplerEntry.size = wgpu::kWholeSize;
samplerEntry.sampler = gfx::sampler_ref(tex.get_descriptor());
++samplerCount;
wgpu::BindGroupEntry& samplerEntry = samplerEntries[textureCount];
wgpu::BindGroupEntry& textureEntry = textureEntries[textureCount];
samplerEntry.binding = textureCount;
textureEntry.binding = textureCount;
textureEntry.size = wgpu::kWholeSize;
textureEntry.textureView = tex.texObj.ref->view;
++textureCount;
// Load palette
const auto& texConfig = config.textureConfig[i];
if (is_palette_format(texConfig.loadFmt)) {
u32 tlut = tex.texObj.tlut;
CHECK(tlut >= GX_TLUT0 && tlut <= GX_BIGTLUT3, "tlut out of bounds {}", tlut);
CHECK(g_gxState.tluts[tlut].ref, "tlut unbound {}", tlut);
wgpu::BindGroupEntry& tlutEntry = textureEntries[textureCount];
tlutEntry.binding = textureCount;
tlutEntry.size = wgpu::kWholeSize;
tlutEntry.textureView = g_gxState.tluts[tlut].ref->view;
++textureCount;
if (tex) {
samplerEntry.sampler = gfx::sampler_ref(tex.get_descriptor());
textureEntry.textureView = tex.texObj.ref->sampleTextureView;
} else {
samplerEntry.sampler = sEmptySampler;
textureEntry.textureView = sEmptyTextureView;
}
++textureCount;
}
const wgpu::BindGroupDescriptor uniformBindGroupDescriptor{
.label = "GX Uniform Bind Group",
@@ -515,7 +488,7 @@ GXBindGroups build_bind_groups(const ShaderInfo& info, const ShaderConfig& confi
const wgpu::BindGroupDescriptor samplerBindGroupDescriptor{
.label = "GX Sampler Bind Group",
.layout = layouts.samplerLayout,
.entryCount = samplerCount,
.entryCount = textureCount,
.entries = samplerEntries.data(),
};
const wgpu::BindGroupDescriptor textureBindGroupDescriptor{
@@ -531,11 +504,10 @@ GXBindGroups build_bind_groups(const ShaderInfo& info, const ShaderConfig& confi
};
}
GXBindGroupLayouts build_bind_group_layouts(const ShaderInfo& info, const ShaderConfig& config) noexcept {
GXBindGroupLayouts build_bind_group_layouts(const ShaderConfig& config) noexcept {
GXBindGroupLayouts out;
Hasher uniformHasher;
uniformHasher.update(info.uniformSize);
uniformHasher.update(config.attrs);
const auto uniformLayoutHash = uniformHasher.digest();
{
@@ -562,7 +534,6 @@ GXBindGroupLayouts build_bind_group_layouts(const ShaderInfo& info, const Shader
wgpu::BufferBindingLayout{
.type = wgpu::BufferBindingType::Uniform,
.hasDynamicOffset = true,
.minBindingSize = info.uniformSize,
},
},
};
@@ -592,79 +563,39 @@ GXBindGroupLayouts build_bind_group_layouts(const ShaderInfo& info, const Shader
sUniformBindGroupLayouts.try_emplace(uniformLayoutHash, out.uniformLayout);
}
Hasher textureHasher;
textureHasher.update(info.sampledTextures);
textureHasher.update(config.textureConfig);
const auto textureLayoutHash = textureHasher.digest();
{
std::lock_guard lock{sBindGroupLayoutMutex};
auto it2 = sTextureBindGroupLayouts.find(textureLayoutHash);
if (it2 != sTextureBindGroupLayouts.end()) {
out.samplerLayout = it2->second.first;
out.textureLayout = it2->second.second;
return out;
}
}
out.samplerLayout = sSamplerBindGroupLayout;
out.textureLayout = sTextureBindGroupLayout;
return out;
}
u32 numSamplers = 0;
void initialize() noexcept {
u32 numTextures = 0;
std::array<wgpu::BindGroupLayoutEntry, MaxTextures> samplerEntries;
std::array<wgpu::BindGroupLayoutEntry, MaxTextures * 2> textureEntries;
for (u32 i = 0; i < info.sampledTextures.size(); ++i) {
if (!info.sampledTextures.test(i)) {
continue;
}
const auto& texConfig = config.textureConfig[i];
bool copyAsPalette = is_palette_format(texConfig.copyFmt);
bool loadAsPalette = is_palette_format(texConfig.loadFmt);
samplerEntries[numSamplers] = {
.binding = numSamplers,
std::array<wgpu::BindGroupLayoutEntry, MaxTextures> textureEntries;
for (u32 i = 0; i < MaxTextures; ++i) {
samplerEntries[numTextures] = {
.binding = numTextures,
.visibility = wgpu::ShaderStage::Fragment,
.sampler = {.type = copyAsPalette && loadAsPalette ? wgpu::SamplerBindingType::NonFiltering
: wgpu::SamplerBindingType::Filtering},
.sampler = {.type = wgpu::SamplerBindingType::Filtering},
};
++numSamplers;
if (loadAsPalette) {
textureEntries[numTextures] = {
.binding = numTextures,
.visibility = wgpu::ShaderStage::Fragment,
.texture =
{
.sampleType = copyAsPalette ? wgpu::TextureSampleType::Sint : wgpu::TextureSampleType::Float,
.viewDimension = wgpu::TextureViewDimension::e2D,
},
};
++numTextures;
textureEntries[numTextures] = {
.binding = numTextures,
.visibility = wgpu::ShaderStage::Fragment,
.texture =
{
.sampleType = wgpu::TextureSampleType::Float,
.viewDimension = wgpu::TextureViewDimension::e2D,
},
};
++numTextures;
} else {
textureEntries[numTextures] = {
.binding = numTextures,
.visibility = wgpu::ShaderStage::Fragment,
.texture =
{
.sampleType = wgpu::TextureSampleType::Float,
.viewDimension = wgpu::TextureViewDimension::e2D,
},
};
++numTextures;
}
textureEntries[numTextures] = {
.binding = numTextures,
.visibility = wgpu::ShaderStage::Fragment,
.texture =
{
.sampleType = wgpu::TextureSampleType::Float,
.viewDimension = wgpu::TextureViewDimension::e2D,
},
};
++numTextures;
}
{
const wgpu::BindGroupLayoutDescriptor descriptor{
.label = "GX Sampler Bind Group Layout",
.entryCount = numSamplers,
.entryCount = numTextures,
.entries = samplerEntries.data(),
};
out.samplerLayout = g_device.CreateBindGroupLayout(&descriptor);
sSamplerBindGroupLayout = g_device.CreateBindGroupLayout(&descriptor);
}
{
const wgpu::BindGroupLayoutDescriptor descriptor{
@@ -672,20 +603,31 @@ GXBindGroupLayouts build_bind_group_layouts(const ShaderInfo& info, const Shader
.entryCount = numTextures,
.entries = textureEntries.data(),
};
out.textureLayout = g_device.CreateBindGroupLayout(&descriptor);
sTextureBindGroupLayout = g_device.CreateBindGroupLayout(&descriptor);
}
{
std::lock_guard lock{sBindGroupLayoutMutex};
sTextureBindGroupLayouts.try_emplace(textureLayoutHash, std::make_pair(out.samplerLayout, out.textureLayout));
constexpr wgpu::SamplerDescriptor descriptor{.label = "Empty sampler"};
sEmptySampler = gfx::sampler_ref(descriptor);
}
{
constexpr wgpu::TextureDescriptor descriptor{
.label = "Empty texture",
.usage = wgpu::TextureUsage::TextureBinding,
.size = {1, 1},
.format = wgpu::TextureFormat::RGBA8Unorm,
};
sEmptyTexture = g_device.CreateTexture(&descriptor);
sEmptyTextureView = sEmptyTexture.CreateView();
}
return out;
}
// TODO this is awkward
extern std::mutex g_gxCachedShadersMutex;
extern absl::flat_hash_map<gfx::ShaderRef, std::pair<wgpu::ShaderModule, gx::ShaderInfo>> g_gxCachedShaders;
extern absl::flat_hash_map<gfx::ShaderRef, std::pair<wgpu::ShaderModule, ShaderInfo>> g_gxCachedShaders;
void shutdown() noexcept {
// TODO we should probably store this all in g_state.gx instead
sSamplerBindGroupLayout = {};
sTextureBindGroupLayout = {};
{
std::lock_guard lock{sBindGroupLayoutMutex};
sUniformBindGroupLayouts.clear();
@@ -716,6 +658,7 @@ static wgpu::AddressMode wgpu_address_mode(GXTexWrapMode mode) {
return wgpu::AddressMode::MirrorRepeat;
}
}
static std::pair<wgpu::FilterMode, wgpu::MipmapFilterMode> wgpu_filter_mode(GXTexFilter filter) {
switch (filter) {
DEFAULT_FATAL("invalid filter mode {}", static_cast<int>(filter));
@@ -733,6 +676,7 @@ static std::pair<wgpu::FilterMode, wgpu::MipmapFilterMode> wgpu_filter_mode(GXTe
return {wgpu::FilterMode::Linear, wgpu::MipmapFilterMode::Linear};
}
}
static u16 wgpu_aniso(GXAnisotropy aniso) {
switch (aniso) {
DEFAULT_FATAL("invalid aniso {}", static_cast<int>(aniso));
@@ -744,19 +688,8 @@ static u16 wgpu_aniso(GXAnisotropy aniso) {
return std::max<u16>(aurora::webgpu::g_graphicsConfig.textureAnisotropy, 1);
}
}
wgpu::SamplerDescriptor aurora::gfx::TextureBind::get_descriptor() const noexcept {
if (gx::requires_copy_conversion(texObj) && gx::is_palette_format(texObj.ref->gxFormat)) {
return {
.label = "Generated Non-Filtering Sampler",
.addressModeU = wgpu_address_mode(texObj.wrapS),
.addressModeV = wgpu_address_mode(texObj.wrapT),
.addressModeW = wgpu::AddressMode::Repeat,
.magFilter = wgpu::FilterMode::Nearest,
.minFilter = wgpu::FilterMode::Nearest,
.mipmapFilter = wgpu::MipmapFilterMode::Nearest,
.maxAnisotropy = 1,
};
}
const auto [minFilter, mipFilter] = wgpu_filter_mode(texObj.minFilter);
const auto [magFilter, _] = wgpu_filter_mode(texObj.magFilter);
return {

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