metaforce/aurora/lib/gfx/stream.cpp

438 lines
14 KiB
C++

#include "stream/shader.hpp"
#include "../gpu.hpp"
#include "common.hpp"
#include "gx.hpp"
#include <utility>
#include <magic_enum.hpp>
namespace aurora::gfx {
using namespace fmt::literals;
static logvisor::Module Log("aurora::gfx::stream");
struct SStreamState {
GX::Primitive primitive;
metaforce::EStreamFlags flags;
uint32_t vertexCount = 0;
ByteBuffer vertexBuffer;
explicit SStreamState(GX::Primitive primitive) noexcept : primitive(primitive) {}
};
static std::optional<SStreamState> sStreamState;
constexpr u32 maxTextures = 8;
struct STextureBind {
aurora::gfx::TextureHandle handle;
metaforce::EClampMode clampMode;
float lod;
STextureBind() noexcept = default;
STextureBind(aurora::gfx::TextureHandle handle, metaforce::EClampMode clampMode, float lod) noexcept
: handle(std::move(handle)), clampMode(clampMode), lod(lod) {}
void reset() noexcept { handle.reset(); };
wgpu::SamplerDescriptor get_descriptor() const noexcept {
wgpu::AddressMode mode;
switch (clampMode) {
case metaforce::EClampMode::Clamp:
mode = wgpu::AddressMode::ClampToEdge;
break;
case metaforce::EClampMode::Repeat:
mode = wgpu::AddressMode::Repeat;
break;
case metaforce::EClampMode::Mirror:
mode = wgpu::AddressMode::MirrorRepeat;
break;
}
return {
.label = "Generated Sampler",
.addressModeU = mode,
.addressModeV = mode,
.addressModeW = mode,
// TODO logic from CTexture?
.magFilter = wgpu::FilterMode::Linear,
.minFilter = wgpu::FilterMode::Linear,
.mipmapFilter = wgpu::FilterMode::Linear,
.maxAnisotropy = gpu::g_graphicsConfig.textureAnistropy,
};
}
operator bool() const noexcept { return handle; }
};
static std::array<STextureBind, maxTextures> sTextures;
void bind_texture(GX::TexMapID id, metaforce::EClampMode clamp, const TextureHandle& tex, float lod) noexcept {
sTextures[static_cast<size_t>(id)] = {tex, clamp, lod};
}
void unbind_texture(GX::TexMapID id) noexcept { sTextures[static_cast<size_t>(id)].reset(); }
void stream_begin(GX::Primitive primitive) noexcept {
if (sStreamState) {
Log.report(logvisor::Fatal, FMT_STRING("Stream began twice!"));
unreachable();
}
sStreamState.emplace(primitive);
}
void stream_vertex(metaforce::EStreamFlags flags, const zeus::CVector3f& pos, const zeus::CVector3f& nrm,
const zeus::CColor& color, const zeus::CVector2f& uv) noexcept {
if (!sStreamState) {
Log.report(logvisor::Fatal, FMT_STRING("Stream not started!"));
unreachable();
}
if (sStreamState->flags) {
if (sStreamState->flags != flags) {
Log.report(logvisor::Fatal, FMT_STRING("Stream changed flags?"));
unreachable();
}
} else {
sStreamState->flags = flags;
// TODO begin shader construction
}
sStreamState->vertexBuffer.append(&pos, 12);
if (flags & metaforce::EStreamFlagBits::fHasNormal) {
sStreamState->vertexBuffer.append(&nrm, 12);
}
if (flags & metaforce::EStreamFlagBits::fHasColor) {
sStreamState->vertexBuffer.append(&color, 16);
}
if (flags & metaforce::EStreamFlagBits::fHasTexture) {
sStreamState->vertexBuffer.append(&uv, 8);
}
sStreamState->vertexCount++;
}
static std::string color_arg_reg(GX::TevColorArg arg, size_t stageIdx) {
switch (arg) {
case GX::CC_CPREV:
return "prev.rgb";
case GX::CC_APREV:
return "prev.a";
case GX::CC_C0:
case GX::CC_A0:
case GX::CC_C1:
case GX::CC_A1:
case GX::CC_C2:
case GX::CC_A2:
Log.report(logvisor::Fatal, FMT_STRING("TODO {}"), arg);
unreachable();
case GX::CC_TEXC:
return fmt::format(FMT_STRING("sampled{}.rgb"), stageIdx);
case GX::CC_TEXA:
return fmt::format(FMT_STRING("sampled{}.a"), stageIdx);
case GX::CC_RASC:
return "rast.rgb";
case GX::CC_RASA:
return "rast.a";
case GX::CC_ONE:
return "1.0";
case GX::CC_HALF:
return "0.5)";
case GX::CC_KONST:
return fmt::format(FMT_STRING("ubuf.kcolor{}.rgb"), stageIdx);
case GX::CC_ZERO:
return "0.0";
}
}
static std::string alpha_arg_reg(GX::TevAlphaArg arg, size_t stageIdx) {
switch (arg) {
case GX::CA_APREV:
return "prev.a";
case GX::CA_A0:
case GX::CA_A1:
case GX::CA_A2:
Log.report(logvisor::Fatal, FMT_STRING("TODO {}"), arg);
unreachable();
case GX::CA_TEXA:
return fmt::format(FMT_STRING("sampled{}.a"), stageIdx);
case GX::CA_RASA:
return "rast.a";
case GX::CA_KONST:
return fmt::format(FMT_STRING("ubuf.kcolor{}.a"), stageIdx);
case GX::CA_ZERO:
return "0.0";
}
}
static std::string_view tev_op(GX::TevOp op) {
switch (op) {
case GX::TEV_ADD:
return "+";
case GX::TEV_SUB:
return "-";
default:
Log.report(logvisor::Fatal, FMT_STRING("TODO {}"), op);
unreachable();
}
}
static std::string_view tev_bias(GX::TevBias bias) {
switch (bias) {
case GX::TB_ZERO:
return " + 0.0";
case GX::TB_ADDHALF:
return " + 0.5";
case GX::TB_SUBHALF:
return " - 0.5";
}
}
static std::string_view tev_scale(GX::TevScale scale) {
switch (scale) {
case GX::CS_SCALE_1:
return " * 1.0";
case GX::CS_SCALE_2:
return " * 2.0";
case GX::CS_SCALE_4:
return " * 4.0";
case GX::CS_DIVIDE_2:
return " / 2.0";
}
}
std::unordered_map<ShaderRef, wgpu::ShaderModule> g_streamCachedShaders;
static ShaderRef generate_shader() {
auto flags = sStreamState->flags;
const auto hash = hash_tev_stages(static_cast<metaforce::EStreamFlags::MaskType>(flags));
if (g_streamCachedShaders.contains(hash)) {
return hash;
}
std::string uniBufAttrs;
if (flags & metaforce::EStreamFlagBits::fHasTexture) {
uniBufAttrs += fmt::format(FMT_STRING("\n tex0_lod: f32;"));
}
std::string sampBindings;
if (flags & metaforce::EStreamFlagBits::fHasTexture) {
sampBindings +=
"\n"
"@group(1) @binding(0)\n"
"var tex0_samp: sampler;";
}
std::string texBindings;
if (flags & metaforce::EStreamFlagBits::fHasTexture) {
texBindings +=
"\n"
"@group(2) @binding(0)\n"
"var tex0: texture_2d<f32>;";
}
std::string vtxOutAttrs;
std::string vtxInAttrs;
std::string vtxXfrAttrs;
{
size_t idx = 0;
if (flags & metaforce::EStreamFlagBits::fHasNormal) {
vtxOutAttrs += fmt::format(FMT_STRING("\n @location({}) nrm: vec3<f32>;"), idx);
vtxInAttrs += fmt::format(FMT_STRING("\n , @location({}) in_nrm: vec3<f32>"), ++idx);
vtxXfrAttrs += fmt::format(FMT_STRING("\n out.nrm = in_nrm;"));
}
if (flags & metaforce::EStreamFlagBits::fHasColor) {
vtxOutAttrs += fmt::format(FMT_STRING("\n @location({}) clr: vec4<f32>;"), idx);
vtxInAttrs += fmt::format(FMT_STRING("\n , @location({}) in_clr: vec4<f32>"), ++idx);
vtxXfrAttrs += fmt::format(FMT_STRING("\n out.clr = in_clr;"));
}
if (flags & metaforce::EStreamFlagBits::fHasTexture) {
vtxOutAttrs += fmt::format(FMT_STRING("\n @location({}) tex0_uv: vec2<f32>;"), idx);
vtxInAttrs += fmt::format(FMT_STRING("\n , @location({}) in_uv: vec2<f32>"), ++idx);
vtxXfrAttrs += fmt::format(FMT_STRING("\n out.tex0_uv = in_uv;"));
}
}
std::string fragmentFn;
bool hasRast = false;
for (size_t idx = 0; const auto& stage : g_tevStages) {
if (!stage) {
idx++;
continue;
}
if (stage->colorPass.x0_a == GX::TevColorArg::CC_TEXC || stage->colorPass.x4_b == GX::TevColorArg::CC_TEXC ||
stage->colorPass.x8_c == GX::TevColorArg::CC_TEXC || stage->colorPass.xc_d == GX::TevColorArg::CC_TEXC ||
stage->alphaPass.x0_a == GX::TevAlphaArg::CA_TEXA || stage->alphaPass.x4_b == GX::TevAlphaArg::CA_TEXA ||
stage->alphaPass.x8_c == GX::TevAlphaArg::CA_TEXA || stage->alphaPass.xc_d == GX::TevAlphaArg::CA_TEXA) {
fragmentFn += fmt::format(
FMT_STRING("\n var sampled{0} = textureSampleBias(tex{0}, tex{0}_samp, in.tex{0}_uv, ubuf.tex{0}_lod);"),
idx);
}
if (!hasRast) {
if (stage->colorPass.x0_a == GX::TevColorArg::CC_RASC || stage->colorPass.x4_b == GX::TevColorArg::CC_RASC ||
stage->colorPass.x8_c == GX::TevColorArg::CC_RASC || stage->colorPass.xc_d == GX::TevColorArg::CC_RASC ||
stage->alphaPass.x0_a == GX::TevAlphaArg::CA_RASA || stage->alphaPass.x4_b == GX::TevAlphaArg::CA_RASA ||
stage->alphaPass.x8_c == GX::TevAlphaArg::CA_RASA || stage->alphaPass.xc_d == GX::TevAlphaArg::CA_RASA) {
fragmentFn += fmt::format(FMT_STRING("\n var rast = in.clr; // TODO lighting")); // TODO lighting
hasRast = true;
}
}
idx++;
}
for (size_t idx = 0; const auto& stage : g_tevStages) {
if (!stage) {
idx++;
continue;
}
{
std::string op;
std::string outReg;
switch (stage->colorOp.x10_regId) {
case GX::TevRegID::TEVPREV:
outReg = "prev";
break;
default:
Log.report(logvisor::Fatal, FMT_STRING("TODO: colorOp outReg {}"),
magic_enum::enum_name(stage->colorOp.x10_regId));
}
op = fmt::format(FMT_STRING("({3} {4} ((1.0 - {2}) * {0} + {2} * {1}){5}){6}"),
color_arg_reg(stage->colorPass.x0_a, idx), color_arg_reg(stage->colorPass.x4_b, idx),
color_arg_reg(stage->colorPass.x8_c, idx), color_arg_reg(stage->colorPass.xc_d, idx),
tev_op(stage->colorOp.x4_op), tev_bias(stage->colorOp.x8_bias),
tev_scale(stage->colorOp.xc_scale));
fragmentFn += fmt::format(FMT_STRING("\n {0} = vec4<f32>({1}, {0}.a);"), outReg, op);
}
{
std::string op;
std::string outReg;
switch (stage->alphaOp.x10_regId) {
case GX::TevRegID::TEVPREV:
outReg = "prev.a";
break;
default:
Log.report(logvisor::Fatal, FMT_STRING("TODO: alphaOp outReg {}"),
magic_enum::enum_name(stage->alphaOp.x10_regId));
}
op = fmt::format(FMT_STRING("({3} {4} ((1.0 - {2}) * {0} + {2} * {1}){5}){6}"),
alpha_arg_reg(stage->alphaPass.x0_a, idx), alpha_arg_reg(stage->alphaPass.x4_b, idx),
alpha_arg_reg(stage->alphaPass.x8_c, idx), alpha_arg_reg(stage->alphaPass.xc_d, idx),
tev_op(stage->alphaOp.x4_op), tev_bias(stage->alphaOp.x8_bias),
tev_scale(stage->alphaOp.xc_scale));
fragmentFn += fmt::format(FMT_STRING("\n {0} = {1};"), outReg, op);
}
idx++;
}
const auto shaderSource =
fmt::format(FMT_STRING(R"""(
struct Uniform {{
xf: mat4x4<f32>;{uniBufAttrs}
}};
@group(0) @binding(0)
var<uniform> ubuf: Uniform;{sampBindings}{texBindings}
struct VertexOutput {{
@builtin(position) pos: vec4<f32>;{vtxOutAttrs}
}};
@stage(vertex)
fn vs_main(
@location(0) in_pos: vec3<f32>{vtxInAttrs}
) -> VertexOutput {{
var out: VertexOutput;
out.pos = ubuf.xf * vec4<f32>(in_pos, 1.0);{vtxXfrAttrs}
return out;
}}
@stage(fragment)
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {{
var prev: vec4<f32>;{fragmentFn}
return prev;
}}
)"""),
"uniBufAttrs"_a = uniBufAttrs, "sampBindings"_a = sampBindings, "texBindings"_a = texBindings,
"vtxOutAttrs"_a = vtxOutAttrs, "vtxInAttrs"_a = vtxInAttrs, "vtxXfrAttrs"_a = vtxXfrAttrs,
"fragmentFn"_a = fragmentFn);
Log.report(logvisor::Info, FMT_STRING("Generated shader: {}"), shaderSource);
wgpu::ShaderModuleWGSLDescriptor wgslDescriptor{};
wgslDescriptor.source = shaderSource.c_str();
const auto shaderDescriptor = wgpu::ShaderModuleDescriptor{
.nextInChain = &wgslDescriptor,
.label = "Generated Shader",
};
auto shader = gpu::g_device.CreateShaderModule(&shaderDescriptor);
g_streamCachedShaders.emplace(hash, std::move(shader));
return hash;
}
void stream_end() noexcept {
if (sStreamState->flags & metaforce::EStreamFlagBits::fHasTexture && !sTextures[0]) {
Log.report(logvisor::Fatal, FMT_STRING("Stream has texture but no texture bound!"));
unreachable();
}
const auto vertRange = push_verts(sStreamState->vertexBuffer.data(), sStreamState->vertexBuffer.size());
ByteBuffer uniBuf;
std::bitset<g_colorRegs.size()> usedColors;
{
const auto xf = get_combined_matrix();
uniBuf.append(&xf, 64);
}
if (sStreamState->flags & metaforce::EStreamFlagBits::fHasTexture) {
uniBuf.append(&sTextures[0].lod, 4);
}
const auto uniRange = push_uniform(uniBuf.data(), uniBuf.size());
const auto shaderRef = generate_shader();
const auto uniform_size = align_uniform(uniBuf.size());
const auto pipeline = pipeline_ref(stream::PipelineConfig{
.shader = shaderRef,
.uniformSize = uniform_size,
.primitive = sStreamState->primitive,
.flags = sStreamState->flags,
.depthCompare = g_depthCompare,
.depthUpdate = g_depthUpdate,
.depthFunc = g_depthFunc,
.cullMode = g_cullMode,
.blendMode = g_blendMode,
.blendFacSrc = g_blendFacSrc,
.blendFacDst = g_blendFacDst,
.blendOp = g_blendOp,
.dstAlpha = g_dstAlpha,
});
BindGroupRef samplerBindGroup{};
BindGroupRef textureBindGroup{};
if (sStreamState->flags & metaforce::EStreamFlagBits::fHasTexture) {
const auto& state = get_state<stream::State>();
{
const std::array samplerEntries{wgpu::BindGroupEntry{
.binding = 0,
.sampler = sampler_ref(sTextures[0].get_descriptor()),
}};
samplerBindGroup = bind_group_ref(wgpu::BindGroupDescriptor{
.label = "Stream Sampler Bind Group",
.layout = state.samplerLayout,
.entryCount = samplerEntries.size(),
.entries = samplerEntries.data(),
});
}
{
const std::array textureEntries{wgpu::BindGroupEntry{
.binding = 0,
.textureView = sTextures[0].handle.ref->view,
}};
textureBindGroup = bind_group_ref(wgpu::BindGroupDescriptor{
.label = "Stream Texture Bind Group",
.layout = state.textureLayout,
.entryCount = textureEntries.size(),
.entries = textureEntries.data(),
});
}
}
push_draw_command(stream::DrawData{
.pipeline = pipeline,
.vertRange = vertRange,
.uniformRange = uniRange,
.vertexCount = sStreamState->vertexCount,
.uniformSize = uniform_size,
.samplerBindGroup = samplerBindGroup,
.textureBindGroup = textureBindGroup,
});
sStreamState.reset();
}
} // namespace aurora::gfx