From 86f9618cb9598a5e3c2eb0b7bc315503b87aaa82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 27 May 2026 17:45:37 +0200 Subject: [PATCH] Fallback to software transform if draw intersects -Wposoff; const s8 *data = (const s8 *)vdata + offset; - float vpZOffset = gstate.getViewportZCenter(); - float vpZScale = gstate.getViewportZScale(); + const float vpZOffset = gstate.getViewportZCenter(); + const float vpZScale = gstate.getViewportZScale(); for (int i = 0; i < vertexCount; i++, data += stride) { Vec4F32 objPos; @@ -366,14 +366,6 @@ static bool TestBoundingBoxFast(const float *worldViewProj, const void *vdata, i break; } Vec4F32 clipPos = objPos.AsVec3ByMatrix44(worldViewProjMat) & vertexMask; - const float projZ = vpZScale * clipPos[2] / clipPos[3]; - if (projZ < minProjZ) { - minProjZ = projZ; - } - if (projZ > maxProjZ) { // else ruins the minss/maxss optimization. - maxProjZ = projZ; - } - Vec4F32 posW = clipPos.ShuffleWWWW(); if (cull) { Vec4F32 posXY = clipPos.ShuffleXXYY(); @@ -386,9 +378,17 @@ static bool TestBoundingBoxFast(const float *worldViewProj, const void *vdata, i if (cull) { insideMaskZ |= planeDistZ.CompareGe(Vec4F32::Zero()); } + const float projZ = vpZScale * clipPos[2] / clipPos[3]; + if (projZ < minProjZ) { + minProjZ = projZ; + } + if (projZ > maxProjZ) { // else ruins the minss/maxss optimization. + maxProjZ = projZ; + } } if (!cull || (AllCompareBitsSet(insideMaskXY) && AllCompareBitsSet(insideMaskZ))) { + depths->valid = true; depths->hitClipSpaceZW = AnyCompareBitsSet(anyOutsideMaskZ); // Before checking later, we apply a viewport to the projZ, so we can later compare against minZ/maxZ or the outer bounds. // But to save operations we don't do it here. @@ -449,7 +449,7 @@ bool DrawEngineCommon::TestBoundingBoxFast(const float *worldViewProj, const voi applyViewport.wx = -(maxViewport.x + minViewport.x) * viewportInvSize.x; applyViewport.wy = -(maxViewport.y + minViewport.y) * viewportInvSize.y; - // TODO: Optimize. + // TODO: Optimize. It's possible to scale/offset a matrix in a quicker way. Matrix4ByMatrix4(mtx, worldViewProj, applyViewport.m); worldViewProj = mtx; } diff --git a/GPU/Common/DrawEngineCommon.h b/GPU/Common/DrawEngineCommon.h index 6f73f5d9ea..f1358a6f6f 100644 --- a/GPU/Common/DrawEngineCommon.h +++ b/GPU/Common/DrawEngineCommon.h @@ -73,10 +73,15 @@ struct alignas(16) Plane8 { }; struct BoundingDepths { + bool valid = false; bool hitClipSpaceZW = false; float minProjZ = FLT_MAX; float maxProjZ = -FLT_MAX; void Merge(const BoundingDepths &other) { + if (!valid) { + *this = other; + return; + } hitClipSpaceZW |= other.hitClipSpaceZW; if (other.minProjZ < minProjZ) { minProjZ = other.minProjZ; @@ -380,4 +385,6 @@ protected: std::vector depthDraws_; double rasterTimeStart_ = 0.0; + + bool lastUseHwTransform_ = true; }; diff --git a/GPU/GPU.h b/GPU/GPU.h index e28e2d282d..0eba9ffb99 100644 --- a/GPU/GPU.h +++ b/GPU/GPU.h @@ -98,6 +98,7 @@ struct GPUStatsPerFrame { int numDrawSyncs; int numListSyncs; int numFlushes; + int numSoftTransformedDraws; int numSoftClippedTriangles; int numBBOXJumps; int numVertsSubmitted; diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index cab15e8b90..a97fbc7d6b 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -1805,7 +1805,7 @@ void GPUCommonHW::FormatGPUStatsCommon(StringWriter &w) { w.F( "DL processing time: %0.2f ms, %d drawsync, %d listsync\n" "Draw: %d (%d dec, %d culled), flushes %d, clears %d, bbox jumps %d\n" - "Vertices: %d dec: %d drawn: %d clipped tris: %d\n" + "%d soft. Vertices: %d dec: %d drawn: %d clipped tris: %d\n" "FBOs active: %d (evaluations: %d, created %d)\n" "Textures: %d, dec: %d, invalidated: %d, hashed: %d kB, clut %d\n" "readbacks %d (%d non-block), upload %d (cached %d), depal %d\n" @@ -1824,6 +1824,7 @@ void GPUCommonHW::FormatGPUStatsCommon(StringWriter &w) { gpuStats.perFrame.numFlushes, gpuStats.perFrame.numClears, gpuStats.perFrame.numBBOXJumps, + gpuStats.perFrame.numSoftTransformedDraws, gpuStats.perFrame.numVertsSubmitted, gpuStats.perFrame.numVertsDecoded, gpuStats.perFrame.numUncachedVertsDrawn, diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 3a2c10be30..c003ebff59 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -242,6 +242,18 @@ void DrawEngineVulkan::Flush() { } bool useHWTransform = CanUseHardwareTransform(prim) && provokingVertexOk; + if (useHWTransform && boundingDepths_.valid) { + if (boundingDepths_.hitClipSpaceZW) { + // Revert to software so we can clip more accurately. + useHWTransform = false; + } + } + + if (useHWTransform != lastUseHwTransform_) { + gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE); + lastUseHwTransform_ = useHWTransform; + } + // TODO: Here we can check depths_ to see if we need to fall back to software transform for clipping. // The optimization to avoid indexing isn't really worth it on Vulkan since it means creating more pipelines. @@ -377,6 +389,8 @@ void DrawEngineVulkan::Flush() { DepthRasterSubmitRaw(prim, dec_, dec_->VertexType(), vertexCount); } } else { + gpuStats.perFrame.numSoftTransformedDraws++; + PROFILE_THIS_SCOPE("soft"); const VertexDecoder *swDec = dec_; if (swDec->nweights != 0) {