mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-10-02 08:09:51 -04:00
6f14bde26a
* Preserve interrupted registers and unwind alarm guards before rescheduling Adapt the RFL interrupt-context and alarm reschedule fixes from KartPad ed8e4ca and 0c9bff0. Keep caller registers private and release the recursion guard before a woken fiber can pump callbacks. * Keep local Wii identity services available when networking is disabled Adapt KartPad a0f3fb5. Only IP and SSL devices require network access; KD request/time and NCD management remain available for offline save and license initialization. * Share repeated LR continuation dispatch in translated functions Adapt KartPad be91d8f/a3f90eb without its floating-point ABI changes. Preserve upstream continuation discovery and all resume labels. Validation: 640 translator tests passed. * Reject inconsistent GPU cache sizes before allocation or copying Adapt KartPad runtime 70951022. Validate raw lengths, compression tags and Zstd frame lengths on the size probe as well as the fetch. Tested against malformed SQLite rows and valid raw/compressed round trips. * Wake compiler workers when pipeline work becomes runnable Adapt KartPad runtime 956d811e. Wake all consumers of the shared condition variable after queue insertion or promotion; retain upstream desktop prewarm policy. A blocked-compiler probe verified progress by an idle worker. * Reuse and release one Metal view per SDL window Adapt KartPad 3606741. Surface recreation reuses the existing view and window property cleanup owns its lifetime. Reviewed against SDL3 cleanup semantics; Apple hardware validation remains outstanding. * Avoid overreading packed three-byte vertex attributes Adapt KartPad 0f6b274. Do not read a second storage word when all three requested bytes fit in the first. Preserve upstream depth and fog corrections. * Keep interpolation history within each split-screen viewport Adapt KartPad d6299b5. Scope exact, material and sibling-palette matching to the logical viewport so identical meshes from different cameras cannot share transforms. * Report graphics startup failures and safely clean up partial ImGui initialization Adapt KartPad runtime 70dc9380 and c4566e50 using the existing WiiCompiled exception/reporting path. A dummy-video-driver probe verified error return and repeated partial shutdown without aborting. * Preserve GX draw boundaries and GPU staging and readback state Adapt the validated renderer fixes from KartPad runtime 31add0c3, 7393dafe, b7f515de, cf46a9c7, fad42a7b, 7cd09b69, 9feea6b2 and Android 2505ae22 to current upstream. Preserve complete primitives and fresh vertex layouts, split staging batches before overflow, retain offscreen state, scope asynchronous callbacks and frame state, and complete texture-copy sources. Add unit regressions and an optional ROM-free GPU pixel test. Validation: 250 GX tests and actual D3D12 pixel/readback, capacity, interpolation and frame-worker checks passed with Dawn validation enabled.
2094 lines
78 KiB
C++
2094 lines
78 KiB
C++
#include "../gfx/common.hpp"
|
|
|
|
#include "../internal.hpp"
|
|
#include "../webgpu/gpu.hpp"
|
|
#include "gx.hpp"
|
|
#include "gx_fmt.hpp"
|
|
#include "shader_info.hpp"
|
|
|
|
#include <dolphin/gx/GXEnum.h>
|
|
|
|
#include <array>
|
|
#include <mutex>
|
|
#include <string_view>
|
|
#include <utility>
|
|
|
|
#include "tracy/Tracy.hpp"
|
|
|
|
namespace aurora::gx {
|
|
using namespace fmt::literals;
|
|
using namespace std::string_literals;
|
|
using namespace std::string_view_literals;
|
|
|
|
static Module Log("aurora::gfx::gx");
|
|
|
|
|
|
static inline std::string_view chan_comp(GXTevColorChan chan) noexcept {
|
|
switch (chan) {
|
|
case GX_CH_RED:
|
|
return "r";
|
|
case GX_CH_GREEN:
|
|
return "g";
|
|
case GX_CH_BLUE:
|
|
return "b";
|
|
case GX_CH_ALPHA:
|
|
return "a";
|
|
default:
|
|
return "?";
|
|
}
|
|
}
|
|
|
|
static bool is_alpha_bump_channel(GXChannelID id) noexcept { return id == GX_ALPHA_BUMP || id == GX_ALPHA_BUMPN; }
|
|
|
|
static std::string_view disabled_texture_color(const ShaderConfig& config) noexcept {
|
|
return config.numTexGens == 0 ? "vec3f(0.0)"sv : "vec3f(1.0)"sv;
|
|
}
|
|
|
|
static std::string_view disabled_texture_alpha(const ShaderConfig& config) noexcept {
|
|
return config.numTexGens == 0 ? "0.0"sv : "1.0"sv;
|
|
}
|
|
|
|
static std::string tev_mask_expr(const std::string& value, u32 mask) {
|
|
// t_IndTexCoord is already expanded into the 0..255 indirect sample domain.
|
|
return fmt::format("(f32(u32({}) & 0x{:X}u) / 255.0)", value, mask);
|
|
}
|
|
|
|
static std::string alpha_bump_sel(size_t stageIdx, const ShaderConfig& config, const TevStage& stage) {
|
|
if (stage.indTexStage >= config.numIndStages || stage.indTexAlphaSel == GX_ITBA_OFF) {
|
|
return "0.0";
|
|
}
|
|
|
|
std::string baseCoord;
|
|
switch (stage.indTexAlphaSel) {
|
|
DEFAULT_FATAL("invalid indTexAlphaSel {} for stage {}", underlying(stage.indTexAlphaSel), stageIdx);
|
|
case GX_ITBA_S:
|
|
baseCoord = fmt::format("t_IndTexCoord{}.x", underlying(stage.indTexStage));
|
|
break;
|
|
case GX_ITBA_T:
|
|
baseCoord = fmt::format("t_IndTexCoord{}.y", underlying(stage.indTexStage));
|
|
break;
|
|
case GX_ITBA_U:
|
|
baseCoord = fmt::format("t_IndTexCoord{}.z", underlying(stage.indTexStage));
|
|
break;
|
|
case GX_ITBA_OFF:
|
|
return "0.0";
|
|
}
|
|
|
|
switch (stage.indTexFormat) {
|
|
DEFAULT_FATAL("invalid indirect format {} for stage {}", underlying(stage.indTexFormat), stageIdx);
|
|
case GX_ITF_8:
|
|
return tev_mask_expr(baseCoord, 0xF8u);
|
|
case GX_ITF_5:
|
|
return tev_mask_expr(baseCoord, 0xE0u);
|
|
case GX_ITF_4:
|
|
return tev_mask_expr(baseCoord, 0xF0u);
|
|
case GX_ITF_3:
|
|
return tev_mask_expr(baseCoord, 0xF8u);
|
|
}
|
|
}
|
|
|
|
u8 color_channel(GXChannelID id) noexcept {
|
|
switch (id) {
|
|
DEFAULT_FATAL("unimplemented color channel {}", id);
|
|
case GX_COLOR0:
|
|
case GX_ALPHA0:
|
|
case GX_COLOR0A0:
|
|
return 0;
|
|
case GX_COLOR1:
|
|
case GX_ALPHA1:
|
|
case GX_COLOR1A1:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static std::string color_arg_reg(GXTevColorArg arg, size_t stageIdx, const ShaderConfig& config,
|
|
const TevStage& stage) {
|
|
switch (arg) {
|
|
DEFAULT_FATAL("invalid color arg {}", underlying(arg));
|
|
case GX_CC_CPREV:
|
|
return "prev.rgb";
|
|
case GX_CC_APREV:
|
|
return "vec3f(prev.a)";
|
|
case GX_CC_C0:
|
|
return "tevreg0.rgb";
|
|
case GX_CC_A0:
|
|
return "vec3f(tevreg0.a)";
|
|
case GX_CC_C1:
|
|
return "tevreg1.rgb";
|
|
case GX_CC_A1:
|
|
return "vec3f(tevreg1.a)";
|
|
case GX_CC_C2:
|
|
return "tevreg2.rgb";
|
|
case GX_CC_A2:
|
|
return "vec3f(tevreg2.a)";
|
|
case GX_CC_TEXC: {
|
|
if (!tev_stage_texture_dependency(config, static_cast<u32>(stageIdx)).canSampleTexture) {
|
|
return std::string(disabled_texture_color(config));
|
|
}
|
|
const auto& swap = config.tevSwapTable[stage.tevSwapTex];
|
|
return fmt::format("sampled{}.{}{}{}", stageIdx, chan_comp(swap.red), chan_comp(swap.green), chan_comp(swap.blue));
|
|
}
|
|
case GX_CC_TEXA: {
|
|
if (!tev_stage_texture_dependency(config, static_cast<u32>(stageIdx)).canSampleTexture) {
|
|
return fmt::format("vec3f({})", disabled_texture_alpha(config));
|
|
}
|
|
const auto& swap = config.tevSwapTable[stage.tevSwapTex];
|
|
return fmt::format("vec3f(sampled{}.{})", stageIdx, chan_comp(swap.alpha));
|
|
}
|
|
case GX_CC_RASC: {
|
|
// CHECK(stage.channelId != GX_COLOR_NULL, "unmapped color channel for stage {}", stageIdx);
|
|
if (stage.channelId == GX_COLOR_ZERO || stage.channelId == GX_COLOR_NULL) {
|
|
return "vec3f(0.0)";
|
|
}
|
|
if (is_alpha_bump_channel(stage.channelId)) {
|
|
std::string alpha = alpha_bump_sel(stageIdx, config, stage);
|
|
if (stage.channelId == GX_ALPHA_BUMPN) {
|
|
alpha = fmt::format("({} * (255.0 / 248.0))", alpha);
|
|
}
|
|
return fmt::format("vec3f({})", alpha);
|
|
}
|
|
u32 idx = color_channel(stage.channelId);
|
|
const auto& swap = config.tevSwapTable[stage.tevSwapRas];
|
|
return fmt::format("rast{}.{}{}{}", idx, chan_comp(swap.red), chan_comp(swap.green), chan_comp(swap.blue));
|
|
}
|
|
case GX_CC_RASA: {
|
|
// CHECK(stage.channelId != GX_COLOR_NULL, "unmapped color channel for stage {}", stageIdx);
|
|
if (stage.channelId == GX_COLOR_ZERO || stage.channelId == GX_COLOR_NULL) {
|
|
return "vec3f(0.0)";
|
|
}
|
|
if (is_alpha_bump_channel(stage.channelId)) {
|
|
std::string alpha = alpha_bump_sel(stageIdx, config, stage);
|
|
if (stage.channelId == GX_ALPHA_BUMPN) {
|
|
alpha = fmt::format("({} * (255.0 / 248.0))", alpha);
|
|
}
|
|
return fmt::format("vec3f({})", alpha);
|
|
}
|
|
u32 idx = color_channel(stage.channelId);
|
|
const auto& swap = config.tevSwapTable[stage.tevSwapRas];
|
|
return fmt::format("vec3f(rast{}.{})", idx, chan_comp(swap.alpha));
|
|
}
|
|
case GX_CC_ONE:
|
|
return "vec3f(1.0)";
|
|
case GX_CC_HALF:
|
|
return "vec3f(0.5)";
|
|
case GX_CC_KONST: {
|
|
switch (stage.kcSel) {
|
|
DEFAULT_FATAL("invalid kcSel {}", underlying(stage.kcSel));
|
|
case GX_TEV_KCSEL_8_8:
|
|
return "vec3f(1.0)";
|
|
case GX_TEV_KCSEL_7_8:
|
|
return "vec3f(7.0/8.0)";
|
|
case GX_TEV_KCSEL_6_8:
|
|
return "vec3f(6.0/8.0)";
|
|
case GX_TEV_KCSEL_5_8:
|
|
return "vec3f(5.0/8.0)";
|
|
case GX_TEV_KCSEL_4_8:
|
|
return "vec3f(4.0/8.0)";
|
|
case GX_TEV_KCSEL_3_8:
|
|
return "vec3f(3.0/8.0)";
|
|
case GX_TEV_KCSEL_2_8:
|
|
return "vec3f(2.0/8.0)";
|
|
case GX_TEV_KCSEL_1_8:
|
|
return "vec3f(1.0/8.0)";
|
|
case GX_TEV_KCSEL_K0:
|
|
return "ubuf.kcolor0.rgb";
|
|
case GX_TEV_KCSEL_K1:
|
|
return "ubuf.kcolor1.rgb";
|
|
case GX_TEV_KCSEL_K2:
|
|
return "ubuf.kcolor2.rgb";
|
|
case GX_TEV_KCSEL_K3:
|
|
return "ubuf.kcolor3.rgb";
|
|
case GX_TEV_KCSEL_K0_R:
|
|
return "vec3f(ubuf.kcolor0.r)";
|
|
case GX_TEV_KCSEL_K1_R:
|
|
return "vec3f(ubuf.kcolor1.r)";
|
|
case GX_TEV_KCSEL_K2_R:
|
|
return "vec3f(ubuf.kcolor2.r)";
|
|
case GX_TEV_KCSEL_K3_R:
|
|
return "vec3f(ubuf.kcolor3.r)";
|
|
case GX_TEV_KCSEL_K0_G:
|
|
return "vec3f(ubuf.kcolor0.g)";
|
|
case GX_TEV_KCSEL_K1_G:
|
|
return "vec3f(ubuf.kcolor1.g)";
|
|
case GX_TEV_KCSEL_K2_G:
|
|
return "vec3f(ubuf.kcolor2.g)";
|
|
case GX_TEV_KCSEL_K3_G:
|
|
return "vec3f(ubuf.kcolor3.g)";
|
|
case GX_TEV_KCSEL_K0_B:
|
|
return "vec3f(ubuf.kcolor0.b)";
|
|
case GX_TEV_KCSEL_K1_B:
|
|
return "vec3f(ubuf.kcolor1.b)";
|
|
case GX_TEV_KCSEL_K2_B:
|
|
return "vec3f(ubuf.kcolor2.b)";
|
|
case GX_TEV_KCSEL_K3_B:
|
|
return "vec3f(ubuf.kcolor3.b)";
|
|
case GX_TEV_KCSEL_K0_A:
|
|
return "vec3f(ubuf.kcolor0.a)";
|
|
case GX_TEV_KCSEL_K1_A:
|
|
return "vec3f(ubuf.kcolor1.a)";
|
|
case GX_TEV_KCSEL_K2_A:
|
|
return "vec3f(ubuf.kcolor2.a)";
|
|
case GX_TEV_KCSEL_K3_A:
|
|
return "vec3f(ubuf.kcolor3.a)";
|
|
}
|
|
}
|
|
case GX_CC_ZERO:
|
|
return "vec3f(0.0)";
|
|
}
|
|
}
|
|
|
|
static std::string alpha_arg_reg(GXTevAlphaArg arg, size_t stageIdx, const ShaderConfig& config,
|
|
const TevStage& stage) {
|
|
switch (arg) {
|
|
DEFAULT_FATAL("invalid alpha arg {}", underlying(arg));
|
|
case GX_CA_APREV:
|
|
return "prev.a";
|
|
case GX_CA_A0:
|
|
return "tevreg0.a";
|
|
case GX_CA_A1:
|
|
return "tevreg1.a";
|
|
case GX_CA_A2:
|
|
return "tevreg2.a";
|
|
case GX_CA_TEXA: {
|
|
if (!tev_stage_texture_dependency(config, static_cast<u32>(stageIdx)).canSampleTexture) {
|
|
return std::string(disabled_texture_alpha(config));
|
|
}
|
|
const auto& swap = config.tevSwapTable[stage.tevSwapTex];
|
|
return fmt::format("sampled{}.{}", stageIdx, chan_comp(swap.alpha));
|
|
}
|
|
case GX_CA_RASA: {
|
|
// CHECK(stage.channelId != GX_COLOR_NULL, "unmapped color channel for stage {}", stageIdx);
|
|
if (stage.channelId == GX_COLOR_ZERO || stage.channelId == GX_COLOR_NULL) {
|
|
return "0.0";
|
|
}
|
|
if (is_alpha_bump_channel(stage.channelId)) {
|
|
std::string alpha = alpha_bump_sel(stageIdx, config, stage);
|
|
if (stage.channelId == GX_ALPHA_BUMPN) {
|
|
alpha = fmt::format("({} * (255.0 / 248.0))", alpha);
|
|
}
|
|
return alpha;
|
|
}
|
|
u32 idx = color_channel(stage.channelId);
|
|
const auto& swap = config.tevSwapTable[stage.tevSwapRas];
|
|
return fmt::format("rast{}.{}", idx, chan_comp(swap.alpha));
|
|
}
|
|
case GX_CA_KONST: {
|
|
switch (stage.kaSel) {
|
|
DEFAULT_FATAL("invalid kaSel {}", underlying(stage.kaSel));
|
|
case GX_TEV_KASEL_8_8:
|
|
return "1.0";
|
|
case GX_TEV_KASEL_7_8:
|
|
return "(7.0/8.0)";
|
|
case GX_TEV_KASEL_6_8:
|
|
return "(6.0/8.0)";
|
|
case GX_TEV_KASEL_5_8:
|
|
return "(5.0/8.0)";
|
|
case GX_TEV_KASEL_4_8:
|
|
return "(4.0/8.0)";
|
|
case GX_TEV_KASEL_3_8:
|
|
return "(3.0/8.0)";
|
|
case GX_TEV_KASEL_2_8:
|
|
return "(2.0/8.0)";
|
|
case GX_TEV_KASEL_1_8:
|
|
return "(1.0/8.0)";
|
|
case GX_TEV_KASEL_K0_R:
|
|
return "ubuf.kcolor0.r";
|
|
case GX_TEV_KASEL_K1_R:
|
|
return "ubuf.kcolor1.r";
|
|
case GX_TEV_KASEL_K2_R:
|
|
return "ubuf.kcolor2.r";
|
|
case GX_TEV_KASEL_K3_R:
|
|
return "ubuf.kcolor3.r";
|
|
case GX_TEV_KASEL_K0_G:
|
|
return "ubuf.kcolor0.g";
|
|
case GX_TEV_KASEL_K1_G:
|
|
return "ubuf.kcolor1.g";
|
|
case GX_TEV_KASEL_K2_G:
|
|
return "ubuf.kcolor2.g";
|
|
case GX_TEV_KASEL_K3_G:
|
|
return "ubuf.kcolor3.g";
|
|
case GX_TEV_KASEL_K0_B:
|
|
return "ubuf.kcolor0.b";
|
|
case GX_TEV_KASEL_K1_B:
|
|
return "ubuf.kcolor1.b";
|
|
case GX_TEV_KASEL_K2_B:
|
|
return "ubuf.kcolor2.b";
|
|
case GX_TEV_KASEL_K3_B:
|
|
return "ubuf.kcolor3.b";
|
|
case GX_TEV_KASEL_K0_A:
|
|
return "ubuf.kcolor0.a";
|
|
case GX_TEV_KASEL_K1_A:
|
|
return "ubuf.kcolor1.a";
|
|
case GX_TEV_KASEL_K2_A:
|
|
return "ubuf.kcolor2.a";
|
|
case GX_TEV_KASEL_K3_A:
|
|
return "ubuf.kcolor3.a";
|
|
}
|
|
}
|
|
case GX_CA_ZERO:
|
|
return "0.0";
|
|
}
|
|
}
|
|
|
|
static std::string tev_bias_i32(GXTevBias bias) {
|
|
switch (bias) {
|
|
DEFAULT_FATAL("invalid tev bias {}", underlying(bias));
|
|
case GX_TB_ZERO:
|
|
return "0";
|
|
case GX_TB_ADDHALF:
|
|
return "128";
|
|
case GX_TB_SUBHALF:
|
|
return "-128";
|
|
}
|
|
}
|
|
|
|
static std::string tev_scale_index(GXTevScale scale) {
|
|
switch (scale) {
|
|
DEFAULT_FATAL("invalid tev scale {}", underlying(scale));
|
|
case GX_CS_SCALE_1:
|
|
return "0u";
|
|
case GX_CS_SCALE_2:
|
|
return "1u";
|
|
case GX_CS_SCALE_4:
|
|
return "2u";
|
|
case GX_CS_DIVIDE_2:
|
|
return "3u";
|
|
}
|
|
}
|
|
|
|
static std::string tev_regular_op(GXTevOp op, GXTevBias bias, GXTevScale scale, std::string_view a,
|
|
std::string_view b, std::string_view c, std::string_view d,
|
|
std::string_view suffix) {
|
|
CHECK(op == GX_TEV_ADD || op == GX_TEV_SUB, "invalid regular tev op {}", underlying(op));
|
|
return fmt::format("tev_regular_{4}({0}, {1}, {2}, {3}, {5}, {6}, {7})", a, b, c, d, suffix,
|
|
tev_bias_i32(bias), tev_scale_index(scale), op == GX_TEV_SUB ? "true" : "false");
|
|
}
|
|
|
|
static std::string tev_op(GXTevOp op, GXTevBias bias, GXTevScale scale, std::string_view a, std::string_view b,
|
|
std::string_view c, std::string_view d, std::string_view zero,
|
|
std::string_view suffix) {
|
|
switch (op) {
|
|
DEFAULT_FATAL("unimplemented tev op {}", underlying(op));
|
|
case GX_TEV_ADD:
|
|
case GX_TEV_SUB:
|
|
return tev_regular_op(op, bias, scale, a, b, c, d, suffix);
|
|
case GX_TEV_COMP_R8_GT:
|
|
return fmt::format("select({3}, {2}, round({0}.r * 255.0) > round({1}.r * 255.0)) + {4}", a, b, c, zero, d);
|
|
case GX_TEV_COMP_R8_EQ:
|
|
return fmt::format("select({3}, {2}, round({0}.r * 255.0) == round({1}.r * 255.0)) + {4}", a, b, c, zero, d);
|
|
case GX_TEV_COMP_GR16_GT:
|
|
return fmt::format(
|
|
"select({3}, {2}, round(dot({0}.rg * 255.0, vec2(1.0, 256.0))) > round(dot({1}.rg * 255.0, vec2(1.0, 256.0))))"
|
|
" + {4}",
|
|
a, b, c, zero, d);
|
|
case GX_TEV_COMP_GR16_EQ:
|
|
return fmt::format(
|
|
"select({3}, {2}, round(dot({0}.rg * 255.0, vec2(1.0, 256.0))) == round(dot({1}.rg * 255.0, vec2(1.0, 256.0))))"
|
|
" + {4}",
|
|
a, b, c, zero, d);
|
|
case GX_TEV_COMP_BGR24_GT:
|
|
return fmt::format(
|
|
"select({3}, {2}, round(dot({0}.rgb * 255.0, vec3(1.0, 256.0, 65536.0))) > round(dot({1}.rgb * 255.0, "
|
|
"vec3(1.0, 256.0, 65536.0)))) + {4}",
|
|
a, b, c, zero, d);
|
|
case GX_TEV_COMP_BGR24_EQ:
|
|
return fmt::format(
|
|
"select({3}, {2}, round(dot({0}.rgb * 255.0, vec3(1.0, 256.0, 65536.0))) == round(dot({1}.rgb * 255.0, "
|
|
"vec3(1.0, 256.0, 65536.0)))) + {4}",
|
|
a, b, c, zero, d);
|
|
case GX_TEV_COMP_RGB8_GT:
|
|
return fmt::format("select({3}, {2}, round({0} * 255.0) > round({1} * 255.0)) + {4}", a, b, c, zero, d);
|
|
case GX_TEV_COMP_RGB8_EQ:
|
|
return fmt::format("select({3}, {2}, round({0} * 255.0) == round({1} * 255.0)) + {4}", a, b, c, zero, d);
|
|
}
|
|
}
|
|
|
|
static std::string tev_color_op(GXTevOp op, GXTevBias bias, GXTevScale scale, bool clamp,
|
|
std::string_view a, std::string_view b, std::string_view c, std::string_view d) {
|
|
const auto overflow = [](std::string_view reg) { return fmt::format("tev_overflow_vec3f({})", reg); };
|
|
std::string expr = tev_op(op, bias, scale, overflow(a), overflow(b), overflow(c), d, "vec3(0)"sv, "vec3f"sv);
|
|
return clamp ? fmt::format("clamp({}, vec3f(0.0), vec3f(1.0))", expr)
|
|
: fmt::format("clamp({}, vec3f(-1024.0 / 255.0), vec3f(1023.0 / 255.0))", expr);
|
|
}
|
|
|
|
static bool alpha_compare_uses_color_inputs(GXTevOp op) noexcept {
|
|
switch (op) {
|
|
case GX_TEV_COMP_R8_GT:
|
|
case GX_TEV_COMP_R8_EQ:
|
|
case GX_TEV_COMP_GR16_GT:
|
|
case GX_TEV_COMP_GR16_EQ:
|
|
case GX_TEV_COMP_BGR24_GT:
|
|
case GX_TEV_COMP_BGR24_EQ:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static std::string tev_alpha_op(GXTevOp op, GXTevBias bias, GXTevScale scale, bool clamp,
|
|
std::string_view a, std::string_view b, std::string_view c, std::string_view d,
|
|
std::string_view colorA, std::string_view colorB) {
|
|
const auto scalarOverflow = [](std::string_view reg) { return fmt::format("tev_overflow_f32({})", reg); };
|
|
std::string expr;
|
|
if (alpha_compare_uses_color_inputs(op)) {
|
|
const auto colorOverflow = [](std::string_view reg) { return fmt::format("tev_overflow_vec3f({})", reg); };
|
|
// GX alpha compares R8/GR16/BGR24 using the color combiner's A/B inputs, while C and D remain the scalar alpha-combiner inputs.
|
|
expr = tev_op(op, bias, scale, colorOverflow(colorA), colorOverflow(colorB), scalarOverflow(c), d, "0.0"sv,
|
|
"f32"sv);
|
|
} else {
|
|
// The numeric RGB8 op aliases are the alpha combiner's A8 compares and therefore continue to compare scalar alpha A/B inputs here.
|
|
expr = tev_op(op, bias, scale, scalarOverflow(a), scalarOverflow(b), scalarOverflow(c), d, "0.0"sv, "f32"sv);
|
|
}
|
|
return clamp ? fmt::format("clamp({}, 0.0, 1.0)", expr) : fmt::format("clamp({}, -1024.0 / 255.0, 1023.0 / 255.0)", expr);
|
|
}
|
|
|
|
struct AlphaCompareExpr {
|
|
std::string expr;
|
|
int constant = -1;
|
|
};
|
|
|
|
static AlphaCompareExpr alpha_compare_const(bool value) { return {value ? "true"s : "false"s, value ? 1 : 0}; }
|
|
|
|
static AlphaCompareExpr alpha_compare_not(const AlphaCompareExpr& expr) {
|
|
if (expr.constant != -1) {
|
|
return alpha_compare_const(expr.constant == 0);
|
|
}
|
|
return {fmt::format("!{}", expr.expr), -1};
|
|
}
|
|
|
|
static AlphaCompareExpr alpha_compare_and(const AlphaCompareExpr& lhs, const AlphaCompareExpr& rhs) {
|
|
if (lhs.constant == 0 || rhs.constant == 0) {
|
|
return alpha_compare_const(false);
|
|
}
|
|
if (lhs.constant == 1) {
|
|
return rhs;
|
|
}
|
|
if (rhs.constant == 1) {
|
|
return lhs;
|
|
}
|
|
return {fmt::format("({} && {})", lhs.expr, rhs.expr), -1};
|
|
}
|
|
|
|
static AlphaCompareExpr alpha_compare_or(const AlphaCompareExpr& lhs, const AlphaCompareExpr& rhs) {
|
|
if (lhs.constant == 1 || rhs.constant == 1) {
|
|
return alpha_compare_const(true);
|
|
}
|
|
if (lhs.constant == 0) {
|
|
return rhs;
|
|
}
|
|
if (rhs.constant == 0) {
|
|
return lhs;
|
|
}
|
|
return {fmt::format("({} || {})", lhs.expr, rhs.expr), -1};
|
|
}
|
|
|
|
static AlphaCompareExpr alpha_compare_xor(const AlphaCompareExpr& lhs, const AlphaCompareExpr& rhs) {
|
|
if (lhs.constant != -1 && rhs.constant != -1) {
|
|
return alpha_compare_const(lhs.constant != rhs.constant);
|
|
}
|
|
if (lhs.constant == 0) {
|
|
return rhs;
|
|
}
|
|
if (rhs.constant == 0) {
|
|
return lhs;
|
|
}
|
|
if (lhs.constant == 1) {
|
|
return alpha_compare_not(rhs);
|
|
}
|
|
if (rhs.constant == 1) {
|
|
return alpha_compare_not(lhs);
|
|
}
|
|
return {fmt::format("({} != {})", lhs.expr, rhs.expr), -1};
|
|
}
|
|
|
|
static AlphaCompareExpr alpha_compare_xnor(const AlphaCompareExpr& lhs, const AlphaCompareExpr& rhs) {
|
|
if (lhs.constant != -1 && rhs.constant != -1) {
|
|
return alpha_compare_const(lhs.constant == rhs.constant);
|
|
}
|
|
if (lhs.constant == 0) {
|
|
return alpha_compare_not(rhs);
|
|
}
|
|
if (rhs.constant == 0) {
|
|
return alpha_compare_not(lhs);
|
|
}
|
|
if (lhs.constant == 1) {
|
|
return rhs;
|
|
}
|
|
if (rhs.constant == 1) {
|
|
return lhs;
|
|
}
|
|
return {fmt::format("({} == {})", lhs.expr, rhs.expr), -1};
|
|
}
|
|
|
|
static AlphaCompareExpr alpha_compare(GXCompare comp, u8 ref) {
|
|
const auto iref = static_cast<u32>(ref);
|
|
switch (comp) {
|
|
default:
|
|
return alpha_compare_const(true);
|
|
case GX_NEVER:
|
|
return alpha_compare_const(false);
|
|
case GX_LESS:
|
|
if (ref == 0) {
|
|
return alpha_compare_const(false);
|
|
}
|
|
return {fmt::format("(alphaCompare < {}u)", iref), -1};
|
|
case GX_LEQUAL:
|
|
if (ref == 255) {
|
|
return alpha_compare_const(true);
|
|
}
|
|
return {fmt::format("(alphaCompare <= {}u)", iref), -1};
|
|
case GX_EQUAL:
|
|
return {fmt::format("(alphaCompare == {}u)", iref), -1};
|
|
case GX_NEQUAL:
|
|
return {fmt::format("(alphaCompare != {}u)", iref), -1};
|
|
case GX_GEQUAL:
|
|
if (ref == 0) {
|
|
return alpha_compare_const(true);
|
|
}
|
|
return {fmt::format("(alphaCompare >= {}u)", iref), -1};
|
|
case GX_GREATER:
|
|
if (ref == 255) {
|
|
return alpha_compare_const(false);
|
|
}
|
|
return {fmt::format("(alphaCompare > {}u)", iref), -1};
|
|
case GX_ALWAYS:
|
|
return alpha_compare_const(true);
|
|
}
|
|
}
|
|
|
|
static inline std::string vtx_attr(const ShaderConfig& config, GXAttr attr) {
|
|
const auto type = config.attrs[attr].attrType;
|
|
if (type == GX_NONE) {
|
|
if (attr == GX_VA_PNMTXIDX) {
|
|
return "ubuf.current_pnmtx";
|
|
}
|
|
if (attr == GX_VA_NRM) {
|
|
// Default normal
|
|
return "vec3f(1.0, 0.0, 0.0)"s;
|
|
}
|
|
if (attr == GX_VA_CLR0 || attr == GX_VA_CLR1) {
|
|
return "vec4f(1.0)"s;
|
|
}
|
|
if (attr >= GX_VA_TEX0 && attr <= GX_VA_TEX7) {
|
|
return "vec2f(0.0, 0.0)"s;
|
|
}
|
|
UNLIKELY FATAL("unmapped vtx attr {}", underlying(attr));
|
|
}
|
|
if (attr == GX_VA_POS) {
|
|
return "in_pos"s;
|
|
}
|
|
if (attr == GX_VA_NRM) {
|
|
return "in_nrm"s;
|
|
}
|
|
if (attr == GX_VA_CLR0 || attr == GX_VA_CLR1) {
|
|
const auto idx = attr - GX_VA_CLR0;
|
|
return fmt::format("in_clr{}", idx);
|
|
}
|
|
if (attr >= GX_VA_TEX0 && attr <= GX_VA_TEX7) {
|
|
const auto idx = attr - GX_VA_TEX0;
|
|
return fmt::format("in_tex{}_uv", idx);
|
|
}
|
|
if (attr == GX_VA_PNMTXIDX) {
|
|
return "in_pnmtxidx"s;
|
|
}
|
|
if (attr >= GX_VA_TEX0MTXIDX && attr <= GX_VA_TEX7MTXIDX) {
|
|
const auto idx = attr - GX_VA_TEX0MTXIDX;
|
|
return fmt::format("in_texmtxidx{}", idx);
|
|
}
|
|
UNLIKELY FATAL("unhandled vtx attr {}", underlying(attr));
|
|
}
|
|
|
|
static std::string vertex_color_attr(const ShaderConfig& config, u32 channel) {
|
|
const int attr = shader_vertex_color_attr(config, channel);
|
|
return attr >= 0 ? vtx_attr(config, static_cast<GXAttr>(attr)) : "vec4f(1.0)"s;
|
|
}
|
|
|
|
constexpr std::array<std::string_view, GX_CC_ZERO + 1> 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,
|
|
};
|
|
constexpr std::array<std::string_view, GX_CA_ZERO + 1> TevAlphaArgNames{
|
|
"APREV"sv, "A0"sv, "A1"sv, "A2"sv, "TEXA"sv, "RASA"sv, "KONST"sv, "ZERO"sv,
|
|
};
|
|
|
|
auto fetch_attr(const AttrConfig& mapping, std::string_view buf, std::string_view offs, bool le, u8 cntOverride = 0) -> std::string {
|
|
const u8 cnt = cntOverride != 0 ? cntOverride : mapping.cnt;
|
|
switch (mapping.compType) {
|
|
case GX_U8:
|
|
return fmt::format("fetch_u8_{}(&{}, {}, {}, {})", cnt, buf, offs, mapping.frac, le);
|
|
case GX_S8:
|
|
return fmt::format("fetch_s8_{}(&{}, {}, {}, {})", cnt, buf, offs, mapping.frac, le);
|
|
case GX_U16:
|
|
return fmt::format("fetch_u16_{}(&{}, {}, {}, {})", cnt, buf, offs, mapping.frac, le);
|
|
case GX_S16:
|
|
return fmt::format("fetch_s16_{}(&{}, {}, {}, {})", cnt, buf, offs, mapping.frac, le);
|
|
case GX_F32:
|
|
return fmt::format("fetch_f32_{}(&{}, {}, {})", cnt, buf, offs, le);
|
|
case GX_RGBA8:
|
|
return fmt::format("unpack4x8unorm(load_u32_raw(&{}, {}))", buf, offs);
|
|
default:
|
|
Log.fatal("fetch_attr: Unimplemented {}", static_cast<GXCompType>(mapping.compType));
|
|
}
|
|
}
|
|
|
|
auto fetch_color_attr(const AttrConfig& mapping, std::string_view buf, std::string_view offs, bool le) -> std::string {
|
|
switch (mapping.compType) {
|
|
case GX_RGB565:
|
|
return fmt::format("fetch_rgb565(&{}, {}, {})", buf, offs, le);
|
|
case GX_RGB8:
|
|
return fmt::format("fetch_rgb8(&{}, {}, {})", buf, offs, le);
|
|
case GX_RGBX8:
|
|
return fmt::format("fetch_rgbx8(&{}, {}, {})", buf, offs, le);
|
|
case GX_RGBA4:
|
|
return fmt::format("fetch_rgba4(&{}, {}, {})", buf, offs, le);
|
|
case GX_RGBA6:
|
|
return fmt::format("fetch_rgba6(&{}, {}, {})", buf, offs, le);
|
|
case GX_RGBA8:
|
|
return fmt::format("fetch_rgba8(&{}, {}, {})", buf, offs, le);
|
|
default:
|
|
Log.fatal("fetch_color_attr: Unimplemented {}", static_cast<GXCompType>(mapping.compType));
|
|
}
|
|
}
|
|
|
|
auto attr_load(const ShaderConfig& config, GXAttr attr, std::string_view vidx) -> std::string;
|
|
|
|
static u32 attr_comp_type_size(u8 compType) noexcept {
|
|
switch (static_cast<GXCompType>(compType)) {
|
|
case GX_U8:
|
|
case GX_S8:
|
|
return 1;
|
|
case GX_U16:
|
|
case GX_S16:
|
|
return 2;
|
|
case GX_F32:
|
|
return 4;
|
|
default:
|
|
Log.fatal("attr_comp_type_size: Unimplemented {}", static_cast<GXCompType>(compType));
|
|
}
|
|
}
|
|
|
|
static std::string offset_plus(std::string_view offs, u32 add) {
|
|
if (add == 0) {
|
|
return std::string(offs);
|
|
}
|
|
return fmt::format("({} + {}u)", offs, add);
|
|
}
|
|
|
|
auto normal_group_load(const ShaderConfig& config, u8 group, std::string_view vidx) -> std::string {
|
|
const auto& mapping = config.attrs[GX_VA_NRM];
|
|
if (mapping.attrType == GX_NONE || mapping.cnt != 9) {
|
|
return attr_load(config, GX_VA_NRM, vidx);
|
|
}
|
|
|
|
auto buf = "vbuf"sv;
|
|
auto offs = fmt::format("ubuf.vtx_start + {} * {}u + {}u", vidx, config.vtxStride, mapping.offset);
|
|
auto le = false; // Vertex buffer is always big endian (for now)
|
|
const bool indexedNbt3 =
|
|
mapping.nrmIndexCount == 3 && (mapping.attrType == GX_INDEX8 || mapping.attrType == GX_INDEX16);
|
|
const u32 groupOffset = indexedNbt3 ? 0 : group * 3u * attr_comp_type_size(mapping.compType);
|
|
|
|
if (mapping.attrType == GX_INDEX8) {
|
|
const std::string indexOffs = offset_plus(offs, mapping.nrmIndexCount == 3 ? group : 0);
|
|
offs = fmt::format("ubuf.array_start[{}] + raw_fetch_u8_1(&{}, {}) * {}u + {}u", GX_VA_NRM - GX_VA_POS, buf,
|
|
indexOffs, mapping.stride, groupOffset);
|
|
buf = "abuf"sv;
|
|
le = mapping.le;
|
|
} else if (mapping.attrType == GX_INDEX16) {
|
|
const std::string indexOffs = offset_plus(offs, mapping.nrmIndexCount == 3 ? group * 2u : 0);
|
|
offs = fmt::format("ubuf.array_start[{}] + raw_fetch_u16_1(&{}, {}, {}) * {}u + {}u", GX_VA_NRM - GX_VA_POS,
|
|
buf, indexOffs, le, mapping.stride, groupOffset);
|
|
buf = "abuf"sv;
|
|
le = mapping.le;
|
|
} else {
|
|
offs = offset_plus(offs, groupOffset);
|
|
}
|
|
|
|
return fetch_attr(mapping, buf, offs, le, 3);
|
|
}
|
|
|
|
auto attr_load(const ShaderConfig& config, GXAttr attr, std::string_view vidx) -> std::string {
|
|
const auto& mapping = config.attrs[attr];
|
|
if (mapping.attrType == GX_NONE) {
|
|
return vtx_attr(config, attr);
|
|
}
|
|
auto buf = "vbuf"sv;
|
|
auto offs = fmt::format("ubuf.vtx_start + {} * {}u + {}u", vidx, config.vtxStride, mapping.offset);
|
|
auto le = false; // Vertex buffer is always big endian (for now)
|
|
if (mapping.attrType == GX_INDEX8) {
|
|
offs = fmt::format("ubuf.array_start[{}] + raw_fetch_u8_1(&{}, {}) * {}u", attr - GX_VA_POS, buf, offs,
|
|
mapping.stride);
|
|
buf = "abuf"sv;
|
|
le = mapping.le;
|
|
} else if (mapping.attrType == GX_INDEX16) {
|
|
offs = fmt::format("ubuf.array_start[{}] + raw_fetch_u16_1(&{}, {}, {}) * {}u", attr - GX_VA_POS, buf, offs, le,
|
|
mapping.stride);
|
|
buf = "abuf"sv;
|
|
le = mapping.le;
|
|
}
|
|
switch (attr) {
|
|
case GX_VA_PNMTXIDX:
|
|
return fmt::format("(raw_fetch_u8_1(&{}, {}) / 3u)", buf, offs);
|
|
case GX_VA_TEX0MTXIDX:
|
|
case GX_VA_TEX1MTXIDX:
|
|
case GX_VA_TEX2MTXIDX:
|
|
case GX_VA_TEX3MTXIDX:
|
|
case GX_VA_TEX4MTXIDX:
|
|
case GX_VA_TEX5MTXIDX:
|
|
case GX_VA_TEX6MTXIDX:
|
|
case GX_VA_TEX7MTXIDX:
|
|
return fmt::format("raw_fetch_u8_1(&{}, {})", buf, offs);
|
|
case GX_VA_POS: {
|
|
const auto posLoad = fetch_attr(mapping, buf, offs, le);
|
|
if (mapping.cnt == 2) {
|
|
return fmt::format("vec3f({}, 0.0)", posLoad);
|
|
}
|
|
return posLoad;
|
|
}
|
|
case GX_VA_NRM:
|
|
return fetch_attr(mapping, buf, offs, le, mapping.cnt == 9 ? 3 : 0);
|
|
case GX_VA_CLR0:
|
|
case GX_VA_CLR1:
|
|
return fetch_color_attr(mapping, buf, offs, le);
|
|
case GX_VA_TEX0:
|
|
case GX_VA_TEX1:
|
|
case GX_VA_TEX2:
|
|
case GX_VA_TEX3:
|
|
case GX_VA_TEX4:
|
|
case GX_VA_TEX5:
|
|
case GX_VA_TEX6:
|
|
case GX_VA_TEX7: {
|
|
const auto texLoad = fetch_attr(mapping, buf, offs, le);
|
|
if (mapping.cnt == 1) {
|
|
return fmt::format("vec2f({}, 0.0)", texLoad);
|
|
}
|
|
return texLoad;
|
|
}
|
|
default:
|
|
Log.fatal("attr_load: Unimplemented {}", attr);
|
|
}
|
|
}
|
|
|
|
auto lighting_func(const ShaderConfig& config, const ColorChannelConfig& cc, u8 i, bool alpha) -> std::string {
|
|
std::string_view swizzle = alpha ? ".a"sv : ".rgb"sv;
|
|
std::string_view intType = alpha ? "i32"sv : "vec3i"sv;
|
|
std::string_view floatType = alpha ? "f32"sv : "vec3f"sv;
|
|
std::string_view intZero = alpha ? "0"sv : "vec3i(0)"sv;
|
|
std::string_view intMax = alpha ? "255"sv : "vec3i(255)"sv;
|
|
std::string_view shift7 = alpha ? "7u"sv : "vec3u(7u)"sv;
|
|
std::string_view shift8 = alpha ? "8u"sv : "vec3u(8u)"sv;
|
|
std::string outVar;
|
|
std::string_view posVar;
|
|
if (UsePerPixelLighting) {
|
|
outVar = fmt::format("rast{}", i);
|
|
posVar = "in.mv_pos"sv;
|
|
} else {
|
|
outVar = fmt::format("out.cc{}", i);
|
|
posVar = "mv_pos"sv;
|
|
}
|
|
std::string ambSrc, matSrc;
|
|
if (cc.ambSrc == GX_SRC_VTX) {
|
|
if (UsePerPixelLighting) {
|
|
ambSrc = fmt::format("in.clr{}", i);
|
|
} else {
|
|
ambSrc = vertex_color_attr(config, i);
|
|
}
|
|
} else if (cc.ambSrc == GX_SRC_REG) {
|
|
ambSrc = fmt::format("ubuf.cc{0}{1}_amb", i, alpha ? "a"sv : ""sv);
|
|
}
|
|
if (cc.matSrc == GX_SRC_VTX) {
|
|
if (UsePerPixelLighting) {
|
|
matSrc = fmt::format("in.clr{}", i);
|
|
} else {
|
|
matSrc = vertex_color_attr(config, i);
|
|
}
|
|
} else if (cc.matSrc == GX_SRC_REG) {
|
|
matSrc = fmt::format("ubuf.cc{0}{1}_mat", i, alpha ? "a"sv : ""sv);
|
|
}
|
|
if (!cc.lightingEnabled) {
|
|
// The XF lighting block consumes and produces 8-bit channel values even when lighting is disabled.
|
|
if (alpha) {
|
|
return fmt::format("\n {0}.a = f32(i32(round({1}.a * 255.0))) / 255.0;", outVar, matSrc);
|
|
}
|
|
return fmt::format("\n {0} = vec4f(vec3f(vec3i(round({1}.rgb * 255.0))) / 255.0, 1.0);", outVar,
|
|
matSrc);
|
|
}
|
|
GXDiffuseFn diffFn = cc.diffFn;
|
|
std::string lightAttnFn;
|
|
if (cc.attnFn == GX_AF_NONE) {
|
|
lightAttnFn = "attn = 1.0;"s;
|
|
} else if (cc.attnFn == GX_AF_SPOT) {
|
|
lightAttnFn = fmt::format(R"""(
|
|
var cosine = max(0.0, dot(ldir, light.dir));
|
|
var cos_attn = dot(light.cos_att, vec3f(1.0, cosine, cosine * cosine));
|
|
var dist_attn = dot(light.dist_att, vec3f(1.0, dist, dist2));
|
|
attn = max(0.0, cos_attn / dist_attn);)""");
|
|
} else if (cc.attnFn == GX_AF_SPEC) {
|
|
std::string_view normal = UsePerPixelLighting ? "in.mv_nrm"sv : "mv_nrm"sv;
|
|
std::string dist_attn = diffFn != GX_DF_NONE
|
|
? "dot(normalize(light.dist_att), vec3f(1.0, attn, attn * attn))"
|
|
: "dot(light.dist_att, vec3f(1.0, attn, attn * attn))";
|
|
lightAttnFn = fmt::format(R"""(
|
|
attn = select(0.0, max(0.0, dot({0}, light.dir)), dot({0}, ldir) >= 0.0);
|
|
var cos_attn = dot(light.cos_att, vec3f(1.0, attn, attn * attn));
|
|
var dist_attn = {1};
|
|
attn = select(select(0.0, 1.0, cos_attn > 0.0), max(0.0, cos_attn / dist_attn), dist_attn != 0.0);)""",
|
|
normal, dist_attn);
|
|
}
|
|
std::string_view normal = UsePerPixelLighting ? "in.mv_nrm"sv : "mv_nrm"sv;
|
|
std::string lightDirSetup;
|
|
if (cc.attnFn == GX_AF_NONE) {
|
|
lightDirSetup = fmt::format(R"""(
|
|
var ldir = light.pos - {0};
|
|
var dist2 = dot(ldir, ldir);
|
|
var dist = sqrt(dist2);
|
|
ldir = select({1}, ldir / max(dist, 1e-20), dist > 0.0);)""",
|
|
posVar, normal);
|
|
} else {
|
|
lightDirSetup = fmt::format(R"""(
|
|
var ldir = light.pos - {0};
|
|
var dist2 = dot(ldir, ldir);
|
|
var dist = sqrt(dist2);
|
|
ldir = ldir / dist;)""",
|
|
posVar);
|
|
}
|
|
std::string_view lightDiffFn;
|
|
if (diffFn == GX_DF_NONE) {
|
|
lightDiffFn = "1.0"sv;
|
|
} else if (diffFn == GX_DF_SIGN) {
|
|
if (UsePerPixelLighting) {
|
|
lightDiffFn = "dot(ldir, in.mv_nrm)"sv;
|
|
} else {
|
|
lightDiffFn = "dot(ldir, mv_nrm)"sv;
|
|
}
|
|
} else if (diffFn == GX_DF_CLAMP) {
|
|
if (UsePerPixelLighting) {
|
|
lightDiffFn = "max(0.0, dot(ldir, in.mv_nrm))"sv;
|
|
} else {
|
|
lightDiffFn = "max(0.0, dot(ldir, mv_nrm))"sv;
|
|
}
|
|
}
|
|
const auto outputTarget = alpha ? fmt::format("{}.a", outVar) : outVar;
|
|
const auto outputValue =
|
|
alpha
|
|
? fmt::format("f32((material * (lacc + (lacc >> {}))) >> {}) / 255.0", shift7, shift8)
|
|
: fmt::format("vec4f(vec3f((material * (lacc + (lacc >> {}))) >> {}) / 255.0, 1.0)", shift7,
|
|
shift8);
|
|
return fmt::format(R"""(
|
|
{{
|
|
var lighting = {11}(round({5}{8} * 255.0));
|
|
for (var i = 0u; i < {1}u; i++) {{
|
|
if ((ubuf.lightState{0}{9} & (1u << i)) == 0u) {{ continue; }}
|
|
var light = ubuf.lights[i];{10}
|
|
var attn: f32;{2}
|
|
var diff = {3};
|
|
lighting = lighting + {11}(round(attn * diff * light.color{8} * 255.0));
|
|
}}
|
|
let lacc = clamp(lighting, {13}, {14});
|
|
let material = {11}(round({4}{8} * 255.0));
|
|
{15} = {16};
|
|
}})""",
|
|
i, GX::MaxLights, lightAttnFn, lightDiffFn, matSrc, ambSrc, posVar, outVar, swizzle,
|
|
alpha ? "a"sv : ""sv, lightDirSetup, intType, floatType, intZero, intMax, outputTarget,
|
|
outputValue);
|
|
}
|
|
|
|
wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
|
|
ZoneScoped;
|
|
const auto hash = xxh3_hash(config);
|
|
const auto info = build_shader_info(config);
|
|
|
|
std::string uniformPre;
|
|
std::string uniBufAttrs;
|
|
std::string texBindings;
|
|
std::string vtxOutAttrs;
|
|
std::string vtxInAttrs;
|
|
std::string vtxXfrAttrsPre;
|
|
std::string vtxXfrAttrs;
|
|
size_t vtxOutIdx = 0;
|
|
|
|
// Load points for line/point expansion
|
|
std::string_view vidxAttr = "vidx"sv;
|
|
if (config.lineMode != 0) {
|
|
vtxInAttrs += ",\n @builtin(instance_index) iidx: u32";
|
|
uniBufAttrs +=
|
|
"\n line_width: f32,"
|
|
"\n line_aspect_y: f32,"
|
|
"\n line_tex_offset: f32,"
|
|
"\n line_texcoord_mask: u32,";
|
|
if (config.lineMode == 3) {
|
|
// GX_POINTS: each instance = one vertex, expand to quad
|
|
vtxXfrAttrsPre += fmt::format(
|
|
"\n let in_vidx = iidx;"
|
|
"\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));
|
|
} else {
|
|
// GX_LINES / GX_LINESTRIP: each instance = two vertices, expand to quad
|
|
vtxXfrAttrsPre += fmt::format(
|
|
"\n let use_b = vidx >= 2u;"
|
|
"\n let vidx_a = iidx * {}u;"
|
|
"\n let vidx_b = vidx_a + 1u;"
|
|
"\n let in_vidx = select(vidx_a, vidx_b, use_b);"
|
|
"\n let pos_a = {};"
|
|
"\n let pos_b = {};"
|
|
"\n let in_pos = select(pos_a, pos_b, use_b);"
|
|
"\n let pnmtxidx_a = {};"
|
|
"\n let pnmtxidx_b = {};"
|
|
"\n let in_pnmtxidx = select(pnmtxidx_a, pnmtxidx_b, use_b);"
|
|
"\n let mv_pos_a = vec4f(pos_a, 1.0) * ubuf.postex_mtx[pnmtxidx_a];"
|
|
"\n let mv_pos_b = vec4f(pos_b, 1.0) * ubuf.postex_mtx[pnmtxidx_b];"
|
|
"\n let mv_pos = select(mv_pos_a, mv_pos_b, use_b);",
|
|
config.lineMode == 1 ? 2 : 1, attr_load(config, GX_VA_POS, "vidx_a"sv),
|
|
attr_load(config, GX_VA_POS, "vidx_b"sv), attr_load(config, GX_VA_PNMTXIDX, "vidx_a"sv),
|
|
attr_load(config, GX_VA_PNMTXIDX, "vidx_b"sv));
|
|
}
|
|
vidxAttr = "in_vidx"sv;
|
|
} else if (config.attrs[GX_VA_PNMTXIDX].attrType == GX_NONE) {
|
|
vtxXfrAttrsPre += "\n let in_pnmtxidx = ubuf.current_pnmtx;";
|
|
}
|
|
|
|
// Load vertex attributes
|
|
for (GXAttr attr = GX_VA_PNMTXIDX; attr <= GX_VA_TEX7; attr = static_cast<GXAttr>(attr + 1)) {
|
|
const auto attrType = config.attrs[attr].attrType;
|
|
if (attrType == GX_NONE) {
|
|
continue;
|
|
}
|
|
// in_pnmtxidx and in_pos written above for line mode
|
|
if ((attr != GX_VA_PNMTXIDX && attr != GX_VA_POS) || config.lineMode == 0) {
|
|
vtxXfrAttrsPre += fmt::format("\n let {} = {};", vtx_attr(config, attr), attr_load(config, attr, vidxAttr));
|
|
}
|
|
}
|
|
|
|
if (config.lineMode == 0) {
|
|
vtxXfrAttrsPre += fmt::format(
|
|
"\n let mv_pos = vec4f({}, 1.0) * ubuf.postex_mtx[in_pnmtxidx];"
|
|
"\n out.pos = vec4f(mv_pos, 1.0) * ubuf.proj;",
|
|
vtx_attr(config, GX_VA_POS));
|
|
} else if (config.lineMode == 3) {
|
|
// GX_POINTS: expand single vertex to axis-aligned screen-space square
|
|
vtxXfrAttrsPre +=
|
|
"\n let clip = vec4f(mv_pos, 1.0) * ubuf.proj;"
|
|
"\n let viewport_scale = ubuf.render_viewport_size / max(ubuf.logical_viewport_size, vec2f(1.0));"
|
|
"\n let point_size = ubuf.line_width * min(viewport_scale.x, viewport_scale.y);"
|
|
"\n let x_sign = select(-1.0, 1.0, (vidx & 1u) != 0u);"
|
|
"\n let y_sign = select(-1.0, 1.0, vidx >= 2u);"
|
|
"\n let offset_px = vec2f(x_sign, y_sign) * (point_size / 2.0);"
|
|
"\n let offset_ndc = (offset_px * 2.0) / ubuf.render_viewport_size;"
|
|
"\n out.pos = vec4f(clip.xy + offset_ndc * clip.w, clip.zw);";
|
|
} else {
|
|
// GX_LINES / GX_LINESTRIP: expand line segment perpendicular to direction
|
|
vtxXfrAttrsPre +=
|
|
"\n let clip_a = vec4f(mv_pos_a, 1.0) * ubuf.proj;"
|
|
"\n let clip_b = vec4f(mv_pos_b, 1.0) * ubuf.proj;"
|
|
"\n let ndc_a = clip_a.xy / clip_a.w;"
|
|
"\n let ndc_b = clip_b.xy / clip_b.w;"
|
|
"\n let viewport_scale = ubuf.render_viewport_size / max(ubuf.logical_viewport_size, vec2f(1.0));"
|
|
"\n let delta_px = (ndc_b - ndc_a) / 2.0 * ubuf.render_viewport_size;"
|
|
"\n let dir_px = select(vec2f(1.0, 0.0), normalize(delta_px), dot(delta_px, delta_px) > 1e-10);"
|
|
"\n let perp_px = vec2f(-dir_px.y, dir_px.x);"
|
|
"\n let line_width = ubuf.line_width * min(viewport_scale.x, viewport_scale.y);"
|
|
"\n let offset_px = perp_px * (line_width / 2.0) * select(-1.0, 1.0, (vidx & 1u) != 0u);"
|
|
"\n let offset_ndc = (offset_px * 2.0) / ubuf.render_viewport_size;"
|
|
"\n let clip_base = select(clip_a, clip_b, use_b);"
|
|
"\n out.pos = vec4f(clip_base.xy + offset_ndc * clip_base.w, clip_base.zw);";
|
|
}
|
|
// The near/far depth correction used to be applied here per-vertex (out.pos.z = -out.pos.z for
|
|
// reversed, or += out.pos.w for forward), redundantly on top of the same correction already
|
|
// folded into ubuf.proj by effective_projection() (shader_info.cpp) - applying it twice canceled
|
|
// out for the common case (any draw where effective_projection() decides to flip), silently
|
|
// making "reversed" Z behave identically to forward Z. It is now applied exactly once, in the
|
|
// projection matrix alone (matching upstream aurora commit 1dde08fa: "Move depth correction to
|
|
// projection matrix"), so nothing needs to happen to out.pos.z here.
|
|
// GX rasterizes at a 7/12 pixel center when antialiasing is disabled, while WebGPU rasterizes at 1/2.
|
|
vtxXfrAttrsPre +=
|
|
"\n let gx_pixel_center_correction = "
|
|
"vec2f(-1.0, 1.0) / (6.0 * max(abs(ubuf.render_viewport_size), vec2f(1.0)));"
|
|
"\n out.pos = vec4f(out.pos.xy + out.pos.w * gx_pixel_center_correction, out.pos.zw);";
|
|
vtxXfrAttrsPre += fmt::format(
|
|
"\n let nrm_tmp = vec4f({}, 0.0) * ubuf.nrm_mtx[in_pnmtxidx];"
|
|
"\n let mv_nrm = select(nrm_tmp, normalize(nrm_tmp), dot(nrm_tmp, nrm_tmp) > 1e-10);",
|
|
vtx_attr(config, GX_VA_NRM));
|
|
|
|
uniBufAttrs += "\n proj: mat4x4f,";
|
|
// Only the matrix slots this shader can read are uploaded, in compacted order; see UniformMatrixLayout.
|
|
uniBufAttrs += fmt::format("\n postex_mtx: array<mat3x4f, {}>,", info.matrixLayout.postexCount);
|
|
uniBufAttrs += fmt::format("\n nrm_mtx: array<mat3x4f, {}>,", info.matrixLayout.nrmCount);
|
|
std::string fragmentFnPre;
|
|
std::string fragmentFn;
|
|
const int zTexStage = tev_z_texture_stage(config);
|
|
const bool usesZTextureDepth = tev_z_texture_enabled(config);
|
|
std::array<bool, MaxTexCoord> emittedFixedTexCoord{};
|
|
const auto fixed_texcoord_name = [&](u32 texCoordId) {
|
|
if (!emittedFixedTexCoord[texCoordId]) {
|
|
fragmentFnPre += fmt::format(
|
|
"\n let tex{0}_fixed_uv = vec2i(tex{0}_uv * ubuf.texcoord{0}_scale.xy * 128.0);", texCoordId);
|
|
emittedFixedTexCoord[texCoordId] = true;
|
|
}
|
|
return fmt::format("tex{}_fixed_uv", texCoordId);
|
|
};
|
|
|
|
static std::array regName{"prev"sv, "tevreg0"sv, "tevreg1"sv, "tevreg2"sv};
|
|
for (u32 idx = 0; idx < config.tevStageCount; ++idx) {
|
|
const auto& stage = config.tevStages[idx];
|
|
std::string colorA = color_arg_reg(stage.colorPass.a, idx, config, stage);
|
|
std::string colorB = color_arg_reg(stage.colorPass.b, idx, config, stage);
|
|
fragmentFn += fmt::format("\n // TEV stage {}", idx);
|
|
if (alpha_compare_uses_color_inputs(stage.alphaOp.op)) {
|
|
const std::string colorAName = fmt::format("tev_stage{}_color_a", idx);
|
|
const std::string colorBName = fmt::format("tev_stage{}_color_b", idx);
|
|
// The color result is emitted before the alpha result below.
|
|
fragmentFn += fmt::format("\n let {0} = {1};\n let {2} = {3};", colorAName, colorA, colorBName, colorB);
|
|
colorA = colorAName;
|
|
colorB = colorBName;
|
|
}
|
|
{
|
|
std::string_view outReg = regName[stage.colorOp.outReg];
|
|
std::string op = tev_color_op(
|
|
stage.colorOp.op, stage.colorOp.bias, stage.colorOp.scale, stage.colorOp.clamp, colorA, colorB,
|
|
color_arg_reg(stage.colorPass.c, idx, config, stage), color_arg_reg(stage.colorPass.d, idx, config, stage));
|
|
fragmentFn += fmt::format("\n {0} = vec4f({1}, {0}.a);", outReg, op);
|
|
}
|
|
{
|
|
std::string_view outReg = regName[stage.alphaOp.outReg];
|
|
std::string op = tev_alpha_op(
|
|
stage.alphaOp.op, stage.alphaOp.bias, stage.alphaOp.scale, stage.alphaOp.clamp,
|
|
alpha_arg_reg(stage.alphaPass.a, idx, config, stage), alpha_arg_reg(stage.alphaPass.b, idx, config, stage),
|
|
alpha_arg_reg(stage.alphaPass.c, idx, config, stage), alpha_arg_reg(stage.alphaPass.d, idx, config, stage),
|
|
colorA, colorB);
|
|
fragmentFn += fmt::format("\n {0}.a = {1};", outReg, op);
|
|
}
|
|
}
|
|
|
|
{
|
|
const auto& lastStage = config.tevStages[config.tevStageCount - 1];
|
|
if (lastStage.colorOp.outReg != 0) {
|
|
fragmentFn += fmt::format("\n prev = vec4f({0}.rgb, prev.a);", regName[lastStage.colorOp.outReg]);
|
|
}
|
|
if (lastStage.alphaOp.outReg != 0) {
|
|
fragmentFn += fmt::format("\n prev.a = {0}.a;", regName[lastStage.alphaOp.outReg]);
|
|
}
|
|
}
|
|
|
|
const auto loadsTevRegs = info.loadsTevRegRgb | info.loadsTevRegAlpha;
|
|
const auto writesTevRegs = info.writesTevRegRgb | info.writesTevRegAlpha;
|
|
if (loadsTevRegs.test(0)) {
|
|
uniBufAttrs += "\n tevprev: vec4f,";
|
|
fragmentFnPre += "\n var prev = ubuf.tevprev;";
|
|
} else {
|
|
fragmentFnPre += "\n var prev: vec4f;";
|
|
}
|
|
for (int i = 1 /* Skip TEVPREV */; i < loadsTevRegs.size(); ++i) {
|
|
if (loadsTevRegs.test(i)) {
|
|
uniBufAttrs += fmt::format("\n tevreg{}: vec4f,", i - 1);
|
|
fragmentFnPre += fmt::format("\n var tevreg{0} = ubuf.tevreg{0};", i - 1);
|
|
} else if (writesTevRegs.test(i)) {
|
|
fragmentFnPre += fmt::format("\n var tevreg{0}: vec4f;", i - 1);
|
|
}
|
|
}
|
|
|
|
if (info.lightingEnabled) {
|
|
uniBufAttrs += fmt::format(FMT_STRING(R"""(
|
|
lights: array<Light, {}>,
|
|
lightState0: u32,
|
|
lightState1: u32,
|
|
lightState0a: u32,
|
|
lightState1a: u32,)"""),
|
|
GX::MaxLights);
|
|
uniformPre +=
|
|
"\n"
|
|
"struct Light {\n"
|
|
" pos: vec3f,\n"
|
|
" dir: vec3f,\n"
|
|
" color: vec4f,\n"
|
|
" cos_att: vec3f,\n"
|
|
" dist_att: vec3f,\n"
|
|
"};";
|
|
if (UsePerPixelLighting) {
|
|
vtxOutAttrs += fmt::format("\n @location({}) mv_pos: vec3f,", vtxOutIdx++);
|
|
vtxOutAttrs += fmt::format("\n @location({}) mv_nrm: vec3f,", vtxOutIdx++);
|
|
vtxXfrAttrs += fmt::format(FMT_STRING(R"""(
|
|
out.mv_pos = mv_pos;
|
|
out.mv_nrm = mv_nrm;)"""));
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < info.sampledColorChannels.size(); ++i) {
|
|
if (!info.sampledColorChannels.test(i)) {
|
|
continue;
|
|
}
|
|
|
|
const auto& cc = config.colorChannels[i];
|
|
const auto& cca = config.colorChannels[i + GX_ALPHA0];
|
|
if (cc.lightingEnabled && cc.ambSrc == GX_SRC_REG) {
|
|
uniBufAttrs += fmt::format("\n cc{0}_amb: vec4f,", i);
|
|
}
|
|
if (cc.matSrc == GX_SRC_REG) {
|
|
uniBufAttrs += fmt::format("\n cc{0}_mat: vec4f,", i);
|
|
}
|
|
if (cca.lightingEnabled && cca.ambSrc == GX_SRC_REG) {
|
|
uniBufAttrs += fmt::format("\n cc{0}a_amb: vec4f,", i);
|
|
}
|
|
if (cca.matSrc == GX_SRC_REG) {
|
|
uniBufAttrs += fmt::format("\n cc{0}a_mat: vec4f,", i);
|
|
}
|
|
|
|
// Output vertex color if necessary
|
|
if (UsePerPixelLighting) {
|
|
if ((cc.lightingEnabled && cc.ambSrc == GX_SRC_VTX) || cc.matSrc == GX_SRC_VTX ||
|
|
(cca.lightingEnabled && cca.ambSrc == GX_SRC_VTX) || cca.matSrc == GX_SRC_VTX) {
|
|
vtxOutAttrs += fmt::format("\n @location({}) clr{}: vec4f,", vtxOutIdx++, i);
|
|
vtxXfrAttrs += fmt::format("\n out.clr{} = {};", i, vertex_color_attr(config, i));
|
|
}
|
|
}
|
|
|
|
if (UsePerPixelLighting) {
|
|
fragmentFnPre += fmt::format("\n var rast{}: vec4f;", i);
|
|
fragmentFnPre += lighting_func(config, cc, i, false);
|
|
fragmentFnPre += lighting_func(config, cca, i, true);
|
|
} else {
|
|
vtxOutAttrs += fmt::format("\n @location({}) cc{}: vec4f,", vtxOutIdx++, i);
|
|
vtxXfrAttrs += lighting_func(config, cc, i, false);
|
|
vtxXfrAttrs += lighting_func(config, cca, i, true);
|
|
fragmentFnPre += fmt::format("\n var rast{0} = in.cc{0};", i);
|
|
}
|
|
}
|
|
for (int i = 0; i < info.sampledKColors.size(); ++i) {
|
|
if (info.sampledKColors.test(i)) {
|
|
uniBufAttrs += fmt::format("\n kcolor{}: vec4f,", i);
|
|
}
|
|
}
|
|
for (int i = 0; i < info.sampledTexCoords.size(); ++i) {
|
|
if (!info.sampledTexCoords.test(i)) {
|
|
continue;
|
|
}
|
|
const auto& tcg = config.tcgs[i];
|
|
if (tcg.type == GX_TG_MTX3x4) {
|
|
vtxOutAttrs += fmt::format("\n @location({}) tex{}_uvw: vec3f,", vtxOutIdx++, i);
|
|
} else {
|
|
vtxOutAttrs += fmt::format("\n @location({}) tex{}_uv: vec2f,", vtxOutIdx++, i);
|
|
}
|
|
if (tcg.src >= GX_TG_TEX0 && tcg.src <= GX_TG_TEX7) {
|
|
vtxXfrAttrs += fmt::format("\n var tc{} = vec4f({}, 1.0, 1.0);", i,
|
|
vtx_attr(config, GXAttr(GX_VA_TEX0 + (tcg.src - GX_TG_TEX0))));
|
|
} else if (tcg.src == GX_MAX_TEXGENSRC) {
|
|
vtxXfrAttrs += fmt::format("\n var tc{} = vec4f({}, 1.0, 1.0);", i,
|
|
vtx_attr(config, GXAttr(GX_VA_TEX0 + i)));
|
|
} else if (tcg.src == GX_TG_POS) {
|
|
vtxXfrAttrs += fmt::format("\n var tc{} = vec4f({}, 1.0);", i, vtx_attr(config, GX_VA_POS));
|
|
} else if (tcg.src == GX_TG_NRM) {
|
|
vtxXfrAttrs += fmt::format("\n var tc{} = vec4f({}, 1.0);", i, vtx_attr(config, GX_VA_NRM));
|
|
} else if (tcg.src == GX_TG_BINRM) {
|
|
// GX source row 3 is Dolphin's tangent group in NBT normal arrays.
|
|
vtxXfrAttrs += fmt::format("\n var tc{} = vec4f({}, 1.0);", i, normal_group_load(config, 1, vidxAttr));
|
|
} else if (tcg.src == GX_TG_TANGENT) {
|
|
// GX source row 4 is Dolphin's binormal group in NBT normal arrays.
|
|
vtxXfrAttrs += fmt::format("\n var tc{} = vec4f({}, 1.0);", i, normal_group_load(config, 2, vidxAttr));
|
|
} else if (tcg.src == GX_TG_COLOR0) {
|
|
vtxXfrAttrs += fmt::format("\n var tc{} = vec4f({}.rgb, 1.0);", i, vtx_attr(config, GX_VA_CLR0));
|
|
} else if (tcg.src == GX_TG_COLOR1) {
|
|
vtxXfrAttrs += fmt::format("\n var tc{} = vec4f({}.rgb, 1.0);", i, vtx_attr(config, GX_VA_CLR1));
|
|
} else
|
|
UNLIKELY FATAL("unhandled tcg src {}", underlying(tcg.src));
|
|
if (tcg.inputFormAB11) {
|
|
vtxXfrAttrs += fmt::format("\n tc{}.z = 1.0;", i);
|
|
}
|
|
if (tcg.type == GX_TG_MTX2x4 || tcg.type == GX_TG_MTX3x4) {
|
|
if (info.indexAttr.test(GX_VA_TEX0MTXIDX + i)) {
|
|
vtxXfrAttrs += fmt::format("\n var tc{0}_tmp = tc{0} * ubuf.postex_mtx[in_texmtxidx{0} / 3u];", i);
|
|
} else if (tcg.mtx == GX_IDENTITY) {
|
|
vtxXfrAttrs += fmt::format("\n var tc{0}_tmp = tc{0}.xyz;", i);
|
|
} else {
|
|
const u32 texMtxSlot = (tcg.mtx) / 3;
|
|
const u32 texMtxIdx = texMtxSlot < MaxPostexMtx ? info.matrixLayout.postexRemap[texMtxSlot]
|
|
: texMtxSlot;
|
|
CHECK(texMtxIdx != UniformMatrixLayout::kAbsent,
|
|
"texgen {} matrix slot {} missing from the uniform layout", i, texMtxSlot);
|
|
vtxXfrAttrs += fmt::format("\n var tc{0}_tmp = tc{0} * ubuf.postex_mtx[{1}];", i, texMtxIdx);
|
|
}
|
|
if (tcg.type == GX_TG_MTX2x4) {
|
|
vtxXfrAttrs += fmt::format("\n tc{0}_tmp.z = 1.0f;", i);
|
|
}
|
|
} else if (tcg.type == GX_TG_SRTG) {
|
|
vtxXfrAttrs += fmt::format("\n var tc{0}_tmp = vec3f(tc{0}.xy, 1.0f);", i);
|
|
}
|
|
if (config.dualTexEnabled && tcg.normalize) {
|
|
vtxXfrAttrs += fmt::format("\n tc{0}_tmp = normalize(tc{0}_tmp);", i);
|
|
}
|
|
if (!config.dualTexEnabled || tcg.postMtx == GX_PTIDENTITY) {
|
|
vtxXfrAttrs += fmt::format("\n var tc{0}_proj = tc{0}_tmp;", i);
|
|
} else {
|
|
u32 postMtxIdx = (tcg.postMtx - GX_PTTEXMTX0) / 3;
|
|
vtxXfrAttrs +=
|
|
fmt::format("\n var tc{0}_proj = vec4f(tc{0}_tmp.xyz, 1.0) * ubuf.postmtx[{1}];", i, postMtxIdx);
|
|
}
|
|
if (tcg.type == GX_TG_MTX2x4 || tcg.type == GX_TG_MTX3x4) {
|
|
vtxXfrAttrs += fmt::format(
|
|
"\n if (tc{0}_proj.z == 0.0) {{"
|
|
"\n tc{0}_proj.x = clamp(tc{0}_proj.x / 2.0, -1.0, 1.0);"
|
|
"\n tc{0}_proj.y = clamp(tc{0}_proj.y / 2.0, -1.0, 1.0);"
|
|
"\n }}",
|
|
i);
|
|
}
|
|
// Apply line/point tex offset
|
|
if (config.lineMode == 3) {
|
|
// GX_POINTS: offset S for right columns, T for bottom rows
|
|
vtxXfrAttrs += fmt::format(
|
|
"\n if ((ubuf.line_texcoord_mask & (1u << {0})) != 0u) {{"
|
|
"\n if ((vidx & 1u) != 0u) {{ tc{0}_proj.x += ubuf.line_tex_offset; }}"
|
|
"\n if (vidx >= 2u) {{ tc{0}_proj.y += ubuf.line_tex_offset; }}"
|
|
"\n }}",
|
|
i);
|
|
} else if (config.lineMode != 0) {
|
|
// GX_LINES / GX_LINESTRIP: offset one axis for perpendicular side
|
|
vtxXfrAttrs += fmt::format(
|
|
"\n if ((ubuf.line_texcoord_mask & (1u << {0})) != 0u && (vidx & 1u) != 0u) {{"
|
|
"\n tc{0}_proj.y += ubuf.line_tex_offset;"
|
|
"\n }}",
|
|
i);
|
|
}
|
|
if (tcg.type == GX_TG_MTX3x4) {
|
|
vtxXfrAttrs += fmt::format("\n out.tex{0}_uvw = tc{0}_proj.xyz;", i);
|
|
fragmentFnPre += fmt::format(
|
|
"\n var tex{0}_uv = in.tex{0}_uvw.xy;"
|
|
"\n if (in.tex{0}_uvw.z != 0.0) {{"
|
|
"\n tex{0}_uv = in.tex{0}_uvw.xy / in.tex{0}_uvw.z;"
|
|
"\n }}",
|
|
i);
|
|
} else {
|
|
vtxXfrAttrs += fmt::format("\n out.tex{0}_uv = tc{0}_proj.xy;", i);
|
|
fragmentFnPre += fmt::format("\n var tex{0}_uv = in.tex{0}_uv.xy;", i);
|
|
}
|
|
}
|
|
// Multiple TEV stages may reference the same indirect stage,
|
|
// so we sample each indirect texture only once.
|
|
const auto ind_scale_shift = [](const GXIndTexScale s) -> u32 {
|
|
switch (s) {
|
|
case GX_ITS_1:
|
|
return 0;
|
|
case GX_ITS_2:
|
|
return 1;
|
|
case GX_ITS_4:
|
|
return 2;
|
|
case GX_ITS_8:
|
|
return 3;
|
|
case GX_ITS_16:
|
|
return 4;
|
|
case GX_ITS_32:
|
|
return 5;
|
|
case GX_ITS_64:
|
|
return 6;
|
|
case GX_ITS_128:
|
|
return 7;
|
|
case GX_ITS_256:
|
|
return 8;
|
|
default:
|
|
FATAL("unhandled indirect scale {}", underlying(s));
|
|
}
|
|
};
|
|
for (int i = 0; i < info.usedIndStages.size(); ++i) {
|
|
if (!info.usedIndStages.test(i)) {
|
|
continue;
|
|
}
|
|
const auto& indStage = config.indStages[i];
|
|
const int effectiveIndTexCoord = tev_effective_texcoord(config, indStage.texCoordId);
|
|
const std::string indFixedUv =
|
|
effectiveIndTexCoord >= 0 ? fixed_texcoord_name(static_cast<u32>(effectiveIndTexCoord)) : "vec2i(0)";
|
|
fragmentFnPre += fmt::format(
|
|
"\n // Indirect stage {0}"
|
|
"\n let ind{0}_sample_fixed = {5} >> vec2u({3}u, {4}u);"
|
|
"\n let t_IndTexCoord{0} = tev_byte_vec3f(textureSampleBias(tex{2}, tex{2}_samp, "
|
|
"vec2f(ind{0}_sample_fixed) / (ubuf.tex{2}_size_bias.xy * 128.0), ubuf.tex{2}_size_bias.z).abg);",
|
|
i, effectiveIndTexCoord, underlying(indStage.texMapId), ind_scale_shift(indStage.scaleS),
|
|
ind_scale_shift(indStage.scaleT), indFixedUv);
|
|
}
|
|
const bool usesFixedTexcoordState = shader_uses_fixed_texcoord_state(config);
|
|
if (usesFixedTexcoordState) {
|
|
fragmentFnPre += "\n var t_TexCoord = vec2i(0);";
|
|
}
|
|
for (int i = 0; i < config.tevStageCount; ++i) {
|
|
const auto& stage = config.tevStages[i];
|
|
const auto textureDependency = tev_stage_texture_dependency(config, static_cast<u32>(i));
|
|
const bool needsIndirectCoord = stage.indTexMtxId != GX_ITM_OFF;
|
|
const bool hasIndirectStage = stage.indTexStage < config.numIndStages;
|
|
const bool needsTevTexCoord = textureDependency.needsFixedTexcoordState;
|
|
const bool needsTextureSample = textureDependency.combinerUsesTexture || static_cast<int>(i) == zTexStage;
|
|
const bool willSampleTexture = tev_texture_sample_enabled(textureDependency, needsTextureSample);
|
|
if (!needsTevTexCoord && !needsTextureSample) {
|
|
continue;
|
|
}
|
|
const bool hasBaseCoord = textureDependency.texCoordId >= 0;
|
|
std::string uvIn;
|
|
if (needsTevTexCoord) {
|
|
fragmentFnPre += fmt::format("\n // TEV stage {} indirect", i);
|
|
|
|
// Apply indirect texture matrix (produces an S17.7 fixed-point offset).
|
|
std::string indirectOffsetFixed;
|
|
if (needsIndirectCoord && hasIndirectStage) {
|
|
u32 fmtShift = 0;
|
|
switch (stage.indTexFormat) {
|
|
case GX_ITF_8:
|
|
break;
|
|
case GX_ITF_5:
|
|
fmtShift = 3;
|
|
break;
|
|
case GX_ITF_4:
|
|
fmtShift = 4;
|
|
break;
|
|
case GX_ITF_3:
|
|
fmtShift = 5;
|
|
break;
|
|
default:
|
|
FATAL("unhandled indirect format {}", underlying(stage.indTexFormat));
|
|
}
|
|
if (fmtShift == 0) {
|
|
fragmentFnPre += fmt::format("\n var ind{0}_coord = t_IndTexCoord{1};", i, underlying(stage.indTexStage));
|
|
} else {
|
|
fragmentFnPre += fmt::format("\n var ind{0}_coord = t_IndTexCoord{1} >> vec3u({2}u);", i,
|
|
underlying(stage.indTexStage), fmtShift);
|
|
}
|
|
|
|
if (stage.indTexBiasSel != GX_ITB_NONE) {
|
|
auto bias = stage.indTexFormat == GX_ITF_8 ? "-128"sv : "1"sv;
|
|
auto biasS = "0"sv, biasT = "0"sv, biasU = "0"sv;
|
|
if (stage.indTexBiasSel == GX_ITB_S || stage.indTexBiasSel == GX_ITB_ST || stage.indTexBiasSel == GX_ITB_SU ||
|
|
stage.indTexBiasSel == GX_ITB_STU) {
|
|
biasS = "1"sv;
|
|
}
|
|
if (stage.indTexBiasSel == GX_ITB_T || stage.indTexBiasSel == GX_ITB_ST || stage.indTexBiasSel == GX_ITB_TU ||
|
|
stage.indTexBiasSel == GX_ITB_STU) {
|
|
biasT = "1"sv;
|
|
}
|
|
if (stage.indTexBiasSel == GX_ITB_U || stage.indTexBiasSel == GX_ITB_SU || stage.indTexBiasSel == GX_ITB_TU ||
|
|
stage.indTexBiasSel == GX_ITB_STU) {
|
|
biasU = "1"sv;
|
|
}
|
|
fragmentFnPre += fmt::format("\n ind{0}_coord = ind{0}_coord + vec3i({1}, {2}, {3}) * {4};", i, biasS,
|
|
biasT, biasU, bias);
|
|
}
|
|
|
|
const auto appendScaleShift = [&](u32 mtxIdx) {
|
|
fragmentFnPre += fmt::format(
|
|
"\n let ind{0}_shift = ubuf.ind_mtx[{1}].z;"
|
|
"\n if (ind{0}_shift >= 0) {{"
|
|
"\n ind{0}_offset_fixed = ind{0}_offset_fixed >> vec2u(u32(ind{0}_shift));"
|
|
"\n }} else {{"
|
|
"\n ind{0}_offset_fixed = ind{0}_offset_fixed << vec2u(u32(-ind{0}_shift));"
|
|
"\n }}",
|
|
i, mtxIdx * 2 + 1);
|
|
indirectOffsetFixed = fmt::format("ind{}_offset_fixed", i);
|
|
};
|
|
|
|
if (stage.indTexMtxId >= GX_ITM_0 && stage.indTexMtxId <= GX_ITM_2) {
|
|
// Static 2x3 matrix: integer dot products, then the hardware's fixed >>3 and exponent shift sequence.
|
|
u32 mtxIdx = stage.indTexMtxId - GX_ITM_0;
|
|
fragmentFnPre += fmt::format(
|
|
"\n let ind{0}_c0 = ubuf.ind_mtx[{1}];"
|
|
"\n let ind{0}_c1 = ubuf.ind_mtx[{2}];"
|
|
"\n var ind{0}_offset_fixed = vec2i("
|
|
"\n (ind{0}_c0.x * ind{0}_coord.x + ind{0}_c0.z * ind{0}_coord.y + "
|
|
"ind{0}_c1.x * ind{0}_coord.z) >> 3u,"
|
|
"\n (ind{0}_c0.y * ind{0}_coord.x + ind{0}_c0.w * ind{0}_coord.y + "
|
|
"ind{0}_c1.y * ind{0}_coord.z) >> 3u);",
|
|
i, mtxIdx * 2, mtxIdx * 2 + 1);
|
|
appendScaleShift(mtxIdx);
|
|
} else if (stage.indTexMtxId >= GX_ITM_S0 && stage.indTexMtxId <= GX_ITM_S2 && hasBaseCoord) {
|
|
// Dynamic S: (fixed UV * indirect S) >> 8, then exponent shift.
|
|
u32 mtxIdx = stage.indTexMtxId - GX_ITM_S0;
|
|
u32 regTexCoord = static_cast<u32>(textureDependency.texCoordId);
|
|
const auto fixedUv = fixed_texcoord_name(regTexCoord);
|
|
fragmentFnPre += fmt::format(
|
|
"\n var ind{0}_offset_fixed = ({1} * vec2i(ind{0}_coord.x)) >> vec2u(8u);", i, fixedUv);
|
|
appendScaleShift(mtxIdx);
|
|
} else if (stage.indTexMtxId >= GX_ITM_T0 && stage.indTexMtxId <= GX_ITM_T2 && hasBaseCoord) {
|
|
// Dynamic T: (fixed UV * indirect T) >> 8, then exponent shift.
|
|
u32 mtxIdx = stage.indTexMtxId - GX_ITM_T0;
|
|
u32 regTexCoord = static_cast<u32>(textureDependency.texCoordId);
|
|
const auto fixedUv = fixed_texcoord_name(regTexCoord);
|
|
fragmentFnPre += fmt::format(
|
|
"\n var ind{0}_offset_fixed = ({1} * vec2i(ind{0}_coord.y)) >> vec2u(8u);", i, fixedUv);
|
|
appendScaleShift(mtxIdx);
|
|
}
|
|
}
|
|
|
|
// Wrap base coord and combine with the indirect translation.
|
|
auto wrap_comp = [](GXIndTexWrap wrap, std::string&& coord) -> std::string {
|
|
const int mask = tev_indirect_wrap_mask(wrap);
|
|
if (mask == -1) {
|
|
return std::move(coord);
|
|
}
|
|
if (mask == 0) {
|
|
return "0";
|
|
}
|
|
CHECK(mask > 0, "invalid indirect wrap {}", underlying(wrap));
|
|
return fmt::format("({} & {})", coord, mask);
|
|
};
|
|
std::string baseCoordExpr = "vec2i(0)";
|
|
if (hasBaseCoord) {
|
|
u32 texCoordId = static_cast<u32>(textureDependency.texCoordId);
|
|
baseCoordExpr = fixed_texcoord_name(texCoordId);
|
|
}
|
|
const std::string wrappedExpr =
|
|
fmt::format("vec2i({}, {})", wrap_comp(stage.indTexWrapS, fmt::format("{}.x", baseCoordExpr)),
|
|
wrap_comp(stage.indTexWrapT, fmt::format("{}.y", baseCoordExpr)));
|
|
std::string finalCoord = wrappedExpr;
|
|
if (!indirectOffsetFixed.empty()) {
|
|
finalCoord = fmt::format("{} + ({})", wrappedExpr, indirectOffsetFixed);
|
|
}
|
|
|
|
if (usesFixedTexcoordState) {
|
|
if (stage.indTexAddPrev) {
|
|
fragmentFnPre += fmt::format("\n t_TexCoord += {};", finalCoord);
|
|
} else {
|
|
fragmentFnPre += fmt::format("\n t_TexCoord = {};", finalCoord);
|
|
}
|
|
fragmentFnPre += "\n t_TexCoord = tev_s24_fixed_vec2i(t_TexCoord);";
|
|
|
|
// ShaderInfo only emits texN_size_bias and texture bindings for a texture that can actually be sampled.
|
|
if (willSampleTexture) {
|
|
u32 texMapId = static_cast<u32>(textureDependency.texMapId);
|
|
fragmentFnPre += fmt::format(
|
|
"\n var ind{0}_uv = vec2f(t_TexCoord) / (ubuf.tex{1}_size_bias.xy * 128.0);", i, texMapId);
|
|
uvIn = fmt::format("ind{0}_uv", i);
|
|
}
|
|
}
|
|
}
|
|
if (!willSampleTexture) {
|
|
continue;
|
|
}
|
|
if (uvIn.empty()) {
|
|
// No indirect texturing
|
|
const auto fixedUv = fixed_texcoord_name(static_cast<u32>(textureDependency.texCoordId));
|
|
uvIn = fmt::format("vec2f(tev_s24_fixed_vec2i({0})) / (ubuf.tex{1}_size_bias.xy * 128.0)", fixedUv,
|
|
textureDependency.texMapId);
|
|
}
|
|
fragmentFnPre +=
|
|
fmt::format("\n var sampled{0} = textureSampleBias(tex{1}, tex{1}_samp, {2}, ubuf.tex{1}_size_bias.z);", i,
|
|
textureDependency.texMapId, uvIn);
|
|
}
|
|
|
|
// in.pos.z is the host NDC z (forward: 0=near/1=far; reversed: 1=near/0=far post-fix), but this
|
|
// expression needs to produce GX's own native distance term (always 0=near/1=far, matching how
|
|
// g_gxState.clearDepth/clear_depth_value() are interpreted before their own UseReversedZ
|
|
// inversion) - forward already matches directly; reversed needs the same 1-x flip everything
|
|
// else reversed-Z-aware uses. This was backwards (verified directly against upstream aurora's
|
|
// identical expression in build_shader_source), which fed both fog density and the GX_ZT_ADD
|
|
// z-texture path the wrong distance value.
|
|
std::string fogDepthExpr = UseReversedZ ? "(1.0 - in.pos.z)" : "in.pos.z";
|
|
std::string fogZCoordExpr =
|
|
fmt::format("u32(round(clamp({}, 0.0, 1.0) * 16777216.0))", fogDepthExpr);
|
|
if (usesZTextureDepth) {
|
|
const u32 zTexBias = config.zTexture & 0x00FFFFFFu;
|
|
const u32 zTexFmt = (config.zTexture >> 24) & 0x3u;
|
|
const u32 zTexOp = (config.zTexture >> 26) & 0x3u;
|
|
const std::string zTexSample = zTexStage >= 0 ? fmt::format("sampled{}", zTexStage) : "vec4f(0.0)";
|
|
|
|
fragmentFn += fmt::format(
|
|
"\n // Z texture"
|
|
"\n let ztexSample = clamp({0}, vec4f(0.0), vec4f(1.0)) * 255.0;"
|
|
"\n let ztexRaw = vec4u(round(ztexSample));",
|
|
zTexSample);
|
|
switch (zTexFmt) {
|
|
case 0:
|
|
fragmentFn += fmt::format("\n var ztexCoord = ({0}u + ztexRaw.a) & 0x00ffffffu;", zTexBias);
|
|
break;
|
|
case 1:
|
|
fragmentFn +=
|
|
fmt::format("\n var ztexCoord = ({0}u + ((ztexRaw.a << 8u) | ztexRaw.r)) & 0x00ffffffu;", zTexBias);
|
|
break;
|
|
case 2:
|
|
default:
|
|
fragmentFn += fmt::format(
|
|
"\n var ztexCoord = ({0}u + ((ztexRaw.r << 16u) | (ztexRaw.g << 8u) | ztexRaw.b)) & 0x00ffffffu;",
|
|
zTexBias);
|
|
break;
|
|
}
|
|
if (zTexOp == GX_ZT_ADD) {
|
|
fragmentFn += fmt::format(
|
|
"\n let oldZ = u32(round(clamp({0}, 0.0, 1.0) * 16777216.0));"
|
|
"\n ztexCoord = (ztexCoord + oldZ) & 0x00ffffffu;",
|
|
UseReversedZ ? "(1.0 - in.pos.z)" : "in.pos.z");
|
|
}
|
|
fragmentFn += "\n let ztexDepth = f32(ztexCoord) / 16777216.0;";
|
|
fogZCoordExpr = "ztexCoord";
|
|
}
|
|
|
|
if (info.usesPTTexMtx.any())
|
|
uniBufAttrs += fmt::format("\n postmtx: array<mat3x4f, {}>,", MaxPTTexMtx);
|
|
if (info.usesFog) {
|
|
uniformPre +=
|
|
"\n"
|
|
"struct Fog {\n"
|
|
" color: vec4f,\n"
|
|
" a: f32,\n"
|
|
" b: f32,\n"
|
|
" c: f32,\n"
|
|
" pad: f32,\n"
|
|
" range_base: vec4f,\n"
|
|
" range_k: array<vec4f, 3>,\n"
|
|
"}";
|
|
uniBufAttrs += "\n fog: Fog,";
|
|
|
|
if ((config.fogType & 0x8u) == 0) {
|
|
fragmentFn += fmt::format(
|
|
"\n // Fog"
|
|
"\n let fogZCoord = {};"
|
|
"\n let fogShiftedZ = fogZCoord >> u32(ubuf.fog.pad);"
|
|
"\n var fogZe = (ubuf.fog.a * 16777216.0) / (ubuf.fog.b - f32(fogShiftedZ));",
|
|
fogZCoordExpr);
|
|
} else {
|
|
fragmentFn += fmt::format(
|
|
"\n // Fog"
|
|
"\n let fogZCoord = {};"
|
|
"\n var fogZe = ubuf.fog.a * f32(fogZCoord) / 16777216.0;",
|
|
fogZCoordExpr);
|
|
}
|
|
if (config.fogRangeAdjust) {
|
|
fragmentFn +=
|
|
"\n let fogRangeOffset = (2.0 * (in.pos.x / ubuf.fog.range_base.y)) - 1.0 - ubuf.fog.range_base.x;"
|
|
"\n let fogRangeFloatIndex = clamp(9.0 - abs(fogRangeOffset) * 9.0, 0.0, 9.0);"
|
|
"\n let fogRangeIndexLower = u32(fogRangeFloatIndex);"
|
|
"\n let fogRangeIndexUpper = fogRangeIndexLower + 1u;"
|
|
"\n let fogRangeKLower = ubuf.fog.range_k[fogRangeIndexLower >> 2u][fogRangeIndexLower & 3u];"
|
|
"\n let fogRangeKUpper = ubuf.fog.range_k[fogRangeIndexUpper >> 2u][fogRangeIndexUpper & 3u];"
|
|
"\n let fogRangeK = mix(fogRangeKLower, fogRangeKUpper, fract(fogRangeFloatIndex));"
|
|
"\n fogZe *= sqrt(fogRangeOffset * fogRangeOffset + fogRangeK * fogRangeK) / fogRangeK;";
|
|
}
|
|
fragmentFn += "\n var fogF = clamp(fogZe - ubuf.fog.c, 0.0, 1.0);";
|
|
switch (config.fogType) {
|
|
DEFAULT_FATAL("invalid fog type {}", config.fogType);
|
|
case static_cast<GXFogType>(1):
|
|
case static_cast<GXFogType>(3):
|
|
case GX_FOG_PERSP_LIN:
|
|
case static_cast<GXFogType>(9):
|
|
case static_cast<GXFogType>(11):
|
|
case GX_FOG_ORTHO_LIN:
|
|
fragmentFn += "\n var fogZ = fogF;";
|
|
break;
|
|
case GX_FOG_PERSP_EXP:
|
|
case GX_FOG_ORTHO_EXP:
|
|
fragmentFn += "\n var fogZ = 1.0 - exp2(-8.0 * fogF);";
|
|
break;
|
|
case GX_FOG_PERSP_EXP2:
|
|
case GX_FOG_ORTHO_EXP2:
|
|
fragmentFn += "\n var fogZ = 1.0 - exp2(-8.0 * fogF * fogF);";
|
|
break;
|
|
case GX_FOG_PERSP_REVEXP:
|
|
case GX_FOG_ORTHO_REVEXP:
|
|
fragmentFn += "\n var fogZ = exp2(-8.0 * (1.0 - fogF));";
|
|
break;
|
|
case GX_FOG_PERSP_REVEXP2:
|
|
case GX_FOG_ORTHO_REVEXP2:
|
|
fragmentFn +=
|
|
"\n fogF = 1.0 - fogF;"
|
|
"\n var fogZ = exp2(-8.0 * fogF * fogF);";
|
|
break;
|
|
}
|
|
fragmentFn += "\n prev = vec4f(mix(prev.rgb, ubuf.fog.color.rgb, clamp(fogZ, 0.0, 1.0)), prev.a);";
|
|
}
|
|
if (info.usedIndTexMtxs.any()) {
|
|
uniBufAttrs += "\n ind_mtx: array<vec4i, 6>,";
|
|
}
|
|
for (int i = 0; i < info.sampledTexCoords.size(); ++i) {
|
|
if (info.sampledTexCoords.test(i)) {
|
|
uniBufAttrs += fmt::format("\n texcoord{}_scale: vec4f,", i);
|
|
}
|
|
}
|
|
for (int i = 0; i < info.sampledTextures.size(); ++i) {
|
|
if (!info.sampledTextures.test(i)) {
|
|
continue;
|
|
}
|
|
uniBufAttrs += fmt::format("\n tex{}_size_bias: vec4f,", i);
|
|
texBindings += fmt::format(
|
|
"\n@group(2) @binding({1})\n"
|
|
"var tex{0}: texture_2d<f32>;\n"
|
|
"@group(2) @binding({2})\n"
|
|
"var tex{0}_samp: sampler;",
|
|
i, i * 2, i * 2 + 1);
|
|
}
|
|
fragmentFn += "\n prev = tev_overflow_vec4f(prev);";
|
|
if (config.alphaCompare) {
|
|
const auto comp0 = alpha_compare(config.alphaCompare.comp0, config.alphaCompare.ref0);
|
|
const auto comp1 = alpha_compare(config.alphaCompare.comp1, config.alphaCompare.ref1);
|
|
AlphaCompareExpr pass;
|
|
switch (config.alphaCompare.op) {
|
|
default:
|
|
pass = alpha_compare_and(comp0, comp1);
|
|
break;
|
|
case GX_AOP_AND:
|
|
pass = alpha_compare_and(comp0, comp1);
|
|
break;
|
|
case GX_AOP_OR:
|
|
pass = alpha_compare_or(comp0, comp1);
|
|
break;
|
|
case GX_AOP_XOR:
|
|
pass = alpha_compare_xor(comp0, comp1);
|
|
break;
|
|
case GX_AOP_XNOR:
|
|
pass = alpha_compare_xnor(comp0, comp1);
|
|
break;
|
|
}
|
|
const auto discard = alpha_compare_not(pass);
|
|
if (discard.constant == 1) {
|
|
fragmentFn += "\n // Alpha compare\n discard;";
|
|
} else if (discard.constant != 0) {
|
|
fragmentFn += "\n // Alpha compare"
|
|
"\n let alphaCompare = u32(round(clamp(prev.a, 0.0, 1.0) * 255.0));";
|
|
fragmentFn += fmt::format("\n if ({}) {{ discard; }}", discard.expr);
|
|
}
|
|
}
|
|
|
|
std::string fragmentReturnType = "@location(0) vec4f";
|
|
std::string fragmentReturn = " return prev;";
|
|
if (usesZTextureDepth) {
|
|
uniformPre +=
|
|
"\n"
|
|
"struct FragmentOutput {\n"
|
|
" @location(0) color: vec4f,\n"
|
|
" @builtin(frag_depth) depth: f32,\n"
|
|
"};";
|
|
|
|
// ztexDepth is in GX's native distance terms (0=near/1=far, see fogDepthExpr's comment above),
|
|
// but frag_depth must be written in the same host NDC-z convention in.pos.z itself uses -
|
|
// forward matches directly (no change), reversed needs the same 1-x flip. This was backwards
|
|
// the same way fogDepthExpr was (verified by the same derivation, since aurora upstream has no
|
|
// directly equivalent line here to cross-check against - this z-texture-depth-output path
|
|
// appears to be specific to this fork).
|
|
fragmentFn += fmt::format("\n let fragDepth = {}ztexDepth;", UseReversedZ ? "1.0 - " : "");
|
|
fragmentReturnType = "FragmentOutput";
|
|
fragmentReturn =
|
|
" var out: FragmentOutput;\n"
|
|
" out.color = prev;\n"
|
|
" out.depth = fragDepth;\n"
|
|
" return out;";
|
|
}
|
|
|
|
const auto shaderSource = fmt::format(R"""(
|
|
fn bswap32(v: u32, le: bool) -> u32 {{
|
|
if (le) {{
|
|
return v;
|
|
}}
|
|
return ((v & 0x000000FFu) << 24u) |
|
|
((v & 0x0000FF00u) << 8u) |
|
|
((v & 0x00FF0000u) >> 8u) |
|
|
((v & 0xFF000000u) >> 24u);
|
|
}}
|
|
|
|
fn bswap16(v: u32, le: bool) -> u32 {{
|
|
return select(((v & 0xFFu) << 8u) | (v >> 8u), v, le);
|
|
}}
|
|
|
|
fn load_u8(p: ptr<storage, array<u32>>, byte_off: u32) -> u32 {{
|
|
let word = p[byte_off / 4u];
|
|
let shift = (byte_off & 3u) * 8u;
|
|
return (word >> shift) & 0xFFu;
|
|
}}
|
|
|
|
fn load_u32_raw(p: ptr<storage, array<u32>>, byte_off: u32) -> u32 {{
|
|
let word_idx = byte_off >> 2u;
|
|
let sub = byte_off & 3u;
|
|
let lo = p[word_idx];
|
|
if (sub == 0u) {{
|
|
return lo;
|
|
}}
|
|
let hi = p[word_idx + 1u];
|
|
let shift = sub * 8u;
|
|
return (lo >> shift) | (hi << (32u - shift));
|
|
}}
|
|
|
|
fn load_u16(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> u32 {{
|
|
let word_idx = byte_off >> 2u;
|
|
let sub = byte_off & 3u;
|
|
let word = p[word_idx];
|
|
if (sub <= 2u) {{
|
|
return bswap16(extractBits(word, sub * 8u, 16u), le);
|
|
}}
|
|
let next = p[word_idx + 1u];
|
|
let raw = extractBits(word, 24u, 8u) | (extractBits(next, 0u, 8u) << 8u);
|
|
return bswap16(raw, le);
|
|
}}
|
|
|
|
fn load_u24_raw(p: ptr<storage, array<u32>>, byte_off: u32) -> u32 {{
|
|
let word_idx = byte_off >> 2u;
|
|
let sub = byte_off & 3u;
|
|
let word = p[word_idx];
|
|
// Three bytes at offsets zero or one fit entirely in this word. Do not
|
|
// access the next word: this attribute may end at the binding boundary.
|
|
if (sub <= 1u) {{
|
|
return (word >> (sub * 8u)) & 0x00FFFFFFu;
|
|
}}
|
|
let next = p[word_idx + 1u];
|
|
let shift = sub * 8u;
|
|
return ((word >> shift) | (next << (32u - shift))) & 0x00FFFFFFu;
|
|
}}
|
|
|
|
fn load_u24(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> u32 {{
|
|
let raw = load_u24_raw(p, byte_off);
|
|
if (le) {{
|
|
return raw;
|
|
}}
|
|
return ((raw & 0x0000FFu) << 16u) |
|
|
(raw & 0x00FF00u) |
|
|
((raw & 0xFF0000u) >> 16u);
|
|
}}
|
|
|
|
fn load_u32(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> u32 {{
|
|
return bswap32(load_u32_raw(p, byte_off), le);
|
|
}}
|
|
|
|
fn load_f32(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> f32 {{
|
|
return bitcast<f32>(load_u32(p, byte_off, le));
|
|
}}
|
|
|
|
fn raw_fetch_u8_1(p: ptr<storage, array<u32>>, byte_off: u32) -> u32 {{
|
|
return load_u8(p, byte_off);
|
|
}}
|
|
|
|
fn raw_fetch_u8_2(p: ptr<storage, array<u32>>, byte_off: u32) -> vec2u {{
|
|
let word_idx = byte_off >> 2u;
|
|
let sub = byte_off & 3u;
|
|
let word = p[word_idx];
|
|
if (sub <= 2u) {{
|
|
let shift = sub * 8u;
|
|
return vec2u(
|
|
extractBits(word, shift + 0u, 8u),
|
|
extractBits(word, shift + 8u, 8u),
|
|
);
|
|
}}
|
|
let next = p[word_idx + 1u];
|
|
return vec2u(
|
|
extractBits(word, 24u, 8u),
|
|
extractBits(next, 0u, 8u),
|
|
);
|
|
}}
|
|
|
|
fn raw_fetch_u8_3(p: ptr<storage, array<u32>>, byte_off: u32) -> vec3u {{
|
|
let raw = load_u24_raw(p, byte_off);
|
|
return vec3u(
|
|
extractBits(raw, 0u, 8u),
|
|
extractBits(raw, 8u, 8u),
|
|
extractBits(raw, 16u, 8u),
|
|
);
|
|
}}
|
|
|
|
fn raw_fetch_u8_4(p: ptr<storage, array<u32>>, byte_off: u32) -> vec4u {{
|
|
let raw = load_u32_raw(p, byte_off);
|
|
return vec4u(
|
|
extractBits(raw, 0u, 8u),
|
|
extractBits(raw, 8u, 8u),
|
|
extractBits(raw, 16u, 8u),
|
|
extractBits(raw, 24u, 8u),
|
|
);
|
|
}}
|
|
|
|
fn raw_fetch_u16_1(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> u32 {{
|
|
return load_u16(p, byte_off, le);
|
|
}}
|
|
|
|
fn raw_fetch_u16_2(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> vec2u {{
|
|
return vec2u(
|
|
load_u16(p, byte_off + 0u, le),
|
|
load_u16(p, byte_off + 2u, le),
|
|
);
|
|
}}
|
|
|
|
fn raw_fetch_u16_3(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> vec3u {{
|
|
return vec3u(
|
|
load_u16(p, byte_off + 0u, le),
|
|
load_u16(p, byte_off + 2u, le),
|
|
load_u16(p, byte_off + 4u, le),
|
|
);
|
|
}}
|
|
|
|
fn raw_fetch_u16_4(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> vec4u {{
|
|
return vec4u(
|
|
load_u16(p, byte_off + 0u, le),
|
|
load_u16(p, byte_off + 2u, le),
|
|
load_u16(p, byte_off + 4u, le),
|
|
load_u16(p, byte_off + 6u, le),
|
|
);
|
|
}}
|
|
|
|
fn raw_fetch_f32_1(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> f32 {{
|
|
return load_f32(p, byte_off, le);
|
|
}}
|
|
|
|
fn raw_fetch_f32_2(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> vec2f {{
|
|
return vec2f(
|
|
load_f32(p, byte_off + 0u, le),
|
|
load_f32(p, byte_off + 4u, le),
|
|
);
|
|
}}
|
|
|
|
fn raw_fetch_f32_3(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> vec3f {{
|
|
return vec3f(
|
|
load_f32(p, byte_off + 0u, le),
|
|
load_f32(p, byte_off + 4u, le),
|
|
load_f32(p, byte_off + 8u, le),
|
|
);
|
|
}}
|
|
|
|
fn raw_fetch_f32_4(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> vec4f {{
|
|
return vec4f(
|
|
load_f32(p, byte_off + 0u, le),
|
|
load_f32(p, byte_off + 4u, le),
|
|
load_f32(p, byte_off + 8u, le),
|
|
load_f32(p, byte_off + 12u, le),
|
|
);
|
|
}}
|
|
|
|
fn fetch_u8_1(p: ptr<storage, array<u32>>, byte_off: u32, frac: u32, le: bool) -> f32 {{
|
|
let v = raw_fetch_u8_1(p, byte_off);
|
|
return f32(v) / f32(1u << frac);
|
|
}}
|
|
|
|
fn fetch_s8_1(p: ptr<storage, array<u32>>, byte_off: u32, frac: u32, le: bool) -> f32 {{
|
|
let v = (bitcast<i32>(raw_fetch_u8_1(p, byte_off)) << 24) >> 24;
|
|
return f32(v) / f32(1u << frac);
|
|
}}
|
|
|
|
fn fetch_u8_2(p: ptr<storage, array<u32>>, byte_off: u32, frac: u32, le: bool) -> vec2f {{
|
|
let v = raw_fetch_u8_2(p, byte_off);
|
|
return vec2f(v) / f32(1u << frac);
|
|
}}
|
|
|
|
fn fetch_s8_2(p: ptr<storage, array<u32>>, byte_off: u32, frac: u32, le: bool) -> vec2f {{
|
|
let v = (bitcast<vec2i>(raw_fetch_u8_2(p, byte_off)) << vec2u(24u)) >> vec2u(24u);
|
|
return vec2f(v) / f32(1u << frac);
|
|
}}
|
|
|
|
fn fetch_u8_3(p: ptr<storage, array<u32>>, byte_off: u32, frac: u32, le: bool) -> vec3f {{
|
|
let v = raw_fetch_u8_3(p, byte_off);
|
|
return vec3f(v) / f32(1u << frac);
|
|
}}
|
|
|
|
fn fetch_s8_3(p: ptr<storage, array<u32>>, byte_off: u32, frac: u32, le: bool) -> vec3f {{
|
|
let v = (bitcast<vec3i>(raw_fetch_u8_3(p, byte_off)) << vec3u(24u)) >> vec3u(24u);
|
|
return vec3f(v) / f32(1u << frac);
|
|
}}
|
|
|
|
fn fetch_u8_4(p: ptr<storage, array<u32>>, byte_off: u32, frac: u32, le: bool) -> vec4f {{
|
|
let v = raw_fetch_u8_4(p, byte_off);
|
|
return vec4f(v) / f32(1u << frac);
|
|
}}
|
|
|
|
fn fetch_s8_4(p: ptr<storage, array<u32>>, byte_off: u32, frac: u32, le: bool) -> vec4f {{
|
|
let v = (bitcast<vec4i>(raw_fetch_u8_4(p, byte_off)) << vec4u(24u)) >> vec4u(24u);
|
|
return vec4f(v) / f32(1u << frac);
|
|
}}
|
|
|
|
fn fetch_u16_1(p: ptr<storage, array<u32>>, byte_off: u32, frac: u32, le: bool) -> f32 {{
|
|
let v = raw_fetch_u16_1(p, byte_off, le);
|
|
return f32(v) / f32(1u << frac);
|
|
}}
|
|
|
|
fn fetch_s16_1(p: ptr<storage, array<u32>>, byte_off: u32, frac: u32, le: bool) -> f32 {{
|
|
let v = bitcast<i32>(raw_fetch_u16_1(p, byte_off, le) << 16u) >> 16;
|
|
return f32(v) / f32(1u << frac);
|
|
}}
|
|
|
|
fn fetch_u16_2(p: ptr<storage, array<u32>>, byte_off: u32, frac: u32, le: bool) -> vec2f {{
|
|
let v = raw_fetch_u16_2(p, byte_off, le);
|
|
return vec2f(v) / f32(1u << frac);
|
|
}}
|
|
|
|
fn fetch_s16_2(p: ptr<storage, array<u32>>, byte_off: u32, frac: u32, le: bool) -> vec2f {{
|
|
let v = (bitcast<vec2i>(raw_fetch_u16_2(p, byte_off, le)) << vec2u(16u)) >> vec2u(16u);
|
|
return vec2f(v) / f32(1u << frac);
|
|
}}
|
|
|
|
fn fetch_u16_3(p: ptr<storage, array<u32>>, byte_off: u32, frac: u32, le: bool) -> vec3f {{
|
|
let v = raw_fetch_u16_3(p, byte_off, le);
|
|
return vec3f(v) / f32(1u << frac);
|
|
}}
|
|
|
|
fn fetch_s16_3(p: ptr<storage, array<u32>>, byte_off: u32, frac: u32, le: bool) -> vec3f {{
|
|
let v = (bitcast<vec3i>(raw_fetch_u16_3(p, byte_off, le)) << vec3u(16u)) >> vec3u(16u);
|
|
return vec3f(v) / f32(1u << frac);
|
|
}}
|
|
|
|
fn fetch_u16_4(p: ptr<storage, array<u32>>, byte_off: u32, frac: u32, le: bool) -> vec4f {{
|
|
let v = raw_fetch_u16_4(p, byte_off, le);
|
|
return vec4f(v) / f32(1u << frac);
|
|
}}
|
|
|
|
fn fetch_s16_4(p: ptr<storage, array<u32>>, byte_off: u32, frac: u32, le: bool) -> vec4f {{
|
|
let v = (bitcast<vec4i>(raw_fetch_u16_4(p, byte_off, le)) << vec4u(16u)) >> vec4u(16u);
|
|
return vec4f(v) / f32(1u << frac);
|
|
}}
|
|
|
|
fn fetch_f32_1(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> f32 {{
|
|
return raw_fetch_f32_1(p, byte_off, le);
|
|
}}
|
|
|
|
fn fetch_f32_2(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> vec2f {{
|
|
return raw_fetch_f32_2(p, byte_off, le);
|
|
}}
|
|
|
|
fn fetch_f32_3(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> vec3f {{
|
|
return raw_fetch_f32_3(p, byte_off, le);
|
|
}}
|
|
|
|
fn fetch_f32_4(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> vec4f {{
|
|
return raw_fetch_f32_4(p, byte_off, le);
|
|
}}
|
|
|
|
fn fetch_rgb565(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> vec4f {{
|
|
let v = load_u16(p, byte_off, le);
|
|
return vec4f(
|
|
f32((v >> 11u) & 0x1Fu) / f32(0x1Fu),
|
|
f32((v >> 5u) & 0x3Fu) / f32(0x3Fu),
|
|
f32((v >> 0u) & 0x1Fu) / f32(0x1Fu),
|
|
1.0,
|
|
);
|
|
}}
|
|
|
|
fn fetch_rgb8(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> vec4f {{
|
|
let v = raw_fetch_u8_3(p, byte_off);
|
|
return vec4f(f32(v.x), f32(v.y), f32(v.z), 255.0) / 255.0;
|
|
}}
|
|
|
|
fn fetch_rgbx8(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> vec4f {{
|
|
let v = raw_fetch_u8_4(p, byte_off);
|
|
return vec4f(f32(v.x), f32(v.y), f32(v.z), 255.0) / 255.0;
|
|
}}
|
|
|
|
fn fetch_rgba4(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> vec4f {{
|
|
let v = load_u16(p, byte_off, le);
|
|
return vec4f(
|
|
f32((v >> 12u) & 0x0Fu) / f32(0x0Fu),
|
|
f32((v >> 8u) & 0x0Fu) / f32(0x0Fu),
|
|
f32((v >> 4u) & 0x0Fu) / f32(0x0Fu),
|
|
f32((v >> 0u) & 0x0Fu) / f32(0x0Fu),
|
|
);
|
|
}}
|
|
|
|
fn fetch_rgba6(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> vec4f {{
|
|
let v = load_u24(p, byte_off, le);
|
|
return vec4f(
|
|
f32((v >> 18u) & 0x3Fu) / f32(0x3Fu),
|
|
f32((v >> 12u) & 0x3Fu) / f32(0x3Fu),
|
|
f32((v >> 6u) & 0x3Fu) / f32(0x3Fu),
|
|
f32((v >> 0u) & 0x3Fu) / f32(0x3Fu),
|
|
);
|
|
}}
|
|
|
|
fn fetch_rgba8(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> vec4f {{
|
|
let v = raw_fetch_u8_4(p, byte_off);
|
|
return vec4f(v) / 255.0;
|
|
}}
|
|
|
|
fn tev_overflow_f32(in: f32) -> f32 {{
|
|
let byte_space = in * 255.0;
|
|
return (byte_space - floor(byte_space / 256.0) * 256.0) / 255.0;
|
|
}}
|
|
|
|
fn tev_overflow_vec3f(in: vec3f) -> vec3f {{
|
|
let byte_space = in * 255.0;
|
|
return (byte_space - floor(byte_space / 256.0) * 256.0) / 255.0;
|
|
}}
|
|
|
|
fn tev_overflow_vec4f(in: vec4f) -> vec4f {{
|
|
let byte_space = in * 255.0;
|
|
return (byte_space - floor(byte_space / 256.0) * 256.0) / 255.0;
|
|
}}
|
|
|
|
fn tev_s24_fixed_vec2i(in: vec2i) -> vec2i {{
|
|
return (in << vec2u(8u)) >> vec2u(8u);
|
|
}}
|
|
|
|
fn tev_byte_f32(in: f32) -> i32 {{
|
|
return i32(round(in * 255.0));
|
|
}}
|
|
|
|
fn tev_byte_vec3f(in: vec3f) -> vec3i {{
|
|
return vec3i(round(in * 255.0));
|
|
}}
|
|
|
|
fn tev_regular_i32(a: i32, b: i32, c: i32, d: i32, bias: i32, scale: u32, is_sub: bool) -> i32 {{
|
|
var d_part = d + bias;
|
|
var lerp_part = (a << 8u) + (b - a) * (c + (c >> 7u));
|
|
if (scale == 1u) {{
|
|
d_part = d_part << 1u;
|
|
lerp_part = lerp_part << 1u;
|
|
}}
|
|
if (scale == 2u) {{
|
|
d_part = d_part << 2u;
|
|
lerp_part = lerp_part << 2u;
|
|
}}
|
|
if (scale != 3u) {{
|
|
lerp_part += select(128, 127, is_sub);
|
|
}}
|
|
let lerp = lerp_part >> 8u;
|
|
var result = select(d_part + lerp, d_part - lerp, is_sub);
|
|
if (scale == 3u) {{
|
|
result = result >> 1u;
|
|
}}
|
|
return result;
|
|
}}
|
|
|
|
fn tev_regular_f32(a: f32, b: f32, c: f32, d: f32, bias: i32, scale: u32, is_sub: bool) -> f32 {{
|
|
return f32(tev_regular_i32(tev_byte_f32(a), tev_byte_f32(b), tev_byte_f32(c), tev_byte_f32(d), bias, scale, is_sub)) /
|
|
255.0;
|
|
}}
|
|
|
|
fn tev_regular_vec3f(a: vec3f, b: vec3f, c: vec3f, d: vec3f, bias: i32, scale: u32, is_sub: bool) -> vec3f {{
|
|
let ai = tev_byte_vec3f(a);
|
|
let bi = tev_byte_vec3f(b);
|
|
let ci = tev_byte_vec3f(c);
|
|
let di = tev_byte_vec3f(d);
|
|
return vec3f(
|
|
f32(tev_regular_i32(ai.x, bi.x, ci.x, di.x, bias, scale, is_sub)) / 255.0,
|
|
f32(tev_regular_i32(ai.y, bi.y, ci.y, di.y, bias, scale, is_sub)) / 255.0,
|
|
f32(tev_regular_i32(ai.z, bi.z, ci.z, di.z, bias, scale, is_sub)) / 255.0,
|
|
);
|
|
}}
|
|
|
|
{8}
|
|
|
|
struct Uniform {{
|
|
vtx_start: u32,
|
|
current_pnmtx: u32,
|
|
render_viewport_size: vec2f,
|
|
logical_viewport_size: vec2f,
|
|
pad: vec2u,
|
|
array_start: array<u32, 12>,{0}
|
|
}};
|
|
@group(0) @binding(0)
|
|
var<storage, read> vbuf: array<u32>;
|
|
@group(0) @binding(1)
|
|
var<storage, read> abuf: array<u32>;
|
|
@group(1) @binding(0)
|
|
var<uniform> ubuf: Uniform;{1}
|
|
|
|
struct VertexOutput {{
|
|
@builtin(position) pos: vec4f,{2}
|
|
}};
|
|
|
|
@vertex
|
|
fn vs_main(
|
|
@builtin(vertex_index) vidx: u32{3}
|
|
) -> VertexOutput {{
|
|
var out: VertexOutput;{7}{4}
|
|
return out;
|
|
}}
|
|
|
|
@fragment
|
|
fn fs_main(in: VertexOutput) -> {9} {{{6}{5}
|
|
{10}
|
|
}}
|
|
)""",
|
|
uniBufAttrs, texBindings, vtxOutAttrs, vtxInAttrs, vtxXfrAttrs, fragmentFn,
|
|
fragmentFnPre, vtxXfrAttrsPre, uniformPre, fragmentReturnType,
|
|
fragmentReturn);
|
|
wgpu::ShaderSourceWGSL wgslDescriptor{};
|
|
wgslDescriptor.code = shaderSource.c_str();
|
|
const auto label = fmt::format("GX Shader {:x}", hash);
|
|
const auto shaderDescriptor = wgpu::ShaderModuleDescriptor{
|
|
.nextInChain = &wgslDescriptor,
|
|
.label = label.c_str(),
|
|
};
|
|
return webgpu::g_device.CreateShaderModule(&shaderDescriptor);
|
|
}
|
|
} // namespace aurora::gx
|