mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-12-30 21:01:04 +01:00
a3951dc2d7
On all platforms, this would result in out of bounds accesses when getting the component sizes (which uses stuff from VertexLoader_Position.h/VertexLoader_TextCoord.h/VertexLoader_Normal.h). On platforms other than x64 and ARM64, this would also be out of bounds accesses when getting function pointers for the non-JIT vertex loader (in VertexLoader_Position.cpp etc.). Usually both of these would get data from other entries in the same multi-dimensional array, but the last few entries would be truly out of bounds. This does mean that an out of bounds function pointer can be called on platforms that don't have a JIT vertex loader, but it is limited to invalid component formats with values 5/6/7 due to the size of the bitfield the formats come from, so it seems unlikely that this could be exploited in practice. This issue affects a few games; Def Jam: Fight for New York (https://bugs.dolphin-emu.org/issues/12719) and Fifa Street are known to be affected. I have not done any hardware testing for this PR specifically, though I *think* I previously determined that at least a value of 5 behaves the same as float (4). That's what I implemented in any case. I did previously determine that both Def Jam: Fight for New York and Fifa Street use an invalid normal format, but don't actually have lighting enabled when that normal vector is used, so it doesn't change rendering in practice. The color component format also has two invalid values, but VertexLoader_Color.h/.cpp do check for those invalid ones and return a default value instead of doing an out of bounds access.
772 lines
22 KiB
C++
772 lines
22 KiB
C++
// Copyright 2008 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#pragma once
|
|
|
|
#include <array>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#include "Common/BitField.h"
|
|
#include "Common/BitSet.h"
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/EnumFormatter.h"
|
|
#include "Common/EnumMap.h"
|
|
#include "Common/MsgHandler.h"
|
|
|
|
enum
|
|
{
|
|
// These commands use the high nybble for the command itself, and the lower nybble is an argument.
|
|
// TODO: However, Dolphin's implementation (in LoadCPReg) and YAGCD disagree about what values are
|
|
// valid for the lower nybble.
|
|
|
|
// YAGCD mentions 0x20 as "?", and does not mention the others
|
|
// Libogc has 0x00 and 0x20, where 0x00 is tied to GX_ClearVCacheMetric and 0x20 related to
|
|
// cpPerfMode. 0x10 may be GX_SetVCacheMetric, but that function is empty. In any case, these all
|
|
// are probably for perf queries, and no title seems to actually need a full implementation.
|
|
UNKNOWN_00 = 0x00,
|
|
UNKNOWN_10 = 0x10,
|
|
UNKNOWN_20 = 0x20,
|
|
// YAGCD says 0x30 only; LoadCPReg allows any
|
|
MATINDEX_A = 0x30,
|
|
// YAGCD says 0x40 only; LoadCPReg allows any
|
|
MATINDEX_B = 0x40,
|
|
// YAGCD says 0x50-0x57 for distinct VCDs; LoadCPReg allows any for a single VCD
|
|
VCD_LO = 0x50,
|
|
// YAGCD says 0x60-0x67 for distinct VCDs; LoadCPReg allows any for a single VCD
|
|
VCD_HI = 0x60,
|
|
// YAGCD and LoadCPReg both agree that only 0x70-0x77 are valid
|
|
CP_VAT_REG_A = 0x70,
|
|
// YAGCD and LoadCPReg both agree that only 0x80-0x87 are valid
|
|
CP_VAT_REG_B = 0x80,
|
|
// YAGCD and LoadCPReg both agree that only 0x90-0x97 are valid
|
|
CP_VAT_REG_C = 0x90,
|
|
// YAGCD and LoadCPReg agree that 0xa0-0xaf are valid
|
|
ARRAY_BASE = 0xa0,
|
|
// YAGCD and LoadCPReg agree that 0xb0-0xbf are valid
|
|
ARRAY_STRIDE = 0xb0,
|
|
|
|
CP_COMMAND_MASK = 0xf0,
|
|
CP_NUM_VAT_REG = 0x08,
|
|
CP_VAT_MASK = 0x07,
|
|
CP_NUM_ARRAYS = 0x10,
|
|
CP_ARRAY_MASK = 0x0f,
|
|
};
|
|
|
|
// Vertex array numbers
|
|
enum class CPArray : u8
|
|
{
|
|
Position = 0,
|
|
Normal = 1,
|
|
|
|
Color0 = 2,
|
|
Color1 = 3,
|
|
|
|
TexCoord0 = 4,
|
|
TexCoord1 = 5,
|
|
TexCoord2 = 6,
|
|
TexCoord3 = 7,
|
|
TexCoord4 = 8,
|
|
TexCoord5 = 9,
|
|
TexCoord6 = 10,
|
|
TexCoord7 = 11,
|
|
|
|
XF_A = 12, // Usually used for position matrices
|
|
XF_B = 13, // Usually used for normal matrices
|
|
XF_C = 14, // Usually used for tex coord matrices
|
|
XF_D = 15, // Usually used for light objects
|
|
};
|
|
template <>
|
|
struct fmt::formatter<CPArray> : EnumFormatter<CPArray::XF_D>
|
|
{
|
|
static constexpr array_type names = {"Position", "Normal", "Color 0", "Color 1",
|
|
"Tex Coord 0", "Tex Coord 1", "Tex Coord 2", "Tex Coord 3",
|
|
"Tex Coord 4", "Tex Coord 5", "Tex Coord 6", "Tex Coord 7",
|
|
"XF A", "XF B", "XF C", "XF D"};
|
|
constexpr formatter() : EnumFormatter(names) {}
|
|
};
|
|
// Intended for offsetting from Color0/TexCoord0
|
|
constexpr CPArray operator+(CPArray array, u8 offset)
|
|
{
|
|
return static_cast<CPArray>(static_cast<u8>(array) + offset);
|
|
}
|
|
|
|
// Number of arrays related to vertex components (position, normal, color, tex coord)
|
|
// Excludes the 4 arrays used for indexed XF loads
|
|
constexpr u8 NUM_VERTEX_COMPONENT_ARRAYS = 12;
|
|
|
|
// Vertex components
|
|
enum class VertexComponentFormat
|
|
{
|
|
NotPresent = 0,
|
|
Direct = 1,
|
|
Index8 = 2,
|
|
Index16 = 3,
|
|
};
|
|
template <>
|
|
struct fmt::formatter<VertexComponentFormat> : EnumFormatter<VertexComponentFormat::Index16>
|
|
{
|
|
constexpr formatter() : EnumFormatter({"Not present", "Direct", "8-bit index", "16-bit index"}) {}
|
|
};
|
|
|
|
constexpr bool IsIndexed(VertexComponentFormat format)
|
|
{
|
|
return format == VertexComponentFormat::Index8 || format == VertexComponentFormat::Index16;
|
|
}
|
|
|
|
enum class ComponentFormat
|
|
{
|
|
UByte = 0, // Invalid for normals
|
|
Byte = 1,
|
|
UShort = 2, // Invalid for normals
|
|
Short = 3,
|
|
Float = 4,
|
|
// Known to be used by Fifa Street and Def Jam: Fight for New York
|
|
// See https://bugs.dolphin-emu.org/issues/12719
|
|
// Assumed to behave the same as float, but further testing is needed
|
|
InvalidFloat5 = 5,
|
|
// Not known to be used
|
|
InvalidFloat6 = 6,
|
|
InvalidFloat7 = 7,
|
|
};
|
|
// NOTE: don't include the invalid formats here, so that EnumFormatter marks them as invalid
|
|
// (EnumFormatter also handles bounds-checking).
|
|
template <>
|
|
struct fmt::formatter<ComponentFormat> : EnumFormatter<ComponentFormat::Float>
|
|
{
|
|
static constexpr array_type names = {"Unsigned Byte", "Byte", "Unsigned Short", "Short", "Float"};
|
|
constexpr formatter() : EnumFormatter(names) {}
|
|
};
|
|
|
|
constexpr u32 GetElementSize(ComponentFormat format)
|
|
{
|
|
switch (format)
|
|
{
|
|
case ComponentFormat::UByte:
|
|
case ComponentFormat::Byte:
|
|
return 1;
|
|
case ComponentFormat::UShort:
|
|
case ComponentFormat::Short:
|
|
return 2;
|
|
case ComponentFormat::Float:
|
|
case ComponentFormat::InvalidFloat5:
|
|
case ComponentFormat::InvalidFloat6:
|
|
case ComponentFormat::InvalidFloat7:
|
|
return 4;
|
|
default:
|
|
PanicAlertFmt("Unknown format {}", format);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
enum class CoordComponentCount
|
|
{
|
|
XY = 0,
|
|
XYZ = 1,
|
|
};
|
|
template <>
|
|
struct fmt::formatter<CoordComponentCount> : EnumFormatter<CoordComponentCount::XYZ>
|
|
{
|
|
constexpr formatter() : EnumFormatter({"2 (x, y)", "3 (x, y, z)"}) {}
|
|
};
|
|
|
|
enum class NormalComponentCount
|
|
{
|
|
N = 0,
|
|
NTB = 1,
|
|
};
|
|
template <>
|
|
struct fmt::formatter<NormalComponentCount> : EnumFormatter<NormalComponentCount::NTB>
|
|
{
|
|
constexpr formatter() : EnumFormatter({"1 (normal)", "3 (normal, tangent, binormal)"}) {}
|
|
};
|
|
|
|
enum class ColorComponentCount
|
|
{
|
|
RGB = 0,
|
|
RGBA = 1,
|
|
};
|
|
template <>
|
|
struct fmt::formatter<ColorComponentCount> : EnumFormatter<ColorComponentCount::RGBA>
|
|
{
|
|
constexpr formatter() : EnumFormatter({"3 (r, g, b)", "4 (r, g, b, a)"}) {}
|
|
};
|
|
|
|
enum class ColorFormat
|
|
{
|
|
RGB565 = 0, // 16b
|
|
RGB888 = 1, // 24b
|
|
RGB888x = 2, // 32b
|
|
RGBA4444 = 3, // 16b
|
|
RGBA6666 = 4, // 24b
|
|
RGBA8888 = 5, // 32b
|
|
};
|
|
template <>
|
|
struct fmt::formatter<ColorFormat> : EnumFormatter<ColorFormat::RGBA8888>
|
|
{
|
|
static constexpr array_type names = {
|
|
"RGB 16 bits 565", "RGB 24 bits 888", "RGB 32 bits 888x",
|
|
"RGBA 16 bits 4444", "RGBA 24 bits 6666", "RGBA 32 bits 8888",
|
|
};
|
|
constexpr formatter() : EnumFormatter(names) {}
|
|
};
|
|
|
|
enum class TexComponentCount
|
|
{
|
|
S = 0,
|
|
ST = 1,
|
|
};
|
|
template <>
|
|
struct fmt::formatter<TexComponentCount> : EnumFormatter<TexComponentCount::ST>
|
|
{
|
|
constexpr formatter() : EnumFormatter({"1 (s)", "2 (s, t)"}) {}
|
|
};
|
|
|
|
struct TVtxDesc
|
|
{
|
|
union Low
|
|
{
|
|
// false: not present
|
|
// true: present
|
|
BitField<0, 1, bool, u32> PosMatIdx;
|
|
BitField<1, 1, bool, u32> Tex0MatIdx;
|
|
BitField<2, 1, bool, u32> Tex1MatIdx;
|
|
BitField<3, 1, bool, u32> Tex2MatIdx;
|
|
BitField<4, 1, bool, u32> Tex3MatIdx;
|
|
BitField<5, 1, bool, u32> Tex4MatIdx;
|
|
BitField<6, 1, bool, u32> Tex5MatIdx;
|
|
BitField<7, 1, bool, u32> Tex6MatIdx;
|
|
BitField<8, 1, bool, u32> Tex7MatIdx;
|
|
BitFieldArray<1, 1, 8, bool, u32> TexMatIdx;
|
|
|
|
BitField<9, 2, VertexComponentFormat> Position;
|
|
BitField<11, 2, VertexComponentFormat> Normal;
|
|
BitField<13, 2, VertexComponentFormat> Color0;
|
|
BitField<15, 2, VertexComponentFormat> Color1;
|
|
BitFieldArray<13, 2, 2, VertexComponentFormat> Color;
|
|
|
|
u32 Hex = 0;
|
|
};
|
|
union High
|
|
{
|
|
BitField<0, 2, VertexComponentFormat> Tex0Coord;
|
|
BitField<2, 2, VertexComponentFormat> Tex1Coord;
|
|
BitField<4, 2, VertexComponentFormat> Tex2Coord;
|
|
BitField<6, 2, VertexComponentFormat> Tex3Coord;
|
|
BitField<8, 2, VertexComponentFormat> Tex4Coord;
|
|
BitField<10, 2, VertexComponentFormat> Tex5Coord;
|
|
BitField<12, 2, VertexComponentFormat> Tex6Coord;
|
|
BitField<14, 2, VertexComponentFormat> Tex7Coord;
|
|
BitFieldArray<0, 2, 8, VertexComponentFormat> TexCoord;
|
|
|
|
u32 Hex = 0;
|
|
};
|
|
|
|
Low low;
|
|
High high;
|
|
};
|
|
template <>
|
|
struct fmt::formatter<TVtxDesc::Low>
|
|
{
|
|
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
|
template <typename FormatContext>
|
|
auto format(const TVtxDesc::Low& desc, FormatContext& ctx) const
|
|
{
|
|
static constexpr std::array<const char*, 2> present = {"Not present", "Present"};
|
|
|
|
return fmt::format_to(
|
|
ctx.out(),
|
|
"Position and normal matrix index: {}\n"
|
|
"Texture Coord 0 matrix index: {}\n"
|
|
"Texture Coord 1 matrix index: {}\n"
|
|
"Texture Coord 2 matrix index: {}\n"
|
|
"Texture Coord 3 matrix index: {}\n"
|
|
"Texture Coord 4 matrix index: {}\n"
|
|
"Texture Coord 5 matrix index: {}\n"
|
|
"Texture Coord 6 matrix index: {}\n"
|
|
"Texture Coord 7 matrix index: {}\n"
|
|
"Position: {}\n"
|
|
"Normal: {}\n"
|
|
"Color 0: {}\n"
|
|
"Color 1: {}",
|
|
present[desc.PosMatIdx], present[desc.Tex0MatIdx], present[desc.Tex1MatIdx],
|
|
present[desc.Tex2MatIdx], present[desc.Tex3MatIdx], present[desc.Tex4MatIdx],
|
|
present[desc.Tex5MatIdx], present[desc.Tex6MatIdx], present[desc.Tex7MatIdx], desc.Position,
|
|
desc.Normal, desc.Color0, desc.Color1);
|
|
}
|
|
};
|
|
template <>
|
|
struct fmt::formatter<TVtxDesc::High>
|
|
{
|
|
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
|
template <typename FormatContext>
|
|
auto format(const TVtxDesc::High& desc, FormatContext& ctx) const
|
|
{
|
|
return fmt::format_to(ctx.out(),
|
|
"Texture Coord 0: {}\n"
|
|
"Texture Coord 1: {}\n"
|
|
"Texture Coord 2: {}\n"
|
|
"Texture Coord 3: {}\n"
|
|
"Texture Coord 4: {}\n"
|
|
"Texture Coord 5: {}\n"
|
|
"Texture Coord 6: {}\n"
|
|
"Texture Coord 7: {}",
|
|
desc.Tex0Coord, desc.Tex1Coord, desc.Tex2Coord, desc.Tex3Coord,
|
|
desc.Tex4Coord, desc.Tex5Coord, desc.Tex6Coord, desc.Tex7Coord);
|
|
}
|
|
};
|
|
template <>
|
|
struct fmt::formatter<TVtxDesc>
|
|
{
|
|
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
|
template <typename FormatContext>
|
|
auto format(const TVtxDesc& desc, FormatContext& ctx) const
|
|
{
|
|
return fmt::format_to(ctx.out(), "{}\n{}", desc.low, desc.high);
|
|
}
|
|
};
|
|
|
|
union UVAT_group0
|
|
{
|
|
u32 Hex = 0;
|
|
// 0:8
|
|
BitField<0, 1, CoordComponentCount> PosElements;
|
|
BitField<1, 3, ComponentFormat> PosFormat;
|
|
BitField<4, 5, u32> PosFrac;
|
|
// 9:12
|
|
BitField<9, 1, NormalComponentCount> NormalElements;
|
|
BitField<10, 3, ComponentFormat> NormalFormat;
|
|
// 13:16
|
|
BitField<13, 1, ColorComponentCount> Color0Elements;
|
|
BitField<14, 3, ColorFormat> Color0Comp;
|
|
// 17:20
|
|
BitField<17, 1, ColorComponentCount> Color1Elements;
|
|
BitField<18, 3, ColorFormat> Color1Comp;
|
|
// 21:29
|
|
BitField<21, 1, TexComponentCount> Tex0CoordElements;
|
|
BitField<22, 3, ComponentFormat> Tex0CoordFormat;
|
|
BitField<25, 5, u8, u32> Tex0Frac;
|
|
// 30:31
|
|
BitField<30, 1, bool, u32> ByteDequant;
|
|
BitField<31, 1, bool, u32> NormalIndex3;
|
|
};
|
|
template <>
|
|
struct fmt::formatter<UVAT_group0>
|
|
{
|
|
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
|
template <typename FormatContext>
|
|
auto format(const UVAT_group0& g0, FormatContext& ctx) const
|
|
{
|
|
static constexpr std::array<const char*, 2> byte_dequant = {
|
|
"shift does not apply to u8/s8 components", "shift applies to u8/s8 components"};
|
|
static constexpr std::array<const char*, 2> normalindex3 = {
|
|
"single index shared by normal, tangent, and binormal",
|
|
"three indices, one each for normal, tangent, and binormal"};
|
|
|
|
return fmt::format_to(ctx.out(),
|
|
"Position elements: {}\n"
|
|
"Position format: {}\n"
|
|
"Position shift: {} ({})\n"
|
|
"Normal elements: {}\n"
|
|
"Normal format: {}\n"
|
|
"Color 0 elements: {}\n"
|
|
"Color 0 format: {}\n"
|
|
"Color 1 elements: {}\n"
|
|
"Color 1 format: {}\n"
|
|
"Texture coord 0 elements: {}\n"
|
|
"Texture coord 0 format: {}\n"
|
|
"Texture coord 0 shift: {} ({})\n"
|
|
"Byte dequant: {}\n"
|
|
"Normal index 3: {}",
|
|
g0.PosElements, g0.PosFormat, g0.PosFrac, 1.f / (1 << g0.PosFrac),
|
|
g0.NormalElements, g0.NormalFormat, g0.Color0Elements, g0.Color0Comp,
|
|
g0.Color1Elements, g0.Color1Comp, g0.Tex0CoordElements,
|
|
g0.Tex0CoordFormat, g0.Tex0Frac, 1.f / (1 << g0.Tex0Frac),
|
|
byte_dequant[g0.ByteDequant], normalindex3[g0.NormalIndex3]);
|
|
}
|
|
};
|
|
|
|
union UVAT_group1
|
|
{
|
|
u32 Hex = 0;
|
|
// 0:8
|
|
BitField<0, 1, TexComponentCount> Tex1CoordElements;
|
|
BitField<1, 3, ComponentFormat> Tex1CoordFormat;
|
|
BitField<4, 5, u8, u32> Tex1Frac;
|
|
// 9:17
|
|
BitField<9, 1, TexComponentCount> Tex2CoordElements;
|
|
BitField<10, 3, ComponentFormat> Tex2CoordFormat;
|
|
BitField<13, 5, u8, u32> Tex2Frac;
|
|
// 18:26
|
|
BitField<18, 1, TexComponentCount> Tex3CoordElements;
|
|
BitField<19, 3, ComponentFormat> Tex3CoordFormat;
|
|
BitField<22, 5, u8, u32> Tex3Frac;
|
|
// 27:30
|
|
BitField<27, 1, TexComponentCount> Tex4CoordElements;
|
|
BitField<28, 3, ComponentFormat> Tex4CoordFormat;
|
|
// 31
|
|
BitField<31, 1, bool, u32> VCacheEnhance;
|
|
};
|
|
template <>
|
|
struct fmt::formatter<UVAT_group1>
|
|
{
|
|
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
|
template <typename FormatContext>
|
|
auto format(const UVAT_group1& g1, FormatContext& ctx) const
|
|
{
|
|
return fmt::format_to(
|
|
ctx.out(),
|
|
"Texture coord 1 elements: {}\n"
|
|
"Texture coord 1 format: {}\n"
|
|
"Texture coord 1 shift: {} ({})\n"
|
|
"Texture coord 2 elements: {}\n"
|
|
"Texture coord 2 format: {}\n"
|
|
"Texture coord 2 shift: {} ({})\n"
|
|
"Texture coord 3 elements: {}\n"
|
|
"Texture coord 3 format: {}\n"
|
|
"Texture coord 3 shift: {} ({})\n"
|
|
"Texture coord 4 elements: {}\n"
|
|
"Texture coord 4 format: {}\n"
|
|
"Enhance VCache (must always be on): {}",
|
|
g1.Tex1CoordElements, g1.Tex1CoordFormat, g1.Tex1Frac, 1.f / (1 << g1.Tex1Frac),
|
|
g1.Tex2CoordElements, g1.Tex2CoordFormat, g1.Tex2Frac, 1.f / (1 << g1.Tex2Frac),
|
|
g1.Tex3CoordElements, g1.Tex3CoordFormat, g1.Tex3Frac, 1.f / (1 << g1.Tex3Frac),
|
|
g1.Tex4CoordElements, g1.Tex4CoordFormat, g1.VCacheEnhance ? "Yes" : "No");
|
|
}
|
|
};
|
|
|
|
union UVAT_group2
|
|
{
|
|
u32 Hex = 0;
|
|
// 0:4
|
|
BitField<0, 5, u8, u32> Tex4Frac;
|
|
// 5:13
|
|
BitField<5, 1, TexComponentCount> Tex5CoordElements;
|
|
BitField<6, 3, ComponentFormat> Tex5CoordFormat;
|
|
BitField<9, 5, u8, u32> Tex5Frac;
|
|
// 14:22
|
|
BitField<14, 1, TexComponentCount> Tex6CoordElements;
|
|
BitField<15, 3, ComponentFormat> Tex6CoordFormat;
|
|
BitField<18, 5, u8, u32> Tex6Frac;
|
|
// 23:31
|
|
BitField<23, 1, TexComponentCount> Tex7CoordElements;
|
|
BitField<24, 3, ComponentFormat> Tex7CoordFormat;
|
|
BitField<27, 5, u8, u32> Tex7Frac;
|
|
};
|
|
template <>
|
|
struct fmt::formatter<UVAT_group2>
|
|
{
|
|
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
|
template <typename FormatContext>
|
|
auto format(const UVAT_group2& g2, FormatContext& ctx) const
|
|
{
|
|
return fmt::format_to(ctx.out(),
|
|
"Texture coord 4 shift: {} ({})\n"
|
|
"Texture coord 5 elements: {}\n"
|
|
"Texture coord 5 format: {}\n"
|
|
"Texture coord 5 shift: {} ({})\n"
|
|
"Texture coord 6 elements: {}\n"
|
|
"Texture coord 6 format: {}\n"
|
|
"Texture coord 6 shift: {} ({})\n"
|
|
"Texture coord 7 elements: {}\n"
|
|
"Texture coord 7 format: {}\n"
|
|
"Texture coord 7 shift: {} ({})",
|
|
g2.Tex4Frac, 1.f / (1 << g2.Tex4Frac), g2.Tex5CoordElements,
|
|
g2.Tex5CoordFormat, g2.Tex5Frac, 1.f / (1 << g2.Tex5Frac),
|
|
g2.Tex6CoordElements, g2.Tex6CoordFormat, g2.Tex6Frac,
|
|
1.f / (1 << g2.Tex6Frac), g2.Tex7CoordElements, g2.Tex7CoordFormat,
|
|
g2.Tex7Frac, 1.f / (1 << g2.Tex7Frac));
|
|
}
|
|
};
|
|
|
|
struct VAT
|
|
{
|
|
UVAT_group0 g0;
|
|
UVAT_group1 g1;
|
|
UVAT_group2 g2;
|
|
|
|
constexpr ColorComponentCount GetColorElements(size_t idx) const
|
|
{
|
|
switch (idx)
|
|
{
|
|
case 0:
|
|
return g0.Color0Elements;
|
|
case 1:
|
|
return g0.Color1Elements;
|
|
default:
|
|
PanicAlertFmt("Invalid color index {}", idx);
|
|
return ColorComponentCount::RGB;
|
|
}
|
|
}
|
|
constexpr ColorFormat GetColorFormat(size_t idx) const
|
|
{
|
|
switch (idx)
|
|
{
|
|
case 0:
|
|
return g0.Color0Comp;
|
|
case 1:
|
|
return g0.Color1Comp;
|
|
default:
|
|
PanicAlertFmt("Invalid color index {}", idx);
|
|
return ColorFormat::RGB565;
|
|
}
|
|
}
|
|
constexpr TexComponentCount GetTexElements(size_t idx) const
|
|
{
|
|
switch (idx)
|
|
{
|
|
case 0:
|
|
return g0.Tex0CoordElements;
|
|
case 1:
|
|
return g1.Tex1CoordElements;
|
|
case 2:
|
|
return g1.Tex2CoordElements;
|
|
case 3:
|
|
return g1.Tex3CoordElements;
|
|
case 4:
|
|
return g1.Tex4CoordElements;
|
|
case 5:
|
|
return g2.Tex5CoordElements;
|
|
case 6:
|
|
return g2.Tex6CoordElements;
|
|
case 7:
|
|
return g2.Tex7CoordElements;
|
|
default:
|
|
PanicAlertFmt("Invalid tex coord index {}", idx);
|
|
return TexComponentCount::S;
|
|
}
|
|
}
|
|
constexpr ComponentFormat GetTexFormat(size_t idx) const
|
|
{
|
|
switch (idx)
|
|
{
|
|
case 0:
|
|
return g0.Tex0CoordFormat;
|
|
case 1:
|
|
return g1.Tex1CoordFormat;
|
|
case 2:
|
|
return g1.Tex2CoordFormat;
|
|
case 3:
|
|
return g1.Tex3CoordFormat;
|
|
case 4:
|
|
return g1.Tex4CoordFormat;
|
|
case 5:
|
|
return g2.Tex5CoordFormat;
|
|
case 6:
|
|
return g2.Tex6CoordFormat;
|
|
case 7:
|
|
return g2.Tex7CoordFormat;
|
|
default:
|
|
PanicAlertFmt("Invalid tex coord index {}", idx);
|
|
return ComponentFormat::UByte;
|
|
}
|
|
}
|
|
constexpr u8 GetTexFrac(size_t idx) const
|
|
{
|
|
switch (idx)
|
|
{
|
|
case 0:
|
|
return g0.Tex0Frac;
|
|
case 1:
|
|
return g1.Tex1Frac;
|
|
case 2:
|
|
return g1.Tex2Frac;
|
|
case 3:
|
|
return g1.Tex3Frac;
|
|
case 4:
|
|
return g2.Tex4Frac;
|
|
case 5:
|
|
return g2.Tex5Frac;
|
|
case 6:
|
|
return g2.Tex6Frac;
|
|
case 7:
|
|
return g2.Tex7Frac;
|
|
default:
|
|
PanicAlertFmt("Invalid tex coord index {}", idx);
|
|
return 0;
|
|
}
|
|
}
|
|
void SetTexElements(size_t idx, TexComponentCount value)
|
|
{
|
|
switch (idx)
|
|
{
|
|
case 0:
|
|
g0.Tex0CoordElements = value;
|
|
return;
|
|
case 1:
|
|
g1.Tex1CoordElements = value;
|
|
return;
|
|
case 2:
|
|
g1.Tex2CoordElements = value;
|
|
return;
|
|
case 3:
|
|
g1.Tex3CoordElements = value;
|
|
return;
|
|
case 4:
|
|
g1.Tex4CoordElements = value;
|
|
return;
|
|
case 5:
|
|
g2.Tex5CoordElements = value;
|
|
return;
|
|
case 6:
|
|
g2.Tex6CoordElements = value;
|
|
return;
|
|
case 7:
|
|
g2.Tex7CoordElements = value;
|
|
return;
|
|
default:
|
|
PanicAlertFmt("Invalid tex coord index {}", idx);
|
|
}
|
|
}
|
|
void SetTexFormat(size_t idx, ComponentFormat value)
|
|
{
|
|
switch (idx)
|
|
{
|
|
case 0:
|
|
g0.Tex0CoordFormat = value;
|
|
return;
|
|
case 1:
|
|
g1.Tex1CoordFormat = value;
|
|
return;
|
|
case 2:
|
|
g1.Tex2CoordFormat = value;
|
|
return;
|
|
case 3:
|
|
g1.Tex3CoordFormat = value;
|
|
return;
|
|
case 4:
|
|
g1.Tex4CoordFormat = value;
|
|
return;
|
|
case 5:
|
|
g2.Tex5CoordFormat = value;
|
|
return;
|
|
case 6:
|
|
g2.Tex6CoordFormat = value;
|
|
return;
|
|
case 7:
|
|
g2.Tex7CoordFormat = value;
|
|
return;
|
|
default:
|
|
PanicAlertFmt("Invalid tex coord index {}", idx);
|
|
}
|
|
}
|
|
void SetTexFrac(size_t idx, u8 value)
|
|
{
|
|
switch (idx)
|
|
{
|
|
case 0:
|
|
g0.Tex0Frac = value;
|
|
return;
|
|
case 1:
|
|
g1.Tex1Frac = value;
|
|
return;
|
|
case 2:
|
|
g1.Tex2Frac = value;
|
|
return;
|
|
case 3:
|
|
g1.Tex3Frac = value;
|
|
return;
|
|
case 4:
|
|
g2.Tex4Frac = value;
|
|
return;
|
|
case 5:
|
|
g2.Tex5Frac = value;
|
|
return;
|
|
case 6:
|
|
g2.Tex6Frac = value;
|
|
return;
|
|
case 7:
|
|
g2.Tex7Frac = value;
|
|
return;
|
|
default:
|
|
PanicAlertFmt("Invalid tex coord index {}", idx);
|
|
}
|
|
}
|
|
};
|
|
template <>
|
|
struct fmt::formatter<VAT>
|
|
{
|
|
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
|
template <typename FormatContext>
|
|
auto format(const VAT& vat, FormatContext& ctx) const
|
|
{
|
|
return fmt::format_to(ctx.out(), "{}\n{}\n{}", vat.g0, vat.g1, vat.g2);
|
|
}
|
|
};
|
|
|
|
// Matrix indices
|
|
union TMatrixIndexA
|
|
{
|
|
BitField<0, 6, u32> PosNormalMtxIdx;
|
|
BitField<6, 6, u32> Tex0MtxIdx;
|
|
BitField<12, 6, u32> Tex1MtxIdx;
|
|
BitField<18, 6, u32> Tex2MtxIdx;
|
|
BitField<24, 6, u32> Tex3MtxIdx;
|
|
u32 Hex;
|
|
};
|
|
template <>
|
|
struct fmt::formatter<TMatrixIndexA>
|
|
{
|
|
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
|
template <typename FormatContext>
|
|
auto format(const TMatrixIndexA& m, FormatContext& ctx) const
|
|
{
|
|
return fmt::format_to(ctx.out(), "PosNormal: {}\nTex0: {}\nTex1: {}\nTex2: {}\nTex3: {}",
|
|
m.PosNormalMtxIdx, m.Tex0MtxIdx, m.Tex1MtxIdx, m.Tex2MtxIdx,
|
|
m.Tex3MtxIdx);
|
|
}
|
|
};
|
|
|
|
union TMatrixIndexB
|
|
{
|
|
BitField<0, 6, u32> Tex4MtxIdx;
|
|
BitField<6, 6, u32> Tex5MtxIdx;
|
|
BitField<12, 6, u32> Tex6MtxIdx;
|
|
BitField<18, 6, u32> Tex7MtxIdx;
|
|
u32 Hex;
|
|
};
|
|
template <>
|
|
struct fmt::formatter<TMatrixIndexB>
|
|
{
|
|
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
|
template <typename FormatContext>
|
|
auto format(const TMatrixIndexB& m, FormatContext& ctx) const
|
|
{
|
|
return fmt::format_to(ctx.out(), "Tex4: {}\nTex5: {}\nTex6: {}\nTex7: {}", m.Tex4MtxIdx,
|
|
m.Tex5MtxIdx, m.Tex6MtxIdx, m.Tex7MtxIdx);
|
|
}
|
|
};
|
|
|
|
class VertexLoaderBase;
|
|
|
|
// STATE_TO_SAVE
|
|
struct CPState final
|
|
{
|
|
CPState() = default;
|
|
explicit CPState(const u32* memory);
|
|
|
|
// Mutates the CP state based on the given command and value.
|
|
void LoadCPReg(u8 sub_cmd, u32 value);
|
|
// Fills memory with data from CP regs. There should be space for 0x100 values in memory.
|
|
void FillCPMemoryArray(u32* memory) const;
|
|
|
|
Common::EnumMap<u32, CPArray::XF_D> array_bases;
|
|
Common::EnumMap<u32, CPArray::XF_D> array_strides;
|
|
TMatrixIndexA matrix_index_a{};
|
|
TMatrixIndexB matrix_index_b{};
|
|
TVtxDesc vtx_desc;
|
|
// Most games only use the first VtxAttr and simply reconfigure it all the time as needed.
|
|
std::array<VAT, CP_NUM_VAT_REG> vtx_attr{};
|
|
};
|
|
static_assert(std::is_trivially_copyable_v<CPState>);
|
|
|
|
class PointerWrap;
|
|
|
|
extern CPState g_main_cp_state;
|
|
extern CPState g_preprocess_cp_state;
|
|
|
|
void CopyPreprocessCPStateFromMain();
|
|
|
|
std::pair<std::string, std::string> GetCPRegInfo(u8 cmd, u32 value);
|