mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 15:00:30 +02:00
Each row of vertices is evaluated by folding the patch's four control point rows into four columns with the v weights, then weighting those along u, four vertices per register with one attribute component each. Cross products, normalization and the pole test become plain per-lane math, and the result is transposed into SimpleVertex order on the way out. The weights are stored one row per term so four steps load as one vector, and the control points are padded to full vectors in a single array. Colors now round to nearest on every platform. The SSE path already did, NEON and the scalar path truncated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1040 lines
28 KiB
C++
1040 lines
28 KiB
C++
#include <algorithm>
|
|
#include <cstdio>
|
|
#include "Common/Common.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/Data/Convert/ColorConv.h"
|
|
#include "GPU/Debugger/State.h"
|
|
#include "GPU/GPU.h"
|
|
#include "GPU/Common/GPUStateUtils.h"
|
|
#include "GPU/GPUCommon.h"
|
|
#include "GPU/GeDisasm.h"
|
|
#include "GPU/Common/VertexDecoderCommon.h"
|
|
#include "GPU/Common/SplineCommon.h"
|
|
#include "Core/System.h"
|
|
|
|
const char *const g_vertexListDecodedColNames[] = {
|
|
"X",
|
|
"Y",
|
|
"Z",
|
|
"U",
|
|
"V",
|
|
"Color",
|
|
"NX",
|
|
"NY",
|
|
"NZ",
|
|
"W0",
|
|
"W1",
|
|
"W2",
|
|
"W3",
|
|
"W4",
|
|
"W5",
|
|
"W6",
|
|
"W7",
|
|
};
|
|
|
|
const char *const g_vertexListTransformedColNames[] = {
|
|
"X",
|
|
"Y",
|
|
"Z",
|
|
"W",
|
|
"U",
|
|
"V",
|
|
"Color0",
|
|
"Color1",
|
|
"Fog",
|
|
};
|
|
|
|
void FormatStateRow(char *dest, size_t destSize, CmdFormatType fmt, u32 value, bool enabled, u32 otherValue, u32 otherValue2) {
|
|
value &= 0xFFFFFF;
|
|
otherValue &= 0xFFFFFF;
|
|
otherValue2 &= 0xFFFFFF;
|
|
switch (fmt) {
|
|
case CMD_FMT_HEX:
|
|
snprintf(dest, destSize, "%06x", value);
|
|
break;
|
|
|
|
case CMD_FMT_HEX_AND_INT:
|
|
snprintf(dest, destSize, "%06x (%d)", value, value);
|
|
break;
|
|
|
|
case CMD_FMT_NUM:
|
|
snprintf(dest, destSize, "%d", value);
|
|
break;
|
|
|
|
case CMD_FMT_FLOAT24:
|
|
snprintf(dest, destSize, "%f", getFloat24(value));
|
|
break;
|
|
|
|
case CMD_FMT_PTRWIDTH:
|
|
value |= (otherValue & 0x00FF0000) << 8;
|
|
otherValue &= 0xFFFF;
|
|
snprintf(dest, destSize, "%08x, w=%d", value, otherValue);
|
|
break;
|
|
|
|
case CMD_FMT_XY:
|
|
{
|
|
int x = value & 0x3FF;
|
|
int y = value >> 10;
|
|
snprintf(dest, destSize, "%d,%d", x, y);
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_XYPLUS1:
|
|
{
|
|
int x = value & 0x3FF;
|
|
int y = value >> 10;
|
|
snprintf(dest, destSize, "%d,%d", x + 1, y + 1);
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_XYXY:
|
|
{
|
|
int x1 = value & 0x3FF;
|
|
int y1 = value >> 10;
|
|
int x2 = otherValue & 0x3FF;
|
|
int y2 = otherValue >> 10;
|
|
snprintf(dest, destSize, "%d,%d - %d,%d", x1, y1, x2, y2);
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_XYZ:
|
|
{
|
|
float x = getFloat24(value);
|
|
float y = getFloat24(otherValue);
|
|
float z = getFloat24(otherValue2);
|
|
snprintf(dest, destSize, "%f, %f, %f", x, y, z);
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_TEXSIZE:
|
|
{
|
|
int w = 1 << (value & 0x1f);
|
|
int h = 1 << ((value >> 8) & 0x1f);
|
|
snprintf(dest, destSize, "%dx%d", w, h);
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_F16_XY:
|
|
{
|
|
float x = (float)value / 16.0f;
|
|
float y = (float)otherValue / 16.0f;
|
|
snprintf(dest, destSize, "%fx%f", x, y);
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_VERTEXTYPE:
|
|
{
|
|
char buffer[256];
|
|
GeDescribeVertexType(value, buffer);
|
|
snprintf(dest, destSize, "%s", buffer);
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_TEXFMT:
|
|
{
|
|
static const char *texformats[] = {
|
|
"5650",
|
|
"5551",
|
|
"4444",
|
|
"8888",
|
|
"CLUT4",
|
|
"CLUT8",
|
|
"CLUT16",
|
|
"CLUT32",
|
|
"DXT1",
|
|
"DXT3",
|
|
"DXT5",
|
|
};
|
|
if (value < (u32)ARRAY_SIZE(texformats)) {
|
|
snprintf(dest, destSize, "%s", texformats[value]);
|
|
} else if ((value & 0xF) < (u32)ARRAY_SIZE(texformats)) {
|
|
snprintf(dest, destSize, "%s (extra bits %06x)", texformats[value & 0xF], value & ~0xF);
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_CLUTFMT:
|
|
{
|
|
const char *clutformats[] = {
|
|
"BGR 5650",
|
|
"ABGR 1555",
|
|
"ABGR 4444",
|
|
"ABGR 8888",
|
|
};
|
|
const u8 palette = (value >> 0) & 3;
|
|
const u8 shift = (value >> 2) & 0x3F;
|
|
const u8 mask = (value >> 8) & 0xFF;
|
|
const u8 offset = (value >> 16) & 0xFF;
|
|
if (offset < 0x20 && shift < 0x20) {
|
|
if (offset == 0 && shift == 0) {
|
|
snprintf(dest, destSize, "%s ind & %02x", clutformats[palette], mask);
|
|
} else {
|
|
snprintf(dest, destSize, "%s (ind >> %d) & %02x, offset +%d", clutformats[palette], shift, mask, offset);
|
|
}
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_COLORTEST:
|
|
{
|
|
static const char *colorTests[] = { "NEVER", "ALWAYS", " == ", " != " };
|
|
const u32 mask = otherValue2;
|
|
const u32 ref = otherValue;
|
|
if (value < (u32)ARRAY_SIZE(colorTests)) {
|
|
snprintf(dest, destSize, "pass if (c & %06x) %s (%06x & %06x)", mask, colorTests[value], ref, mask);
|
|
} else {
|
|
snprintf(dest, destSize, "%06x, ref=%06x, maks=%06x", value, ref, mask);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_ALPHATEST:
|
|
case CMD_FMT_STENCILTEST:
|
|
{
|
|
static const char *alphaTestFuncs[] = { "NEVER", "ALWAYS", "==", "!=", "<", "<=", ">", ">=" };
|
|
const u8 mask = (value >> 16) & 0xff;
|
|
const u8 ref = (value >> 8) & 0xff;
|
|
const u8 func = (value >> 0) & 0xff;
|
|
if (func < (u8)ARRAY_SIZE(alphaTestFuncs)) {
|
|
if (fmt == CMD_FMT_ALPHATEST) {
|
|
snprintf(dest, destSize, "pass if (a & %02x) %s (%02x & %02x)", mask, alphaTestFuncs[func], ref, mask);
|
|
} else if (fmt == CMD_FMT_STENCILTEST) {
|
|
// Stencil test is the other way around.
|
|
snprintf(dest, destSize, "pass if (%02x & %02x) %s (a & %02x)", ref, mask, alphaTestFuncs[func], mask);
|
|
}
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_ZTEST:
|
|
{
|
|
static const char *zTestFuncs[] = { "NEVER", "ALWAYS", "==", "!=", "<", "<=", ">", ">=" };
|
|
if (value < (u32)ARRAY_SIZE(zTestFuncs)) {
|
|
snprintf(dest, destSize, "pass if src %s dst", zTestFuncs[value]);
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_OFFSETADDR:
|
|
snprintf(dest, destSize, "%08x", gpu->GetRelativeAddress(0));
|
|
break;
|
|
|
|
case CMD_FMT_VADDR:
|
|
snprintf(dest, destSize, "%08x", gpu->GetVertexAddress());
|
|
break;
|
|
|
|
case CMD_FMT_IADDR:
|
|
snprintf(dest, destSize, "%08x", gpu->GetIndexAddress());
|
|
break;
|
|
|
|
case CMD_FMT_MATERIALUPDATE:
|
|
{
|
|
static const char *materialTypes[] = {
|
|
"none",
|
|
"ambient",
|
|
"diffuse",
|
|
"ambient, diffuse",
|
|
"specular",
|
|
"ambient, specular",
|
|
"diffuse, specular",
|
|
"ambient, diffuse, specular",
|
|
};
|
|
if (value < (u32)ARRAY_SIZE(materialTypes)) {
|
|
snprintf(dest, destSize, "%s", materialTypes[value]);
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_SHADEMODEL:
|
|
if (value == 0) {
|
|
snprintf(dest, destSize, "flat");
|
|
} else if (value == 1) {
|
|
snprintf(dest, destSize, "gouraud");
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_STENCILOP:
|
|
{
|
|
static constexpr const char *stencilOps[] = { "KEEP", "ZERO", "REPLACE", "INVERT", "INCREMENT", "DECREMENT" };
|
|
const u8 sfail = (value >> 0) & 0xFF;
|
|
const u8 zfail = (value >> 8) & 0xFF;
|
|
const u8 pass = (value >> 16) & 0xFF;
|
|
constexpr u8 totalValid = (u8)ARRAY_SIZE(stencilOps);
|
|
if (sfail < totalValid && zfail < totalValid && pass < totalValid) {
|
|
snprintf(dest, destSize, "fail=%s, pass/depthfail=%s, pass=%s", stencilOps[sfail], stencilOps[zfail], stencilOps[pass]);
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_BLENDMODE:
|
|
{
|
|
const char *blendModes[] = {
|
|
"add",
|
|
"subtract",
|
|
"reverse subtract",
|
|
"min",
|
|
"max",
|
|
"abs subtract",
|
|
};
|
|
const char *blendFactorsA[] = {
|
|
"dst",
|
|
"1.0 - dst",
|
|
"src.a",
|
|
"1.0 - src.a",
|
|
"dst.a",
|
|
"1.0 - dst.a",
|
|
"2.0 * src.a",
|
|
"1.0 - 2.0 * src.a",
|
|
"2.0 * dst.a",
|
|
"1.0 - 2.0 * dst.a",
|
|
"fixed",
|
|
};
|
|
const char *blendFactorsB[] = {
|
|
"src",
|
|
"1.0 - src",
|
|
"src.a",
|
|
"1.0 - src.a",
|
|
"dst.a",
|
|
"1.0 - dst.a",
|
|
"2.0 * src.a",
|
|
"1.0 - 2.0 * src.a",
|
|
"2.0 * dst.a",
|
|
"1.0 - 2.0 * dst.a",
|
|
"fixed",
|
|
};
|
|
const u8 blendFactorA = (value >> 0) & 0xF;
|
|
const u8 blendFactorB = (value >> 4) & 0xF;
|
|
const u32 blendMode = (value >> 8);
|
|
|
|
if (blendFactorA < (u8)ARRAY_SIZE(blendFactorsA) && blendFactorB < (u8)ARRAY_SIZE(blendFactorsB) && blendMode < (u32)ARRAY_SIZE(blendModes)) {
|
|
snprintf(dest, destSize, "%s: %s, %s", blendModes[blendMode], blendFactorsA[blendFactorA], blendFactorsB[blendFactorB]);
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_CLEARMODE:
|
|
if (value == 0) {
|
|
snprintf(dest, destSize, "%d", value);
|
|
} else if ((value & ~(GE_CLEARMODE_ALL | 1)) == 0) {
|
|
const char *clearmodes[] = {
|
|
"1, write disabled",
|
|
"1, write color",
|
|
"1, write alpha/stencil",
|
|
"1, write color, alpha/stencil",
|
|
"1, write depth",
|
|
"1, write color, depth",
|
|
"1, write alpha/stencil, depth",
|
|
"1, write color, alpha/stencil, depth",
|
|
};
|
|
snprintf(dest, destSize, "%s", clearmodes[value >> 8]);
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_TEXFUNC:
|
|
{
|
|
const char *texfuncs[] = {
|
|
"modulate",
|
|
"decal",
|
|
"blend",
|
|
"replace",
|
|
"add",
|
|
};
|
|
const u8 func = (value >> 0) & 0xFF;
|
|
const u8 rgba = (value >> 8) & 0xFF;
|
|
const u8 colorDouble = (value >> 16) & 0xFF;
|
|
|
|
if (rgba <= 1 && colorDouble <= 1 && func < (u8)ARRAY_SIZE(texfuncs)) {
|
|
snprintf(dest, destSize, "%s, %s%s", texfuncs[func], rgba ? "RGBA" : "RGB", colorDouble ? ", color doubling" : "");
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_TEXMODE:
|
|
{
|
|
const u8 swizzle = (value >> 0) & 0xFF;
|
|
const u8 clutLevels = (value >> 8) & 0xFF;
|
|
const u8 maxLevel = (value >> 16) & 0xFF;
|
|
|
|
if (swizzle <= 1 && clutLevels <= 1 && maxLevel <= 7) {
|
|
snprintf(dest, destSize, "%s%d levels%s", swizzle ? "swizzled, " : "", maxLevel + 1, clutLevels ? ", CLUT per level" : "");
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_LOGICOP:
|
|
{
|
|
const char *logicOps[] = {
|
|
"clear",
|
|
"and",
|
|
"reverse and",
|
|
"copy",
|
|
"inverted and",
|
|
"noop",
|
|
"xor",
|
|
"or",
|
|
"negated or",
|
|
"equivalence",
|
|
"inverted",
|
|
"reverse or",
|
|
"inverted copy",
|
|
"inverted or",
|
|
"negated and",
|
|
"set",
|
|
};
|
|
|
|
if (value < ARRAY_SIZE(logicOps)) {
|
|
snprintf(dest, destSize, "%s", logicOps[value]);
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_TEXWRAP:
|
|
{
|
|
if ((value & ~0x0101) == 0) {
|
|
const bool clampS = (value & 0x0001) != 0;
|
|
const bool clampT = (value & 0x0100) != 0;
|
|
snprintf(dest, destSize, "%s s, %s t", clampS ? "clamp" : "wrap", clampT ? "clamp" : "wrap");
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_TEXLEVEL:
|
|
{
|
|
static const char *mipLevelModes[3] = {
|
|
"auto + bias",
|
|
"bias",
|
|
"slope + bias",
|
|
};
|
|
const int mipLevel = value & 0xFFFF;
|
|
const int biasFixed = (s8)(value >> 16);
|
|
const float bias = (float)biasFixed / 16.0f;
|
|
|
|
if (mipLevel == 0 || mipLevel == 1 || mipLevel == 2) {
|
|
snprintf(dest, destSize, "%s: %f", mipLevelModes[mipLevel], bias);
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_TEXFILTER:
|
|
{
|
|
const char *textureFilters[] = {
|
|
"nearest",
|
|
"linear",
|
|
NULL,
|
|
NULL,
|
|
"nearest, mipmap nearest",
|
|
"linear, mipmap nearest",
|
|
"nearest, mipmap linear",
|
|
"linear, mipmap linear",
|
|
};
|
|
if ((value & ~0x0107) == 0 && textureFilters[value & 7] != NULL) {
|
|
const int min = (value & 0x0007) >> 0;
|
|
const int mag = (value & 0x0100) >> 8;
|
|
snprintf(dest, destSize, "min: %s, mag: %s", textureFilters[min], textureFilters[mag]);
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_TEXMAPMODE:
|
|
{
|
|
static constexpr const char * uvGenModes[] = {
|
|
"tex coords",
|
|
"tex matrix",
|
|
"tex env map",
|
|
"unknown (tex coords?)",
|
|
};
|
|
static constexpr const char * uvProjModes[] = {
|
|
"pos",
|
|
"uv",
|
|
"normalized normal",
|
|
"normal",
|
|
};
|
|
if ((value & ~0x0303) == 0) {
|
|
const int uvGen = (value & 0x0003) >> 0;
|
|
const int uvProj = (value & 0x0300) >> 8;
|
|
snprintf(dest, destSize, "gen: %s, proj: %s", uvGenModes[uvGen], uvProjModes[uvProj]);
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_TEXSHADELS:
|
|
if ((value & ~0x0303) == 0) {
|
|
const int sLight = (value & 0x0003) >> 0;
|
|
const int tLight = (value & 0x0300) >> 8;
|
|
snprintf(dest, destSize, "s: %d, t: %d", sLight, tLight);
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_LIGHTMODE:
|
|
if (value == 0) {
|
|
snprintf(dest, destSize, "mixed color");
|
|
} else if (value == 1) {
|
|
snprintf(dest, destSize, "separate specular");
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_LIGHTTYPE:
|
|
{
|
|
static constexpr const char * lightComputations[] = {
|
|
"diffuse",
|
|
"diffuse + spec",
|
|
"pow(diffuse)",
|
|
"unknown (diffuse?)",
|
|
};
|
|
static constexpr const char * lightTypes[] = {
|
|
"directional",
|
|
"point",
|
|
"spot",
|
|
"unknown (directional?)",
|
|
};
|
|
if ((value & ~0x0303) == 0) {
|
|
const int comp = (value & 0x0003) >> 0;
|
|
const int type = (value & 0x0300) >> 8;
|
|
snprintf(dest, destSize, "type: %s, comp: %s", lightTypes[type], lightComputations[comp]);
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_CULL:
|
|
if (value == 0) {
|
|
snprintf(dest, destSize, "front (CW)");
|
|
} else if (value == 1) {
|
|
snprintf(dest, destSize, "back (CCW)");
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_PATCHPRIMITIVE:
|
|
{
|
|
static constexpr const char * patchPrims[] = {
|
|
"triangles",
|
|
"lines",
|
|
"points",
|
|
};
|
|
if (value < (u32)ARRAY_SIZE(patchPrims)) {
|
|
snprintf(dest, destSize, "%s", patchPrims[value]);
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_FMT_FLAG:
|
|
if ((value & ~1) == 0) {
|
|
snprintf(dest, destSize, "%d", value);
|
|
} else {
|
|
snprintf(dest, destSize, "%06x", value);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
snprintf(dest, destSize, "BAD FORMAT %06x", value);
|
|
}
|
|
|
|
// TODO: Turn row grey or some such?
|
|
if (!enabled) {
|
|
strcat(dest, " (disabled)");
|
|
}
|
|
}
|
|
|
|
void FormatVertColTransformed(char *dest, size_t destSize, const GPUDebugVertex &vert, VertexListTransformedCol col) {
|
|
switch (col) {
|
|
case VertexListTransformedCol::X: snprintf(dest, destSize, "%f", vert.x); break;
|
|
case VertexListTransformedCol::Y: snprintf(dest, destSize, "%f", vert.y); break;
|
|
case VertexListTransformedCol::Z: snprintf(dest, destSize, "%f", vert.z); break;
|
|
case VertexListTransformedCol::W: snprintf(dest, destSize, "%f", vert.w); break;
|
|
case VertexListTransformedCol::U: snprintf(dest, destSize, "%f", vert.u); break;
|
|
case VertexListTransformedCol::V: snprintf(dest, destSize, "%f", vert.v); break;
|
|
case VertexListTransformedCol::COLOR0:
|
|
snprintf(dest, destSize, "%02x%02x%02x%02x", vert.c0[0], vert.c0[1], vert.c0[2], vert.c0[3]);
|
|
break;
|
|
case VertexListTransformedCol::COLOR1:
|
|
snprintf(dest, destSize, "%02x%02x%02x%02x", vert.c1[0], vert.c1[1], vert.c1[2], vert.c1[3]);
|
|
break;
|
|
case VertexListTransformedCol::FOG: snprintf(dest, destSize, "%f", vert.fog); break;
|
|
default:
|
|
truncate_cpy(dest, destSize, "Invalid");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void FormatVertColDecoded(char *dest, size_t destSize, const GPUDebugVertex &vert, VertexListDecodedCol col) {
|
|
if (PSP_GetBootState() != BootState::Complete) {
|
|
truncate_cpy(dest, destSize, "Invalid");
|
|
return;
|
|
}
|
|
|
|
switch (col) {
|
|
case VertexListDecodedCol::X: snprintf(dest, destSize, "%f", vert.x); break;
|
|
case VertexListDecodedCol::Y: snprintf(dest, destSize, "%f", vert.y); break;
|
|
case VertexListDecodedCol::Z: snprintf(dest, destSize, "%f", vert.z); break;
|
|
case VertexListDecodedCol::U: snprintf(dest, destSize, "%f", vert.u); break;
|
|
case VertexListDecodedCol::V: snprintf(dest, destSize, "%f", vert.v); break;
|
|
case VertexListDecodedCol::COLOR:
|
|
snprintf(dest, destSize, "%02x%02x%02x%02x", vert.c0[0], vert.c0[1], vert.c0[2], vert.c0[3]);
|
|
break;
|
|
case VertexListDecodedCol::NX: snprintf(dest, destSize, "%f", vert.nx); break;
|
|
case VertexListDecodedCol::NY: snprintf(dest, destSize, "%f", vert.ny); break;
|
|
case VertexListDecodedCol::NZ: snprintf(dest, destSize, "%f", vert.nz); break;
|
|
case VertexListDecodedCol::W0: snprintf(dest, destSize, "%f", vert.weights[0]); break;
|
|
case VertexListDecodedCol::W1: snprintf(dest, destSize, "%f", vert.weights[1]); break;
|
|
case VertexListDecodedCol::W2: snprintf(dest, destSize, "%f", vert.weights[2]); break;
|
|
case VertexListDecodedCol::W3: snprintf(dest, destSize, "%f", vert.weights[3]); break;
|
|
case VertexListDecodedCol::W4: snprintf(dest, destSize, "%f", vert.weights[4]); break;
|
|
case VertexListDecodedCol::W5: snprintf(dest, destSize, "%f", vert.weights[5]); break;
|
|
case VertexListDecodedCol::W6: snprintf(dest, destSize, "%f", vert.weights[6]); break;
|
|
case VertexListDecodedCol::W7: snprintf(dest, destSize, "%f", vert.weights[7]); break;
|
|
default:
|
|
truncate_cpy(dest, destSize, "Invalid");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void SwapUVs(GPUDebugVertex &a, GPUDebugVertex &b) {
|
|
float tempu = a.u;
|
|
float tempv = a.v;
|
|
a.u = b.u;
|
|
a.v = b.v;
|
|
b.u = tempu;
|
|
b.v = tempv;
|
|
}
|
|
|
|
static void RotateUVThrough(GPUDebugVertex v[4]) {
|
|
float x1 = v[2].x;
|
|
float x2 = v[0].x;
|
|
float y1 = v[2].y;
|
|
float y2 = v[0].y;
|
|
|
|
if ((x1 < x2 && y1 > y2) || (x1 > x2 && y1 < y2))
|
|
SwapUVs(v[1], v[3]);
|
|
}
|
|
|
|
static void ExpandRectangles(std::vector<GPUDebugVertex> &vertices, std::vector<u16> &indices, int &count, bool throughMode) {
|
|
static std::vector<GPUDebugVertex> newVerts;
|
|
static std::vector<u16> newInds;
|
|
|
|
bool useInds = true;
|
|
size_t numInds = indices.size();
|
|
if (indices.empty()) {
|
|
useInds = false;
|
|
numInds = count;
|
|
}
|
|
|
|
//rectangles always need 2 vertices, disregard the last one if there's an odd number
|
|
numInds = numInds & ~1;
|
|
|
|
// Will need 4 coords and 6 points per rectangle (currently 2 each.)
|
|
newVerts.resize(numInds * 2);
|
|
newInds.resize(numInds * 3);
|
|
|
|
u16 v = 0;
|
|
GPUDebugVertex *vert = &newVerts[0];
|
|
u16 *ind = &newInds[0];
|
|
for (size_t i = 0; i < numInds; i += 2) {
|
|
const auto &orig_tl = useInds ? vertices[indices[i + 0]] : vertices[i + 0];
|
|
const auto &orig_br = useInds ? vertices[indices[i + 1]] : vertices[i + 1];
|
|
|
|
vert[0] = orig_br;
|
|
|
|
// Top right.
|
|
vert[1] = orig_br;
|
|
vert[1].y = orig_tl.y;
|
|
vert[1].v = orig_tl.v;
|
|
|
|
vert[2] = orig_tl;
|
|
|
|
// Bottom left.
|
|
vert[3] = orig_br;
|
|
vert[3].x = orig_tl.x;
|
|
vert[3].u = orig_tl.u;
|
|
|
|
// That's the four corners. Now process UV rotation.
|
|
// This is the same for through and non-through, since it's already transformed.
|
|
RotateUVThrough(vert);
|
|
|
|
// Build the two 3 point triangles from our 4 coordinates.
|
|
*ind++ = v + 0;
|
|
*ind++ = v + 1;
|
|
*ind++ = v + 2;
|
|
*ind++ = v + 3;
|
|
*ind++ = v + 0;
|
|
*ind++ = v + 2;
|
|
|
|
vert += 4;
|
|
v += 4;
|
|
}
|
|
|
|
std::swap(vertices, newVerts);
|
|
std::swap(indices, newInds);
|
|
count *= 3;
|
|
}
|
|
|
|
static void ExpandBezier(int &count, int op, const std::vector<SimpleVertex> &simpleVerts, const std::vector<u16> &indices, std::vector<SimpleVertex> &generatedVerts, std::vector<u16> &generatedInds) {
|
|
using namespace Spline;
|
|
|
|
int count_u = (op >> 0) & 0xFF;
|
|
int count_v = (op >> 8) & 0xFF;
|
|
// Real hardware seems to draw nothing when given < 4 either U or V.
|
|
if (count_u < 4 || count_v < 4)
|
|
return;
|
|
|
|
BezierSurface surface;
|
|
surface.num_points_u = count_u;
|
|
surface.num_points_v = count_v;
|
|
surface.tess_u = gstate.getPatchDivisionU();
|
|
surface.tess_v = gstate.getPatchDivisionV();
|
|
surface.num_patches_u = (count_u - 1) / 3;
|
|
surface.num_patches_v = (count_v - 1) / 3;
|
|
surface.primType = gstate.getPatchPrimitiveType();
|
|
surface.patchFacing = false;
|
|
|
|
int num_points = count_u * count_v;
|
|
// Make an array of pointers to the control points, to get rid of indices.
|
|
std::vector<const SimpleVertex *> points(num_points);
|
|
for (int idx = 0; idx < num_points; idx++)
|
|
points[idx] = simpleVerts.data() + (!indices.empty() ? indices[idx] : idx);
|
|
|
|
int total_patches = surface.num_patches_u * surface.num_patches_v;
|
|
generatedVerts.resize((surface.tess_u + 1) * (surface.tess_v + 1) * total_patches);
|
|
generatedInds.resize(surface.tess_u * surface.tess_v * 6 * total_patches);
|
|
|
|
OutputBuffers output;
|
|
output.vertices = generatedVerts.data();
|
|
output.indices = generatedInds.data();
|
|
output.count = 0;
|
|
|
|
std::vector<ControlPoint> controlPoints(num_points);
|
|
ControlPoints cpoints;
|
|
cpoints.points = controlPoints.data();
|
|
cpoints.Convert(points.data(), num_points);
|
|
|
|
surface.Init((int)generatedVerts.size());
|
|
SoftwareTessellation(output, surface, gstate.vertType, cpoints);
|
|
count = output.count;
|
|
}
|
|
|
|
static void ExpandSpline(int &count, int op, const std::vector<SimpleVertex> &simpleVerts, const std::vector<u16> &indices, std::vector<SimpleVertex> &generatedVerts, std::vector<u16> &generatedInds) {
|
|
using namespace Spline;
|
|
|
|
int count_u = (op >> 0) & 0xFF;
|
|
int count_v = (op >> 8) & 0xFF;
|
|
// Real hardware seems to draw nothing when given < 4 either U or V.
|
|
if (count_u < 4 || count_v < 4)
|
|
return;
|
|
|
|
SplineSurface surface;
|
|
surface.num_points_u = count_u;
|
|
surface.num_points_v = count_v;
|
|
surface.tess_u = gstate.getPatchDivisionU();
|
|
surface.tess_v = gstate.getPatchDivisionV();
|
|
surface.type_u = (op >> 16) & 0x3;
|
|
surface.type_v = (op >> 18) & 0x3;
|
|
surface.num_patches_u = count_u - 3;
|
|
surface.num_patches_v = count_v - 3;
|
|
surface.primType = gstate.getPatchPrimitiveType();
|
|
surface.patchFacing = false;
|
|
|
|
int num_points = count_u * count_v;
|
|
// Make an array of pointers to the control points, to get rid of indices.
|
|
std::vector<const SimpleVertex *> points(num_points);
|
|
for (int idx = 0; idx < num_points; idx++)
|
|
points[idx] = simpleVerts.data() + (!indices.empty() ? indices[idx] : idx);
|
|
|
|
int patch_div_s = surface.num_patches_u * surface.tess_u;
|
|
int patch_div_t = surface.num_patches_v * surface.tess_v;
|
|
generatedVerts.resize((patch_div_s + 1) * (patch_div_t + 1));
|
|
generatedInds.resize(patch_div_s * patch_div_t * 6);
|
|
|
|
OutputBuffers output;
|
|
output.vertices = generatedVerts.data();
|
|
output.indices = generatedInds.data();
|
|
output.count = 0;
|
|
|
|
std::vector<ControlPoint> controlPoints(num_points);
|
|
ControlPoints cpoints;
|
|
cpoints.points = controlPoints.data();
|
|
cpoints.Convert(points.data(), num_points);
|
|
|
|
surface.Init((int)generatedVerts.size());
|
|
SoftwareTessellation(output, surface, gstate.vertType, cpoints);
|
|
count = output.count;
|
|
}
|
|
|
|
bool GetPrimPreview(u32 op, GEPrimitiveType *prim, std::vector<GPUDebugVertex> *vertices, std::vector<u16> *indices, int *lowerIndexBound, bool transformed) {
|
|
int count_u = 0;
|
|
int count_v = 0;
|
|
|
|
int count = 0;
|
|
|
|
*lowerIndexBound = 0;
|
|
|
|
const GECommand cmd = static_cast<GECommand>(op >> 24);
|
|
if (cmd == GE_CMD_PRIM) {
|
|
*prim = static_cast<GEPrimitiveType>((op >> 16) & 0x7); // irrelevant for bbox
|
|
count = op & 0xFFFF;
|
|
} else if (cmd == GE_CMD_BOUNDINGBOX) {
|
|
*prim = GE_PRIM_POINTS; // There's no set order for these, so drawing lines might look strange. Maybe if we draw lines from every vertex to every other...
|
|
count = op & 0xFFFF;
|
|
} else {
|
|
constexpr GEPrimitiveType primLookup[] = { GE_PRIM_TRIANGLES, GE_PRIM_LINES, GE_PRIM_POINTS, GE_PRIM_POINTS };
|
|
*prim = primLookup[gstate.getPatchPrimitiveType()];
|
|
count_u = (op & 0x00FF) >> 0;
|
|
count_v = (op & 0xFF00) >> 8;
|
|
count = count_u * count_v;
|
|
}
|
|
|
|
if (*prim >= 7) {
|
|
ERROR_LOG(Log::G3D, "Unsupported prim type: %x", op);
|
|
return false;
|
|
}
|
|
if (!gpu) {
|
|
ERROR_LOG(Log::G3D, "Invalid debugging environment, shutting down?");
|
|
return false;
|
|
}
|
|
if (count == 0) {
|
|
return false;
|
|
}
|
|
|
|
DebugVertexFlags flags = DebugVertexFlags::DrawCoords;
|
|
if (cmd == GE_CMD_PRIM) {
|
|
flags |= DebugVertexFlags::Clipped;
|
|
if (transformed) {
|
|
flags |= DebugVertexFlags::Transformed;
|
|
}
|
|
} else {
|
|
flags |= DebugVertexFlags::Transformed;
|
|
}
|
|
|
|
GEPrimitiveType outPrim;
|
|
if (!gpu->GetCurrentDrawAsDebugVertices(cmd, *prim, &outPrim, count, vertices, indices, lowerIndexBound, nullptr, flags)) {
|
|
ERROR_LOG(Log::G3D, "Vertex preview not yet supported");
|
|
return false;
|
|
}
|
|
|
|
// Sanity check the output (for debugging)
|
|
if (indices->size()) {
|
|
for (int i = 0; i < indices->size(); ++i) {
|
|
int offsetIndex = (*indices)[i] - *lowerIndexBound;
|
|
if (offsetIndex >= vertices->size() || offsetIndex < 0) {
|
|
ERROR_LOG(Log::G3D, "Invalid vertex index %d (%d) (vertex count %d)", (*indices)[i], offsetIndex, (int)vertices->size());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
*prim = outPrim;
|
|
|
|
if (cmd != GE_CMD_PRIM && cmd != GE_CMD_BOUNDINGBOX) {
|
|
static std::vector<SimpleVertex> generatedVerts;
|
|
static std::vector<u16> generatedInds;
|
|
|
|
static std::vector<SimpleVertex> simpleVerts;
|
|
simpleVerts.resize(vertices->size());
|
|
for (size_t i = 0; i < vertices->size(); ++i) {
|
|
// For now, let's just copy back so we can use TessellateBezierPatch/TessellateSplinePatch...
|
|
simpleVerts[i].uv[0] = (*vertices)[i].u;
|
|
simpleVerts[i].uv[1] = (*vertices)[i].v;
|
|
simpleVerts[i].pos = Vec3Packedf((*vertices)[i].x, (*vertices)[i].y, (*vertices)[i].z);
|
|
}
|
|
|
|
*lowerIndexBound = 0;
|
|
if (cmd == GE_CMD_BEZIER) {
|
|
ExpandBezier(count, op, simpleVerts, *indices, generatedVerts, generatedInds);
|
|
} else if (cmd == GE_CMD_SPLINE) {
|
|
ExpandSpline(count, op, simpleVerts, *indices, generatedVerts, generatedInds);
|
|
}
|
|
|
|
vertices->resize(generatedVerts.size());
|
|
for (size_t i = 0; i < vertices->size(); ++i) {
|
|
auto vertex = &(*vertices)[i];
|
|
vertex->u = generatedVerts[i].uv[0];
|
|
vertex->v = generatedVerts[i].uv[1];
|
|
vertex->x = generatedVerts[i].pos.x;
|
|
vertex->y = generatedVerts[i].pos.y;
|
|
vertex->z = generatedVerts[i].pos.z;
|
|
}
|
|
*indices = generatedInds;
|
|
}
|
|
|
|
/*
|
|
auto wrapCoord = [](float &coord) {
|
|
if (coord < 0.0f) {
|
|
coord += ceilf(-coord);
|
|
}
|
|
if (coord > 1.0f) {
|
|
coord -= floorf(coord);
|
|
}
|
|
};
|
|
|
|
const float invTexWidth = 1.0f / gpu->GetGState().getTextureWidth(0);
|
|
const float invTexHeight = 1.0f / gpu->GetGState().getTextureHeight(0);
|
|
bool clampS = gpu->GetGState().isTexCoordClampedS();
|
|
bool clampT = gpu->GetGState().isTexCoordClampedT();
|
|
for (u16 i = minIndex; i <= maxIndex; ++i) {
|
|
vertices[i].u *= invTexWidth;
|
|
vertices[i].v *= invTexHeight;
|
|
if (!clampS)
|
|
wrapCoord(vertices[i].u);
|
|
if (!clampT)
|
|
wrapCoord(vertices[i].v);
|
|
}
|
|
*/
|
|
return true;
|
|
}
|
|
|
|
void DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]) {
|
|
switch (fmt) {
|
|
case GPU_DBG_FORMAT_565:
|
|
case GPU_DBG_FORMAT_565_REV:
|
|
case GPU_DBG_FORMAT_5551:
|
|
case GPU_DBG_FORMAT_5551_REV:
|
|
case GPU_DBG_FORMAT_5551_BGRA:
|
|
case GPU_DBG_FORMAT_4444:
|
|
case GPU_DBG_FORMAT_4444_REV:
|
|
case GPU_DBG_FORMAT_4444_BGRA:
|
|
case GPU_DBG_FORMAT_8888:
|
|
case GPU_DBG_FORMAT_8888_BGRA:
|
|
DescribePixelRGBA(pix, fmt, x, y, desc);
|
|
break;
|
|
|
|
case GPU_DBG_FORMAT_16BIT:
|
|
snprintf(desc, 256, "%d,%d: %d / %f", x, y, pix, pix * (1.0f / 65535.0f));
|
|
break;
|
|
|
|
case GPU_DBG_FORMAT_8BIT:
|
|
snprintf(desc, 256, "%d,%d: %d / %f", x, y, pix, pix * (1.0f / 255.0f));
|
|
break;
|
|
|
|
case GPU_DBG_FORMAT_24BIT_8X:
|
|
{
|
|
// These are only ever going to be depth values, so let's also show scaled to 16 bit.
|
|
snprintf(desc, 256, "%d,%d: %d / %f / %f", x, y, pix & 0x00FFFFFF, (pix & 0x00FFFFFF) * (1.0f / 16777215.0f), 65535.0f * ((pix & 0x00FFFFFF) * (1.0f / 16777215.0f)));
|
|
break;
|
|
}
|
|
|
|
case GPU_DBG_FORMAT_24X_8BIT:
|
|
snprintf(desc, 256, "%d,%d: %d / %f", x, y, (pix >> 24) & 0xFF, ((pix >> 24) & 0xFF) * (1.0f / 255.0f));
|
|
break;
|
|
|
|
case GPU_DBG_FORMAT_FLOAT:
|
|
{
|
|
const float pixf = *(const float *)&pix;
|
|
snprintf(desc, 256, "%d,%d: %f / %f", x, y, pixf, 65535.0 * pixf);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
snprintf(desc, 256, "Unexpected format");
|
|
}
|
|
}
|
|
|
|
void DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]) {
|
|
u32 r = -1, g = -1, b = -1, a = -1;
|
|
|
|
switch (fmt) {
|
|
case GPU_DBG_FORMAT_565:
|
|
r = Convert5To8((pix >> 0) & 0x1F);
|
|
g = Convert6To8((pix >> 5) & 0x3F);
|
|
b = Convert5To8((pix >> 11) & 0x1F);
|
|
break;
|
|
case GPU_DBG_FORMAT_565_REV:
|
|
b = Convert5To8((pix >> 0) & 0x1F);
|
|
g = Convert6To8((pix >> 5) & 0x3F);
|
|
r = Convert5To8((pix >> 11) & 0x1F);
|
|
break;
|
|
case GPU_DBG_FORMAT_5551:
|
|
r = Convert5To8((pix >> 0) & 0x1F);
|
|
g = Convert5To8((pix >> 5) & 0x1F);
|
|
b = Convert5To8((pix >> 10) & 0x1F);
|
|
a = (pix >> 15) & 1 ? 255 : 0;
|
|
break;
|
|
case GPU_DBG_FORMAT_5551_REV:
|
|
a = pix & 1 ? 255 : 0;
|
|
b = Convert5To8((pix >> 1) & 0x1F);
|
|
g = Convert5To8((pix >> 6) & 0x1F);
|
|
r = Convert5To8((pix >> 11) & 0x1F);
|
|
break;
|
|
case GPU_DBG_FORMAT_5551_BGRA:
|
|
b = Convert5To8((pix >> 0) & 0x1F);
|
|
g = Convert5To8((pix >> 5) & 0x1F);
|
|
r = Convert5To8((pix >> 10) & 0x1F);
|
|
a = (pix >> 15) & 1 ? 255 : 0;
|
|
break;
|
|
case GPU_DBG_FORMAT_4444:
|
|
r = Convert4To8((pix >> 0) & 0x0F);
|
|
g = Convert4To8((pix >> 4) & 0x0F);
|
|
b = Convert4To8((pix >> 8) & 0x0F);
|
|
a = Convert4To8((pix >> 12) & 0x0F);
|
|
break;
|
|
case GPU_DBG_FORMAT_4444_REV:
|
|
a = Convert4To8((pix >> 0) & 0x0F);
|
|
b = Convert4To8((pix >> 4) & 0x0F);
|
|
g = Convert4To8((pix >> 8) & 0x0F);
|
|
r = Convert4To8((pix >> 12) & 0x0F);
|
|
break;
|
|
case GPU_DBG_FORMAT_4444_BGRA:
|
|
b = Convert4To8((pix >> 0) & 0x0F);
|
|
g = Convert4To8((pix >> 4) & 0x0F);
|
|
r = Convert4To8((pix >> 8) & 0x0F);
|
|
a = Convert4To8((pix >> 12) & 0x0F);
|
|
break;
|
|
case GPU_DBG_FORMAT_8888:
|
|
r = (pix >> 0) & 0xFF;
|
|
g = (pix >> 8) & 0xFF;
|
|
b = (pix >> 16) & 0xFF;
|
|
a = (pix >> 24) & 0xFF;
|
|
break;
|
|
case GPU_DBG_FORMAT_8888_BGRA:
|
|
b = (pix >> 0) & 0xFF;
|
|
g = (pix >> 8) & 0xFF;
|
|
r = (pix >> 16) & 0xFF;
|
|
a = (pix >> 24) & 0xFF;
|
|
break;
|
|
|
|
default:
|
|
snprintf(desc, 256, "Unexpected format");
|
|
return;
|
|
}
|
|
|
|
snprintf(desc, 256, "%d,%d: r=%d, g=%d, b=%d, a=%d", x, y, r, g, b, a);
|
|
}
|