From 33d0d14fda0c84ff02e17c182663ee8ec3c7eefd Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 4 Mar 2022 22:36:54 -0500 Subject: [PATCH] Initial working CGraphics Stream API --- Runtime/Graphics/CGraphics.cpp | 14 +- Runtime/Graphics/CGraphics.hpp | 1 + Runtime/Graphics/CTevCombiners.cpp | 6 +- Runtime/Graphics/CTexture.cpp | 33 +- Runtime/Graphics/CTexture.hpp | 2 +- Runtime/Graphics/GX.hpp | 1 + aurora/CMakeLists.txt | 5 +- aurora/include/aurora/gfx.hpp | 10 +- aurora/lib/aurora.cpp | 2 +- aurora/lib/gfx/colored_quad/shader.hpp | 4 +- aurora/lib/gfx/common.cpp | 83 ++++- aurora/lib/gfx/common.hpp | 29 +- aurora/lib/gfx/movie_player/shader.hpp | 2 + aurora/lib/gfx/stream.cpp | 453 +++++++++++++++++++++++- aurora/lib/gfx/stream/shader.cpp | 322 +++++++++++++++++ aurora/lib/gfx/stream/shader.hpp | 47 +++ aurora/lib/gfx/textured_quad/shader.hpp | 2 + aurora/lib/gpu.hpp | 4 +- 18 files changed, 976 insertions(+), 44 deletions(-) create mode 100644 aurora/lib/gfx/stream/shader.cpp create mode 100644 aurora/lib/gfx/stream/shader.hpp diff --git a/Runtime/Graphics/CGraphics.cpp b/Runtime/Graphics/CGraphics.cpp index 84e4162ca..fe02b4ec6 100644 --- a/Runtime/Graphics/CGraphics.cpp +++ b/Runtime/Graphics/CGraphics.cpp @@ -150,10 +150,20 @@ void CGraphics::EndScene() { UpdateFPSCounter(); } +void CGraphics::Render2D(const CTexture& tex, u32 x, u32 y, u32 w, u32 h, const zeus::CColor& col) { + const auto oldProj = g_Proj; + CGraphics::SetOrtho(-g_Viewport.x8_width / 2, g_Viewport.x8_width / 2, g_Viewport.xc_height / 2, + -g_Viewport.xc_height / 2, -1.f, -10.f); + // TODO + g_Proj = oldProj; + FlushProjection(); +} + bool CGraphics::BeginRender2D(const CTexture& tex) { return false; } void CGraphics::DoRender2D(const CTexture& tex, s32 x, s32 y, s32 w1, s32 w2, s32 w3, s32 w4, s32 w5, const zeus::CColor& col) {} + void CGraphics::EndRender2D(bool v) {} void CGraphics::SetAlphaCompare(ERglAlphaFunc comp0, u8 ref0, ERglAlphaOp op, ERglAlphaFunc comp1, u8 ref1) {} @@ -565,7 +575,5 @@ void CGraphics::StreamVertex(const zeus::CVector3f& pos) { aurora::gfx::stream_vertex(sStreamFlags, pos, sQueuedNormal, sQueuedColor, sQueuedTexCoord); } -void CGraphics::StreamEnd() { - aurora::gfx::stream_end(); -} +void CGraphics::StreamEnd() { aurora::gfx::stream_end(); } } // namespace metaforce diff --git a/Runtime/Graphics/CGraphics.hpp b/Runtime/Graphics/CGraphics.hpp index 8914ecc1b..a83bef3cd 100644 --- a/Runtime/Graphics/CGraphics.hpp +++ b/Runtime/Graphics/CGraphics.hpp @@ -155,6 +155,7 @@ public: static void SetCullMode(ERglCullMode); static void BeginScene(); static void EndScene(); + static void Render2D(const CTexture& tex, u32 x, u32 y, u32 w, u32 h, const zeus::CColor& col); static bool BeginRender2D(const CTexture& tex); static void DoRender2D(const CTexture& tex, s32 x, s32 y, s32 w1, s32 w2, s32 w3, s32 w4, s32 w5, const zeus::CColor& col); diff --git a/Runtime/Graphics/CTevCombiners.cpp b/Runtime/Graphics/CTevCombiners.cpp index 3c2fe5aa3..6e36b47e5 100644 --- a/Runtime/Graphics/CTevCombiners.cpp +++ b/Runtime/Graphics/CTevCombiners.cpp @@ -4,7 +4,11 @@ namespace metaforce::CTevCombiners { u32 CTevPass::sNextUniquePass = 0; void CTevPass::Execute(ERglTevStage stage) const { - aurora::gfx::update_tev_stage(stage, x4_colorPass, x14_alphaPass, x24_colorOp, x38_alphaOp); + if (*this == skPassThru) { + aurora::gfx::disable_tev_stage(stage); + } else { + aurora::gfx::update_tev_stage(stage, x4_colorPass, x14_alphaPass, x24_colorOp, x38_alphaOp); + } } constexpr u32 maxTevPasses = 2; diff --git a/Runtime/Graphics/CTexture.cpp b/Runtime/Graphics/CTexture.cpp index 9f67fee73..cfa10382c 100644 --- a/Runtime/Graphics/CTexture.cpp +++ b/Runtime/Graphics/CTexture.cpp @@ -159,24 +159,25 @@ void CTexture::Load(GX::TexMapID id, EClampMode clamp) { // GXInitObjectData(x20_texObj, image_ptr); // GXLoadObj(x20_texObj, id); + aurora::gfx::bind_texture(id, clamp, x20_texObj, 0.f); sLoadedTextures[id] = this; x64_frameAllocated = sCurrentFrameCount; } } -void CTexture::LoadMipLevel(s32 mip, GX::TexMapID id, EClampMode clamp) { - auto image_ptr = /*x44_aramToken.GetMRAMSafe() */ x44_aramToken_x4_buff.get(); - u32 width = x4_w; - u32 height = x6_h; - u32 iVar15 = 0; - u32 offset = 0; - if (mip > 0) { - for (u32 i = 0; i < mip; ++i) { - offset += ROUND_UP_32(x9_bitsPerPixel * (ROUND_UP_4(width) * ROUND_UP_4(height))); - width /= 2; - height /= 2; - } - } +void CTexture::LoadMipLevel(float lod, GX::TexMapID id, EClampMode clamp) { + // auto image_ptr = /*x44_aramToken.GetMRAMSafe() */ x44_aramToken_x4_buff.get(); + // u32 width = x4_w; + // u32 height = x6_h; + // u32 iVar15 = 0; + // u32 offset = 0; + // if (mip > 0) { + // for (u32 i = 0; i < mip; ++i) { + // offset += ROUND_UP_32(x9_bitsPerPixel * (ROUND_UP_4(width) * ROUND_UP_4(height))); + // width /= 2; + // height /= 2; + // } + // } // GXTexObj texObj; // GXInitTexObj(&texObj, image_ptr + offset, width, height, x18_gxFormat); @@ -186,6 +187,7 @@ void CTexture::LoadMipLevel(s32 mip, GX::TexMapID id, EClampMode clamp) { xa_25_canLoadPalette = false; } // GXLoadTexObj(&texObj, mapId); + aurora::gfx::bind_texture(id, clamp, x20_texObj, lod); x64_frameAllocated = sCurrentFrameCount; sLoadedTextures[id] = nullptr; } @@ -333,7 +335,10 @@ bool CTexture::sMangleMips = false; u32 CTexture::sCurrentFrameCount = 0; u32 CTexture::sTotalAllocatedMemory = 0; -void CTexture::InvalidateTexMap(GX::TexMapID id) { sLoadedTextures[id] = nullptr; } +void CTexture::InvalidateTexMap(GX::TexMapID id) { + aurora::gfx::unbind_texture(id); + sLoadedTextures[id] = nullptr; +} CFactoryFnReturn FTextureFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms, CObjectReference* selfRef) { diff --git a/Runtime/Graphics/CTexture.hpp b/Runtime/Graphics/CTexture.hpp index fcf0905e4..a9aaf6adf 100644 --- a/Runtime/Graphics/CTexture.hpp +++ b/Runtime/Graphics/CTexture.hpp @@ -90,7 +90,7 @@ public: [[nodiscard]] u8* Lock(); void UnLock(); void Load(GX::TexMapID id, EClampMode clamp); - void LoadMipLevel(s32 mip, GX::TexMapID id, EClampMode clamp); + void LoadMipLevel(float lod, GX::TexMapID id, EClampMode clamp); // was an s32 mip parameter, adjusted to use lod // void UnloadBitmapData(u32) const; // void TryReloadBitmapData(CResFactory&) const; // void LoadToMRAM() const; diff --git a/Runtime/Graphics/GX.hpp b/Runtime/Graphics/GX.hpp index a3b16e457..6a3ee9cf7 100644 --- a/Runtime/Graphics/GX.hpp +++ b/Runtime/Graphics/GX.hpp @@ -278,6 +278,7 @@ enum TextureFormat : uint32_t { enum TexMapID { TEXMAP0, + TEXMAP1, TEXMAP2, TEXMAP3, TEXMAP4, diff --git a/aurora/CMakeLists.txt b/aurora/CMakeLists.txt index 1996dcc5a..28cf6e80f 100644 --- a/aurora/CMakeLists.txt +++ b/aurora/CMakeLists.txt @@ -24,11 +24,12 @@ add_library(aurora STATIC lib/dawn/Hacks.cpp lib/gfx/common.cpp lib/gfx/texture.cpp + lib/gfx/stream.cpp + lib/gfx/texture_convert.cpp lib/gfx/movie_player/shader.cpp lib/gfx/textured_quad/shader.cpp lib/gfx/colored_quad/shader.cpp - lib/gfx/stream.cpp - lib/gfx/texture_convert.cpp + lib/gfx/stream/shader.cpp ) target_compile_definitions(aurora PRIVATE IMGUI_USER_CONFIG="imconfig_user.h") # IMGUI_USE_WCHAR32 target_include_directories(aurora PUBLIC include ../) diff --git a/aurora/include/aurora/gfx.hpp b/aurora/include/aurora/gfx.hpp index 7797d2838..d359c9798 100644 --- a/aurora/include/aurora/gfx.hpp +++ b/aurora/include/aurora/gfx.hpp @@ -45,7 +45,9 @@ enum class ERglBlendFactor { SrcAlpha = 4, InvSrcAlpha = 5, DstAlpha = 6, - InvDstAlpha = 7 + InvDstAlpha = 7, + DstColor = 8, + InvDstColor = 9, }; enum class ERglLogicOp { @@ -137,7 +139,7 @@ struct CFogState { enum class EStreamFlagBits : u8 { fHasNormal = 0x1, fHasColor = 0x2, - fHasTexture = 0x3, + fHasTexture = 0x4, }; using EStreamFlags = Flags; @@ -220,7 +222,9 @@ enum class ZComp : uint8_t { [[nodiscard]] bool get_dxt_compression_supported() noexcept; -void bind_texture(GX::TexMapID id, metaforce::EClampMode clamp) noexcept; +void bind_texture(GX::TexMapID id, metaforce::EClampMode clamp, const TextureHandle& tex, float lod) noexcept; +void unbind_texture(GX::TexMapID id) noexcept; +void disable_tev_stage(metaforce::ERglTevStage stage) noexcept; void update_tev_stage(metaforce::ERglTevStage stage, const metaforce::CTevCombiners::ColorPass& colPass, const metaforce::CTevCombiners::AlphaPass& alphaPass, const metaforce::CTevCombiners::CTevOp& colorOp, diff --git a/aurora/lib/aurora.cpp b/aurora/lib/aurora.cpp index 8a1192bee..3cb7d45b0 100644 --- a/aurora/lib/aurora.cpp +++ b/aurora/lib/aurora.cpp @@ -257,7 +257,7 @@ void app_run(std::unique_ptr app, Icon icon, int argc, char** argv) default: break; } - g_window = SDL_CreateWindow("Metaforce", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, flags); + g_window = SDL_CreateWindow("Metaforce", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 960, flags); if (g_window == nullptr) { Log.report(logvisor::Fatal, FMT_STRING("Error creating window: {}"), SDL_GetError()); unreachable(); diff --git a/aurora/lib/gfx/colored_quad/shader.hpp b/aurora/lib/gfx/colored_quad/shader.hpp index b777238cc..96091942c 100644 --- a/aurora/lib/gfx/colored_quad/shader.hpp +++ b/aurora/lib/gfx/colored_quad/shader.hpp @@ -1,3 +1,5 @@ +#pragma once + #include "../common.hpp" namespace aurora::gfx::colored_quad { @@ -44,4 +46,4 @@ DrawData make_draw_data_verts(const State& state, CameraFilterType filter_type, const zeus::CColor& color, const ArrayRef& pos); void render(const State& state, const DrawData& data, const wgpu::RenderPassEncoder& pass); -} // namespace aurora::gfx::colored_quad \ No newline at end of file +} // namespace aurora::gfx::colored_quad diff --git a/aurora/lib/gfx/common.cpp b/aurora/lib/gfx/common.cpp index d1e91eb6d..6538cbda4 100644 --- a/aurora/lib/gfx/common.cpp +++ b/aurora/lib/gfx/common.cpp @@ -2,8 +2,9 @@ #include "../gpu.hpp" #include "colored_quad/shader.hpp" -#include "textured_quad/shader.hpp" #include "movie_player/shader.hpp" +#include "stream/shader.hpp" +#include "textured_quad/shader.hpp" #include #include @@ -21,6 +22,7 @@ struct ShaderState { movie_player::State moviePlayer; colored_quad::State coloredQuad; textured_quad::State texturedQuad; + stream::State stream; }; struct ShaderDrawCommand { ShaderType type; @@ -28,6 +30,7 @@ struct ShaderDrawCommand { movie_player::DrawData moviePlayer; colored_quad::DrawData coloredQuad; textured_quad::DrawData texturedQuad; + stream::DrawData stream; }; }; struct PipelineCreateCommand { @@ -36,6 +39,7 @@ struct PipelineCreateCommand { movie_player::PipelineConfig moviePlayer; colored_quad::PipelineConfig coloredQuad; textured_quad::PipelineConfig texturedQuad; + stream::PipelineConfig stream; }; }; enum class CommandType { @@ -65,6 +69,19 @@ zeus::CMatrix4f g_mv; zeus::CMatrix4f g_mvInv; zeus::CMatrix4f g_proj; metaforce::CFogState g_fogState; +// GX state +metaforce::ERglCullMode g_cullMode; +metaforce::ERglBlendMode g_blendMode; +metaforce::ERglBlendFactor g_blendFacSrc; +metaforce::ERglBlendFactor g_blendFacDst; +metaforce::ERglLogicOp g_blendOp; +bool g_depthCompare; +bool g_depthUpdate; +metaforce::ERglEnum g_depthFunc; +std::array g_colorRegs; +bool g_alphaUpdate; +std::optional g_dstAlpha; +zeus::CColor g_clearColor; using NewPipelineCallback = std::function; static std::mutex g_pipelineMutex; @@ -74,6 +91,7 @@ static std::condition_variable g_pipelineCv; static std::unordered_map g_pipelines; static std::deque> g_queuedPipelines; static std::unordered_map g_cachedBindGroups; +static std::unordered_map g_cachedSamplers; std::atomic_uint32_t queuedPipelines; std::atomic_uint32_t createdPipelines; @@ -118,14 +136,29 @@ static void push_draw_command(ShaderDrawCommand data) { g_commands.push_back({Co bool get_dxt_compression_supported() noexcept { return g_device.HasFeature(wgpu::FeatureName::TextureCompressionBC); } // GX state -void set_cull_mode(metaforce::ERglCullMode mode) noexcept {} +void set_cull_mode(metaforce::ERglCullMode mode) noexcept { g_cullMode = mode; } void set_blend_mode(metaforce::ERglBlendMode mode, metaforce::ERglBlendFactor src, metaforce::ERglBlendFactor dst, - metaforce::ERglLogicOp op) noexcept {} -void set_depth_mode(bool compare_enable, metaforce::ERglEnum func, bool update_enable) noexcept {} -void set_gx_reg1_color(const zeus::CColor& color) noexcept {} -void set_alpha_update(bool enabled) noexcept {} -void set_dst_alpha(bool enabled, float value) noexcept {} -void set_clear_color(const zeus::CColor& color) noexcept {} + metaforce::ERglLogicOp op) noexcept { + g_blendMode = mode; + g_blendFacSrc = src; + g_blendFacDst = dst; + g_blendOp = op; +} +void set_depth_mode(bool compare_enable, metaforce::ERglEnum func, bool update_enable) noexcept { + g_depthCompare = compare_enable; + g_depthFunc = func; + g_depthUpdate = update_enable; +} +void set_gx_reg1_color(const zeus::CColor& color) noexcept { g_colorRegs[1] = color; } +void set_alpha_update(bool enabled) noexcept { g_alphaUpdate = enabled; } +void set_dst_alpha(bool enabled, float value) noexcept { + if (enabled) { + g_dstAlpha = value; + } else { + g_dstAlpha.reset(); + } +} +void set_clear_color(const zeus::CColor& color) noexcept { g_clearColor = color; } // Model state void set_alpha_discard(bool v) {} @@ -195,7 +228,6 @@ void queue_colored_quad(CameraFilterType filter_type, ZComp z_comparison, bool z auto data = colored_quad::make_draw_data(g_state.coloredQuad, filter_type, z_comparison, z_test, color, rect, z); push_draw_command({.type = ShaderType::ColoredQuad, .coloredQuad = data}); } - template <> PipelineRef pipeline_ref(colored_quad::PipelineConfig config) { return find_pipeline({.type = ShaderType::ColoredQuad, .coloredQuad = config}, @@ -214,6 +246,20 @@ PipelineRef pipeline_ref(movie_player::PipelineConfig config) { [=]() { return create_pipeline(g_state.moviePlayer, config); }); } +template <> +const stream::State& get_state() { + return g_state.stream; +} +template <> +void push_draw_command(stream::DrawData data) { + push_draw_command({.type = ShaderType::Stream, .stream = data}); +} +template <> +PipelineRef pipeline_ref(stream::PipelineConfig config) { + return find_pipeline({.type = ShaderType::Stream, .stream = config}, + [=]() { return create_pipeline(g_state.stream, config); }); +} + static void pipeline_worker() { bool hasMore = false; while (true) { @@ -276,6 +322,7 @@ void initialize() { g_state.moviePlayer = movie_player::construct_state(); g_state.coloredQuad = colored_quad::construct_state(); g_state.texturedQuad = textured_quad::construct_state(); + g_state.stream = stream::construct_state(); } void shutdown() { @@ -336,6 +383,9 @@ void render(const wgpu::RenderPassEncoder& pass) { case ShaderType::MoviePlayer: movie_player::render(g_state.moviePlayer, draw.moviePlayer, pass); break; + case ShaderType::Stream: + stream::render(g_state.stream, draw.stream, pass); + break; } } break; } @@ -392,4 +442,19 @@ const wgpu::BindGroup& find_bind_group(BindGroupRef id) { } return g_cachedBindGroups[id]; } + +const wgpu::Sampler& sampler_ref(const wgpu::SamplerDescriptor& descriptor) { + const auto id = xxh3_hash(descriptor); + if (!g_cachedSamplers.contains(id)) { + g_cachedSamplers[id] = g_device.CreateSampler(&descriptor); + } + return g_cachedSamplers[id]; +} + +uint32_t align_uniform(uint32_t value) { + wgpu::SupportedLimits limits; + g_device.GetLimits(&limits); // TODO cache + const auto uniform_alignment = limits.limits.minUniformBufferOffsetAlignment; + return ALIGN(value, uniform_alignment); +} } // namespace aurora::gfx diff --git a/aurora/lib/gfx/common.hpp b/aurora/lib/gfx/common.hpp index db4fbcf78..280a9e824 100644 --- a/aurora/lib/gfx/common.hpp +++ b/aurora/lib/gfx/common.hpp @@ -146,6 +146,19 @@ extern zeus::CMatrix4f g_mv; extern zeus::CMatrix4f g_mvInv; extern zeus::CMatrix4f g_proj; extern metaforce::CFogState g_fogState; +// GX state +extern metaforce::ERglCullMode g_cullMode; +extern metaforce::ERglBlendMode g_blendMode; +extern metaforce::ERglBlendFactor g_blendFacSrc; +extern metaforce::ERglBlendFactor g_blendFacDst; +extern metaforce::ERglLogicOp g_blendOp; +extern bool g_depthCompare; +extern bool g_depthUpdate; +extern metaforce::ERglEnum g_depthFunc; +extern std::array g_colorRegs; +extern bool g_alphaUpdate; +extern std::optional g_dstAlpha; +extern zeus::CColor g_clearColor; extern wgpu::Buffer g_vertexBuffer; extern wgpu::Buffer g_uniformBuffer; @@ -169,8 +182,10 @@ struct TextureRef { , gameFormat(gameFormat) {} }; -using PipelineRef = uint64_t; using BindGroupRef = uint64_t; +using PipelineRef = uint64_t; +using SamplerRef = uint64_t; +using ShaderRef = uint64_t; using Range = std::pair; enum class ShaderType { @@ -178,6 +193,7 @@ enum class ShaderType { ColoredQuad, TexturedQuad, MoviePlayer, + Stream, }; void initialize(); @@ -201,6 +217,11 @@ static inline Range push_uniform(const T& data) { return push_uniform(reinterpret_cast(&data), sizeof(T)); } +template +const State& get_state(); +template +void push_draw_command(DrawData data); + template PipelineRef pipeline_ref(PipelineConfig config); bool bind_pipeline(PipelineRef ref, const wgpu::RenderPassEncoder& pass); @@ -208,5 +229,9 @@ bool bind_pipeline(PipelineRef ref, const wgpu::RenderPassEncoder& pass); BindGroupRef bind_group_ref(const wgpu::BindGroupDescriptor& descriptor); const wgpu::BindGroup& find_bind_group(BindGroupRef id); -static inline zeus::CMatrix4f get_combined_matrix() { return g_proj * g_mv; } +const wgpu::Sampler& sampler_ref(const wgpu::SamplerDescriptor& descriptor); + +uint32_t align_uniform(uint32_t value); + +static inline Mat4x4 get_combined_matrix() { return g_proj * g_mv; } } // namespace aurora::gfx diff --git a/aurora/lib/gfx/movie_player/shader.hpp b/aurora/lib/gfx/movie_player/shader.hpp index c4a56b4f2..5b9ec1193 100644 --- a/aurora/lib/gfx/movie_player/shader.hpp +++ b/aurora/lib/gfx/movie_player/shader.hpp @@ -1,3 +1,5 @@ +#pragma once + #include "../common.hpp" namespace aurora::gfx::movie_player { diff --git a/aurora/lib/gfx/stream.cpp b/aurora/lib/gfx/stream.cpp index 37969088b..860f14fdd 100644 --- a/aurora/lib/gfx/stream.cpp +++ b/aurora/lib/gfx/stream.cpp @@ -1,15 +1,458 @@ +#include "stream/shader.hpp" + +#include "../gpu.hpp" #include "common.hpp" +#include + +#include + namespace aurora::gfx { +using namespace fmt::literals; + +static logvisor::Module Log("aurora::gfx::stream"); + +struct STevStage { + metaforce::CTevCombiners::ColorPass colorPass; + metaforce::CTevCombiners::AlphaPass alphaPass; + metaforce::CTevCombiners::CTevOp colorOp; + metaforce::CTevCombiners::CTevOp alphaOp; + + STevStage(const metaforce::CTevCombiners::ColorPass& colPass, const metaforce::CTevCombiners::AlphaPass& alphaPass, + const metaforce::CTevCombiners::CTevOp& colorOp, const metaforce::CTevCombiners::CTevOp& alphaOp) + : colorPass(colPass), alphaPass(alphaPass), colorOp(colorOp), alphaOp(alphaOp) {} +}; constexpr u32 maxTevStages = 2; -//static std::array sTevStages; +static std::array, maxTevStages> sTevStages; + +void disable_tev_stage(metaforce::ERglTevStage stage) noexcept { sTevStages[static_cast(stage)].reset(); } void update_tev_stage(metaforce::ERglTevStage stage, const metaforce::CTevCombiners::ColorPass& colPass, const metaforce::CTevCombiners::AlphaPass& alphaPass, const metaforce::CTevCombiners::CTevOp& colorOp, - const metaforce::CTevCombiners::CTevOp& alphaOp) noexcept {} -void stream_begin(GX::Primitive primitive) noexcept {} + const metaforce::CTevCombiners::CTevOp& alphaOp) noexcept { + sTevStages[static_cast(stage)] = {colPass, alphaPass, colorOp, alphaOp}; +} + +struct SStreamState { + GX::Primitive primitive; + metaforce::EStreamFlags flags; + uint32_t vertexCount = 0; + ByteBuffer vertexBuffer; + + explicit SStreamState(GX::Primitive primitive) noexcept : primitive(primitive) {} +}; +static std::optional sStreamState; + +constexpr u32 maxTextures = 8; +struct STextureBind { + aurora::gfx::TextureHandle handle; + metaforce::EClampMode clampMode; + float lod; + + STextureBind() noexcept = default; + STextureBind(aurora::gfx::TextureHandle handle, metaforce::EClampMode clampMode, float lod) noexcept + : handle(std::move(handle)), clampMode(clampMode), lod(lod) {} + void reset() noexcept { handle.reset(); }; + wgpu::SamplerDescriptor get_descriptor() const noexcept { + wgpu::AddressMode mode; + switch (clampMode) { + case metaforce::EClampMode::Clamp: + mode = wgpu::AddressMode::ClampToEdge; + break; + case metaforce::EClampMode::Repeat: + mode = wgpu::AddressMode::Repeat; + break; + case metaforce::EClampMode::Mirror: + mode = wgpu::AddressMode::MirrorRepeat; + break; + } + return { + .label = "Generated Sampler", + .addressModeU = mode, + .addressModeV = mode, + .addressModeW = mode, + // TODO logic from CTexture? + .magFilter = wgpu::FilterMode::Linear, + .minFilter = wgpu::FilterMode::Linear, + .mipmapFilter = wgpu::FilterMode::Linear, + .maxAnisotropy = gpu::g_graphicsConfig.textureAnistropy, + }; + } + operator bool() const noexcept { return handle; } +}; +static std::array sTextures; + +void bind_texture(GX::TexMapID id, metaforce::EClampMode clamp, const TextureHandle& tex, float lod) noexcept { + sTextures[static_cast(id)] = {tex, clamp, lod}; +} + +void unbind_texture(GX::TexMapID id) noexcept { sTextures[static_cast(id)].reset(); } + +void stream_begin(GX::Primitive primitive) noexcept { + if (sStreamState) { + Log.report(logvisor::Fatal, FMT_STRING("Stream began twice!")); + unreachable(); + } + sStreamState.emplace(primitive); +} + void stream_vertex(metaforce::EStreamFlags flags, const zeus::CVector3f& pos, const zeus::CVector3f& nrm, - const zeus::CColor& color, const zeus::CVector2f& uv) noexcept {} -void stream_end() noexcept {} + const zeus::CColor& color, const zeus::CVector2f& uv) noexcept { + if (!sStreamState) { + Log.report(logvisor::Fatal, FMT_STRING("Stream not started!")); + unreachable(); + } + if (sStreamState->flags) { + if (sStreamState->flags != flags) { + Log.report(logvisor::Fatal, FMT_STRING("Stream changed flags?")); + unreachable(); + } + } else { + sStreamState->flags = flags; + // TODO begin shader construction + } + sStreamState->vertexBuffer.append(&pos, 12); + if (flags & metaforce::EStreamFlagBits::fHasNormal) { + sStreamState->vertexBuffer.append(&nrm, 12); + } + if (flags & metaforce::EStreamFlagBits::fHasColor) { + sStreamState->vertexBuffer.append(&color, 16); + } + if (flags & metaforce::EStreamFlagBits::fHasTexture) { + sStreamState->vertexBuffer.append(&uv, 8); + } + sStreamState->vertexCount++; +} + +static std::string color_arg_reg(GX::TevColorArg arg, size_t stageIdx) { + switch (arg) { + case GX::CC_CPREV: + return "prev.rgb"; + case GX::CC_APREV: + return "prev.a"; + case GX::CC_C0: + case GX::CC_A0: + case GX::CC_C1: + case GX::CC_A1: + case GX::CC_C2: + case GX::CC_A2: + Log.report(logvisor::Fatal, FMT_STRING("TODO {}"), arg); + unreachable(); + case GX::CC_TEXC: + return fmt::format(FMT_STRING("sampled{}.rgb"), stageIdx); + case GX::CC_TEXA: + return fmt::format(FMT_STRING("sampled{}.a"), stageIdx); + case GX::CC_RASC: + return "rast.rgb"; + case GX::CC_RASA: + return "rast.a"; + case GX::CC_ONE: + return "1.0"; + case GX::CC_HALF: + return "0.5)"; + case GX::CC_KONST: + return fmt::format(FMT_STRING("ubuf.kcolor{}.rgb"), stageIdx); + case GX::CC_ZERO: + return "0.0"; + } +} + +static std::string alpha_arg_reg(GX::TevAlphaArg arg, size_t stageIdx) { + switch (arg) { + case GX::CA_APREV: + return "prev.a"; + case GX::CA_A0: + case GX::CA_A1: + case GX::CA_A2: + Log.report(logvisor::Fatal, FMT_STRING("TODO {}"), arg); + unreachable(); + case GX::CA_TEXA: + return fmt::format(FMT_STRING("sampled{}.a"), stageIdx); + case GX::CA_RASA: + return "rast.a"; + case GX::CA_KONST: + return fmt::format(FMT_STRING("ubuf.kcolor{}.a"), stageIdx); + case GX::CA_ZERO: + return "0.0"; + } +} + +static std::string_view tev_op(GX::TevOp op) { + switch (op) { + case GX::TEV_ADD: + return "+"; + case GX::TEV_SUB: + return "-"; + default: + Log.report(logvisor::Fatal, FMT_STRING("TODO {}"), op); + unreachable(); + } +} + +static std::string_view tev_bias(GX::TevBias bias) { + switch (bias) { + case GX::TB_ZERO: + return " + 0.0"; + case GX::TB_ADDHALF: + return " + 0.5"; + case GX::TB_SUBHALF: + return " - 0.5"; + } +} + +static std::string_view tev_scale(GX::TevScale scale) { + switch (scale) { + case GX::CS_SCALE_1: + return " * 1.0"; + case GX::CS_SCALE_2: + return " * 2.0"; + case GX::CS_SCALE_4: + return " * 4.0"; + case GX::CS_DIVIDE_2: + return " / 2.0"; + } +} + +std::unordered_map g_streamCachedShaders; + +static ShaderRef generate_shader() { + auto flags = sStreamState->flags; + const auto hash = xxh3_hash(sTevStages, static_cast(flags)); + if (g_streamCachedShaders.contains(hash)) { + return hash; + } + + std::string uniBufAttrs; + if (flags & metaforce::EStreamFlagBits::fHasTexture) { + uniBufAttrs += fmt::format(FMT_STRING("\n tex0_lod: f32;")); + } + std::string sampBindings; + if (flags & metaforce::EStreamFlagBits::fHasTexture) { + sampBindings += + "\n" + "@group(1) @binding(0)\n" + "var tex0_samp: sampler;"; + } + std::string texBindings; + if (flags & metaforce::EStreamFlagBits::fHasTexture) { + texBindings += + "\n" + "@group(2) @binding(0)\n" + "var tex0: texture_2d;"; + } + std::string vtxOutAttrs; + std::string vtxInAttrs; + std::string vtxXfrAttrs; + { + size_t idx = 0; + if (flags & metaforce::EStreamFlagBits::fHasNormal) { + vtxOutAttrs += fmt::format(FMT_STRING("\n @location({}) nrm: vec3;"), idx); + vtxInAttrs += fmt::format(FMT_STRING("\n , @location({}) in_nrm: vec3"), ++idx); + vtxXfrAttrs += fmt::format(FMT_STRING("\n out.nrm = in_nrm;")); + } + if (flags & metaforce::EStreamFlagBits::fHasColor) { + vtxOutAttrs += fmt::format(FMT_STRING("\n @location({}) clr: vec4;"), idx); + vtxInAttrs += fmt::format(FMT_STRING("\n , @location({}) in_clr: vec4"), ++idx); + vtxXfrAttrs += fmt::format(FMT_STRING("\n out.clr = in_clr;")); + } + if (flags & metaforce::EStreamFlagBits::fHasTexture) { + vtxOutAttrs += fmt::format(FMT_STRING("\n @location({}) tex0_uv: vec2;"), idx); + vtxInAttrs += fmt::format(FMT_STRING("\n , @location({}) in_uv: vec2"), ++idx); + vtxXfrAttrs += fmt::format(FMT_STRING("\n out.tex0_uv = in_uv;")); + } + } + std::string fragmentFn; + bool hasRast = false; + for (size_t idx = 0; const auto& stage : sTevStages) { + if (!stage) { + idx++; + continue; + } + if (stage->colorPass.x0_a == GX::TevColorArg::CC_TEXC || stage->colorPass.x4_b == GX::TevColorArg::CC_TEXC || + stage->colorPass.x8_c == GX::TevColorArg::CC_TEXC || stage->colorPass.xc_d == GX::TevColorArg::CC_TEXC || + stage->alphaPass.x0_a == GX::TevAlphaArg::CA_TEXA || stage->alphaPass.x4_b == GX::TevAlphaArg::CA_TEXA || + stage->alphaPass.x8_c == GX::TevAlphaArg::CA_TEXA || stage->alphaPass.xc_d == GX::TevAlphaArg::CA_TEXA) { + fragmentFn += fmt::format( + FMT_STRING("\n var sampled{0} = textureSampleBias(tex{0}, tex{0}_samp, in.tex{0}_uv, ubuf.tex{0}_lod);"), + idx); + } + if (!hasRast) { + if (stage->colorPass.x0_a == GX::TevColorArg::CC_RASC || stage->colorPass.x4_b == GX::TevColorArg::CC_RASC || + stage->colorPass.x8_c == GX::TevColorArg::CC_RASC || stage->colorPass.xc_d == GX::TevColorArg::CC_RASC || + stage->alphaPass.x0_a == GX::TevAlphaArg::CA_RASA || stage->alphaPass.x4_b == GX::TevAlphaArg::CA_RASA || + stage->alphaPass.x8_c == GX::TevAlphaArg::CA_RASA || stage->alphaPass.xc_d == GX::TevAlphaArg::CA_RASA) { + fragmentFn += fmt::format(FMT_STRING("\n var rast = in.clr; // TODO lighting")); // TODO lighting + hasRast = true; + } + } + idx++; + } + for (size_t idx = 0; const auto& stage : sTevStages) { + if (!stage) { + idx++; + continue; + } + { + std::string op; + std::string outReg; + switch (stage->colorOp.xc_regId) { + case GX::TevRegID::TEVPREV: + outReg = "prev"; + break; + default: + Log.report(logvisor::Fatal, FMT_STRING("TODO: colorOp outReg {}"), + magic_enum::enum_name(stage->colorOp.xc_regId)); + } + op = fmt::format(FMT_STRING("({3} {4} ((1.0 - {2}) * {0} + {2} * {1}){5}){6}"), + color_arg_reg(stage->colorPass.x0_a, idx), color_arg_reg(stage->colorPass.x4_b, idx), + color_arg_reg(stage->colorPass.x8_c, idx), color_arg_reg(stage->colorPass.xc_d, idx), + tev_op(stage->colorOp.x4_op), tev_bias(stage->colorOp.x8_bias), + tev_scale(stage->colorOp.xc_scale)); + fragmentFn += fmt::format(FMT_STRING("\n {0} = vec4({1}, {0}.a);"), outReg, op); + } + { + std::string op; + std::string outReg; + switch (stage->alphaOp.xc_regId) { + case GX::TevRegID::TEVPREV: + outReg = "prev.a"; + break; + default: + Log.report(logvisor::Fatal, FMT_STRING("TODO: alphaOp outReg {}"), + magic_enum::enum_name(stage->alphaOp.xc_regId)); + } + op = fmt::format(FMT_STRING("({3} {4} ((1.0 - {2}) * {0} + {2} * {1}){5}){6}"), + alpha_arg_reg(stage->alphaPass.x0_a, idx), alpha_arg_reg(stage->alphaPass.x4_b, idx), + alpha_arg_reg(stage->alphaPass.x8_c, idx), alpha_arg_reg(stage->alphaPass.xc_d, idx), + tev_op(stage->alphaOp.x4_op), tev_bias(stage->alphaOp.x8_bias), + tev_scale(stage->alphaOp.xc_scale)); + fragmentFn += fmt::format(FMT_STRING("\n {0} = {1};"), outReg, op); + } + idx++; + } + + const auto shaderSource = + fmt::format(FMT_STRING(R"""( +struct Uniform {{ + xf: mat4x4;{uniBufAttrs} +}}; +@group(0) @binding(0) +var ubuf: Uniform;{sampBindings}{texBindings} + +struct VertexOutput {{ + @builtin(position) pos: vec4;{vtxOutAttrs} +}}; + +@stage(vertex) +fn vs_main( + @location(0) in_pos: vec3{vtxInAttrs} +) -> VertexOutput {{ + var out: VertexOutput; + out.pos = ubuf.xf * vec4(in_pos, 1.0);{vtxXfrAttrs} + return out; +}} + +@stage(fragment) +fn fs_main(in: VertexOutput) -> @location(0) vec4 {{ + var prev: vec4;{fragmentFn} + return prev; +}} +)"""), + "uniBufAttrs"_a = uniBufAttrs, "sampBindings"_a = sampBindings, "texBindings"_a = texBindings, + "vtxOutAttrs"_a = vtxOutAttrs, "vtxInAttrs"_a = vtxInAttrs, "vtxXfrAttrs"_a = vtxXfrAttrs, + "fragmentFn"_a = fragmentFn); + Log.report(logvisor::Info, FMT_STRING("Generated shader: {}"), shaderSource); + + wgpu::ShaderModuleWGSLDescriptor wgslDescriptor{}; + wgslDescriptor.source = shaderSource.c_str(); + const auto shaderDescriptor = wgpu::ShaderModuleDescriptor{ + .nextInChain = &wgslDescriptor, + .label = "Generated Shader", + }; + auto shader = gpu::g_device.CreateShaderModule(&shaderDescriptor); + g_streamCachedShaders.emplace(hash, std::move(shader)); + + return hash; +} + +void stream_end() noexcept { + if (sStreamState->flags & metaforce::EStreamFlagBits::fHasTexture && !sTextures[0]) { + Log.report(logvisor::Fatal, FMT_STRING("Stream has texture but no texture bound!")); + unreachable(); + } + const auto vertRange = push_verts(sStreamState->vertexBuffer.data(), sStreamState->vertexBuffer.size()); + + ByteBuffer uniBuf; + std::bitset usedColors; + { + const auto xf = get_combined_matrix(); + uniBuf.append(&xf, 64); + } + if (sStreamState->flags & metaforce::EStreamFlagBits::fHasTexture) { + uniBuf.append(&sTextures[0].lod, 4); + } + const auto uniRange = push_uniform(uniBuf.data(), uniBuf.size()); + + const auto shaderRef = generate_shader(); + + const auto uniform_size = align_uniform(uniBuf.size()); + const auto pipeline = pipeline_ref(stream::PipelineConfig{ + .shader = shaderRef, + .uniformSize = uniform_size, + .primitive = sStreamState->primitive, + .flags = sStreamState->flags, + .depthCompare = g_depthCompare, + .depthUpdate = g_depthUpdate, + .depthFunc = g_depthFunc, + .cullMode = g_cullMode, + .blendMode = g_blendMode, + .blendFacSrc = g_blendFacSrc, + .blendFacDst = g_blendFacDst, + .blendOp = g_blendOp, + .dstAlpha = g_dstAlpha, + }); + + BindGroupRef samplerBindGroup{}; + BindGroupRef textureBindGroup{}; + if (sStreamState->flags & metaforce::EStreamFlagBits::fHasTexture) { + const auto& state = get_state(); + { + const std::array samplerEntries{wgpu::BindGroupEntry{ + .binding = 0, + .sampler = sampler_ref(sTextures[0].get_descriptor()), + }}; + samplerBindGroup = bind_group_ref(wgpu::BindGroupDescriptor{ + .label = "Stream Sampler Bind Group", + .layout = state.samplerLayout, + .entryCount = samplerEntries.size(), + .entries = samplerEntries.data(), + }); + } + { + const std::array textureEntries{wgpu::BindGroupEntry{ + .binding = 0, + .textureView = sTextures[0].handle.ref->view, + }}; + textureBindGroup = bind_group_ref(wgpu::BindGroupDescriptor{ + .label = "Stream Texture Bind Group", + .layout = state.textureLayout, + .entryCount = textureEntries.size(), + .entries = textureEntries.data(), + }); + } + } + + push_draw_command(stream::DrawData{ + .pipeline = pipeline, + .vertRange = vertRange, + .uniformRange = uniRange, + .vertexCount = sStreamState->vertexCount, + .uniformSize = uniform_size, + .samplerBindGroup = samplerBindGroup, + .textureBindGroup = textureBindGroup, + }); + + sStreamState.reset(); +} } // namespace aurora::gfx diff --git a/aurora/lib/gfx/stream/shader.cpp b/aurora/lib/gfx/stream/shader.cpp new file mode 100644 index 000000000..641dfcd9d --- /dev/null +++ b/aurora/lib/gfx/stream/shader.cpp @@ -0,0 +1,322 @@ +#include "shader.hpp" + +#include "../../gpu.hpp" +#include "../common.hpp" + +#include + +namespace aurora::gfx { +extern std::unordered_map g_streamCachedShaders; +} // namespace aurora::gfx + +namespace aurora::gfx::stream { +static logvisor::Module Log("aurora::gfx::stream"); + +using gpu::g_device; +using gpu::g_graphicsConfig; +using gpu::utils::make_vertex_state; + +static wgpu::BlendFactor to_blend_factor(metaforce::ERglBlendFactor fac) { + switch (fac) { + case metaforce::ERglBlendFactor::Zero: + return wgpu::BlendFactor::Zero; + case metaforce::ERglBlendFactor::One: + return wgpu::BlendFactor::One; + case metaforce::ERglBlendFactor::SrcColor: + return wgpu::BlendFactor::Src; + case metaforce::ERglBlendFactor::InvSrcColor: + return wgpu::BlendFactor::OneMinusSrc; + case metaforce::ERglBlendFactor::SrcAlpha: + return wgpu::BlendFactor::SrcAlpha; + case metaforce::ERglBlendFactor::InvSrcAlpha: + return wgpu::BlendFactor::OneMinusSrcAlpha; + case metaforce::ERglBlendFactor::DstAlpha: + return wgpu::BlendFactor::DstAlpha; + case metaforce::ERglBlendFactor::InvDstAlpha: + return wgpu::BlendFactor::OneMinusDstAlpha; + case metaforce::ERglBlendFactor::DstColor: + return wgpu::BlendFactor::Dst; + case metaforce::ERglBlendFactor::InvDstColor: + return wgpu::BlendFactor::OneMinusDst; + } +} + +wgpu::RenderPipeline create_pipeline(const State& state, [[maybe_unused]] PipelineConfig config) { + std::array attributes{}; + attributes[0] = wgpu::VertexAttribute{ + .format = wgpu::VertexFormat::Float32x3, + .offset = 0, + .shaderLocation = 0, + }; + uint64_t offset = 12; + uint32_t shaderLocation = 1; + if (config.flags & metaforce::EStreamFlagBits::fHasNormal) { + attributes[shaderLocation] = wgpu::VertexAttribute{ + .format = wgpu::VertexFormat::Float32x3, + .offset = offset, + .shaderLocation = shaderLocation, + }; + offset += 12; + shaderLocation++; + } + if (config.flags & metaforce::EStreamFlagBits::fHasColor) { + attributes[shaderLocation] = wgpu::VertexAttribute{ + .format = wgpu::VertexFormat::Float32x4, + .offset = offset, + .shaderLocation = shaderLocation, + }; + offset += 16; + shaderLocation++; + } + if (config.flags & metaforce::EStreamFlagBits::fHasTexture) { + attributes[shaderLocation] = wgpu::VertexAttribute{ + .format = wgpu::VertexFormat::Float32x2, + .offset = offset, + .shaderLocation = shaderLocation, + }; + offset += 8; + shaderLocation++; + } + const std::array vertexBuffers{wgpu::VertexBufferLayout{ + .arrayStride = offset, + .attributeCount = shaderLocation, + .attributes = attributes.data(), + }}; + + wgpu::CompareFunction depthCompare; + switch (config.depthFunc) { + case metaforce::ERglEnum::Never: + depthCompare = wgpu::CompareFunction::Never; + break; + case metaforce::ERglEnum::Less: + depthCompare = wgpu::CompareFunction::Less; + break; + case metaforce::ERglEnum::Equal: + depthCompare = wgpu::CompareFunction::Equal; + break; + case metaforce::ERglEnum::LEqual: + depthCompare = wgpu::CompareFunction::LessEqual; + break; + case metaforce::ERglEnum::Greater: + depthCompare = wgpu::CompareFunction::Greater; + break; + case metaforce::ERglEnum::NEqual: + depthCompare = wgpu::CompareFunction::NotEqual; + break; + case metaforce::ERglEnum::GEqual: + depthCompare = wgpu::CompareFunction::GreaterEqual; + break; + case metaforce::ERglEnum::Always: + depthCompare = wgpu::CompareFunction::Always; + break; + } + const auto depthStencil = wgpu::DepthStencilState{ + .format = g_graphicsConfig.depthFormat, + .depthWriteEnabled = config.depthUpdate, + .depthCompare = depthCompare, + }; + + if (config.blendMode != metaforce::ERglBlendMode::Blend) { + Log.report(logvisor::Fatal, FMT_STRING("How to {}?"), magic_enum::enum_name(config.blendMode)); + } + const auto colorBlendComponent = wgpu::BlendComponent{ + .operation = wgpu::BlendOperation::Add, + .srcFactor = to_blend_factor(config.blendFacSrc), + .dstFactor = to_blend_factor(config.blendFacDst), + }; + auto alphaBlendComponent = colorBlendComponent; + if (config.dstAlpha) { + alphaBlendComponent = wgpu::BlendComponent{ + .operation = wgpu::BlendOperation::Add, + .srcFactor = wgpu::BlendFactor::Zero, + .dstFactor = wgpu::BlendFactor::Constant, + }; + } + const auto blendState = wgpu::BlendState{ + .color = colorBlendComponent, + .alpha = alphaBlendComponent, + }; + auto writeMask = wgpu::ColorWriteMask::Red | wgpu::ColorWriteMask::Green | wgpu::ColorWriteMask::Blue; + if (config.alphaUpdate) { + writeMask = writeMask | wgpu::ColorWriteMask::Alpha; + } + const std::array colorTargets{wgpu::ColorTargetState{ + .format = g_graphicsConfig.colorFormat, + .blend = &blendState, + .writeMask = writeMask, + }}; + const auto& shader = g_streamCachedShaders[config.shader]; + const auto fragmentState = wgpu::FragmentState{ + .module = shader, + .entryPoint = "fs_main", + .targetCount = colorTargets.size(), + .targets = colorTargets.data(), + }; + + wgpu::PrimitiveTopology primitive; + switch (config.primitive) { + case GX::POINTS: + primitive = wgpu::PrimitiveTopology::PointList; + break; + case GX::LINES: + primitive = wgpu::PrimitiveTopology::LineList; + break; + case GX::LINESTRIP: + primitive = wgpu::PrimitiveTopology::LineStrip; + break; + case GX::TRIANGLES: + primitive = wgpu::PrimitiveTopology::TriangleList; + break; + case GX::TRIANGLESTRIP: + primitive = wgpu::PrimitiveTopology::TriangleStrip; + break; + default: + Log.report(logvisor::Fatal, FMT_STRING("Unsupported primitive type {}"), magic_enum::enum_name(config.primitive)); + unreachable(); + } + + wgpu::FrontFace frontFace; + wgpu::CullMode cullMode; + switch (config.cullMode) { + case metaforce::ERglCullMode::Front: + frontFace = wgpu::FrontFace::CW; + cullMode = wgpu::CullMode::Front; + break; + case metaforce::ERglCullMode::Back: + frontFace = wgpu::FrontFace::CCW; + cullMode = wgpu::CullMode::Back; + break; + default: + frontFace = wgpu::FrontFace::CCW; + cullMode = wgpu::CullMode::None; + break; + } + + wgpu::BindGroupLayout uniformLayout; + if (state.uniform.contains(config.uniformSize)) { + uniformLayout = state.uniform.at(config.uniformSize).layout; + } else { + const std::array uniformLayoutEntries{wgpu::BindGroupLayoutEntry{ + .binding = 0, + .visibility = wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment, + .buffer = + wgpu::BufferBindingLayout{ + .type = wgpu::BufferBindingType::Uniform, + .hasDynamicOffset = true, + .minBindingSize = config.uniformSize, + }, + }}; + const auto uniformLayoutDescriptor = wgpu::BindGroupLayoutDescriptor{ + .label = "Stream Uniform Bind Group Layout", + .entryCount = uniformLayoutEntries.size(), + .entries = uniformLayoutEntries.data(), + }; + uniformLayout = g_device.CreateBindGroupLayout(&uniformLayoutDescriptor); + + const std::array uniformBindGroupEntries{wgpu::BindGroupEntry{ + .binding = 0, + .buffer = g_uniformBuffer, + .size = config.uniformSize, + }}; + const auto uniformBindGroupDescriptor = wgpu::BindGroupDescriptor{ + .label = "Stream Quad Uniform Bind Group", + .layout = uniformLayout, + .entryCount = uniformBindGroupEntries.size(), + .entries = uniformBindGroupEntries.data(), + }; + auto uniformBindGroup = g_device.CreateBindGroup(&uniformBindGroupDescriptor); + + state.uniform.try_emplace(config.uniformSize, uniformLayout, std::move(uniformBindGroup)); + } + + const std::array bindGroupLayouts{ + uniformLayout, + state.samplerLayout, + state.textureLayout, + }; + const auto pipelineLayoutDescriptor = wgpu::PipelineLayoutDescriptor{ + .label = "Stream Pipeline Layout", + .bindGroupLayoutCount = + // TODO avoid creating bind group layouts if no tex? + static_cast(config.flags & metaforce::EStreamFlagBits::fHasTexture ? bindGroupLayouts.size() : 1), + .bindGroupLayouts = bindGroupLayouts.data(), + }; + auto pipelineLayout = g_device.CreatePipelineLayout(&pipelineLayoutDescriptor); + + const auto pipelineDescriptor = wgpu::RenderPipelineDescriptor{ + .label = "Stream Pipeline", + .layout = pipelineLayout, + .vertex = make_vertex_state(shader, vertexBuffers), + .primitive = + wgpu::PrimitiveState{ + .topology = primitive, + .frontFace = frontFace, + .cullMode = cullMode, + }, + .depthStencil = &depthStencil, + .multisample = + wgpu::MultisampleState{ + .count = g_graphicsConfig.msaaSamples, + }, + .fragment = &fragmentState, + }; + return g_device.CreateRenderPipeline(&pipelineDescriptor); +} + +State construct_state() { + const auto samplerBinding = wgpu::SamplerBindingLayout{ + .type = wgpu::SamplerBindingType::Filtering, + }; + const std::array samplerLayoutEntries{ + wgpu::BindGroupLayoutEntry{ + .binding = 0, + .visibility = wgpu::ShaderStage::Fragment, + .sampler = samplerBinding, + }, + }; + const auto samplerLayoutDescriptor = wgpu::BindGroupLayoutDescriptor{ + .label = "Stream Sampler Bind Group Layout", + .entryCount = samplerLayoutEntries.size(), + .entries = samplerLayoutEntries.data(), + }; + auto samplerLayout = g_device.CreateBindGroupLayout(&samplerLayoutDescriptor); + + const auto textureBinding = wgpu::TextureBindingLayout{ + .sampleType = wgpu::TextureSampleType::Float, + .viewDimension = wgpu::TextureViewDimension::e2D, + }; + const std::array textureLayoutEntries{ + wgpu::BindGroupLayoutEntry{ + .binding = 0, + .visibility = wgpu::ShaderStage::Fragment, + .texture = textureBinding, + }, + }; + const auto textureLayoutDescriptor = wgpu::BindGroupLayoutDescriptor{ + .label = "Stream Texture Bind Group Layout", + .entryCount = textureLayoutEntries.size(), + .entries = textureLayoutEntries.data(), + }; + auto textureLayout = g_device.CreateBindGroupLayout(&textureLayoutDescriptor); + + return { + .samplerLayout = samplerLayout, + .textureLayout = textureLayout, + }; +} + +void render(const State& state, const DrawData& data, const wgpu::RenderPassEncoder& pass) { + if (!bind_pipeline(data.pipeline, pass)) { + return; + } + + const std::array offsets{data.uniformRange.first}; + pass.SetBindGroup(0, state.uniform.at(data.uniformSize).bindGroup, offsets.size(), offsets.data()); + if (data.samplerBindGroup && data.textureBindGroup) { + pass.SetBindGroup(1, find_bind_group(data.samplerBindGroup)); + pass.SetBindGroup(2, find_bind_group(data.textureBindGroup)); + } + pass.SetVertexBuffer(0, g_vertexBuffer, data.vertRange.first, data.vertRange.second); + pass.Draw(data.vertexCount); +} +} // namespace aurora::gfx::stream diff --git a/aurora/lib/gfx/stream/shader.hpp b/aurora/lib/gfx/stream/shader.hpp new file mode 100644 index 000000000..c949c8f5e --- /dev/null +++ b/aurora/lib/gfx/stream/shader.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "../common.hpp" + +namespace aurora::gfx::stream { +struct DrawData { + PipelineRef pipeline; + Range vertRange; + Range uniformRange; + uint32_t vertexCount; + uint32_t uniformSize; + BindGroupRef samplerBindGroup; + BindGroupRef textureBindGroup; +}; + +struct PipelineConfig { + ShaderRef shader; + uint32_t uniformSize; + + GX::Primitive primitive; + metaforce::EStreamFlags flags; + bool depthCompare, depthUpdate, alphaUpdate; + metaforce::ERglEnum depthFunc; + metaforce::ERglCullMode cullMode; + metaforce::ERglBlendMode blendMode; + metaforce::ERglBlendFactor blendFacSrc, blendFacDst; + metaforce::ERglLogicOp blendOp; + std::optional dstAlpha; +}; + +struct CachedBindGroup { + wgpu::BindGroupLayout layout; + wgpu::BindGroup bindGroup; + CachedBindGroup(wgpu::BindGroupLayout layout, wgpu::BindGroup&& group) + : layout(std::move(layout)), bindGroup(std::move(group)) {} +}; +struct State { + wgpu::BindGroupLayout samplerLayout; + wgpu::BindGroupLayout textureLayout; + mutable std::unordered_map uniform; + mutable std::unordered_map sampler; +}; + +State construct_state(); +wgpu::RenderPipeline create_pipeline(const State& state, [[maybe_unused]] PipelineConfig config); +void render(const State& state, const DrawData& data, const wgpu::RenderPassEncoder& pass); +} // namespace aurora::gfx::stream diff --git a/aurora/lib/gfx/textured_quad/shader.hpp b/aurora/lib/gfx/textured_quad/shader.hpp index ba3840d68..0efda3796 100644 --- a/aurora/lib/gfx/textured_quad/shader.hpp +++ b/aurora/lib/gfx/textured_quad/shader.hpp @@ -1,3 +1,5 @@ +#pragma once + #include "../common.hpp" namespace aurora::gfx::textured_quad { diff --git a/aurora/lib/gpu.hpp b/aurora/lib/gpu.hpp index e75375566..8704cb13d 100644 --- a/aurora/lib/gpu.hpp +++ b/aurora/lib/gpu.hpp @@ -21,7 +21,7 @@ struct GraphicsConfig { wgpu::TextureFormat colorFormat; wgpu::TextureFormat depthFormat; uint32_t msaaSamples; - uint8_t textureAnistropy; + uint16_t textureAnistropy; }; struct TextureWithSampler { wgpu::Texture texture; @@ -58,7 +58,7 @@ namespace aurora::gpu::utils { template static consteval std::array make_vertex_attributes(std::array formats) { - std::array attributes{}; + std::array attributes; uint64_t offset = 0; for (uint32_t i = 0; i < N; ++i) { auto format = formats[i];