From 28b2b1793b23b79c919ca0f7a124a9d8182ea089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 10 Jul 2026 13:45:43 +0200 Subject: [PATCH] Pipeline checker: Add support for checking for identical pipelines with different vertex formats This turned out not to be very fruitful. --- GPU/Common/DrawEngineCommon.cpp | 2 +- GPU/Common/VertexDecoderArm.cpp | 4 +- GPU/Common/VertexDecoderArm64.cpp | 4 +- GPU/Common/VertexDecoderCommon.cpp | 91 ++++++++++++++++++------- GPU/Common/VertexDecoderCommon.h | 10 ++- GPU/Common/VertexDecoderLoongArch64.cpp | 4 +- GPU/Common/VertexDecoderRiscV.cpp | 4 +- GPU/Common/VertexDecoderX86.cpp | 4 +- GPU/Vulkan/PipelineManagerVulkan.h | 2 +- UI/DevScreens.cpp | 13 ++++ 10 files changed, 98 insertions(+), 40 deletions(-) diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index 5cfc1389dd..1d6e6ce83c 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -999,7 +999,7 @@ bool DrawEngineCommon::DescribeCodePtr(const u8 *ptr, std::string &name) const { if (found) { char temp[256]; - found->ToString(temp, false); + found->ToString(temp, sizeof(temp), false); name = temp; snprintf(temp, sizeof(temp), "_%08X", foundKey); name += temp; diff --git a/GPU/Common/VertexDecoderArm.cpp b/GPU/Common/VertexDecoderArm.cpp index cd0f68f59c..e377723a36 100644 --- a/GPU/Common/VertexDecoderArm.cpp +++ b/GPU/Common/VertexDecoderArm.cpp @@ -259,7 +259,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int // Reset the code ptr and return zero to indicate that we failed. ResetCodePtr(GetOffset(start)); 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); return 0; } @@ -287,7 +287,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int /* DisassembleArm(start, GetCodePtr() - start); char temp[1024] = {0}; - dec.ToString(temp, true); + dec.ToString(temp, sizeof(temp), true); INFO_LOG(Log::G3D, "%s", temp); */ diff --git a/GPU/Common/VertexDecoderArm64.cpp b/GPU/Common/VertexDecoderArm64.cpp index 0b58ee9843..655ac4d014 100644 --- a/GPU/Common/VertexDecoderArm64.cpp +++ b/GPU/Common/VertexDecoderArm64.cpp @@ -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. ResetCodePtr(GetOffset(start)); 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); return nullptr; } @@ -291,7 +291,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int if (log) { 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)); std::vector lines = DisassembleArm64(start, (int)(GetCodePtr() - start)); for (auto line : lines) { diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index f48e653bef..5673abf00c 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -23,6 +23,7 @@ #include "Common/CommonTypes.h" #include "Common/Data/Convert/ColorConv.h" +#include "Common/Data/Text/StringWriter.h" #include "Common/Math/CrossSIMD.h" #include "Common/Log.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() { 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) { 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); } @@ -1550,8 +1586,8 @@ void VertexDecoder::CompareToJit(const u8 *startPtr, u8 *decodedptr, int count, controlReader.Goto(i); jittedReader.Goto(i); if (!DecodedVertsAreSimilar(controlReader, jittedReader)) { - char name[512]{}; - ToString(name, true); + char name[256]{}; + ToString(name, sizeof(name), true); ERROR_LOG(Log::G3D, "Encountered vertexjit mismatch at %d/%d for %s", i, count, name); if (morphcount > 1) { 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 colnames[8] = { "", "?", "?", "?", "565", "5551", "4444", "8888" }; -int VertexDecoder::ToString(char *output, bool spaces) const { - 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)"); +// There's a direct ToString function for vertex types in the GPU debugger, GeDescribeVertexType. - 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) { size_t len = strlen(start); @@ -1635,17 +1675,16 @@ int VertexDecoder::ToString(char *output, bool spaces) const { } #ifdef _DEBUG - output += sprintf(output, " (%llu)", (long long)decodedCount); + w.F(" (%llu)", (long long)decodedCount); #endif - - return output - start; + return (int)w.size(); } std::string VertexDecoder::GetString(DebugShaderStringType stringType) const { char buffer[256]; switch (stringType) { case SHADER_STRING_SHORT_DESC: - ToString(buffer, true); + ToString(buffer, sizeof(buffer), true); return std::string(buffer); case SHADER_STRING_SOURCE_CODE: { diff --git a/GPU/Common/VertexDecoderCommon.h b/GPU/Common/VertexDecoderCommon.h index f7ae78503c..a7c1513114 100644 --- a/GPU/Common/VertexDecoderCommon.h +++ b/GPU/Common/VertexDecoderCommon.h @@ -46,7 +46,7 @@ // Keep this in 4 bits. enum { - DEC_NONE, + DEC_NONE = 0, DEC_FLOAT_1, DEC_FLOAT_2, DEC_FLOAT_3, @@ -63,6 +63,8 @@ enum { DEC_U16_4, }; +const char *DecFmtComponentToString(u8 fmt); + // DecVtxFormat - vertex formats for PC // Kind of like a D3D VertexDeclaration. // No morph support, that is taken care of by the VertexDecoder. @@ -81,6 +83,8 @@ struct DecVtxFormat { void InitializeFromID(uint32_t id); static u8 PosFmt() { return DEC_FLOAT_3; } + + std::string ToString() const; }; 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); } +std::string DecFmtIdToString(u32 id); + class VertexDecoder { public: // A jit cache is not mandatory. @@ -258,7 +264,7 @@ public: // output must be big for safety. // Returns number of chars written. // 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 { return ptr >= (const uint8_t *)jitted_ && ptr < ((const uint8_t *)jitted_ + jittedSize_); diff --git a/GPU/Common/VertexDecoderLoongArch64.cpp b/GPU/Common/VertexDecoderLoongArch64.cpp index ba5ba90433..7b622cdf1a 100644 --- a/GPU/Common/VertexDecoderLoongArch64.cpp +++ b/GPU/Common/VertexDecoderLoongArch64.cpp @@ -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. ResetCodePtr(GetOffset(start)); 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); return nullptr; } @@ -338,7 +338,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int if (log) { char temp[1024]{}; - dec.ToString(temp, true); + dec.ToString(temp, sizeof(temp), true); INFO_LOG(Log::JIT, "=== %s (%d bytes) ===", temp, (int)(GetCodePtr() - start)); std::vector lines = DisassembleLA64(start, (int)(GetCodePtr() - start)); for (auto line : lines) { diff --git a/GPU/Common/VertexDecoderRiscV.cpp b/GPU/Common/VertexDecoderRiscV.cpp index 6a1444deb4..0bc5bd59cb 100644 --- a/GPU/Common/VertexDecoderRiscV.cpp +++ b/GPU/Common/VertexDecoderRiscV.cpp @@ -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. ResetCodePtr(GetOffset(start)); 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); return nullptr; } @@ -349,7 +349,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int if (log) { char temp[1024]{}; - dec.ToString(temp, true); + dec.ToString(temp, sizeof(temp), true); INFO_LOG(Log::JIT, "=== %s (%d bytes) ===", temp, (int)(GetCodePtr() - start)); std::vector lines = DisassembleRV64(start, (int)(GetCodePtr() - start)); for (auto line : lines) { diff --git a/GPU/Common/VertexDecoderX86.cpp b/GPU/Common/VertexDecoderX86.cpp index 765a5587da..38e7043adb 100644 --- a/GPU/Common/VertexDecoderX86.cpp +++ b/GPU/Common/VertexDecoderX86.cpp @@ -281,8 +281,8 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int EndWrite(); // Reset the code ptr and return zero to indicate that we failed. ResetCodePtr(GetOffset(start)); - char temp[1024]{}; - dec.ToString(temp, true); + char temp[256]{}; + 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); return 0; } diff --git a/GPU/Vulkan/PipelineManagerVulkan.h b/GPU/Vulkan/PipelineManagerVulkan.h index e5c43c066a..6f908a1c49 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.h +++ b/GPU/Vulkan/PipelineManagerVulkan.h @@ -45,7 +45,7 @@ struct VulkanPipelineKey { VulkanPipelineRasterStateKey raster; // prim is included here VShaderID vid; FShaderID fid; - uint32_t vtxFmtId; + uint32_t vtxFmtId; // Note: This is the decoded format ID, not the source format! bool useHWTransform; void ToString(std::string *str) const { diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 6398553f80..de8cc80534 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -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; } +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) { #if !PPSSPP_PLATFORM(UWP) GPU_Vulkan *vgpu = dynamic_cast(gpu); @@ -538,6 +542,15 @@ void ShaderListScreen::AddPipelineAnalysis(UI::LinearLayout *tabContent) { 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()) { tabContent->Add(new UI::TextView(keys[i].GetDescription(SHADER_STRING_SHORT_DESC), FLAG_DYNAMIC_ASCII | FLAG_WRAP_TEXT, true));