From bb573e6e0cfd02717d71331ab46c97a5d1e3d537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 1 Jun 2026 21:49:19 +0200 Subject: [PATCH] Well, it builds, but doesn't work yet. --- Common/Math/CrossSIMD.h | 10 +- GPU/Common/GPUDebugInterface.h | 18 +- GPU/Common/ShaderUniforms.cpp | 4 +- GPU/Common/SoftwareTransformCommon.cpp | 274 ++++++++++++++++--------- GPU/Common/SoftwareTransformCommon.h | 15 +- GPU/Debugger/State.cpp | 60 +++--- GPU/Debugger/State.h | 8 +- GPU/GPUCommon.cpp | 37 ++-- GPU/GPUCommon.h | 10 +- GPU/GPUStateSIMDUtil.h | 13 +- GPU/Software/SoftGpu.cpp | 8 +- GPU/Software/SoftGpu.h | 2 +- GPU/Software/TransformUnit.cpp | 5 +- GPU/Software/TransformUnit.h | 2 +- UI/ImDebugger/ImDebugger.h | 2 + UI/ImDebugger/ImGe.cpp | 218 ++++++++++++-------- UI/ImDebugger/ImGe.h | 3 +- UI/ImDebugger/ImJitViewer.cpp | 2 +- Windows/GEDebugger/TabState.cpp | 6 +- Windows/GEDebugger/TabVertices.cpp | 6 +- Windows/GEDebugger/TabVertices.h | 1 + Windows/GEDebugger/VertexPreview.cpp | 13 +- 22 files changed, 454 insertions(+), 263 deletions(-) diff --git a/Common/Math/CrossSIMD.h b/Common/Math/CrossSIMD.h index 00e9c1646b..fe3f53237c 100644 --- a/Common/Math/CrossSIMD.h +++ b/Common/Math/CrossSIMD.h @@ -245,9 +245,7 @@ struct Vec4F32 { return Vec4F32{ _mm_or_ps(_mm_and_ps(value, _mm_load_ps((const float *)mask)), _mm_load_ps(onelane3)) }; } - static Vec4F32 LoadF24x3_DontCare(const uint32_t *src) { - alignas(16) static const uint32_t mask[4] = {0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0}; - + static Vec4F32 LoadF24x4(const uint32_t *src) { return Vec4F32{_mm_castsi128_ps(_mm_slli_epi32(_mm_loadu_si128((const __m128i *)src), 8))}; } @@ -619,7 +617,7 @@ struct Vec4F32 { static Vec4F32 LoadF24x3_One(const uint32_t *src) { return Vec4F32{ vsetq_lane_f32(1.0f, vreinterpretq_f32_u32(vshlq_n_u32(vld1q_u32(src), 8)), 3) }; } - static Vec4F32 LoadF24x3_DontCare(const uint32_t *src) { + static Vec4F32 LoadF24x4(const uint32_t *src) { return Vec4F32{vreinterpretq_f32_u32(vshlq_n_u32(vld1q_u32(src), 8))}; } @@ -1082,7 +1080,7 @@ struct Vec4F32 { value = __lsx_vinsgr2vr_w(value, kOneF32Bits, 3); return Vec4F32{ (__m128)value }; } - static Vec4F32 LoadF24x3_DontCare(const uint32_t *src) { + static Vec4F32 LoadF24x4(const uint32_t *src) { return Vec4F32{(__m128)__lsx_vslli_w(__lsx_vld(src, 0), 8)}; } @@ -1530,7 +1528,7 @@ struct Vec4F32 { return temp; } - static Vec4F32 LoadF24x3_DontCare(const uint32_t *src) { + static Vec4F32 LoadF24x4(const uint32_t *src) { return LoadR24x3_One(src); } diff --git a/GPU/Common/GPUDebugInterface.h b/GPU/Common/GPUDebugInterface.h index 20e32432a5..ec75baaddb 100644 --- a/GPU/Common/GPUDebugInterface.h +++ b/GPU/Common/GPUDebugInterface.h @@ -202,18 +202,34 @@ private: int scaleFactor_ = 0; }; +// TODO: Replace with TransformedVertex? struct GPUDebugVertex { float u; float v; + float x; float y; float z; - u8 c[4]; + float w; + + union { + u8 c[4]; + u32 color0_32; + }; + + float fog; + float nx; float ny; float nz; }; +enum class DebugVertexFlags { + Transformed = 1 << 0, + Clipped = 1 << 1, +}; +ENUM_CLASS_BITOPS(DebugVertexFlags); + class StringWriter; class GPUCommon; diff --git a/GPU/Common/ShaderUniforms.cpp b/GPU/Common/ShaderUniforms.cpp index 85647a2ec9..2d9ba0cd9a 100644 --- a/GPU/Common/ShaderUniforms.cpp +++ b/GPU/Common/ShaderUniforms.cpp @@ -97,8 +97,8 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool useBuffe if (dirtyUniforms & DIRTY_VIEWPORT_UNIFORMS) { // TODO: This should be a couple of SIMD instructions. - Vec4F32 vpScale = Vec4F32::LoadF24x3_DontCare(&gstate.viewportxscale); - Vec4F32 vpOffset = Vec4F32::LoadF24x3_DontCare(&gstate.viewportxcenter); + Vec4F32 vpScale = Vec4F32::LoadF24x4(&gstate.viewportxscale); + Vec4F32 vpOffset = Vec4F32::LoadF24x4(&gstate.viewportxcenter); vpScale.Store(ub->vpScale); vpOffset.Store(ub->vpOffset); ub->NaN = std::numeric_limits::quiet_NaN(); // Used in the shader for range culling. diff --git a/GPU/Common/SoftwareTransformCommon.cpp b/GPU/Common/SoftwareTransformCommon.cpp index 09a4bc2a1c..a88cca06c3 100644 --- a/GPU/Common/SoftwareTransformCommon.cpp +++ b/GPU/Common/SoftwareTransformCommon.cpp @@ -418,8 +418,8 @@ static void ProjectVertices(const GPUgstate &gstate, TransformedVertex *transfor transformed[i].z = xyz.z; } #else - const Vec4F32 vpOffset = GetViewportOffsetVec(gstate); - const Vec4F32 vpScale = GetViewportScaleVec(gstate); + const Vec4F32 vpOffset = LoadViewportOffsetVec(gstate); + const Vec4F32 vpScale = LoadViewportScaleVec(gstate); // CrossSIMD implementation. for (int i = 0; i < vertexCount; i++) { Vec4F32 xyzw = Vec4F32::Load(&transformed[i].x); @@ -461,9 +461,8 @@ inline void LerpTransformedVertex(TransformedVertex *dest, TransformedVertex &a, static void ClipTrianglesAgainstNearPlane( TransformedVertex *transformed, int &transformedCount, int maxTransformed, u16 *indicesIn, int numIndicesIn, - u16 *indicesOut, int &numIndicesOut, int maxIndicesOut + u16 *indicesOut, int &numIndicesOut, int maxIndicesOut, TransformStats *stats ) { - int clipCount = 0; // Process one triangle (3 indices) at a time for (size_t i = 0; i < numIndicesIn; i += 3) { const u16 idx0 = indicesIn[i]; @@ -494,16 +493,18 @@ static void ClipTrianglesAgainstNearPlane( // Case 2: Entirely clipped / behind near plane else if (insideCount == 0) { // Cull, no clipping needed. + stats->culledTrianglesNear++; continue; } // Case 3: Entirely beyond far plane else if (insideFarCount == 0) { // All are beyond the far plane. Cull. + stats->culledTrianglesFar++; continue; } // Case 3: Partially clipped else { - clipCount++; + stats->clippedTriangles++; // We need to organize vertices cleanly to calculate intersections. // We will create a local polygon array of the inside/outside states. @@ -591,7 +592,6 @@ static SoftwareTransformAction ProjectClipAndExpand(SoftwareTransformParams &par bool throughmode = (vertType & GE_VTYPE_THROUGH_MASK) != 0; // Step 2: expand and process primitives. - result->drawBuffer = transformed; int drawIndexCount = 0; // NOTE: ExpandRectanges/lines/etc should do clipping while they're at it. @@ -607,6 +607,7 @@ static SoftwareTransformAction ProjectClipAndExpand(SoftwareTransformParams &par result->drawVertexCount = 0; result->drawIndexCount = 0; result->pixelMapped = false; + result->drawBuffer = nullptr; return SW_CULLED; } result->drawBuffer = transformedExpanded; @@ -635,6 +636,7 @@ static SoftwareTransformAction ProjectClipAndExpand(SoftwareTransformParams &par if (!ExpandPoints(vertexCount, numDecodedVerts, vertsSize, inds, indsSize, transformed, transformedExpanded, &drawIndexCount, throughmode)) { result->drawVertexCount = 0; result->drawIndexCount = 0; + result->drawBuffer = nullptr; return SW_CULLED; } result->drawBuffer = transformedExpanded; @@ -650,16 +652,21 @@ static SoftwareTransformAction ProjectClipAndExpand(SoftwareTransformParams &par if (!ExpandLines(vertexCount, numDecodedVerts, vertsSize, inds, indsSize, transformed, transformedExpanded, &drawIndexCount, throughmode)) { result->drawVertexCount = 0; result->drawIndexCount = 0; + result->drawBuffer = nullptr; return SW_CULLED; } result->drawBuffer = transformedExpanded; } else if (prim == GE_PRIM_TRIANGLES) { // Triangles. We can simply draw the unexpanded buffer, although we do also take the opportunity to perform culling. + result->drawBuffer = transformed; + // We might actually write more vertics at the end of transformed. + drawIndexCount = vertexCount; result->pixelMapped = false; if (throughmode) { - // Nothing to do, pass the vertices right through as-is. Well, we can go look for pixel mapping, but we don't do any culling or clipping. + // Nothing to do, pass the vertices right through as-is. Well, we can go look for pixel mapping, + // but we don't do any projection, culling or clipping. if (g_Config.bSmart2DTexFiltering && !gstate_c.textureIsVideo) { // We check some common cases for pixel mapping. // TODO: It's not really optimal that some previous step has removed the triangle strip. @@ -711,7 +718,7 @@ static SoftwareTransformAction ProjectClipAndExpand(SoftwareTransformParams &par if (gstate.isDepthClipEnabled()) { const u16 *indsIn = (const u16 *)inds; int newIndexCount = 0; - ClipTrianglesAgainstNearPlane(transformed, numDecodedVerts, 65536, inds, vertexCount, indsOut, newIndexCount, 65336); + ClipTrianglesAgainstNearPlane(transformed, numDecodedVerts, 65536, inds, vertexCount, indsOut, newIndexCount, 65336, &result->stats); drawIndexCount = newIndexCount; } else { std::vector outsideZ; @@ -1300,127 +1307,202 @@ static Vec3f ScreenToDrawing(const Vec3f& coords) { return ret; } -// TODO: This probably is not the best interface. // drawEngine is just for the vertex decoder lookup. // This is really just for vertex preview in the debugger, not for actual rendering! -bool GetCurrentDrawAsDebugVertices(DrawEngineCommon *drawEngine, int count, std::vector &vertices, std::vector &indices) { +// TODO: Support tessellation!! That's currently entirely broken (I guess maybe we'll draw the control points as something). +// count is the input vertex count (describes the draw together with prim). +bool GetCurrentDrawAsDebugVertices(DrawEngineCommon *drawEngine, GEPrimitiveType prim, GEPrimitiveType *outPrim, int count, std::vector &debugVertices, std::vector &debugIndices, int *outLowerIndexBound, TransformStats *stats, DebugVertexFlags flags) { // This is always for the current vertices. u16 indexLowerBound = 0; u16 indexUpperBound = count - 1; - if (!Memory::IsValidAddress(gstate_c.vertexAddr) || count == 0) + if (!Memory::IsValidAddress(gstate_c.vertexAddr) || count == 0) { return false; + } + if ((gstate.vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE && !Memory::IsValidAddress(gstate_c.indexAddr)) { + return false; + } + + const u32 vertTypeID = GetVertTypeID(gstate.vertType, gstate.getUVGenMode(), true); bool savedVertexFullAlpha = gstate_c.vertexFullAlpha; - if ((gstate.vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) { - const u8 *inds = Memory::GetPointer(gstate_c.indexAddr); - const u16_le *inds16 = (const u16_le *)inds; - const u32_le *inds32 = (const u32_le *)inds; + // Points is the only primitive that generates 6x as many vertices as input indices (2 triangles per point). + std::vector indexTemp; + indexTemp.resize(65536); // (prim == GEPrimitiveType::GE_PRIM_POINTS ? count * 6 : count * 3) * 4); - if (inds) { - GetIndexBounds(inds, count, gstate.vertType, &indexLowerBound, &indexUpperBound); - indices.resize(count); - switch (gstate.vertType & GE_VTYPE_IDX_MASK) { - case GE_VTYPE_IDX_8BIT: - for (int i = 0; i < count; ++i) { - indices[i] = inds[i]; - } - break; - case GE_VTYPE_IDX_16BIT: - for (int i = 0; i < count; ++i) { - indices[i] = inds16[i]; - } - break; - case GE_VTYPE_IDX_32BIT: - for (int i = 0; i < count; ++i) { - // These are rare. Only the bottom 16 bits are used. - indices[i] = (u16)inds32[i]; - } - break; - } + // First, inspect the indices to find the range we need to decode. + const u8 *indsPtr = Memory::GetPointerUnchecked(gstate_c.indexAddr); + if ((vertTypeID & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) { + const u16_le *inds16 = (const u16_le *)indsPtr; + const u32_le *inds32 = (const u32_le *)indsPtr; + if (indsPtr) { + GetIndexBounds(indsPtr, count, vertTypeID, &indexLowerBound, &indexUpperBound); } else { - indices.clear(); + // Bad index buffer. + return false; } } else { - indices.clear(); + // No indices, so we just use the count as the upper bound. + indexUpperBound = count - 1; + indexLowerBound = 0; } - static std::vector temp_buffer; - static std::vector simpleVertices; - temp_buffer.resize(std::max((int)indexUpperBound, 8192) * 128 / sizeof(u32)); - simpleVertices.resize(indexUpperBound + 1); + int verticesToDecode = indexUpperBound + 1 - indexLowerBound; - // We always want "applyskinindecode" here, faster than letting NormalizeVertices handle it. - const u32 vertTypeID = GetVertTypeID(gstate.vertType, gstate.getUVGenMode(), true); + const u8 *verts = Memory::GetPointerUnchecked(gstate_c.vertexAddr); + + std::vector vertsTemp; + + // Next, run the vertex decoder. We enforce software skinning here. VertexDecoder *dec = drawEngine->GetVertexDecoder(vertTypeID); - NormalizeVertices(&simpleVertices[0], (u8 *)(&temp_buffer[0]), Memory::GetPointerUnchecked(gstate_c.vertexAddr), indexLowerBound, indexUpperBound, dec, gstate.vertType); + const int stride = (int)dec->GetDecVtxFmt().stride; + vertsTemp.resize(stride * verticesToDecode + 32); // Add some padding bytes for "over-writes". - float world[16]; - float view[16]; - float worldview[16]; - float worldviewproj[16]; - ConvertMatrix4x3To4x4(world, gstate.worldMatrix); - ConvertMatrix4x3To4x4(view, gstate.viewMatrix); - Matrix4ByMatrix4(worldview, world, view); - Matrix4ByMatrix4(worldviewproj, worldview, gstate.projMatrix); + UVScale uvScale{}; + LoadUVScaleOffsetVec(gstate).Store(&uvScale.uScale); - // This transforms the vertices. - // NOTE: We really should just run the full software transform? + const u8 *startPos = verts + indexLowerBound * dec->VertexSize(); + dec->DecodeVerts(vertsTemp.data(), startPos, &uvScale, verticesToDecode); - vertices.resize(indexUpperBound + 1); - uint32_t vertType = gstate.vertType; - for (int i = indexLowerBound; i <= indexUpperBound; ++i) { - const SimpleVertex &vert = simpleVertices[i]; + int numDecodedVerts = verticesToDecode; + u16 *inds = indexTemp.data(); - if ((vertType & GE_VTYPE_THROUGH) != 0) { - if (vertType & GE_VTYPE_TC_MASK) { - vertices[i].u = vert.uv[0]; - vertices[i].v = vert.uv[1]; + if (!(flags & DebugVertexFlags::Transformed)) { + // Output the untransformed vertices (although with skinning and morph baked-in from decode), + // and the original indices. Might be interesting to look at. + debugVertices.resize(verticesToDecode); + VertexReader reader(vertsTemp.data(), dec->GetDecVtxFmt(), vertTypeID); + + const u8 defaultColor[4] = { + (u8)gstate.getMaterialAmbientR(), + (u8)gstate.getMaterialAmbientG(), + (u8)gstate.getMaterialAmbientB(), + (u8)gstate.getMaterialAmbientA(), + }; + + for (int i = 0; i < verticesToDecode; i++) { + reader.Goto(i); + GPUDebugVertex &sv = debugVertices[i]; + if (vertTypeID & GE_VTYPE_TC_MASK) { + reader.ReadUV(&sv.u); } else { - vertices[i].u = 0.0f; - vertices[i].v = 0.0f; + sv.u = 0.0f; + sv.v = 0.0f; } - vertices[i].x = vert.pos.x; - vertices[i].y = vert.pos.y; - vertices[i].z = vert.pos.z; - if (vertType & GE_VTYPE_COL_MASK) { - memcpy(vertices[i].c, vert.color, sizeof(vertices[i].c)); + if (vertTypeID & GE_VTYPE_COL_MASK) { + sv.color0_32 = reader.ReadColor0_8888(); } else { - memset(vertices[i].c, 0, sizeof(vertices[i].c)); + memcpy(sv.c, defaultColor, 4); } - vertices[i].nx = 0.0f; // No meaningful normals in through mode - vertices[i].ny = 0.0f; - vertices[i].nz = 1.0f; - } else { - float clipPos[4]; - Vec3ByMatrix44(clipPos, vert.pos.AsArray(), worldviewproj); - Vec3f screenPos = ClipToScreen(clipPos); - Vec3f drawPos = ScreenToDrawing(screenPos); - if (vertType & GE_VTYPE_TC_MASK) { - vertices[i].u = vert.uv[0] * (float)gstate.getTextureWidth(0); - vertices[i].v = vert.uv[1] * (float)gstate.getTextureHeight(0); + if (vertTypeID & GE_VTYPE_NRM_MASK) { + reader.ReadNrm((float *)&sv.nx); } else { - vertices[i].u = 0.0f; - vertices[i].v = 0.0f; + sv.nx = 0.0f; + sv.ny = 0.0f; + sv.nz = 1.0f; } - // Should really have separate coordinates for before and after transform. - vertices[i].x = drawPos.x; - vertices[i].y = drawPos.y; - vertices[i].z = drawPos.z; - if (vertType & GE_VTYPE_COL_MASK) { - memcpy(vertices[i].c, vert.color, sizeof(vertices[i].c)); - } else { - memset(vertices[i].c, 0, sizeof(vertices[i].c)); - } - vertices[i].nx = vert.nrm.x; - vertices[i].ny = vert.nrm.y; - vertices[i].nz = vert.nrm.z; + reader.ReadPosAuto((float *)&sv.x); } + + // Output the indices straight + switch (vertTypeID & GE_VTYPE_IDX_MASK) { + case GE_VTYPE_IDX_NONE: + debugIndices.clear(); // it's just a sequence, effectively. + break; + case GE_VTYPE_IDX_8BIT: + debugIndices.resize(verticesToDecode); + for (int i = 0; i < verticesToDecode; i++) { + debugIndices[i] = ((const u8 *)indsPtr)[i]; + } + break; + case GE_VTYPE_IDX_16BIT: + debugIndices.resize(verticesToDecode); + for (int i = 0; i < verticesToDecode; i++) { + debugIndices[i] = ((const u16_le *)indsPtr)[i]; + } + break; + case GE_VTYPE_IDX_32BIT: + debugIndices.resize(verticesToDecode); + for (int i = 0; i < verticesToDecode; i++) { + debugIndices[i] = ((const u32_le *)indsPtr)[i]; + } + break; + } + + *outLowerIndexBound = indexLowerBound; + *outPrim = prim; // before it changes. + return true; } + IndexGenerator indexGen; + indexGen.Setup(indexTemp.data()); + const int indexOffset = -indexLowerBound; + + // This corresponds to DecodeInds in DrawEngineCommon. + const bool clockwise = true; + switch ((vertTypeID & GE_VTYPE_IDX_MASK)) { + case GE_VTYPE_IDX_NONE: + indexGen.AddPrim(prim, count, indexOffset, true); + break; + case GE_VTYPE_IDX_8BIT: + indexGen.TranslatePrim(prim, count, (const u8 *)indsPtr, indexOffset, clockwise); + break; + case GE_VTYPE_IDX_16BIT: + indexGen.TranslatePrim(prim, count, (const u16_le *)indsPtr, indexOffset, clockwise); + break; + case GE_VTYPE_IDX_32BIT: + indexGen.TranslatePrim(prim, count, (const u32_le *)indsPtr, indexOffset, clockwise); + break; + } + + int generatedIndices = indexGen.VertexCount(); + + // We need two temp buffers, transformed and transformedExpanded (the latter is only used for non-triangle primitives). + std::vector transformed(65536); + std::vector transformedExpanded(65536); + // OK, time to run the software transform on these. + SoftwareTransformParams params{}; + SoftwareTransformResult result{}; + params.allowClear = false; + params.decoded = vertsTemp.data(); + params.transformed = transformed.data(); + params.transformedExpanded = transformedExpanded.data(); + + RunSoftwareTransform(params, prim, verticesToDecode, dec->GetDecVtxFmt(), numDecodedVerts, 65536, generatedIndices, inds, (int)indexTemp.size(), &result); + + // Output of software transform is always triangles. + prim = GE_PRIM_TRIANGLES; + + if (stats) { + *stats = result.stats; + } + // Convert the transformed outputs. + gstate_c.vertexFullAlpha = savedVertexFullAlpha; + // Supply indices in a correctly-sized vector. + debugIndices.resize(result.drawIndexCount); + memcpy(debugIndices.data(), inds, result.drawIndexCount * sizeof(u16)); + + // Convert the transformed vertices to the debug vertex format. + debugVertices.resize(result.drawVertexCount); + for (int i = 0; i < result.drawVertexCount; i++) { + const TransformedVertex &vtx = result.drawBuffer[i]; + GPUDebugVertex &dv = debugVertices[i]; + dv.x = vtx.x; + dv.y = vtx.y; + dv.z = vtx.z; + dv.w = vtx.pos_w; + dv.u = vtx.u; + dv.v = vtx.v; + dv.fog = vtx.fog; + dv.c[0] = (vtx.color0_32 >> 24) & 0xFF; + dv.c[1] = (vtx.color0_32 >> 16) & 0xFF; + dv.c[2] = (vtx.color0_32 >> 8) & 0xFF; + dv.c[3] = vtx.color0_32 & 0xFF; + } + *outPrim = prim; return true; } diff --git a/GPU/Common/SoftwareTransformCommon.h b/GPU/Common/SoftwareTransformCommon.h index dfb8cf63d1..aa9572870f 100644 --- a/GPU/Common/SoftwareTransformCommon.h +++ b/GPU/Common/SoftwareTransformCommon.h @@ -33,6 +33,12 @@ enum SoftwareTransformAction { SW_CULLED, // don't draw }; +struct TransformStats { + u16 culledTrianglesNear; + u16 culledTrianglesFar; + u16 clippedTriangles; // Only near plane is clipped against +}; + struct SoftwareTransformResult { u32 color; float depth; @@ -48,6 +54,8 @@ struct SoftwareTransformResult { int drawVertexCount; int drawIndexCount; bool pixelMapped; + + TransformStats stats; }; struct SoftwareTransformParams { @@ -66,10 +74,15 @@ void IndexBufferProvokingLastToFirst(int prim, u16 *inds, int indsSize); // indsSize is in indices, not bytes. // NOTE: In case of clipping, this might write extra vertices after params.transformed. +// NOTE2: The output is ALWAYS an indexed triangle list, no matter the input primitive. SoftwareTransformAction RunSoftwareTransform(SoftwareTransformParams ¶ms, int prim, u32 vertexType, const DecVtxFormat &decVtxFormat, int &numDecodedVerts, int vertsSize, int vertexCount, u16 *&inds, int indsSize, SoftwareTransformResult *result); class DrawEngineCommon; // Slow. See description in the cpp file. u32 NormalizeVertices(SimpleVertex *sverts, u8 *bufPtr, const u8 *inPtr, int lowerBound, int upperBound, const VertexDecoder *dec, u32 vertType); -bool GetCurrentDrawAsDebugVertices(DrawEngineCommon *drawEngine, int count, std::vector &vertices, std::vector &indices); + +// In the returned data, you should subtract the value of lowerIndexBound from the indices to get the actual vertex index in the vertices array. +// This is because some draws in some games use very large indices, but they only use a small range of them in each PRIM submission. +// Additionally, if the transformed flag is set in flags, the indices will be transformed into "generic" types (triangles instead of strips), etc. +bool GetCurrentDrawAsDebugVertices(DrawEngineCommon *drawEngine, GEPrimitiveType prim, GEPrimitiveType *outPrim, int count, std::vector &vertices, std::vector &indices, int *lowerIndexBound, TransformStats *stats, DebugVertexFlags flags); diff --git a/GPU/Debugger/State.cpp b/GPU/Debugger/State.cpp index 963a20554a..e2ff8aed15 100644 --- a/GPU/Debugger/State.cpp +++ b/GPU/Debugger/State.cpp @@ -12,7 +12,7 @@ #include "GPU/Common/SplineCommon.h" #include "Core/System.h" -void FormatStateRow(GPUCommon *gpudebug, char *dest, size_t destSize, CmdFormatType fmt, u32 value, bool enabled, u32 otherValue, u32 otherValue2) { +void FormatStateRow(char *dest, size_t destSize, CmdFormatType fmt, u32 value, bool enabled, u32 otherValue, u32 otherValue2) { value &= 0xFFFFFF; otherValue &= 0xFFFFFF; otherValue2 &= 0xFFFFFF; @@ -545,6 +545,7 @@ void FormatVertCol(char *dest, size_t destSize, const GPUDebugVertex &vert, int case VERTEXLIST_COL_X: snprintf(dest, destSize, "%f", vert.x); break; case VERTEXLIST_COL_Y: snprintf(dest, destSize, "%f", vert.y); break; case VERTEXLIST_COL_Z: snprintf(dest, destSize, "%f", vert.z); break; + case VERTEXLIST_COL_W: snprintf(dest, destSize, "%f", vert.w); break; case VERTEXLIST_COL_U: snprintf(dest, destSize, "%f", vert.u); break; case VERTEXLIST_COL_V: snprintf(dest, destSize, "%f", vert.v); break; case VERTEXLIST_COL_COLOR: @@ -553,6 +554,7 @@ void FormatVertCol(char *dest, size_t destSize, const GPUDebugVertex &vert, int case VERTEXLIST_COL_NX: snprintf(dest, destSize, "%f", vert.nx); break; case VERTEXLIST_COL_NY: snprintf(dest, destSize, "%f", vert.ny); break; case VERTEXLIST_COL_NZ: snprintf(dest, destSize, "%f", vert.nz); break; + case VERTEXLIST_COL_FOG: snprintf(dest, destSize, "%f", vert.fog); break; default: truncate_cpy(dest, destSize, "Invalid"); @@ -834,11 +836,13 @@ static void ExpandSpline(int &count, int op, const std::vector &si FreeAlignedMemory(cpoints.col); } -bool GetPrimPreview(u32 op, GEPrimitiveType &prim, std::vector &vertices, std::vector &indices, int &count) { +bool GetPrimPreview(u32 op, GEPrimitiveType &prim, std::vector &vertices, std::vector &indices, int *lowerIndexBound, bool transformed) { u32 prim_type = GE_PRIM_INVALID; int count_u = 0; int count_v = 0; + int count = 0; + const u32 cmd = op >> 24; if (cmd == GE_CMD_PRIM) { prim_type = (op >> 16) & 0x7; @@ -865,11 +869,35 @@ bool GetPrimPreview(u32 op, GEPrimitiveType &prim, std::vector & prim = static_cast(prim_type); - if (!gpu->GetCurrentDrawAsDebugVertices(count, vertices, indices)) { + DebugVertexFlags flags{}; + if (cmd == GE_CMD_PRIM) { + flags |= DebugVertexFlags::Clipped; + if (transformed) { + flags |= DebugVertexFlags::Transformed; + } + } else { + flags = DebugVertexFlags::Transformed; + } + + GEPrimitiveType outPrim; + if (!gpu->GetCurrentDrawAsDebugVertices(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) { static std::vector generatedVerts; static std::vector generatedInds; @@ -882,7 +910,8 @@ bool GetPrimPreview(u32 op, GEPrimitiveType &prim, std::vector & 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) { @@ -900,26 +929,7 @@ bool GetPrimPreview(u32 op, GEPrimitiveType &prim, std::vector & indices = generatedInds; } - if (prim == GE_PRIM_RECTANGLES) { - ExpandRectangles(vertices, indices, count, gpu->GetGState().isModeThrough()); - } - - // TODO: Probably there's a better way and place to do this. - u16 minIndex = 0; - u16 maxIndex = count - 1; - if (!indices.empty()) { - minIndex = 0xFFFF; - maxIndex = 0; - for (int i = 0; i < std::min((size_t)count, indices.size()); ++i) { - if (minIndex > indices[i]) { - minIndex = indices[i]; - } - if (maxIndex < indices[i]) { - maxIndex = indices[i]; - } - } - } - + /* auto wrapCoord = [](float &coord) { if (coord < 0.0f) { coord += ceilf(-coord); @@ -941,7 +951,7 @@ bool GetPrimPreview(u32 op, GEPrimitiveType &prim, std::vector & if (!clampT) wrapCoord(vertices[i].v); } - + */ return true; } diff --git a/GPU/Debugger/State.h b/GPU/Debugger/State.h index de65c77d6f..0e0e2d4063 100644 --- a/GPU/Debugger/State.h +++ b/GPU/Debugger/State.h @@ -14,17 +14,17 @@ enum VertexListCols { VERTEXLIST_COL_X, VERTEXLIST_COL_Y, VERTEXLIST_COL_Z, + VERTEXLIST_COL_W, VERTEXLIST_COL_U, VERTEXLIST_COL_V, VERTEXLIST_COL_COLOR, VERTEXLIST_COL_NX, VERTEXLIST_COL_NY, VERTEXLIST_COL_NZ, + VERTEXLIST_COL_FOG, VERTEXLIST_COL_COUNT, }; -class GPUCommon; - extern const GECommand g_stateFlagsRows[]; extern const GECommand g_stateLightingRows[]; extern const GECommand g_stateTextureRows[]; @@ -37,11 +37,11 @@ extern const size_t g_stateSettingsRowsSize; struct GPUDebugVertex; class VertexDecoder; -void FormatStateRow(GPUCommon *debug, char *dest, size_t destSize, CmdFormatType fmt, u32 value, bool enabled, u32 otherValue, u32 otherValue2); +void FormatStateRow(char *dest, size_t destSize, CmdFormatType fmt, u32 value, bool enabled, u32 otherValue, u32 otherValue2); void FormatVertCol(char *dest, size_t destSize, const GPUDebugVertex &vert, int col); void FormatVertColRaw(VertexDecoder *decoder, char *dest, size_t destSize, int row, int col); // These are utilities used by the debugger vertex preview. -bool GetPrimPreview(u32 op, GEPrimitiveType &prim, std::vector &vertices, std::vector &indices, int &count); +bool GetPrimPreview(u32 op, GEPrimitiveType &prim, std::vector &vertices, std::vector &indices, int *lowerIndexBound, bool transformed); void DescribePixel(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); void DescribePixelRGBA(u32 pix, GPUDebugBufferFormat fmt, int x, int y, char desc[256]); diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 535b2b823e..b7a315e905 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -1569,7 +1569,7 @@ void GPUCommon::SyncEnd(GPUSyncType waitType, int listid, bool wokeThreads) { } } -bool GPUCommon::GetCurrentDisplayList(DisplayList &list) { +bool GPUCommon::GetCurrentDisplayList(DisplayList &list) const { if (!currentList) { return false; } @@ -1577,25 +1577,31 @@ bool GPUCommon::GetCurrentDisplayList(DisplayList &list) { return true; } -int GPUCommon::GetCurrentPrimCount() { +int GPUCommon::GetCurrentPrimCount(GEPrimitiveType *prim) const { DisplayList list; + u32 cmd; if (GetCurrentDisplayList(list)) { - u32 cmd = Memory::Read_U32(list.pc); - if ((cmd >> 24) == GE_CMD_PRIM || (cmd >> 24) == GE_CMD_BOUNDINGBOX) { - return cmd & 0xFFFF; - } else if ((cmd >> 24) == GE_CMD_BEZIER || (cmd >> 24) == GE_CMD_SPLINE) { - u32 u = (cmd & 0x00FF) >> 0; - u32 v = (cmd & 0xFF00) >> 8; - return u * v; - } - return true; + cmd = Memory::Read_U32(list.pc); } else { // Current prim value. - return gstate.cmdmem[GE_CMD_PRIM] & 0xFFFF; + cmd = gstate.cmdmem[GE_CMD_PRIM]; + } + + if ((cmd >> 24) == GE_CMD_PRIM || (cmd >> 24) == GE_CMD_BOUNDINGBOX) { + *prim = GEPrimitiveType((cmd >> 16) & 7); + return cmd & 0xFFFF; + } else if ((cmd >> 24) == GE_CMD_BEZIER || (cmd >> 24) == GE_CMD_SPLINE) { + *prim = GE_PRIM_TRIANGLES; // no correct answer + u32 u = (cmd & 0x00FF) >> 0; + u32 v = (cmd & 0xFF00) >> 8; + return u * v; + } else { + // Unknown primitive. + return 0; } } -std::vector GPUCommon::ActiveDisplayLists() { +std::vector GPUCommon::ActiveDisplayLists() const { std::vector result; result.reserve(dlQueue.size()); @@ -2020,9 +2026,8 @@ bool GPUCommon::PerformWriteStencilFromMemory(u32 dest, int size, WriteStencil f return false; } -bool GPUCommon::GetCurrentDrawAsDebugVertices(int count, std::vector &vertices, std::vector &indices) { - gstate_c.UpdateUVScaleOffset(); - return ::GetCurrentDrawAsDebugVertices(drawEngineCommon_, count, vertices, indices); +bool GPUCommon::GetCurrentDrawAsDebugVertices(GEPrimitiveType prim, GEPrimitiveType *outPrim, int count, std::vector &vertices, std::vector &indices, int *lowerIndexBound, TransformStats *stats, DebugVertexFlags flags) const { + return ::GetCurrentDrawAsDebugVertices(drawEngineCommon_, prim, outPrim, count, vertices, indices, lowerIndexBound, stats, flags); } bool GPUCommon::DescribeCodePtr(const u8 *ptr, std::string &name) { diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 1b6deb630f..557fa2fb39 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -52,6 +52,7 @@ inline bool IsTrianglePrim(GEPrimitiveType prim) { return prim > GE_PRIM_LINE_STRIP && prim != GE_PRIM_RECTANGLES; } +struct TransformStats; class GPUCommon { public: // The constructor might run on the loader thread. @@ -72,9 +73,10 @@ public: virtual bool GetCurrentTexture(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) { return false; } virtual bool GetCurrentClut(GPUDebugBuffer &buffer) { return false;} virtual bool GetOutputFramebuffer(GPUDebugBuffer &buffer) { return false; } - bool GetCurrentDisplayList(DisplayList &list); - virtual bool GetCurrentDrawAsDebugVertices(int count, std::vector &vertices, std::vector &indices); - int GetCurrentPrimCount(); + + bool GetCurrentDisplayList(DisplayList &list) const; + virtual bool GetCurrentDrawAsDebugVertices(GEPrimitiveType prim, GEPrimitiveType *outPrim, int count, std::vector &vertices, std::vector &indices, int *lowerIndexBound, TransformStats *stats, DebugVertexFlags flags) const; + int GetCurrentPrimCount(GEPrimitiveType *prim) const; // FinishInitOnMainThread runs on the main thread, of course. virtual void FinishInitOnMainThread() {} @@ -207,7 +209,7 @@ public: virtual bool DescribeCodePtr(const u8 *ptr, std::string &name); - std::vector ActiveDisplayLists(); + std::vector ActiveDisplayLists() const; void ResetListPC(int listID, u32 pc); void ResetListStall(int listID, u32 stall); void ResetListState(int listID, DisplayListState state); diff --git a/GPU/GPUStateSIMDUtil.h b/GPU/GPUStateSIMDUtil.h index 6dfd110b0b..175944a60f 100644 --- a/GPU/GPUStateSIMDUtil.h +++ b/GPU/GPUStateSIMDUtil.h @@ -3,9 +3,14 @@ #include "Common/Math/CrossSIMD.h" #include "GPU/GPUState.h" -inline Vec4F32 GetViewportOffsetVec(const GPUgstate &gstate) { - return Vec4F32::LoadF24x3_DontCare(&gstate.viewportxcenter); +inline Vec4F32 LoadViewportOffsetVec(const GPUgstate &gstate) { + // We ignore the last member. + return Vec4F32::LoadF24x4(&gstate.viewportxcenter); } -inline Vec4F32 GetViewportScaleVec(const GPUgstate &gstate) { - return Vec4F32::LoadF24x3_DontCare(&gstate.viewportxscale); +inline Vec4F32 LoadViewportScaleVec(const GPUgstate &gstate) { + // We ignore the last member. + return Vec4F32::LoadF24x4(&gstate.viewportxscale); +} +inline Vec4F32 LoadUVScaleOffsetVec(const GPUgstate &gstate) { + return Vec4F32::LoadF24x4(&gstate.texscaleu); } diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index d1d683b182..f58f976338 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -1440,9 +1440,13 @@ bool SoftGPU::GetCurrentClut(GPUDebugBuffer &buffer) { return true; } -bool SoftGPU::GetCurrentDrawAsDebugVertices(int count, std::vector &vertices, std::vector &indices) { +bool SoftGPU::GetCurrentDrawAsDebugVertices(GEPrimitiveType prim, GEPrimitiveType *outPrim, int count, std::vector &vertices, std::vector &indices, int *lowerIndexBound, TransformStats *stats, DebugVertexFlags flags) const { gstate_c.UpdateUVScaleOffset(); - return drawEngine_->transformUnit.GetCurrentDrawAsDebugVertices(count, vertices, indices); + + // We just pad out the vertex array here. + *lowerIndexBound = 0; + *outPrim = prim; + return drawEngine_->transformUnit.GetCurrentDrawAsDebugVertices(count, vertices, indices, flags); } bool SoftGPU::DescribeCodePtr(const u8 *ptr, std::string &name) { diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index d1c0f62fe5..041a99b644 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -172,7 +172,7 @@ public: bool GetCurrentStencilbuffer(GPUDebugBuffer &buffer) override; bool GetCurrentTexture(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) override; bool GetCurrentClut(GPUDebugBuffer &buffer) override; - bool GetCurrentDrawAsDebugVertices(int count, std::vector &vertices, std::vector &indices) override; + bool GetCurrentDrawAsDebugVertices(GEPrimitiveType prim, GEPrimitiveType *outPrim, int count, std::vector &vertices, std::vector &indices, int *lowerIndexBound, TransformStats *stats, DebugVertexFlags flags) const override; bool DescribeCodePtr(const u8 *ptr, std::string &name) override; diff --git a/GPU/Software/TransformUnit.cpp b/GPU/Software/TransformUnit.cpp index 6f4ebc8d33..49b8bae762 100644 --- a/GPU/Software/TransformUnit.cpp +++ b/GPU/Software/TransformUnit.cpp @@ -909,9 +909,8 @@ void TransformUnit::NotifyClutUpdate(const void *src) { binner_->UpdateClut(src); } -// TODO: This probably is not the best interface. -// Also, we should try to merge this into the similar function in DrawEngineCommon. -bool TransformUnit::GetCurrentDrawAsDebugVertices(int count, std::vector &vertices, std::vector &indices) { +// We should try to merge this into the similar function in SoftwareTransformCommon. It's really backend-independent (or should be...) +bool TransformUnit::GetCurrentDrawAsDebugVertices(int count, std::vector &vertices, std::vector &indices, DebugVertexFlags flags) { // This is always for the current vertices. u16 indexLowerBound = 0; u16 indexUpperBound = count - 1; diff --git a/GPU/Software/TransformUnit.h b/GPU/Software/TransformUnit.h index 69fd6b0855..23038d756c 100644 --- a/GPU/Software/TransformUnit.h +++ b/GPU/Software/TransformUnit.h @@ -132,7 +132,7 @@ public: void SubmitPrimitive(const void* vertices, const void* indices, GEPrimitiveType prim_type, int vertex_count, u32 vertex_type, int *bytesRead, SoftwareDrawEngine *drawEngine); void SubmitImmVertex(const ClipVertexData &vert, SoftwareDrawEngine *drawEngine); - static bool GetCurrentDrawAsDebugVertices(int count, std::vector &vertices, std::vector &indices); + static bool GetCurrentDrawAsDebugVertices(int count, std::vector &vertices, std::vector &indices, DebugVertexFlags flags); void Flush(GPUCommon *common, const char *reason); void FlushIfOverlap(GPUCommon *common, const char *reason, bool modifying, uint32_t addr, uint32_t stride, uint32_t w, uint32_t h); diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index f2539d6d26..129e6e666c 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -121,6 +121,8 @@ struct ImConfig { int requesterToken; bool sasShowAllVoices = false; + bool vertexListTransformed = false; + bool vertexListClipped = false; float fbViewerZoom = 1.0f; diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index f02d664e1c..4ea4f8f5ec 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -10,6 +10,7 @@ #include "GPU/Common/FramebufferManagerCommon.h" #include "GPU/Common/TextureCacheCommon.h" #include "GPU/Common/VertexDecoderCommon.h" +#include "GPU/Common/SoftwareTransformCommon.h" #include "Core/HLE/sceDisplay.h" #include "Core/HW/Display.h" @@ -889,88 +890,109 @@ static const char *DLStateToString(DisplayListState state) { } } -static void DrawPreviewPrimitive(ImDrawList *drawList, ImVec2 p0, GEPrimitiveType prim, const std::vector &indices, const std::vector &verts, int count, bool uvToPos, float sx = 1.0f, float sy = 1.0f) { - if (count) { - auto x = [sx, uvToPos](const GPUDebugVertex &vert) { - return sx * (uvToPos ? vert.u : vert.x); - }; - auto y = [sy, uvToPos](const GPUDebugVertex &vert) { - return sy * (uvToPos ? vert.v : vert.y); - }; +// TODO: Backport this to the Win32 debugger (ugh). +static void DrawPreviewPrimitive(ImDrawList *drawList, ImVec2 p0, GEPrimitiveType prim, const std::vector &indices, const std::vector &verts, int indexOffset, bool transformed, bool uvToPos, float sx = 1.0f, float sy = 1.0f) { + auto x = [sx, uvToPos](const GPUDebugVertex &vert) { + return sx * (uvToPos ? vert.u : vert.x); + }; + auto y = [sy, uvToPos](const GPUDebugVertex &vert) { + return sy * (uvToPos ? vert.v : vert.y); + }; - // TODO: Maybe not the best idea to use the heavy AddTriangleFilled API instead of just adding raw triangles. - switch (prim) { - case GE_PRIM_TRIANGLES: - case GE_PRIM_RECTANGLES: - { - for (int i = 0; i < count - 2; i += 3) { - const auto &v1 = indices.empty() ? verts[i] : verts[indices[i]]; - const auto &v2 = indices.empty() ? verts[i + 1] : verts[indices[i + 1]]; - const auto &v3 = indices.empty() ? verts[i + 2] : verts[indices[i + 2]]; - drawList->AddTriangleFilled( - ImVec2(p0.x + x(v1), p0.y + y(v1)), - ImVec2(p0.x + x(v2), p0.y + y(v2)), - ImVec2(p0.x + x(v3), p0.y + y(v3)), ImColor(0x600000FF)); - } - break; + // TODO: Maybe not the best idea to use the heavy AddTriangleFilled API instead of just adding raw triangles. + + // TODO: If vertices are transformed, we can look for clipped triangles (by checking if any point equals w=-z) + // and color them differently. + + // TODO: If untransformed, we can apply a transformation here so we can let the user spin around the untransformed mesh data + // if desired. + + constexpr u32 defaultColor = 0x600000FF; + constexpr u32 clippedColor = 0x60FF0000; + + int count = indices.empty() ? verts.size() : indices.size(); + + switch (prim) { + case GE_PRIM_TRIANGLES: + { + for (int i = 0; i < count; i += 3) { + const auto &v1 = indices.empty() ? verts[i + 0] : verts[indices[indexOffset + i + 0]]; + const auto &v2 = indices.empty() ? verts[i + 1] : verts[indices[indexOffset + i + 1]]; + const auto &v3 = indices.empty() ? verts[i + 2] : verts[indices[indexOffset + i + 2]]; + drawList->AddTriangleFilled( + ImVec2(p0.x + x(v1), p0.y + y(v1)), + ImVec2(p0.x + x(v2), p0.y + y(v2)), + ImVec2(p0.x + x(v3), p0.y + y(v3)), ImColor(defaultColor)); } - case GE_PRIM_TRIANGLE_FAN: - { - for (int i = 0; i < count - 2; i++) { - const auto &v1 = indices.empty() ? verts[0] : verts[indices[0]]; - const auto &v2 = indices.empty() ? verts[i + 1] : verts[indices[i + 1]]; - const auto &v3 = indices.empty() ? verts[i + 2] : verts[indices[i + 2]]; - drawList->AddTriangleFilled( - ImVec2(p0.x + x(v1), p0.y + y(v1)), - ImVec2(p0.x + x(v2), p0.y + y(v2)), - ImVec2(p0.x + x(v3), p0.y + y(v3)), ImColor(0x600000FF)); - } - break; + break; + } + case GE_PRIM_RECTANGLES: + { + for (int i = 0; i < count - 2; i += 3) { + const auto &tl = indices.empty() ? verts[i] : verts[indices[indexOffset + i]]; + const auto &br = indices.empty() ? verts[i + 1] : verts[indices[indexOffset + i + 1]]; + drawList->AddRectFilled( + ImVec2(p0.x + x(tl), p0.y + y(tl)), + ImVec2(p0.x + x(br), p0.y + y(br)), ImColor(defaultColor)); } - case GE_PRIM_TRIANGLE_STRIP: - { - int t = 2; - for (int i = 0; i < count - 2; i++) { - int i0 = i; - int i1 = i + t; - int i2 = i + (t ^ 3); - const auto &v1 = indices.empty() ? verts[i0] : verts[indices[i0]]; - const auto &v2 = indices.empty() ? verts[i1] : verts[indices[i1]]; - const auto &v3 = indices.empty() ? verts[i2] : verts[indices[i2]]; - drawList->AddTriangleFilled( - ImVec2(p0.x + x(v1), p0.y + y(v1)), - ImVec2(p0.x + x(v2), p0.y + y(v2)), - ImVec2(p0.x + x(v3), p0.y + y(v3)), ImColor(0x600000FF)); - t ^= 3; - } - break; + break; + } + case GE_PRIM_TRIANGLE_FAN: + { + for (int i = 0; i < count - 2; i++) { + const auto &v1 = indices.empty() ? verts[0] : verts[indices[indexOffset + 0]]; + const auto &v2 = indices.empty() ? verts[i + 1] : verts[indices[indexOffset + i + 1]]; + const auto &v3 = indices.empty() ? verts[i + 2] : verts[indices[indexOffset + i + 2]]; + drawList->AddTriangleFilled( + ImVec2(p0.x + x(v1), p0.y + y(v1)), + ImVec2(p0.x + x(v2), p0.y + y(v2)), + ImVec2(p0.x + x(v3), p0.y + y(v3)), ImColor(defaultColor)); } - case GE_PRIM_LINES: - { - for (int i = 0; i < count - 1; i += 2) { - const auto &v1 = indices.empty() ? verts[i] : verts[indices[i]]; - const auto &v2 = indices.empty() ? verts[i + 1] : verts[indices[i + 1]]; - drawList->AddLine( - ImVec2(p0.x + x(v1), p0.y + y(v1)), - ImVec2(p0.x + x(v2), p0.y + y(v2)), ImColor(0x600000FF)); - } - break; + break; + } + case GE_PRIM_TRIANGLE_STRIP: + { + int t = 2; + for (int i = 0; i < count - 2; i++) { + int i0 = i; + int i1 = i + t; + int i2 = i + (t ^ 3); + const auto &v1 = indices.empty() ? verts[i0] : verts[indices[indexOffset + i0]]; + const auto &v2 = indices.empty() ? verts[i1] : verts[indices[indexOffset + i1]]; + const auto &v3 = indices.empty() ? verts[i2] : verts[indices[indexOffset + i2]]; + drawList->AddTriangleFilled( + ImVec2(p0.x + x(v1), p0.y + y(v1)), + ImVec2(p0.x + x(v2), p0.y + y(v2)), + ImVec2(p0.x + x(v3), p0.y + y(v3)), ImColor(defaultColor)); + t ^= 3; } - case GE_PRIM_LINE_STRIP: - { - for (int i = 0; i < count - 2; i++) { - const auto &v1 = indices.empty() ? verts[i] : verts[indices[i]]; - const auto &v2 = indices.empty() ? verts[i + 1] : verts[indices[i + 1]]; - drawList->AddLine( - ImVec2(p0.x + x(v1), p0.y + y(v1)), - ImVec2(p0.x + x(v2), p0.y + y(v2)), ImColor(0x600000FF)); - } - break; + break; + } + case GE_PRIM_LINES: + { + for (int i = 0; i < count - 1; i += 2) { + const auto &v1 = indices.empty() ? verts[i] : verts[indices[indexOffset + i]]; + const auto &v2 = indices.empty() ? verts[i + 1] : verts[indices[indexOffset + i + 1]]; + drawList->AddLine( + ImVec2(p0.x + x(v1), p0.y + y(v1)), + ImVec2(p0.x + x(v2), p0.y + y(v2)), ImColor(defaultColor)); } - default: - // TODO: Support lines etc. - break; + break; + } + case GE_PRIM_LINE_STRIP: + { + for (int i = 0; i < count - 2; i++) { + const auto &v1 = indices.empty() ? verts[i] : verts[indices[indexOffset + i]]; + const auto &v2 = indices.empty() ? verts[i + 1] : verts[indices[indexOffset + i + 1]]; + drawList->AddLine( + ImVec2(p0.x + x(v1), p0.y + y(v1)), + ImVec2(p0.x + x(v2), p0.y + y(v2)), ImColor(defaultColor)); } + break; + } + default: + // TODO: Support lines etc. + break; } } @@ -1197,7 +1219,7 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUCommon *gpuD case GE_CMD_PRIM: isOnPrim = true; if (reloadPreview_) { - GetPrimPreview(op, previewPrim_, previewVertices_, previewIndices_, previewCount_); + GetPrimPreview(op, previewPrim_, previewVertices_, previewIndices_, &previewIndexOffset_, previewTransformed_); reloadPreview_ = false; } break; @@ -1206,7 +1228,10 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUCommon *gpuD break; default: // Disable the current preview. - previewCount_ = 0; + previewVertices_.clear(); + previewIndices_.clear(); + previewIndexOffset_ = 0; + previewTransformed_ = true; break; } @@ -1291,7 +1316,7 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUCommon *gpuD } // Draw vertex preview on top! - DrawPreviewPrimitive(drawList, p0, previewPrim_, previewIndices_, previewVertices_, previewCount_, false, scale * previewZoom_, scale * previewZoom_); + DrawPreviewPrimitive(drawList, p0, previewPrim_, previewIndices_, previewVertices_, previewIndexOffset_, previewTransformed_, scale * previewZoom_, scale * previewZoom_); drawList->PopClipRect(); @@ -1340,7 +1365,9 @@ void ImGeDebuggerWindow::Draw(ImConfig &cfg, ImControl &control, GPUCommon *gpuD drawList->PushClipRect(p0, p1, true); ImGui::Image(texId, ImVec2(texW, texH)); - DrawPreviewPrimitive(drawList, p0, previewPrim_, previewIndices_, previewVertices_, previewCount_, true, texW, texH); + + // Show the preview as texcoords. TODO: We should probably use unclipped data here? + DrawPreviewPrimitive(drawList, p0, previewPrim_, previewIndices_, previewVertices_, previewIndexOffset_, previewTransformed_, texW, texH); drawList->PopClipRect(); @@ -1604,11 +1631,11 @@ void ImGeStateWindow::Draw(ImConfig &cfg, ImControl &control, GPUCommon *gpuDebu ImGui::SameLine(); ImGui::Text("w=%d", otherValue & 0xFFFF); } else { - FormatStateRow(gpuDebug, temp, sizeof(temp), info.fmt, value, true, otherValue, gstate.cmdmem[info.otherCmd2]); + FormatStateRow(temp, sizeof(temp), info.fmt, value, true, otherValue, gstate.cmdmem[info.otherCmd2]); ImGui::TextUnformatted(temp); } if (diff && ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { - FormatStateRow(gpuDebug, temp2, sizeof(temp2), info.fmt, lastState.cmdmem[info.cmd], true, lastState.cmdmem[info.otherCmd], lastState.cmdmem[info.otherCmd2]); + FormatStateRow(temp2, sizeof(temp2), info.fmt, lastState.cmdmem[info.cmd], true, lastState.cmdmem[info.otherCmd], lastState.cmdmem[info.otherCmd2]); ImGui::SetTooltip("Previous: %s", temp2); } } @@ -1681,18 +1708,38 @@ void DrawImGeVertsWindow(ImConfig &cfg, ImControl &control, GPUCommon *gpuDebug) ImGui::End(); return; } + + ImGui::Checkbox("Transformed", &cfg.vertexListTransformed); + ImGui::SameLine(); + ImGui::Checkbox("Clipped", &cfg.vertexListClipped); + const ImGuiTableFlags tableFlags = ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_ScrollY; + if (ImGui::BeginTabBar("vertexmode", ImGuiTabBarFlags_None)) { auto state = gpuDebug->GetGState(); char fmtTemp[256]; - FormatStateRow(gpuDebug, fmtTemp, sizeof(fmtTemp), CMD_FMT_VERTEXTYPE, state.vertType, true, false, false); + FormatStateRow(fmtTemp, sizeof(fmtTemp), CMD_FMT_VERTEXTYPE, state.vertType, true, false, false); ImGui::TextUnformatted(fmtTemp); + // Let's see if it's fast enough to just do all this each frame. - int rowCount_ = gpuDebug->GetCurrentPrimCount(); + GEPrimitiveType prim; + int rowCount_ = gpuDebug->GetCurrentPrimCount(&prim); std::vector vertices; std::vector indices; - if (!gpuDebug->GetCurrentDrawAsDebugVertices(rowCount_, vertices, indices)) { + int previewIndexOffset_ = 0; + + DebugVertexFlags flags{}; + if (cfg.vertexListTransformed) { + flags |= DebugVertexFlags::Transformed; + } + if (cfg.vertexListClipped) { + flags |= DebugVertexFlags::Clipped; + } + + // This performs software transform, if transformed is checked. We might want to cache it? Although, it's only for a single draw... + TransformStats stats; + if (!gpuDebug->GetCurrentDrawAsDebugVertices(prim, &prim, rowCount_, vertices, indices, &previewIndexOffset_, &stats, flags)) { rowCount_ = 0; } auto buildVertexTable = [&](bool raw) { @@ -1708,12 +1755,14 @@ void DrawImGeVertsWindow(ImConfig &cfg, ImControl &control, GPUCommon *gpuDebug) "X", "Y", "Z", + "W", "U", "V", "Color", "NX", "NY", "NZ", + "Fog", }; for (int i = 0; i < ARRAY_SIZE(colNames); i++) { ImGui::TableSetupColumn(colNames[i], ImGuiTableColumnFlags_WidthFixed, 0.0f, i); @@ -1756,6 +1805,7 @@ void DrawImGeVertsWindow(ImConfig &cfg, ImControl &control, GPUCommon *gpuDebug) ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Transformed")) { + ImGui::Text("Culled far: %d near: %d Clipped: %d", stats.culledTrianglesFar, stats.culledTrianglesNear, stats.clippedTriangles); buildVertexTable(false); ImGui::EndTabItem(); } diff --git a/UI/ImDebugger/ImGe.h b/UI/ImDebugger/ImGe.h index 8c0b64c402..c114ed92e9 100644 --- a/UI/ImDebugger/ImGe.h +++ b/UI/ImDebugger/ImGe.h @@ -161,7 +161,8 @@ private: GEPrimitiveType previewPrim_ = GEPrimitiveType::GE_PRIM_TRIANGLES; std::vector previewIndices_; std::vector previewVertices_; - int previewCount_ = 0; + int previewIndexOffset_ = 0; + bool previewTransformed_ = true; Draw::Aspect selectedAspect_; float previewZoom_ = 1.0f; }; diff --git a/UI/ImDebugger/ImJitViewer.cpp b/UI/ImDebugger/ImJitViewer.cpp index fb7d289273..f10d87bf6b 100644 --- a/UI/ImDebugger/ImJitViewer.cpp +++ b/UI/ImDebugger/ImJitViewer.cpp @@ -121,7 +121,7 @@ void ImJitViewerWindow::Draw(ImConfig &cfg, ImControl &control) { if (delta == 0) { return a.addr < b.addr; } - if (spec->SortDirection == ImGuiSortDirection_Ascending) { + if (!spec || spec->SortDirection == ImGuiSortDirection_Ascending) { return delta < 0; } else { return delta > 0; diff --git a/Windows/GEDebugger/TabState.cpp b/Windows/GEDebugger/TabState.cpp index 2464843256..4a0c97de2e 100644 --- a/Windows/GEDebugger/TabState.cpp +++ b/Windows/GEDebugger/TabState.cpp @@ -310,13 +310,13 @@ void CtrlStateValues::GetColumnText(wchar_t *dest, size_t destSize, int row, int } const auto info = GECmdInfoByCmd(rows_[row]); - const auto state = gpu->GetGState(); + const auto &state = gpu->GetGState(); const bool enabled = info.enableCmd == 0 || (state.cmdmem[info.enableCmd] & 1) == 1; const u32 value = state.cmdmem[info.cmd] & 0xFFFFFF; const u32 otherValue = state.cmdmem[info.otherCmd] & 0xFFFFFF; const u32 otherValue2 = state.cmdmem[info.otherCmd2] & 0xFFFFFF; char temp[256]; - FormatStateRow(gpu, temp, sizeof(temp), info.fmt, value, enabled, otherValue, otherValue2); + FormatStateRow(temp, sizeof(temp), info.fmt, value, enabled, otherValue, otherValue2); ConvertUTF8ToWString(dest, destSize, temp); break; } @@ -444,7 +444,7 @@ void CtrlStateValues::OnRightClick(int row, int column, const POINT &point) { const u32 otherValue2 = state.cmdmem[info.otherCmd2] & 0xFFFFFF; char dest[512]; - FormatStateRow(gpu, dest, sizeof(dest), info.fmt, value, enabled, otherValue, otherValue2); + FormatStateRow(dest, sizeof(dest), info.fmt, value, enabled, otherValue, otherValue2); System_CopyStringToClipboard(dest); break; } diff --git a/Windows/GEDebugger/TabVertices.cpp b/Windows/GEDebugger/TabVertices.cpp index 35e9f73c1a..a92ca6faf4 100644 --- a/Windows/GEDebugger/TabVertices.cpp +++ b/Windows/GEDebugger/TabVertices.cpp @@ -169,8 +169,10 @@ int CtrlVertexList::GetRowCount() { // TODO: Maybe there are smarter ways? Also, is this the best place to recalc? auto state = gpu->GetGState(); - rowCount_ = gpu->GetCurrentPrimCount(); - if (!gpu->GetCurrentDrawAsDebugVertices(rowCount_, vertices, indices)) { + GEPrimitiveType prim; + rowCount_ = gpu->GetCurrentPrimCount(&prim); + + if (!gpu->GetCurrentDrawAsDebugVertices(prim, &prim, rowCount_, vertices, indices, &previewIndexOffset_, nullptr, DebugVertexFlags::Transformed)) { rowCount_ = 0; } VertexDecoderOptions options{}; diff --git a/Windows/GEDebugger/TabVertices.h b/Windows/GEDebugger/TabVertices.h index f4f6054205..c39824642b 100644 --- a/Windows/GEDebugger/TabVertices.h +++ b/Windows/GEDebugger/TabVertices.h @@ -43,6 +43,7 @@ private: std::vector vertices; std::vector indices; int rowCount_; + int previewIndexOffset_; bool raw_; VertexDecoder *decoder; }; diff --git a/Windows/GEDebugger/VertexPreview.cpp b/Windows/GEDebugger/VertexPreview.cpp index 7f4da8489e..068d8fc828 100644 --- a/Windows/GEDebugger/VertexPreview.cpp +++ b/Windows/GEDebugger/VertexPreview.cpp @@ -96,9 +96,10 @@ void CGEDebugger::UpdatePrimPreview(u32 op, int which) { static std::vector vertices; static std::vector indices; - int count = 0; GEPrimitiveType prim; - if (!GetPrimPreview(op, prim, vertices, indices, count)) { + int previewIndexOffset; + bool previewTransformed; + if (!GetPrimPreview(op, prim, vertices, indices, &previewIndexOffset, &previewTransformed)) { return; } @@ -163,9 +164,9 @@ void CGEDebugger::UpdatePrimPreview(u32 op, int which) { } if (indices.empty()) { - glDrawArrays(glprim[prim], 0, count); + glDrawArrays(glprim[prim], 0, (GLsizei)vertices.size()); } else { - glDrawElements(glprim[prim], count, GL_UNSIGNED_SHORT, previewVao != 0 ? 0 : indices.data()); + glDrawElements(glprim[prim], (GLsizei)indices.size(), GL_UNSIGNED_SHORT, previewVao != 0 ? 0 : indices.data()); } if (previewVao == 0) { @@ -230,9 +231,9 @@ void CGEDebugger::UpdatePrimPreview(u32 op, int which) { } if (indices.empty()) { - glDrawArrays(glprim[prim], 0, count); + glDrawArrays(glprim[prim], 0, vertices.size()); } else { - glDrawElements(glprim[prim], count, GL_UNSIGNED_SHORT, texPreviewVao != 0 ? 0 : indices.data()); + glDrawElements(glprim[prim], indices.size(), GL_UNSIGNED_SHORT, texPreviewVao != 0 ? 0 : indices.data()); } if (texPreviewVao == 0) {