diff --git a/CMakeLists.txt b/CMakeLists.txt index 106b830..a8ce753 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/cmake/aurora_gx.cmake b/cmake/aurora_gx.cmake index 159c048..3030562 100644 --- a/cmake/aurora_gx.cmake +++ b/cmake/aurora_gx.cmake @@ -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 diff --git a/include/aurora/gfx.h b/include/aurora/gfx.h index 0ddced1..e1df519 100644 --- a/include/aurora/gfx.h +++ b/include/aurora/gfx.h @@ -25,6 +25,7 @@ typedef struct { uint32_t lastUniformSize; uint32_t lastIndexSize; uint32_t lastStorageSize; + uint32_t lastTextureUploadSize; } AuroraStats; const AuroraStats* aurora_get_stats(); diff --git a/lib/dolphin/gx/GXFrameBuffer.cpp b/lib/dolphin/gx/GXFrameBuffer.cpp index 33dc004..0657574 100644 --- a/lib/dolphin/gx/GXFrameBuffer.cpp +++ b/lib/dolphin/gx/GXFrameBuffer.cpp @@ -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 diff --git a/lib/dolphin/gx/GXPixel.cpp b/lib/dolphin/gx/GXPixel.cpp index 80b30ea..7483010 100644 --- a/lib/dolphin/gx/GXPixel.cpp +++ b/lib/dolphin/gx/GXPixel.cpp @@ -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(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(pix_fmt) - GX_PF_Y8) & 0x3); + GX_WRITE_RAS_REG(__gx->cmode1); + } + + __gx->bpSent = 1; } void GXSetDither(GXBool dither) { diff --git a/lib/dolphin/gx/GXTexture.cpp b/lib/dolphin/gx/GXTexture.cpp index 2b26fe8..bbbe34e 100644 --- a/lib/dolphin/gx/GXTexture.cpp +++ b/lib/dolphin/gx/GXTexture.cpp @@ -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 @@ -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(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(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? } diff --git a/lib/gfx/clear.cpp b/lib/gfx/clear.cpp new file mode 100644 index 0000000..8c57cdb --- /dev/null +++ b/lib/gfx/clear.cpp @@ -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, +}; + +var pos: array, 3> = array, 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(pos[vtxIdx], 0.0, 1.0); + return out; +} + +@fragment +fn fs_main() -> @location(0) vec4 { + return vec4(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(size.width), static_cast(size.height), data.depth, data.depth); + pass.SetScissorRect(0, 0, size.width, size.height); + pass.Draw(3); +} +} // namespace aurora::gfx::clear \ No newline at end of file diff --git a/lib/gfx/clear.hpp b/lib/gfx/clear.hpp new file mode 100644 index 0000000..b064f2b --- /dev/null +++ b/lib/gfx/clear.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "common.hpp" + +#include + +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); + +wgpu::RenderPipeline create_pipeline(const PipelineConfig& config); +void render(const DrawData& data, const wgpu::RenderPassEncoder& pass); +} // namespace aurora::gfx::clear \ No newline at end of file diff --git a/lib/gfx/common.cpp b/lib/gfx/common.cpp index 833e9db..c579a69 100644 --- a/lib/gfx/common.cpp +++ b/lib/gfx/common.cpp @@ -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 @@ -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 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; struct RenderPass { TextureHandle resolveTarget; ClipRect resolveRect; - Vec4 clearColor{0.f, 0.f, 0.f, 0.f}; - float clearDepth = gx::UseReversedZ ? 0.f : 1.f; + Vec4 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 copyConvs; + std::vector paletteConvs; }; static std::vector 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 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 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(pipelineCache.data() + offset); offset += sizeof(u32); switch (type) { + case ShaderType::Clear: { + if (size != sizeof(clear::PipelineConfig)) { + break; + } + const auto config = *reinterpret_cast(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(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; diff --git a/lib/gfx/common.hpp b/lib/gfx/common.hpp index d0b3de4..e723208 100644 --- a/lib/gfx/common.hpp +++ b/lib/gfx/common.hpp @@ -200,6 +200,7 @@ struct TextureRef; using TextureHandle = std::shared_ptr; 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 clearColor); +void resolve_pass(TextureHandle texture, ClipRect rect, bool clearColor, bool clearAlpha, bool clearDepth, + Vec4 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 diff --git a/lib/gfx/tex_copy_conv.cpp b/lib/gfx/tex_copy_conv.cpp new file mode 100644 index 0000000..667c53f --- /dev/null +++ b/lib/gfx/tex_copy_conv.cpp @@ -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 + +#include + +#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; + +struct VertexOutput { + @builtin(position) pos: vec4f, + @location(0) uv: vec2f, +}; + +var positions: array = array( + vec2f(-1.0, 1.0), + vec2f(-1.0, -3.0), + vec2f(3.0, 1.0), +); +var uvs: array = 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 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(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 diff --git a/lib/gfx/tex_copy_conv.hpp b/lib/gfx/tex_copy_conv.hpp new file mode 100644 index 0000000..55a699b --- /dev/null +++ b/lib/gfx/tex_copy_conv.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "common.hpp" + +#include + +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 diff --git a/lib/gfx/tex_palette_conv.cpp b/lib/gfx/tex_palette_conv.cpp new file mode 100644 index 0000000..842cca3 --- /dev/null +++ b/lib/gfx/tex_palette_conv.cpp @@ -0,0 +1,260 @@ +#include "tex_palette_conv.hpp" + +#include "../internal.hpp" +#include "../webgpu/gpu.hpp" +#include "texture.hpp" + +#include + +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 positions: array = array( + vec2f(-1.0, 1.0), + vec2f(-1.0, -3.0), + vec2f(3.0, 1.0), +); +var uvs: array = 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; +@group(0) @binding(2) var tlut: texture_2d; + +@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; +@group(0) @binding(2) var tlut: texture_2d; + +@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; +@group(0) @binding(2) var tlut: texture_2d; + +@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(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 diff --git a/lib/gfx/tex_palette_conv.hpp b/lib/gfx/tex_palette_conv.hpp new file mode 100644 index 0000000..fca8a38 --- /dev/null +++ b/lib/gfx/tex_palette_conv.hpp @@ -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 diff --git a/lib/gfx/texture.cpp b/lib/gfx/texture.cpp index 59c5953..96088b5 100644 --- a/lib/gfx/texture.cpp +++ b/lib/gfx/texture.cpp @@ -5,6 +5,7 @@ #include "aurora/aurora.h" #include "texture.hpp" #include "texture_convert.hpp" +#include "../gx/gx_fmt.hpp" #include #include @@ -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(std::move(texture), std::move(textureView), size, wgpuFormat, mips, format, - false); + return std::make_shared(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(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(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(std::move(texture), std::move(sampleTextureView), + std::move(attachmentTextureView), size, wgpuFormat, 1, gxFormat, false); } void write_texture(const TextureRef& ref, ArrayRef data) noexcept { @@ -209,26 +275,17 @@ void write_texture(const TextureRef& ref, ArrayRef 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()) { diff --git a/lib/gfx/texture.hpp b/lib/gfx/texture.hpp index 28c4321..8c3af65 100644 --- a/lib/gfx/texture.hpp +++ b/lib/gfx/texture.hpp @@ -17,17 +17,19 @@ extern std::vector 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 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 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 data) noexcept; }; // namespace aurora::gfx diff --git a/lib/gfx/texture_convert.cpp b/lib/gfx/texture_convert.cpp index 5a24a55..599a9d5 100644 --- a/lib/gfx/texture_convert.cpp +++ b/lib/gfx/texture_convert.cpp @@ -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 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; } }; diff --git a/lib/gfx/texture_convert.hpp b/lib/gfx/texture_convert.hpp index 268f4cf..4713154 100644 --- a/lib/gfx/texture_convert.hpp +++ b/lib/gfx/texture_convert.hpp @@ -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: diff --git a/lib/gx/command_processor.cpp b/lib/gx/command_processor.cpp index 476c805..04b1644 100644 --- a/lib/gx/command_processor.cpp +++ b/lib/gx/command_processor.cpp @@ -120,6 +120,38 @@ static inline u32 read_u32(const u8* ptr, bool bigEndian) { static_cast(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(bp_get(value, 3, 3)); + g_gxState.zCompLocBeforeTex = bp_get(value, 1, 6) != 0; + g_gxState.stateDirty = true; break; } diff --git a/lib/gx/gx.cpp b/lib/gx/gx.cpp index 611176b..3be8ff5 100644 --- a/lib/gx/gx.cpp +++ b/lib/gx/gx.cpp @@ -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(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 vtxBuffers, wgpu::ShaderModule shader, - const char* label) noexcept { +wgpu::RenderPipeline build_pipeline(const PipelineConfig& config, ArrayRef 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(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 sUniformBindGroupLayouts; static absl::flat_hash_map> 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 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 samplerEntries; - std::array textureEntries; - memset(&samplerEntries, 0, sizeof(samplerEntries)); - memset(&textureEntries, 0, sizeof(textureEntries)); - u32 samplerCount = 0; + std::array 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 samplerEntries; - std::array 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 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> g_gxCachedShaders; +extern absl::flat_hash_map> 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_filter_mode(GXTexFilter filter) { switch (filter) { DEFAULT_FATAL("invalid filter mode {}", static_cast(filter)); @@ -733,6 +676,7 @@ static std::pair 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(aniso)); @@ -744,19 +688,8 @@ static u16 wgpu_aniso(GXAnisotropy aniso) { return std::max(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 { diff --git a/lib/gx/gx.hpp b/lib/gx/gx.hpp index d15cce9..92d841f 100644 --- a/lib/gx/gx.hpp +++ b/lib/gx/gx.hpp @@ -298,6 +298,9 @@ struct GXState { GXCompare depthFunc = GX_LEQUAL; Vec4 clearColor{0.f, 0.f, 0.f, 1.f}; u32 clearDepth = 0xFFFFFF; + GXPixelFmt pixelFmt = GX_PF_RGB8_Z24; + GXZFmt16 zFmt = GX_ZC_LINEAR; + bool zCompLocBeforeTex = false; u32 dstAlpha; // u8; UINT32_MAX = disabled AlphaCompare alphaCompare; std::array, MaxTevRegs> colorRegs; @@ -342,6 +345,7 @@ struct GXState { }; absl::flat_hash_map copyTextures; absl::flat_hash_map copyTextureCache; + absl::flat_hash_map convTextureCache; bool depthCompare = true; bool depthUpdate = true; bool colorUpdate = true; @@ -356,29 +360,16 @@ struct GXState { }; extern GXState g_gxState; +void initialize() noexcept; void shutdown() noexcept; void clear_copy_texture_cache() noexcept; const gfx::TextureBind& get_texture(GXTexMapID id) noexcept; -static inline bool requires_copy_conversion(const GXTexObj_& obj) { - if (!obj.ref) { - return false; - } - if (obj.ref->isRenderTexture) { - return true; - } - switch (obj.ref->gxFormat) { - // case GX_TF_RGB565: - // case GX_TF_I4: - // case GX_TF_I8: - case GX_TF_C4: - case GX_TF_C8: - case GX_TF_C14X2: - return true; - default: - return false; - } +inline float clear_depth_value() { + const float normalizedDepth = static_cast(g_gxState.clearDepth) / 16777215.f; + return UseReversedZ ? (1.f - normalizedDepth) : normalizedDepth; } + static inline bool requires_load_conversion(const GXTexObj_& obj) { if (!obj.ref) { return false; @@ -397,17 +388,6 @@ static inline bool requires_load_conversion(const GXTexObj_& obj) { } static inline bool is_palette_format(u32 fmt) { return fmt == GX_TF_C4 || fmt == GX_TF_C8 || fmt == GX_TF_C14X2; } -struct TextureConfig { - u32 copyFmt = gfx::InvalidTextureFormat; // Underlying texture format - u32 loadFmt = gfx::InvalidTextureFormat; // Texture format being bound - bool renderTex = false; // Perform conversion - u8 _p1 = 0; - u8 _p2 = 0; - u8 _p3 = 0; - - bool operator==(const TextureConfig& rhs) const { return memcmp(this, &rhs, sizeof(*this)) == 0; } -}; -static_assert(std::has_unique_object_representations_v); struct AttrConfig { u8 attrType = GX_NONE; // GXAttrType u8 cnt = 0xFF; // Actual count; not GXCompCnt @@ -431,7 +411,6 @@ struct ShaderConfig { std::array colorChannels; std::array tcgs; AlphaCompare alphaCompare; - std::array textureConfig; std::array indStages{}; u32 numIndStages = 0; @@ -473,11 +452,10 @@ struct BindGroupRanges { std::array vaRanges{}; }; void populate_pipeline_config(PipelineConfig& config, GXPrimitive primitive, GXVtxFmt fmt) noexcept; -wgpu::RenderPipeline build_pipeline(const PipelineConfig& config, const ShaderInfo& info, - ArrayRef vtxBuffers, wgpu::ShaderModule shader, - const char* label) noexcept; -wgpu::ShaderModule build_shader(const ShaderConfig& config, const ShaderInfo& info) noexcept; -GXBindGroupLayouts build_bind_group_layouts(const ShaderInfo& info, const ShaderConfig& config) noexcept; +wgpu::RenderPipeline build_pipeline(const PipelineConfig& config, ArrayRef vtxBuffers, + wgpu::ShaderModule shader, const char* label) noexcept; +wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept; +GXBindGroupLayouts build_bind_group_layouts(const ShaderConfig& config) noexcept; GXBindGroups build_bind_groups(const ShaderInfo& info, const ShaderConfig& config, const BindGroupRanges& ranges) noexcept; diff --git a/lib/gx/gx_fmt.hpp b/lib/gx/gx_fmt.hpp index 73cd4d3..0cca1a1 100644 --- a/lib/gx/gx_fmt.hpp +++ b/lib/gx/gx_fmt.hpp @@ -876,3 +876,62 @@ inline std::string format_as(const GXVtxFmt& fmt) { return fmt::format("GXVtxFmt({})", underlying(fmt)); } } + +inline std::string format_as(const GXTexFmt& fmt) { + switch (fmt) { + case GX_TF_I4: + return "GX_TF_I4"; + case GX_TF_I8: + return "GX_TF_I8"; + case GX_TF_IA4: + return "GX_TF_IA4"; + case GX_TF_IA8: + return "GX_TF_IA8"; + case GX_TF_RGB565: + return "GX_TF_RGB565"; + case GX_TF_RGB5A3: + return "GX_TF_RGB5A3"; + case GX_TF_RGBA8: + return "GX_TF_RGBA8"; + case GX_TF_CMPR: + return "GX_TF_CMPR"; + case GX_CTF_R4: + return "GX_CTF_R4"; + case GX_CTF_RA4: + return "GX_CTF_RA4"; + case GX_CTF_RA8: + return "GX_CTF_RA8"; + case GX_CTF_YUVA8: + return "GX_CTF_YUVA8"; + case GX_CTF_A8: + return "GX_CTF_A8"; + case GX_CTF_R8: + return "GX_CTF_R8"; + case GX_CTF_G8: + return "GX_CTF_G8"; + case GX_CTF_B8: + return "GX_CTF_B8"; + case GX_CTF_RG8: + return "GX_CTF_RG8"; + case GX_CTF_GB8: + return "GX_CTF_GB8"; + case GX_TF_Z8: + return "GX_TF_Z8"; + case GX_TF_Z16: + return "GX_TF_Z16"; + case GX_TF_Z24X8: + return "GX_TF_Z24X8"; + case GX_CTF_Z4: + return "GX_CTF_Z4"; + case GX_CTF_Z8M: + return "GX_CTF_Z8M"; + case GX_CTF_Z8L: + return "GX_CTF_Z8L"; + case GX_CTF_Z16L: + return "GX_CTF_Z16L"; + case GX_TF_R8_PC: + return "GX_TF_R8_PC"; + case GX_TF_RGBA8_PC: + return "GX_TF_RGBA8_PC"; + } +} \ No newline at end of file diff --git a/lib/gx/pipeline.cpp b/lib/gx/pipeline.cpp index c7c6172..b851175 100644 --- a/lib/gx/pipeline.cpp +++ b/lib/gx/pipeline.cpp @@ -2,23 +2,17 @@ #include "../webgpu/gpu.hpp" #include "gx_fmt.hpp" -#include "command_processor.hpp" #include "shader_info.hpp" -#include - namespace aurora::gx { static Module Log("aurora::gx"); -State construct_state() { return {}; } - -wgpu::RenderPipeline create_pipeline(const State& state, const PipelineConfig& config) { - const auto info = build_shader_info(config.shaderConfig); // TODO remove - const auto shader = build_shader(config.shaderConfig, info); - return build_pipeline(config, info, {}, shader, "GX Pipeline"); +wgpu::RenderPipeline create_pipeline(const PipelineConfig& config) { + const auto shader = build_shader(config.shaderConfig); + return build_pipeline(config, {}, shader, "GX Pipeline"); } -void render(const State& state, const DrawData& data, const wgpu::RenderPassEncoder& pass) { +void render(const DrawData& data, const wgpu::RenderPassEncoder& pass) { if (!gfx::bind_pipeline(data.pipeline, pass)) { return; } diff --git a/lib/gx/pipeline.hpp b/lib/gx/pipeline.hpp index da2e541..6196ca8 100644 --- a/lib/gx/pipeline.hpp +++ b/lib/gx/pipeline.hpp @@ -17,7 +17,7 @@ struct DrawData { u32 dstAlpha; }; -constexpr u32 GXPipelineConfigVersion = 9; +constexpr u32 GXPipelineConfigVersion = 10; struct PipelineConfig { u32 version = GXPipelineConfigVersion; ShaderConfig shaderConfig; @@ -31,11 +31,8 @@ struct PipelineConfig { }; static_assert(std::has_unique_object_representations_v); -struct State {}; - -State construct_state(); -wgpu::RenderPipeline create_pipeline(const State& state, [[maybe_unused]] const PipelineConfig& config); -void render(const State& state, const DrawData& data, const wgpu::RenderPassEncoder& pass); +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; } // namespace aurora::gx diff --git a/lib/gx/shader.cpp b/lib/gx/shader.cpp index 1e4ab30..e8fec75 100644 --- a/lib/gx/shader.cpp +++ b/lib/gx/shader.cpp @@ -4,6 +4,7 @@ #include "../webgpu/gpu.hpp" #include "gx.hpp" #include "gx_fmt.hpp" +#include "shader_info.hpp" #include @@ -89,7 +90,7 @@ static bool uses_texture_sample(const TevStage& stage) noexcept { a.c == GX_CA_TEXA || a.d == GX_CA_TEXA; } -u8 color_channel(GXChannelID id) { +u8 color_channel(GXChannelID id) noexcept { switch (id) { DEFAULT_FATAL("unimplemented color channel {}", id); case GX_COLOR0: @@ -476,38 +477,6 @@ static inline std::string vtx_attr(const ShaderConfig& config, GXAttr attr) { UNLIKELY FATAL("unhandled vtx attr {}", underlying(attr)); } -static inline std::string texture_conversion(const TextureConfig& tex, u32 stageIdx, u32 texMapId) { - std::string out; - if (tex.renderTex) - switch (tex.copyFmt) { - default: - break; - case GX_TF_RGB565: - // Set alpha channel to 1.0 - out += fmt::format("\n sampled{0}.a = 1.0;", stageIdx); - break; - case GX_TF_I4: - case GX_TF_I8: - // FIXME HACK - if (!is_palette_format(tex.loadFmt)) { - // Perform intensity conversion - out += fmt::format("\n sampled{0} = vec4f(intensityF32(sampled{0}.rgb), 0.f, 0.f, 1.f);", stageIdx); - } - break; - } - switch (tex.loadFmt) { - default: - break; - case GX_TF_I4: - case GX_TF_I8: - case GX_TF_R8_PC: - // Splat R to RGBA - out += fmt::format("\n sampled{0} = vec4f(sampled{0}.r);", stageIdx); - break; - } - return out; -} - constexpr std::array TevColorArgNames{ "CPREV"sv, "APREV"sv, "C0"sv, "A0"sv, "C1"sv, "A1"sv, "C2"sv, "A2"sv, "TEXC"sv, "TEXA"sv, "RASC"sv, "RASA"sv, "ONE"sv, "HALF"sv, "KONST"sv, "ZERO"sv, @@ -620,7 +589,7 @@ auto attr_load(const ShaderConfig& config, GXAttr attr, std::string_view vidx) - } } -wgpu::ShaderModule build_shader(const ShaderConfig& config, const ShaderInfo& info) noexcept { +wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept { const auto hash = xxh3_hash(config); { std::lock_guard lock{g_gxCachedShadersMutex}; @@ -631,6 +600,7 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config, const ShaderInfo& in } } + const auto info = build_shader_info(config); if (EnableDebugPrints) { Log.info("Shader config (hash {:x}):", hash); { @@ -733,8 +703,7 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config, const ShaderInfo& in "\n let in_pos = {};" "\n let in_pnmtxidx = {};" "\n let mv_pos = vec4f(in_pos, 1.0) * ubuf.postex_mtx[in_pnmtxidx];", - attr_load(config, GX_VA_POS, "in_vidx"sv), - attr_load(config, GX_VA_PNMTXIDX, "in_vidx"sv)); + attr_load(config, GX_VA_POS, "in_vidx"sv), attr_load(config, GX_VA_PNMTXIDX, "in_vidx"sv)); } else { // GX_LINES / GX_LINESTRIP: each instance = two vertices, expand to quad vtxXfrAttrsPre += fmt::format( @@ -1365,31 +1334,9 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config, const ShaderInfo& in // No indirect texturing uvIn = fmt::format("tex{0}_uv", underlying(stage.texCoordId)); } - const auto& texConfig = config.textureConfig[stage.texMapId]; - if (is_palette_format(texConfig.loadFmt)) { - std::string_view suffix; - if (!is_palette_format(texConfig.copyFmt)) { - switch (texConfig.loadFmt) { - DEFAULT_FATAL("unimplemented palette format {}", texConfig.loadFmt); - case GX_TF_C4: - suffix = "I4"sv; - break; - case GX_TF_C8: - suffix = "I8"sv; - break; - // case GX_TF_C14X2: - // suffix = "I14X2"; - // break; - } - } - fragmentFnPre += fmt::format("\n var sampled{0} = textureSamplePalette{3}(tex{1}, tex{1}_samp, {2}, tlut{1});", - i, underlying(stage.texMapId), uvIn, suffix); - } else { - fragmentFnPre += - fmt::format("\n var sampled{0} = textureSampleBias(tex{1}, tex{1}_samp, {2}, ubuf.tex{1}_size_bias.z);", i, - underlying(stage.texMapId), uvIn); - } - fragmentFnPre += texture_conversion(texConfig, i, stage.texMapId); + fragmentFnPre += + fmt::format("\n var sampled{0} = textureSampleBias(tex{1}, tex{1}_samp, {2}, ubuf.tex{1}_size_bias.z);", i, + underlying(stage.texMapId), uvIn); } if (info.usesPTTexMtx.any()) uniBufAttrs += fmt::format("\n postmtx: array,", MaxPTTexMtx); @@ -1438,38 +1385,19 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config, const ShaderInfo& in if (info.usedIndTexMtxs.any()) { uniBufAttrs += "\n ind_mtx: array,"; } - size_t sampBindIdx = 0; - size_t texBindIdx = 0; for (int i = 0; i < info.sampledTextures.size(); ++i) { if (!info.sampledTextures.test(i)) { continue; } uniBufAttrs += fmt::format("\n tex{}_size_bias: vec4f,", i); - sampBindings += fmt::format( - "\n@group(1) @binding({})\n" - "var tex{}_samp: sampler;", - sampBindIdx, i); - ++sampBindIdx; - - const auto& texConfig = config.textureConfig[i]; - if (is_palette_format(texConfig.loadFmt)) { - texBindings += fmt::format( - "\n@group(2) @binding({})\n" - "var tex{}: texture_2d<{}>;", - texBindIdx, i, is_palette_format(texConfig.copyFmt) ? "i32"sv : "f32"sv); - ++texBindIdx; - texBindings += fmt::format( - "\n@group(2) @binding({})\n" - "var tlut{}: texture_2d;", - texBindIdx, i); - } else { - texBindings += fmt::format( - "\n@group(2) @binding({})\n" - "var tex{}: texture_2d;", - texBindIdx, i); - } - ++texBindIdx; + "\n@group(1) @binding({0})\n" + "var tex{0}_samp: sampler;", + i); + texBindings += fmt::format( + "\n@group(2) @binding({0})\n" + "var tex{0}: texture_2d;", + i); } fragmentFn += "\n prev = tev_overflow_vec4f(prev);"; if (config.alphaCompare) { @@ -1832,74 +1760,6 @@ struct VertexOutput {{ @builtin(position) pos: vec4f,{4} }}; -fn intensityF32(rgb: vec3f) -> f32 {{ - // RGB to intensity conversion - // https://github.com/dolphin-emu/dolphin/blob/4cd48e609c507e65b95bca5afb416b59eaf7f683/Source/Core/VideoCommon/TextureConverterShaderGen.cpp#L237-L241 - return dot(rgb, vec3(0.257, 0.504, 0.098)) + 16.0 / 255.0; -}} -fn intensityI4(rgb: vec3f) -> i32 {{ - return i32(intensityF32(rgb) * 16.f); -}} -fn intensityI8(rgb: vec3f) -> i32 {{ - return i32(intensityF32(rgb)); -}} -fn textureSamplePalette(tex: texture_2d, samp: sampler, uv: vec2f, tlut: texture_2d) -> vec4f {{ - // Gather index values - var i = textureGather(0, tex, samp, uv); - // Load palette colors - var c0 = textureLoad(tlut, vec2i(i[0], 0), 0); - var c1 = textureLoad(tlut, vec2i(i[1], 0), 0); - var c2 = textureLoad(tlut, vec2i(i[2], 0), 0); - var c3 = textureLoad(tlut, vec2i(i[3], 0), 0); - // Perform bilinear filtering - var f = fract(uv * vec2f(textureDimensions(tex)) + 0.5); - var t0 = mix(c3, c2, f.x); - var t1 = mix(c0, c1, f.x); - return mix(t0, t1, f.y); -}} -fn textureSamplePaletteI4(tex: texture_2d, samp: sampler, uv: vec2f, tlut: texture_2d) -> vec4f {{ - // Gather RGB channels - var iR = textureGather(0, tex, samp, uv); - var iG = textureGather(1, tex, samp, uv); - var iB = textureGather(2, tex, samp, uv); - // Perform intensity conversion - var i0 = intensityI4(vec3f(iR[0], iG[0], iB[0])); - var i1 = intensityI4(vec3f(iR[1], iG[1], iB[1])); - var i2 = intensityI4(vec3f(iR[2], iG[2], iB[2])); - var i3 = intensityI4(vec3f(iR[3], iG[3], iB[3])); - // Load palette colors - var c0 = textureLoad(tlut, vec2i(i0, 0), 0); - var c1 = textureLoad(tlut, vec2i(i1, 0), 0); - var c2 = textureLoad(tlut, vec2i(i2, 0), 0); - var c3 = textureLoad(tlut, vec2i(i3, 0), 0); - // Perform bilinear filtering - var f = fract(uv * vec2f(textureDimensions(tex)) + 0.5); - var t0 = mix(c3, c2, f.x); - var t1 = mix(c0, c1, f.x); - return mix(t0, t1, f.y); -}} -fn textureSamplePaletteI8(tex: texture_2d, samp: sampler, uv: vec2f, tlut: texture_2d) -> vec4f {{ - // Gather RGB channels - var iR = textureGather(0, tex, samp, uv); - var iG = textureGather(1, tex, samp, uv); - var iB = textureGather(2, tex, samp, uv); - // Perform intensity conversion - var i0 = intensityI8(vec3f(iR[0], iG[0], iB[0])); - var i1 = intensityI8(vec3f(iR[1], iG[1], iB[1])); - var i2 = intensityI8(vec3f(iR[2], iG[2], iB[2])); - var i3 = intensityI8(vec3f(iR[3], iG[3], iB[3])); - // Load palette colors - var c0 = textureLoad(tlut, vec2i(i0, 0), 0); - var c1 = textureLoad(tlut, vec2i(i1, 0), 0); - var c2 = textureLoad(tlut, vec2i(i2, 0), 0); - var c3 = textureLoad(tlut, vec2i(i3, 0), 0); - // Perform bilinear filtering - var f = fract(uv * vec2f(textureDimensions(tex)) + 0.5); - var t0 = mix(c3, c2, f.x); - var t1 = mix(c0, c1, f.x); - return mix(t0, t1, f.y); -}} - @vertex fn vs_main( @builtin(vertex_index) vidx: u32{5} diff --git a/lib/gx/shader_info.cpp b/lib/gx/shader_info.cpp index d0aebea..63865f7 100644 --- a/lib/gx/shader_info.cpp +++ b/lib/gx/shader_info.cpp @@ -216,6 +216,10 @@ ShaderInfo build_shader_info(const ShaderConfig& config) noexcept { } for (int i = 0; i < config.tevStageCount; ++i) { const auto& stage = config.tevStages[i]; + // Skip if not enabled + if (stage.indTexStage >= config.numIndStages) { + continue; + } const bool usesIndStage = stage.indTexMtxId != GX_ITM_OFF || is_alpha_bump_channel(stage.channelId); const bool usesTevTexCoord = stage.indTexMtxId != GX_ITM_OFF || stage.indTexWrapS != GX_ITW_OFF || stage.indTexWrapT != GX_ITW_OFF || stage.indTexAddPrev; @@ -229,10 +233,6 @@ ShaderInfo build_shader_info(const ShaderConfig& config) noexcept { if (!usesIndStage) { continue; } - // Skip if not enabled - if (stage.indTexStage >= config.numIndStages) { - continue; - } info.usedIndStages.set(stage.indTexStage); const auto& indStage = config.indStages[stage.indTexStage]; info.sampledTextures.set(indStage.texMapId); diff --git a/lib/webgpu/gpu.cpp b/lib/webgpu/gpu.cpp index 64cf4bd..a22bd33 100644 --- a/lib/webgpu/gpu.cpp +++ b/lib/webgpu/gpu.cpp @@ -70,8 +70,6 @@ TextureWithSampler create_render_texture(bool multisampled) { const wgpu::TextureViewDescriptor viewDescriptor{ .label = "Render texture view", .dimension = wgpu::TextureViewDimension::e2D, - .mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED, - .arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED, }; auto view = texture.CreateView(&viewDescriptor); @@ -119,8 +117,6 @@ static TextureWithSampler create_depth_texture() { const wgpu::TextureViewDescriptor viewDescriptor{ .label = "Depth texture view", .dimension = wgpu::TextureViewDimension::e2D, - .mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED, - .arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED, }; auto view = texture.CreateView(&viewDescriptor); @@ -421,12 +417,10 @@ bool initialize(AuroraBackend auroraBackend) { .maxStorageBuffersPerShaderStage = supportedLimits.maxStorageBuffersPerShaderStage == 0 ? WGPU_LIMIT_U32_UNDEFINED : supportedLimits.maxStorageBuffersPerShaderStage, - .minUniformBufferOffsetAlignment = supportedLimits.minUniformBufferOffsetAlignment < 64 - ? 64 - : supportedLimits.minUniformBufferOffsetAlignment, - .minStorageBufferOffsetAlignment = supportedLimits.minStorageBufferOffsetAlignment < 16 - ? 16 - : supportedLimits.minStorageBufferOffsetAlignment, + .minUniformBufferOffsetAlignment = + supportedLimits.minUniformBufferOffsetAlignment < 64 ? 64 : supportedLimits.minUniformBufferOffsetAlignment, + .minStorageBufferOffsetAlignment = + supportedLimits.minStorageBufferOffsetAlignment < 16 ? 16 : supportedLimits.minStorageBufferOffsetAlignment, }; Log.info( "Using limits:" @@ -603,8 +597,8 @@ void resize_swapchain(uint32_t width, uint32_t height, bool force) { if (!g_surface || !g_device || width == 0 || height == 0) { return; } - const bool sizeChanged = g_graphicsConfig.surfaceConfiguration.width != width || - g_graphicsConfig.surfaceConfiguration.height != height; + const bool sizeChanged = + g_graphicsConfig.surfaceConfiguration.width != width || g_graphicsConfig.surfaceConfiguration.height != height; if (!force && !sizeChanged) { return; } diff --git a/lib/webgpu/gpu.hpp b/lib/webgpu/gpu.hpp index a0833c9..d8f5423 100644 --- a/lib/webgpu/gpu.hpp +++ b/lib/webgpu/gpu.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include "wgpu.hpp" @@ -41,4 +42,6 @@ void shutdown(); bool refresh_surface(bool recreate = true); void resize_swapchain(uint32_t width, uint32_t height, bool force = false); TextureWithSampler create_render_texture(bool multisampled); +void draw_clear(const wgpu::RenderPassEncoder& pass, bool clearColor, bool clearAlpha, bool clearDepth, + const Vec4& clearColorValue, float clearDepthValue); } // namespace aurora::webgpu diff --git a/lib/webgpu/wgpu.hpp b/lib/webgpu/wgpu.hpp index 5fd1aa4..a449fb7 100644 --- a/lib/webgpu/wgpu.hpp +++ b/lib/webgpu/wgpu.hpp @@ -1,3 +1,5 @@ +#pragma once + #include #ifdef EMSCRIPTEN #include diff --git a/tests/gx_fifo_test.cpp b/tests/gx_fifo_test.cpp index 8c62c57..a6a70b7 100644 --- a/tests/gx_fifo_test.cpp +++ b/tests/gx_fifo_test.cpp @@ -6,10 +6,16 @@ #include "gx_test_common.hpp" +#include #include using aurora::gx::g_gxState; +static bool has_bp_write(const std::vector& bytes, u8 reg) { + const std::array pattern{0x61, reg}; + return std::search(bytes.begin(), bytes.end(), pattern.begin(), pattern.end()) != bytes.end(); +} + // ============================================================================ // BP registers (direct FIFO writes, no dirty state flush needed) // ============================================================================ @@ -190,6 +196,44 @@ TEST_F(GXFifoTest, DstAlpha_Disabled) { EXPECT_EQ(g_gxState.dstAlpha, UINT32_MAX); } +// --- GXSetPixelFmt (BP 0x43, 0x42 + genMode flush) --- + +TEST_F(GXFifoTest, PixelFmt_Rgb565Z16_Decode) { + GXSetPixelFmt(GX_PF_RGB565_Z16, GX_ZC_FAR); + auto bytes = flush_and_capture(); + + EXPECT_TRUE(has_bp_write(bytes, 0x43)); + EXPECT_TRUE(has_bp_write(bytes, 0x00)); + + reset_gx_state(); + g_gxState.pixelFmt = GX_PF_RGB8_Z24; + g_gxState.zFmt = GX_ZC_LINEAR; + decode_fifo(bytes); + + EXPECT_EQ(g_gxState.pixelFmt, GX_PF_RGB565_Z16); + EXPECT_EQ(g_gxState.zFmt, GX_ZC_FAR); + EXPECT_TRUE(g_gxState.zCompLocBeforeTex); +} + +TEST_F(GXFifoTest, PixelFmt_U8_Decode) { + GXSetPixelFmt(GX_PF_U8, GX_ZC_MID); + auto bytes = flush_and_capture(); + + EXPECT_TRUE(has_bp_write(bytes, 0x43)); + EXPECT_TRUE(has_bp_write(bytes, 0x42)); + EXPECT_TRUE(has_bp_write(bytes, 0x00)); + + reset_gx_state(); + g_gxState.pixelFmt = GX_PF_RGB8_Z24; + g_gxState.zFmt = GX_ZC_LINEAR; + decode_fifo(bytes); + + EXPECT_EQ(g_gxState.pixelFmt, GX_PF_U8); + EXPECT_EQ(g_gxState.zFmt, GX_ZC_MID); + EXPECT_EQ(g_gxState.dstAlpha, UINT32_MAX); + EXPECT_TRUE(g_gxState.zCompLocBeforeTex); +} + // ============================================================================ // TEV registers (direct FIFO writes) // ============================================================================ diff --git a/tests/gx_test_stubs.cpp b/tests/gx_test_stubs.cpp index b6b5aca..e05e30c 100644 --- a/tests/gx_test_stubs.cpp +++ b/tests/gx_test_stubs.cpp @@ -3,10 +3,15 @@ // These allow the test binary to link without pulling in WebGPU runtime. #include "gx/gx.hpp" +#include "gfx/clear.hpp" +#include "gfx/common.hpp" +#include "gfx/tex_copy_conv.hpp" +#include "gfx/tex_palette_conv.hpp" +#include "gfx/texture.hpp" #include "gx/pipeline.hpp" #include "gx/shader_info.hpp" -#include "gfx/texture.hpp" #include "internal.hpp" +#include "webgpu/gpu.hpp" #include #include @@ -38,6 +43,10 @@ wgpu::Buffer g_indexBuffer; wgpu::Buffer g_storageBuffer; } // namespace aurora::gfx +namespace aurora::webgpu { +GraphicsConfig g_graphicsConfig{}; +} // namespace aurora::webgpu + // --- GXState (the real instance -- tests validate this) --- namespace aurora::gx { GXState g_gxState{}; @@ -86,9 +95,16 @@ void set_scissor(uint32_t x, uint32_t y, uint32_t w, uint32_t h) noexcept {} } // namespace aurora::gfx // --- Pipeline/draw command stubs --- -// These are template instantiations that command_processor.cpp needs namespace aurora::gfx { template <> +PipelineRef pipeline_ref(clear::PipelineConfig config) { + return 0; +} +template <> +void push_draw_command(clear::DrawData data) { + // No-op +} +template <> PipelineRef pipeline_ref(gx::PipelineConfig config) { return 0; } @@ -109,19 +125,35 @@ wgpu::SamplerDescriptor TextureBind::get_descriptor() const noexcept { return wg // --- Texture creation/write stubs --- namespace aurora::gfx { -TextureHandle new_static_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 format, ArrayRef data, - bool tlut, const char* label) noexcept { +TextureHandle new_static_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 gxFormat, + ArrayRef data, bool tlut, const char* label) noexcept { return {}; } -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 { return {}; } -TextureHandle new_render_texture(uint32_t width, uint32_t height, u32 fmt, const char* label) noexcept { return {}; } +TextureHandle new_render_texture(uint32_t width, uint32_t height, u32 gxFormat, const char* label) noexcept { + return {}; +} +TextureHandle new_conv_texture(uint32_t width, uint32_t height, u32 gxFormat, const char* label) noexcept { return {}; } void write_texture(const TextureRef& ref, ArrayRef data) noexcept {} -void resolve_pass(TextureHandle texture, ClipRect rect, bool clear, Vec4 clearColor) {} +void resolve_pass(TextureHandle texture, ClipRect rect, bool clearColor, bool clearAlpha, bool clearDepth, + Vec4 clearColorValue, float clearDepthValue) {} +void queue_copy_conv(tex_copy_conv::ConvRequest req) {} +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 { +void queue(ConvRequest req) {} +} // namespace aurora::gfx::tex_palette_conv + // --- Window stub --- #include "../lib/window.hpp" namespace aurora::window { @@ -130,6 +162,9 @@ AuroraWindowSize get_window_size() { return {640, 480, 640, 480, 1.0f}; } // --- WebGPU C API stubs (prevent linker errors from wgpu:: destructors) --- extern "C" { +void wgpuDeviceRelease(WGPUDevice) {} +void wgpuQueueRelease(WGPUQueue) {} +void wgpuSurfaceRelease(WGPUSurface) {} void wgpuBufferRelease(WGPUBuffer) {} void wgpuTextureRelease(WGPUTexture) {} void wgpuTextureViewRelease(WGPUTextureView) {} @@ -139,16 +174,17 @@ void wgpuRenderPipelineRelease(WGPURenderPipeline) {} void wgpuBindGroupRelease(WGPUBindGroup) {} void wgpuBindGroupLayoutRelease(WGPUBindGroupLayout) {} void wgpuPipelineLayoutRelease(WGPUPipelineLayout) {} +void wgpuInstanceRelease(WGPUInstance) {} +void wgpuDeviceAddRef(WGPUDevice) {} +void wgpuQueueAddRef(WGPUQueue) {} +void wgpuSurfaceAddRef(WGPUSurface) {} void wgpuBufferAddRef(WGPUBuffer) {} void wgpuTextureAddRef(WGPUTexture) {} void wgpuTextureViewAddRef(WGPUTextureView) {} +void wgpuInstanceAddRef(WGPUInstance) {} } -void aurora::gfx::push_debug_group(std::string) { -} -void push_debug_group(const char*) { -} -void pop_debug_group() { -} -void aurora::gfx::insert_debug_marker(std::string) { -} \ No newline at end of file +void aurora::gfx::push_debug_group(std::string) {} +void push_debug_group(const char*) {} +void pop_debug_group() {} +void aurora::gfx::insert_debug_marker(std::string) {}