#include "gx.hpp" #include "../webgpu/gpu.hpp" #include "../window.hpp" #include "../internal.hpp" #include "common.hpp" #include #include #include using aurora::gfx::gx::g_gxState; static aurora::Module Log("aurora::gx"); namespace aurora::gfx { static Module Log("aurora::gfx::gx"); namespace gx { using webgpu::g_device; using webgpu::g_graphicsConfig; GXState g_gxState{}; const TextureBind& get_texture(GXTexMapID id) noexcept { return g_gxState.textures[static_cast(id)]; } static inline WGPUBlendFactor to_blend_factor(GXBlendFactor fac, bool isDst) { switch (fac) { case GX_BL_ZERO: return WGPUBlendFactor_Zero; case GX_BL_ONE: return WGPUBlendFactor_One; case GX_BL_SRCCLR: // + GX_BL_DSTCLR if (isDst) { return WGPUBlendFactor_Src; } else { return WGPUBlendFactor_Dst; } case GX_BL_INVSRCCLR: // + GX_BL_INVDSTCLR if (isDst) { return WGPUBlendFactor_OneMinusSrc; } else { return WGPUBlendFactor_OneMinusDst; } case GX_BL_SRCALPHA: return WGPUBlendFactor_SrcAlpha; case GX_BL_INVSRCALPHA: return WGPUBlendFactor_OneMinusSrcAlpha; case GX_BL_DSTALPHA: return WGPUBlendFactor_DstAlpha; case GX_BL_INVDSTALPHA: return WGPUBlendFactor_OneMinusDstAlpha; default: Log.report(LOG_FATAL, FMT_STRING("invalid blend factor {}"), fac); unreachable(); } } static inline WGPUCompareFunction to_compare_function(GXCompare func) { switch (func) { case GX_NEVER: return WGPUCompareFunction_Never; case GX_LESS: return WGPUCompareFunction_Less; case GX_EQUAL: return WGPUCompareFunction_Equal; case GX_LEQUAL: return WGPUCompareFunction_LessEqual; case GX_GREATER: return WGPUCompareFunction_Greater; case GX_NEQUAL: return WGPUCompareFunction_NotEqual; case GX_GEQUAL: return WGPUCompareFunction_GreaterEqual; case GX_ALWAYS: return WGPUCompareFunction_Always; default: Log.report(LOG_FATAL, FMT_STRING("invalid depth fn {}"), func); unreachable(); } } static inline WGPUBlendState to_blend_state(GXBlendMode mode, GXBlendFactor srcFac, GXBlendFactor dstFac, GXLogicOp op, u32 dstAlpha) { WGPUBlendComponent colorBlendComponent; switch (mode) { case GX_BM_NONE: colorBlendComponent = { .operation = WGPUBlendOperation_Add, .srcFactor = WGPUBlendFactor_One, .dstFactor = WGPUBlendFactor_Zero, }; break; case GX_BM_BLEND: colorBlendComponent = { .operation = WGPUBlendOperation_Add, .srcFactor = to_blend_factor(srcFac, false), .dstFactor = to_blend_factor(dstFac, true), }; break; case GX_BM_SUBTRACT: colorBlendComponent = { .operation = WGPUBlendOperation_ReverseSubtract, .srcFactor = WGPUBlendFactor_One, .dstFactor = WGPUBlendFactor_One, }; break; case GX_BM_LOGIC: switch (op) { case GX_LO_CLEAR: colorBlendComponent = { .operation = WGPUBlendOperation_Add, .srcFactor = WGPUBlendFactor_Zero, .dstFactor = WGPUBlendFactor_Zero, }; break; case GX_LO_COPY: colorBlendComponent = { .operation = WGPUBlendOperation_Add, .srcFactor = WGPUBlendFactor_One, .dstFactor = WGPUBlendFactor_Zero, }; break; case GX_LO_NOOP: colorBlendComponent = { .operation = WGPUBlendOperation_Add, .srcFactor = WGPUBlendFactor_Zero, .dstFactor = WGPUBlendFactor_One, }; break; default: Log.report(LOG_FATAL, FMT_STRING("unsupported logic op {}"), op); unreachable(); } break; default: Log.report(LOG_FATAL, FMT_STRING("unsupported blend mode {}"), mode); unreachable(); } WGPUBlendComponent alphaBlendComponent{ .operation = WGPUBlendOperation_Add, .srcFactor = WGPUBlendFactor_One, .dstFactor = WGPUBlendFactor_Zero, }; if (dstAlpha != UINT32_MAX) { alphaBlendComponent = WGPUBlendComponent{ .operation = WGPUBlendOperation_Add, .srcFactor = WGPUBlendFactor_Constant, .dstFactor = WGPUBlendFactor_Zero, }; } return { .color = colorBlendComponent, .alpha = alphaBlendComponent, }; } static inline WGPUColorWriteMaskFlags to_write_mask(bool colorUpdate, bool alphaUpdate) { WGPUColorWriteMaskFlags writeMask = WGPUColorWriteMask_None; if (colorUpdate) { writeMask |= WGPUColorWriteMask_Red | WGPUColorWriteMask_Green | WGPUColorWriteMask_Blue; } if (alphaUpdate) { writeMask |= WGPUColorWriteMask_Alpha; } return writeMask; } static inline WGPUPrimitiveState to_primitive_state(GXPrimitive gx_prim, GXCullMode gx_cullMode) { WGPUPrimitiveTopology primitive = WGPUPrimitiveTopology_TriangleList; switch (gx_prim) { case GX_TRIANGLES: break; case GX_TRIANGLESTRIP: primitive = WGPUPrimitiveTopology_TriangleStrip; break; default: Log.report(LOG_FATAL, FMT_STRING("Unsupported primitive type {}"), gx_prim); unreachable(); } WGPUCullMode cullMode = WGPUCullMode_None; switch (gx_cullMode) { case GX_CULL_FRONT: cullMode = WGPUCullMode_Front; break; case GX_CULL_BACK: cullMode = WGPUCullMode_Back; break; case GX_CULL_NONE: break; default: Log.report(LOG_FATAL, FMT_STRING("Unsupported cull mode {}"), gx_cullMode); unreachable(); } return { .topology = primitive, .frontFace = WGPUFrontFace_CW, .cullMode = cullMode, }; } WGPURenderPipeline build_pipeline(const PipelineConfig& config, const ShaderInfo& info, ArrayRef vtxBuffers, WGPUShaderModule shader, const char* label) noexcept { const WGPUDepthStencilState depthStencil{ .format = g_graphicsConfig.depthFormat, .depthWriteEnabled = config.depthUpdate, .depthCompare = to_compare_function(config.depthFunc), .stencilFront = WGPUStencilFaceState{ .compare = WGPUCompareFunction_Always, }, .stencilBack = WGPUStencilFaceState{ .compare = WGPUCompareFunction_Always, }, }; const auto blendState = to_blend_state(config.blendMode, config.blendFacSrc, config.blendFacDst, config.blendOp, config.dstAlpha); const std::array colorTargets{WGPUColorTargetState{ .format = g_graphicsConfig.colorFormat, .blend = &blendState, .writeMask = to_write_mask(config.colorUpdate, config.alphaUpdate), }}; const WGPUFragmentState fragmentState{ .module = shader, .entryPoint = "fs_main", .targetCount = colorTargets.size(), .targets = colorTargets.data(), }; auto layouts = build_bind_group_layouts(info, config.shaderConfig); const std::array bindGroupLayouts{ layouts.uniformLayout, layouts.samplerLayout, layouts.textureLayout, }; const WGPUPipelineLayoutDescriptor pipelineLayoutDescriptor{ .label = "GX Pipeline Layout", .bindGroupLayoutCount = static_cast(info.sampledTextures.any() ? bindGroupLayouts.size() : 1), .bindGroupLayouts = bindGroupLayouts.data(), }; auto pipelineLayout = wgpuDeviceCreatePipelineLayout(g_device, &pipelineLayoutDescriptor); const WGPURenderPipelineDescriptor descriptor{ .label = label, .layout = pipelineLayout, .vertex = { .module = shader, .entryPoint = "vs_main", .bufferCount = static_cast(vtxBuffers.size()), .buffers = vtxBuffers.data(), }, .primitive = to_primitive_state(config.primitive, config.cullMode), .depthStencil = &depthStencil, .multisample = WGPUMultisampleState{ .count = g_graphicsConfig.msaaSamples, .mask = UINT32_MAX, }, .fragment = &fragmentState, }; auto pipeline = wgpuDeviceCreateRenderPipeline(g_device, &descriptor); wgpuPipelineLayoutRelease(pipelineLayout); return pipeline; } void populate_pipeline_config(PipelineConfig& config, GXPrimitive primitive) noexcept { config.shaderConfig.fogType = g_gxState.fog.type; config.shaderConfig.vtxAttrs = g_gxState.vtxDesc; int lastIndexedAttr = -1; for (int i = 0; i < GX_VA_MAX_ATTR; ++i) { const auto type = g_gxState.vtxDesc[i]; if (type != GX_INDEX8 && type != GX_INDEX16) { config.shaderConfig.attrMapping[i] = GX_VA_NULL; continue; } const auto& array = g_gxState.arrays[i]; if (lastIndexedAttr >= 0 && array == g_gxState.arrays[lastIndexedAttr]) { // Map attribute to previous attribute config.shaderConfig.attrMapping[i] = config.shaderConfig.attrMapping[lastIndexedAttr]; } else { // Map attribute to its own storage config.shaderConfig.attrMapping[i] = static_cast(i); } lastIndexedAttr = i; } config.shaderConfig.tevSwapTable = g_gxState.tevSwapTable; for (u8 i = 0; i < g_gxState.numTevStages; ++i) { config.shaderConfig.tevStages[i] = g_gxState.tevStages[i]; } config.shaderConfig.tevStageCount = g_gxState.numTevStages; for (u8 i = 0; i < g_gxState.numChans * 2; ++i) { const auto& cc = g_gxState.colorChannelConfig[i]; if (cc.lightingEnabled) { config.shaderConfig.colorChannels[i] = cc; } else { // Only matSrc matters when lighting disabled config.shaderConfig.colorChannels[i] = { .matSrc = cc.matSrc, }; } } for (u8 i = 0; i < g_gxState.numTexGens; ++i) { config.shaderConfig.tcgs[i] = g_gxState.tcgs[i]; } if (g_gxState.alphaCompare) { config.shaderConfig.alphaCompare = g_gxState.alphaCompare; } config.shaderConfig.indexedAttributeCount = std::count_if(config.shaderConfig.vtxAttrs.begin(), config.shaderConfig.vtxAttrs.end(), [](const auto type) { return type == GX_INDEX8 || type == GX_INDEX16; }); 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, .primitive = primitive, .depthFunc = g_gxState.depthFunc, .cullMode = g_gxState.cullMode, .blendMode = g_gxState.blendMode, .blendFacSrc = g_gxState.blendFacSrc, .blendFacDst = g_gxState.blendFacDst, .blendOp = g_gxState.blendOp, .dstAlpha = g_gxState.dstAlpha, .depthCompare = g_gxState.depthCompare, .depthUpdate = g_gxState.depthUpdate, .alphaUpdate = g_gxState.alphaUpdate, .colorUpdate = g_gxState.colorUpdate, }; } Range build_uniform(const ShaderInfo& info) noexcept { auto [buf, range] = map_uniform(info.uniformSize); { buf.append(&g_gxState.pnMtx[g_gxState.currentPnMtx], 128); buf.append(&g_gxState.proj, 64); } for (int i = 0; i < info.loadsTevReg.size(); ++i) { if (!info.loadsTevReg.test(i)) { continue; } buf.append(&g_gxState.colorRegs[i], 16); } bool lightingEnabled = false; for (int i = 0; i < info.sampledColorChannels.size(); ++i) { if (!info.sampledColorChannels.test(i)) { continue; } const auto& ccc = g_gxState.colorChannelConfig[i * 2]; const auto& ccca = g_gxState.colorChannelConfig[i * 2 + 1]; if (ccc.lightingEnabled || ccca.lightingEnabled) { lightingEnabled = true; break; } } if (lightingEnabled) { // Lights static_assert(sizeof(g_gxState.lights) == 80 * GX::MaxLights); buf.append(&g_gxState.lights, 80 * GX::MaxLights); // Light state for all channels for (int i = 0; i < 4; ++i) { u32 lightState = g_gxState.colorChannelState[i].lightMask.to_ulong(); buf.append(&lightState, 4); } } for (int i = 0; i < info.sampledColorChannels.size(); ++i) { if (!info.sampledColorChannels.test(i)) { continue; } const auto& ccc = g_gxState.colorChannelConfig[i * 2]; const auto& ccs = g_gxState.colorChannelState[i * 2]; if (ccc.lightingEnabled && ccc.ambSrc == GX_SRC_REG) { buf.append(&ccs.ambColor, 16); } if (ccc.matSrc == GX_SRC_REG) { buf.append(&ccs.matColor, 16); } const auto& ccca = g_gxState.colorChannelConfig[i * 2 + 1]; const auto& ccsa = g_gxState.colorChannelState[i * 2 + 1]; if (ccca.lightingEnabled && ccca.ambSrc == GX_SRC_REG) { buf.append(&ccsa.ambColor, 16); } if (ccca.matSrc == GX_SRC_REG) { buf.append(&ccsa.matColor, 16); } } for (int i = 0; i < info.sampledKColors.size(); ++i) { if (!info.sampledKColors.test(i)) { continue; } buf.append(&g_gxState.kcolors[i], 16); } for (int i = 0; i < info.usesTexMtx.size(); ++i) { if (!info.usesTexMtx.test(i)) { continue; } const auto& state = g_gxState; switch (info.texMtxTypes[i]) { case GX_TG_MTX2x4: if (std::holds_alternative>(state.texMtxs[i])) { buf.append(&std::get>(state.texMtxs[i]), 32); } else if (std::holds_alternative>(g_gxState.texMtxs[i])) { // TODO: SMB hits this? Mat4x2 mtx{ {1.f, 0.f}, {0.f, 1.f}, {0.f, 0.f}, {0.f, 0.f}, }; buf.append(&mtx, 32); } else { Log.report(LOG_FATAL, FMT_STRING("expected 2x4 mtx in idx {}"), i); unreachable(); } break; case GX_TG_MTX3x4: if (std::holds_alternative>(g_gxState.texMtxs[i])) { const auto& mat = std::get>(g_gxState.texMtxs[i]); buf.append(&mat, 64); } else { Log.report(LOG_FATAL, FMT_STRING("expected 3x4 mtx in idx {}"), i); buf.append(&Mat4x4_Identity, 64); } break; default: Log.report(LOG_FATAL, FMT_STRING("unhandled tex mtx type {}"), info.texMtxTypes[i]); unreachable(); } } for (int i = 0; i < info.usesPTTexMtx.size(); ++i) { if (!info.usesPTTexMtx.test(i)) { continue; } buf.append(&g_gxState.ptTexMtxs[i], 64); } if (info.usesFog) { const auto& state = g_gxState.fog; struct Fog { Vec4 color = state.color; float a = 0.f; float b = 0.5f; float c = 0.f; float pad = FLT_MAX; } fog{}; static_assert(sizeof(Fog) == 32); if (state.nearZ != state.farZ && state.startZ != state.endZ) { const float depthRange = state.farZ - state.nearZ; const float fogRange = state.endZ - state.startZ; fog.a = (state.farZ * state.nearZ) / (depthRange * fogRange); fog.b = state.farZ / depthRange; fog.c = state.startZ / fogRange; } buf.append(&fog, 32); } for (int i = 0; i < info.sampledTextures.size(); ++i) { if (!info.sampledTextures.test(i)) { continue; } const auto& tex = get_texture(static_cast(i)); if (!tex) { Log.report(LOG_FATAL, FMT_STRING("unbound texture {}"), i); unreachable(); } buf.append(&tex.texObj.lodBias, 4); } g_gxState.stateDirty = false; return range; } static absl::flat_hash_map sUniformBindGroupLayouts; static absl::flat_hash_map> sTextureBindGroupLayouts; GXBindGroups build_bind_groups(const ShaderInfo& info, const ShaderConfig& config, const BindGroupRanges& ranges) noexcept { const auto layouts = build_bind_group_layouts(info, config); std::array uniformEntries{ WGPUBindGroupEntry{ .binding = 0, .buffer = g_uniformBuffer, .size = info.uniformSize, }, }; u32 uniformBindIdx = 1; for (u32 i = 0; i < GX_VA_MAX_ATTR; ++i) { const Range& range = ranges.vaRanges[i]; if (range.size <= 0) { continue; } uniformEntries[uniformBindIdx] = WGPUBindGroupEntry{ .binding = uniformBindIdx, .buffer = g_storageBuffer, .size = range.size, }; ++uniformBindIdx; } std::array samplerEntries; std::array textureEntries; u32 samplerCount = 0; u32 textureCount = 0; for (u32 i = 0; i < info.sampledTextures.size(); ++i) { if (!info.sampledTextures.test(i)) { continue; } const auto& tex = g_gxState.textures[i]; if (!tex) { Log.report(LOG_FATAL, FMT_STRING("unbound texture {}"), i); unreachable(); } samplerEntries[samplerCount] = { .binding = samplerCount, .sampler = sampler_ref(tex.get_descriptor()), }; ++samplerCount; textureEntries[textureCount] = { .binding = textureCount, .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; if (tlut < GX_TLUT0 || tlut > GX_TLUT7) { Log.report(LOG_FATAL, FMT_STRING("tlut out of bounds {}"), tlut); unreachable(); } else if (!g_gxState.tluts[tlut].ref) { Log.report(LOG_FATAL, FMT_STRING("tlut unbound {}"), tlut); unreachable(); } textureEntries[textureCount] = { .binding = textureCount, .textureView = g_gxState.tluts[tlut].ref->view, }; ++textureCount; } } return { .uniformBindGroup = bind_group_ref(WGPUBindGroupDescriptor{ .label = "GX Uniform Bind Group", .layout = layouts.uniformLayout, .entryCount = uniformBindIdx, .entries = uniformEntries.data(), }), .samplerBindGroup = bind_group_ref(WGPUBindGroupDescriptor{ .label = "GX Sampler Bind Group", .layout = layouts.samplerLayout, .entryCount = samplerCount, .entries = samplerEntries.data(), }), .textureBindGroup = bind_group_ref(WGPUBindGroupDescriptor{ .label = "GX Texture Bind Group", .layout = layouts.textureLayout, .entryCount = textureCount, .entries = textureEntries.data(), }), }; } GXBindGroupLayouts build_bind_group_layouts(const ShaderInfo& info, const ShaderConfig& config) noexcept { GXBindGroupLayouts out; u32 uniformSizeKey = info.uniformSize + (config.indexedAttributeCount > 0 ? 1 : 0); const auto uniformIt = sUniformBindGroupLayouts.find(uniformSizeKey); if (uniformIt != sUniformBindGroupLayouts.end()) { out.uniformLayout = uniformIt->second; } else { std::array uniformLayoutEntries{ WGPUBindGroupLayoutEntry{ .binding = 0, .visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment, .buffer = WGPUBufferBindingLayout{ .type = WGPUBufferBindingType_Uniform, .hasDynamicOffset = true, .minBindingSize = info.uniformSize, }, }, }; u32 bindIdx = 1; for (int i = 0; i < GX_VA_MAX_ATTR; ++i) { if (config.attrMapping[i] == static_cast(i)) { uniformLayoutEntries[bindIdx] = WGPUBindGroupLayoutEntry{ .binding = bindIdx, .visibility = WGPUShaderStage_Vertex, .buffer = WGPUBufferBindingLayout{ .type = WGPUBufferBindingType_ReadOnlyStorage, .hasDynamicOffset = true, }, }; ++bindIdx; } } const auto uniformLayoutDescriptor = WGPUBindGroupLayoutDescriptor{ .label = "GX Uniform Bind Group Layout", .entryCount = bindIdx, .entries = uniformLayoutEntries.data(), }; out.uniformLayout = wgpuDeviceCreateBindGroupLayout(g_device, &uniformLayoutDescriptor); // sUniformBindGroupLayouts.try_emplace(uniformSizeKey, out.uniformLayout); } // u32 textureCount = info.sampledTextures.count(); // const auto textureIt = sTextureBindGroupLayouts.find(textureCount); // if (textureIt != sTextureBindGroupLayouts.end()) { // const auto& [sl, tl] = textureIt->second; // out.samplerLayout = sl; // out.textureLayout = tl; // } else { u32 numSamplers = 0; 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, .visibility = WGPUShaderStage_Fragment, .sampler = {.type = copyAsPalette && loadAsPalette ? WGPUSamplerBindingType_NonFiltering : WGPUSamplerBindingType_Filtering}, }; ++numSamplers; if (loadAsPalette) { textureEntries[numTextures] = { .binding = numTextures, .visibility = WGPUShaderStage_Fragment, .texture = { .sampleType = copyAsPalette ? WGPUTextureSampleType_Sint : WGPUTextureSampleType_Float, .viewDimension = WGPUTextureViewDimension_2D, }, }; ++numTextures; textureEntries[numTextures] = { .binding = numTextures, .visibility = WGPUShaderStage_Fragment, .texture = { .sampleType = WGPUTextureSampleType_Float, .viewDimension = WGPUTextureViewDimension_2D, }, }; ++numTextures; } else { textureEntries[numTextures] = { .binding = numTextures, .visibility = WGPUShaderStage_Fragment, .texture = { .sampleType = WGPUTextureSampleType_Float, .viewDimension = WGPUTextureViewDimension_2D, }, }; ++numTextures; } } { const WGPUBindGroupLayoutDescriptor descriptor{ .label = "GX Sampler Bind Group Layout", .entryCount = numSamplers, .entries = samplerEntries.data(), }; out.samplerLayout = wgpuDeviceCreateBindGroupLayout(g_device, &descriptor); } { const WGPUBindGroupLayoutDescriptor descriptor{ .label = "GX Texture Bind Group Layout", .entryCount = numTextures, .entries = textureEntries.data(), }; out.textureLayout = wgpuDeviceCreateBindGroupLayout(g_device, &descriptor); } // sTextureBindGroupLayouts.try_emplace(textureCount, out.samplerLayout, out.textureLayout); // } return out; } // TODO this is awkward extern absl::flat_hash_map> g_gxCachedShaders; void shutdown() noexcept { // TODO we should probably store this all in g_state.gx instead for (const auto& item : sUniformBindGroupLayouts) { wgpuBindGroupLayoutRelease(item.second); } sUniformBindGroupLayouts.clear(); for (const auto& item : sTextureBindGroupLayouts) { wgpuBindGroupLayoutRelease(item.second.first); wgpuBindGroupLayoutRelease(item.second.second); } sTextureBindGroupLayouts.clear(); for (auto& item : g_gxState.textures) { item.texObj.ref.reset(); } for (auto& item : g_gxState.tluts) { item.ref.reset(); } for (const auto& item : g_gxCachedShaders) { wgpuShaderModuleRelease(item.second.first); } g_gxCachedShaders.clear(); } } // namespace gx static WGPUAddressMode wgpu_address_mode(GXTexWrapMode mode) { switch (mode) { case GX_CLAMP: return WGPUAddressMode_ClampToEdge; case GX_REPEAT: return WGPUAddressMode_Repeat; case GX_MIRROR: return WGPUAddressMode_MirrorRepeat; default: Log.report(LOG_FATAL, FMT_STRING("invalid wrap mode {}"), mode); unreachable(); } } static std::pair wgpu_filter_mode(GXTexFilter filter) { switch (filter) { case GX_NEAR: return {WGPUFilterMode_Nearest, WGPUFilterMode_Linear}; case GX_LINEAR: return {WGPUFilterMode_Linear, WGPUFilterMode_Linear}; case GX_NEAR_MIP_NEAR: return {WGPUFilterMode_Nearest, WGPUFilterMode_Nearest}; case GX_LIN_MIP_NEAR: return {WGPUFilterMode_Linear, WGPUFilterMode_Nearest}; case GX_NEAR_MIP_LIN: return {WGPUFilterMode_Nearest, WGPUFilterMode_Linear}; case GX_LIN_MIP_LIN: return {WGPUFilterMode_Linear, WGPUFilterMode_Linear}; default: Log.report(LOG_FATAL, FMT_STRING("invalid filter mode {}"), filter); unreachable(); } } static u16 wgpu_aniso(GXAnisotropy aniso) { switch (aniso) { case GX_ANISO_1: return 1; case GX_ANISO_2: return std::max(webgpu::g_graphicsConfig.textureAnisotropy / 2, 1); case GX_ANISO_4: return std::max(webgpu::g_graphicsConfig.textureAnisotropy, 1); default: Log.report(LOG_FATAL, FMT_STRING("invalid aniso mode {}"), aniso); unreachable(); } } WGPUSamplerDescriptor 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 = WGPUAddressMode_Repeat, .magFilter = WGPUFilterMode_Nearest, .minFilter = WGPUFilterMode_Nearest, .mipmapFilter = WGPUFilterMode_Nearest, .lodMinClamp = 0.f, .lodMaxClamp = 1000.f, .maxAnisotropy = 1, }; } const auto [minFilter, mipFilter] = wgpu_filter_mode(texObj.minFilter); const auto [magFilter, _] = wgpu_filter_mode(texObj.magFilter); return { .label = "Generated Filtering Sampler", .addressModeU = wgpu_address_mode(texObj.wrapS), .addressModeV = wgpu_address_mode(texObj.wrapT), .addressModeW = WGPUAddressMode_Repeat, .magFilter = magFilter, .minFilter = minFilter, .mipmapFilter = mipFilter, .lodMinClamp = 0.f, .lodMaxClamp = 1000.f, .maxAnisotropy = wgpu_aniso(texObj.maxAniso), }; } } // namespace aurora::gfx