Pipeline checker: Add support for checking for identical pipelines with different vertex formats

This turned out not to be very fruitful.
This commit is contained in:
Henrik Rydgård
2026-07-10 13:45:43 +02:00
parent a954e6d719
commit 28b2b1793b
10 changed files with 98 additions and 40 deletions
+1 -1
View File
@@ -999,7 +999,7 @@ bool DrawEngineCommon::DescribeCodePtr(const u8 *ptr, std::string &name) const {
if (found) { if (found) {
char temp[256]; char temp[256];
found->ToString(temp, false); found->ToString(temp, sizeof(temp), false);
name = temp; name = temp;
snprintf(temp, sizeof(temp), "_%08X", foundKey); snprintf(temp, sizeof(temp), "_%08X", foundKey);
name += temp; name += temp;
+2 -2
View File
@@ -259,7 +259,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int
// Reset the code ptr and return zero to indicate that we failed. // Reset the code ptr and return zero to indicate that we failed.
ResetCodePtr(GetOffset(start)); ResetCodePtr(GetOffset(start));
char temp[1024]{}; char temp[1024]{};
dec.ToString(temp, true); dec.ToString(temp, sizeof(temp), true);
WARN_LOG(Log::G3D, "Could not compile vertex decoder, failed at step %s: %s", GetStepFunctionName(dec.steps_[i]), temp); WARN_LOG(Log::G3D, "Could not compile vertex decoder, failed at step %s: %s", GetStepFunctionName(dec.steps_[i]), temp);
return 0; return 0;
} }
@@ -287,7 +287,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int
/* /*
DisassembleArm(start, GetCodePtr() - start); DisassembleArm(start, GetCodePtr() - start);
char temp[1024] = {0}; char temp[1024] = {0};
dec.ToString(temp, true); dec.ToString(temp, sizeof(temp), true);
INFO_LOG(Log::G3D, "%s", temp); INFO_LOG(Log::G3D, "%s", temp);
*/ */
+2 -2
View File
@@ -256,7 +256,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int
// Reset the code ptr (effectively undoing what we generated) and return zero to indicate that we failed. // Reset the code ptr (effectively undoing what we generated) and return zero to indicate that we failed.
ResetCodePtr(GetOffset(start)); ResetCodePtr(GetOffset(start));
char temp[1024]{}; char temp[1024]{};
dec.ToString(temp, true); dec.ToString(temp, sizeof(temp), true);
WARN_LOG(Log::G3D, "Could not compile vertex decoder, failed at step %s: %s", GetStepFunctionName(dec.steps_[i]), temp); WARN_LOG(Log::G3D, "Could not compile vertex decoder, failed at step %s: %s", GetStepFunctionName(dec.steps_[i]), temp);
return nullptr; return nullptr;
} }
@@ -291,7 +291,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int
if (log) { if (log) {
char temp[1024] = { 0 }; char temp[1024] = { 0 };
dec.ToString(temp, true); dec.ToString(temp, sizeof(temp), true);
INFO_LOG(Log::JIT, "=== %s (%d bytes) ===", temp, (int)(GetCodePtr() - start)); INFO_LOG(Log::JIT, "=== %s (%d bytes) ===", temp, (int)(GetCodePtr() - start));
std::vector<std::string> lines = DisassembleArm64(start, (int)(GetCodePtr() - start)); std::vector<std::string> lines = DisassembleArm64(start, (int)(GetCodePtr() - start));
for (auto line : lines) { for (auto line : lines) {
+65 -26
View File
@@ -23,6 +23,7 @@
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Data/Convert/ColorConv.h" #include "Common/Data/Convert/ColorConv.h"
#include "Common/Data/Text/StringWriter.h"
#include "Common/Math/CrossSIMD.h" #include "Common/Math/CrossSIMD.h"
#include "Common/Log.h" #include "Common/Log.h"
#include "Common/LogReporting.h" #include "Common/LogReporting.h"
@@ -84,6 +85,41 @@ static int DecFmtSize(u8 fmt) {
} }
} }
const char *DecFmtComponentToString(u8 fmt) {
switch (fmt) {
case DEC_NONE: return "NONE";
case DEC_FLOAT_1: return "FLOAT_1";
case DEC_FLOAT_2: return "FLOAT_2";
case DEC_FLOAT_3: return "FLOAT_3";
case DEC_FLOAT_4: return "FLOAT_4";
case DEC_S8_3: return "S8_3";
case DEC_S16_3: return "S16_3";
case DEC_U8_1: return "U8_1";
case DEC_U8_2: return "U8_2";
case DEC_U8_3: return "U8_3";
case DEC_U8_4: return "U8_4";
case DEC_U16_1: return "U16_1";
case DEC_U16_2: return "U16_2";
case DEC_U16_3: return "U16_3";
case DEC_U16_4: return "U16_4";
default: return "UNKNOWN";
}
}
std::string DecVtxFormat::ToString() const {
char buf[256];
StringWriter w(buf, sizeof(buf));
if (w0fmt) {
w.F("W0: %s ", DecFmtComponentToString(w0fmt));
}
w.F("W1: %s ", DecFmtComponentToString(w1fmt));
w.F("UV: %s ", DecFmtComponentToString(uvfmt));
w.F("C0: %s ", DecFmtComponentToString(c0fmt));
w.F("C1: %s ", DecFmtComponentToString(c1fmt));
w.F("N: %s", DecFmtComponentToString(nrmfmt));
return w.as_string();
}
void DecVtxFormat::ComputeID() { void DecVtxFormat::ComputeID() {
id = w0fmt | (w1fmt << 4) | (uvfmt << 8) | (c0fmt << 12) | (c1fmt << 16) | (nrmfmt << 20); id = w0fmt | (w1fmt << 4) | (uvfmt << 8) | (c0fmt << 12) | (c1fmt << 16) | (nrmfmt << 20);
} }
@@ -1413,7 +1449,7 @@ void VertexDecoder::SetVertexType(u32 fmt, const VertexDecoderOptions &options,
if (reportNoPos) { if (reportNoPos) {
char temp[256]{}; char temp[256]{};
ToString(temp, true); ToString(temp, sizeof(temp), true);
ERROR_LOG(Log::G3D, "Vertices without position found (and ignored): (%08x) %s", fmt_, temp); ERROR_LOG(Log::G3D, "Vertices without position found (and ignored): (%08x) %s", fmt_, temp);
} }
@@ -1550,8 +1586,8 @@ void VertexDecoder::CompareToJit(const u8 *startPtr, u8 *decodedptr, int count,
controlReader.Goto(i); controlReader.Goto(i);
jittedReader.Goto(i); jittedReader.Goto(i);
if (!DecodedVertsAreSimilar(controlReader, jittedReader)) { if (!DecodedVertsAreSimilar(controlReader, jittedReader)) {
char name[512]{}; char name[256]{};
ToString(name, true); ToString(name, sizeof(name), true);
ERROR_LOG(Log::G3D, "Encountered vertexjit mismatch at %d/%d for %s", i, count, name); ERROR_LOG(Log::G3D, "Encountered vertexjit mismatch at %d/%d for %s", i, count, name);
if (morphcount > 1) { if (morphcount > 1) {
printf("Morph:\n"); printf("Morph:\n");
@@ -1605,26 +1641,30 @@ static const char * const idxnames[4] = { "-", "u8", "u16", "?" };
static const char * const weightnames[4] = { "-", "u8", "u16", "f" }; static const char * const weightnames[4] = { "-", "u8", "u16", "f" };
static const char * const colnames[8] = { "", "?", "?", "?", "565", "5551", "4444", "8888" }; static const char * const colnames[8] = { "", "?", "?", "?", "565", "5551", "4444", "8888" };
int VertexDecoder::ToString(char *output, bool spaces) const { // There's a direct ToString function for vertex types in the GPU debugger, GeDescribeVertexType.
char *start = output;
output += sprintf(output, "[%08x] ", fmt_);
output += sprintf(output, "P: %s ", posnames[pos]);
if (nrm)
output += sprintf(output, "N: %s ", nrmnames[nrm]);
if (col)
output += sprintf(output, "C: %s ", colnames[col]);
if (tc)
output += sprintf(output, "T: %s ", tcnames[tc]);
if (weighttype)
output += sprintf(output, "W: %s (%ix) ", weightnames[weighttype], nweights);
if (idx)
output += sprintf(output, "I: %s ", idxnames[idx]);
if (morphcount > 1)
output += sprintf(output, "Morph: %i ", morphcount);
if (throughmode)
output += sprintf(output, " (through)");
output += sprintf(output, " (%ib)", VertexSize()); int VertexDecoder::ToString(char *buf, int bufSize, bool spaces) const {
StringWriter w(buf, bufSize);
char *start = buf;
w.F("[%08x] ", fmt_);
w.F("P: %s ", posnames[pos]);
if (nrm)
w.F("N: %s ", nrmnames[nrm]);
if (col)
w.F("C: %s ", colnames[col]);
if (tc)
w.F("T: %s ", tcnames[tc]);
if (weighttype)
w.F("W: %s (%ix) ", weightnames[weighttype], nweights);
if (idx)
w.F("I: %s ", idxnames[idx]);
if (morphcount > 1)
w.F("Morph: %i ", morphcount);
if (throughmode)
w.F(" (through)");
w.F(" (%ib)", VertexSize());
if (!spaces) { if (!spaces) {
size_t len = strlen(start); size_t len = strlen(start);
@@ -1635,17 +1675,16 @@ int VertexDecoder::ToString(char *output, bool spaces) const {
} }
#ifdef _DEBUG #ifdef _DEBUG
output += sprintf(output, " (%llu)", (long long)decodedCount); w.F(" (%llu)", (long long)decodedCount);
#endif #endif
return (int)w.size();
return output - start;
} }
std::string VertexDecoder::GetString(DebugShaderStringType stringType) const { std::string VertexDecoder::GetString(DebugShaderStringType stringType) const {
char buffer[256]; char buffer[256];
switch (stringType) { switch (stringType) {
case SHADER_STRING_SHORT_DESC: case SHADER_STRING_SHORT_DESC:
ToString(buffer, true); ToString(buffer, sizeof(buffer), true);
return std::string(buffer); return std::string(buffer);
case SHADER_STRING_SOURCE_CODE: case SHADER_STRING_SOURCE_CODE:
{ {
+8 -2
View File
@@ -46,7 +46,7 @@
// Keep this in 4 bits. // Keep this in 4 bits.
enum { enum {
DEC_NONE, DEC_NONE = 0,
DEC_FLOAT_1, DEC_FLOAT_1,
DEC_FLOAT_2, DEC_FLOAT_2,
DEC_FLOAT_3, DEC_FLOAT_3,
@@ -63,6 +63,8 @@ enum {
DEC_U16_4, DEC_U16_4,
}; };
const char *DecFmtComponentToString(u8 fmt);
// DecVtxFormat - vertex formats for PC // DecVtxFormat - vertex formats for PC
// Kind of like a D3D VertexDeclaration. // Kind of like a D3D VertexDeclaration.
// No morph support, that is taken care of by the VertexDecoder. // No morph support, that is taken care of by the VertexDecoder.
@@ -81,6 +83,8 @@ struct DecVtxFormat {
void InitializeFromID(uint32_t id); void InitializeFromID(uint32_t id);
static u8 PosFmt() { return DEC_FLOAT_3; } static u8 PosFmt() { return DEC_FLOAT_3; }
std::string ToString() const;
}; };
void GetIndexBounds(const void *inds, int count, u32 vertType, u16 *indexLowerBound, u16 *indexUpperBound); void GetIndexBounds(const void *inds, int count, u32 vertType, u16 *indexLowerBound, u16 *indexUpperBound);
@@ -153,6 +157,8 @@ inline GETexMapMode VertTypeIDUVGenMode(uint32_t vertType) {
return (GETexMapMode)((vertType >> 24) & 3); return (GETexMapMode)((vertType >> 24) & 3);
} }
std::string DecFmtIdToString(u32 id);
class VertexDecoder { class VertexDecoder {
public: public:
// A jit cache is not mandatory. // A jit cache is not mandatory.
@@ -258,7 +264,7 @@ public:
// output must be big for safety. // output must be big for safety.
// Returns number of chars written. // Returns number of chars written.
// Ugly for speed. // Ugly for speed.
int ToString(char *output, bool spaces) const; int ToString(char *buf, int bufSize, bool spaces) const;
bool IsInSpace(const uint8_t *ptr) const { bool IsInSpace(const uint8_t *ptr) const {
return ptr >= (const uint8_t *)jitted_ && ptr < ((const uint8_t *)jitted_ + jittedSize_); return ptr >= (const uint8_t *)jitted_ && ptr < ((const uint8_t *)jitted_ + jittedSize_);
+2 -2
View File
@@ -297,7 +297,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int
// Reset the code ptr (effectively undoing what we generated) and return zero to indicate that we failed. // Reset the code ptr (effectively undoing what we generated) and return zero to indicate that we failed.
ResetCodePtr(GetOffset(start)); ResetCodePtr(GetOffset(start));
char temp[1024]{}; char temp[1024]{};
dec.ToString(temp, true); dec.ToString(temp, sizeof(temp), true);
WARN_LOG(Log::G3D, "Could not compile vertex decoder, failed at step %s: %s", GetStepFunctionName(dec.steps_[i]), temp); WARN_LOG(Log::G3D, "Could not compile vertex decoder, failed at step %s: %s", GetStepFunctionName(dec.steps_[i]), temp);
return nullptr; return nullptr;
} }
@@ -338,7 +338,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int
if (log) { if (log) {
char temp[1024]{}; char temp[1024]{};
dec.ToString(temp, true); dec.ToString(temp, sizeof(temp), true);
INFO_LOG(Log::JIT, "=== %s (%d bytes) ===", temp, (int)(GetCodePtr() - start)); INFO_LOG(Log::JIT, "=== %s (%d bytes) ===", temp, (int)(GetCodePtr() - start));
std::vector<std::string> lines = DisassembleLA64(start, (int)(GetCodePtr() - start)); std::vector<std::string> lines = DisassembleLA64(start, (int)(GetCodePtr() - start));
for (auto line : lines) { for (auto line : lines) {
+2 -2
View File
@@ -316,7 +316,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int
// Reset the code ptr (effectively undoing what we generated) and return zero to indicate that we failed. // Reset the code ptr (effectively undoing what we generated) and return zero to indicate that we failed.
ResetCodePtr(GetOffset(start)); ResetCodePtr(GetOffset(start));
char temp[1024]{}; char temp[1024]{};
dec.ToString(temp, true); dec.ToString(temp, sizeof(temp), true);
WARN_LOG(Log::G3D, "Could not compile vertex decoder, failed at step %s: %s", GetStepFunctionName(dec.steps_[i]), temp); WARN_LOG(Log::G3D, "Could not compile vertex decoder, failed at step %s: %s", GetStepFunctionName(dec.steps_[i]), temp);
return nullptr; return nullptr;
} }
@@ -349,7 +349,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int
if (log) { if (log) {
char temp[1024]{}; char temp[1024]{};
dec.ToString(temp, true); dec.ToString(temp, sizeof(temp), true);
INFO_LOG(Log::JIT, "=== %s (%d bytes) ===", temp, (int)(GetCodePtr() - start)); INFO_LOG(Log::JIT, "=== %s (%d bytes) ===", temp, (int)(GetCodePtr() - start));
std::vector<std::string> lines = DisassembleRV64(start, (int)(GetCodePtr() - start)); std::vector<std::string> lines = DisassembleRV64(start, (int)(GetCodePtr() - start));
for (auto line : lines) { for (auto line : lines) {
+2 -2
View File
@@ -281,8 +281,8 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int
EndWrite(); EndWrite();
// Reset the code ptr and return zero to indicate that we failed. // Reset the code ptr and return zero to indicate that we failed.
ResetCodePtr(GetOffset(start)); ResetCodePtr(GetOffset(start));
char temp[1024]{}; char temp[256]{};
dec.ToString(temp, true); dec.ToString(temp, sizeof(temp), true);
WARN_LOG(Log::G3D, "Could not compile vertex decoder, failed at step %s: %s", GetStepFunctionName(dec.steps_[i]), temp); WARN_LOG(Log::G3D, "Could not compile vertex decoder, failed at step %s: %s", GetStepFunctionName(dec.steps_[i]), temp);
return 0; return 0;
} }
+1 -1
View File
@@ -45,7 +45,7 @@ struct VulkanPipelineKey {
VulkanPipelineRasterStateKey raster; // prim is included here VulkanPipelineRasterStateKey raster; // prim is included here
VShaderID vid; VShaderID vid;
FShaderID fid; FShaderID fid;
uint32_t vtxFmtId; uint32_t vtxFmtId; // Note: This is the decoded format ID, not the source format!
bool useHWTransform; bool useHWTransform;
void ToString(std::string *str) const { void ToString(std::string *str) const {
+13
View File
@@ -510,6 +510,10 @@ const bool IsSameExceptForShaders(const VulkanPipelineKey &a, const VulkanPipeli
return a.useHWTransform == b.useHWTransform && a.raster == b.raster && a.vtxFmtId == b.vtxFmtId; return a.useHWTransform == b.useHWTransform && a.raster == b.raster && a.vtxFmtId == b.vtxFmtId;
} }
const bool IsSameExceptForVertexFormat(const VulkanPipelineKey &a, const VulkanPipelineKey &b) {
return a.useHWTransform == b.useHWTransform && a.raster == b.raster && a.vid == b.vid && a.fid == b.fid;
}
void ShaderListScreen::AddPipelineAnalysis(UI::LinearLayout *tabContent) { void ShaderListScreen::AddPipelineAnalysis(UI::LinearLayout *tabContent) {
#if !PPSSPP_PLATFORM(UWP) #if !PPSSPP_PLATFORM(UWP)
GPU_Vulkan *vgpu = dynamic_cast<GPU_Vulkan *>(gpu); GPU_Vulkan *vgpu = dynamic_cast<GPU_Vulkan *>(gpu);
@@ -538,6 +542,15 @@ void ShaderListScreen::AddPipelineAnalysis(UI::LinearLayout *tabContent) {
toBeMerged += "FShader: " + keyA.fid.Description() + " vs " + keyB.fid.Description() + "\n"; toBeMerged += "FShader: " + keyA.fid.Description() + " vs " + keyB.fid.Description() + "\n";
} }
} }
if (IsSameExceptForVertexFormat(keyA, keyB)) {
if (keyA.vtxFmtId != 0 && keyB.vtxFmtId != 0 && keyA.vtxFmtId != keyB.vtxFmtId) {
DecVtxFormat fmtA;
DecVtxFormat fmtB;
fmtA.InitializeFromID(keyA.vtxFmtId);
fmtB.InitializeFromID(keyB.vtxFmtId);
toBeMerged += "VertexFmt: " + fmtA.ToString() + " vs " + fmtB.ToString() + "\n";
}
}
} }
if (!toBeMerged.empty()) { if (!toBeMerged.empty()) {
tabContent->Add(new UI::TextView(keys[i].GetDescription(SHADER_STRING_SHORT_DESC), FLAG_DYNAMIC_ASCII | FLAG_WRAP_TEXT, true)); tabContent->Add(new UI::TextView(keys[i].GetDescription(SHADER_STRING_SHORT_DESC), FLAG_DYNAMIC_ASCII | FLAG_WRAP_TEXT, true));