Files
ppsspp/GPU/Common/DrawEngineCommon.h
T
Henrik RydgårdandClaude Opus 5.5 7c5eda25f4 Software transform clipping: split clipped quads like the GE, cull beyond far first
With one vertex behind the near plane, the GE splits the remaining quad from
the vertex before the clipped one (gpu/probe exp43, exp44, as in SoftGPU's
Clipper.cpp). The other diagonal could put a far off-screen near-plane vertex
in both halves, so range culling dropped the whole triangle: the floor in
#16131's box test vanished. The index generator reverses triangles to flip
culling, so the batch's order is passed along.

Triangles entirely beyond the far plane are now culled even when in front of
the near plane, with the cull-distance epsilon, so z/w = 1.0000172 (exactly
1.0 to the GE) still counts as inside.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 16:16:24 +02:00

383 lines
13 KiB
C++

// Copyright (c) 2013- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include <vector>
#include <algorithm>
#include <cfloat>
#include "Common/CommonTypes.h"
#include "Common/Data/Collections/Hashmaps.h"
#include "GPU/Math3D.h"
#include "GPU/GPUState.h"
#include "GPU/GPUDefinitions.h"
#include "GPU/Common/GPUStateUtils.h"
#include "GPU/Common/IndexGenerator.h"
#include "GPU/Common/VertexDecoderCommon.h"
class VertexDecoder;
struct DepthDraw;
enum {
VERTEX_BUFFER_MAX = 65536,
DECODED_VERTEX_BUFFER_SIZE = VERTEX_BUFFER_MAX * 2 * 36, // 36 == sizeof(SimpleVertex)
DECODED_INDEX_BUFFER_SIZE = VERTEX_BUFFER_MAX * 6 * 6 * 2, // * 6 for spline tessellation, then * 6 again for converting into points/lines, and * 2 for 2 bytes per index
// TestBoundingBox handles up to 1025 vertices: corners (SimpleVertex), then positions, then decoded vertices.
BBOX_SCRATCH_CORNERS_OFFSET = 0,
BBOX_SCRATCH_VERTS_OFFSET = 64 * 1024,
BBOX_SCRATCH_TEMP_OFFSET = 128 * 1024,
BBOX_SCRATCH_SIZE = 256 * 1024,
};
enum {
TEX_SLOT_PSP_TEXTURE = 0,
TEX_SLOT_SHADERBLEND_SRC = 1,
TEX_SLOT_ALPHATEST = 2,
TEX_SLOT_CLUT = 3,
};
enum FBOTexState {
FBO_TEX_NONE,
FBO_TEX_COPY_BIND_TEX,
FBO_TEX_READ_FRAMEBUFFER,
};
struct SimpleVertex;
namespace Spline { struct Weight2D; }
// Culling plane, group of 8.
struct alignas(16) Plane8 {
float x[8], y[8], z[8], w[8];
void Set(int i, float _x, float _y, float _z, float _w) { x[i] = _x; y[i] = _y; z[i] = _z; w[i] = _w; }
float Test(int i, const float f[3]) const { return x[i] * f[0] + y[i] * f[1] + z[i] * f[2] + w[i]; }
};
class DrawEngineCommon {
public:
DrawEngineCommon(const DrawEngineCommon &) = delete;
DrawEngineCommon &operator=(const DrawEngineCommon &) = delete;
DrawEngineCommon();
virtual ~DrawEngineCommon();
void Init();
virtual void BeginFrame() {}
void SetGPUCommon(GPUCommon *gpuCommon) {
gpuCommon_ = gpuCommon;
}
virtual void DeviceLost() = 0;
virtual void DeviceRestore(Draw::DrawContext *draw) = 0;
// Dispatches the queued-up draws.
virtual void Flush() = 0;
// This would seem to be unnecessary now, but is still required for splines/beziers to work in the software backend since SubmitPrim
// is different. Should probably refactor that.
// Note that vertTypeID should be computed using GetVertTypeID().
virtual void DispatchSubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertTypeID, bool clockwise, int *bytesRead, ClipInfoFlags clipInfoFlags) {
VertexDecoder *dec = GetVertexDecoder(vertTypeID);
SubmitPrim(verts, inds, prim, vertexCount, dec, vertTypeID, clockwise, bytesRead, clipInfoFlags);
}
virtual void DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode, bool continuation);
bool TestBoundingBox(const void *control_points, const void *inds, int vertexCount, const VertexDecoder *dec, u32 vertType);
// This is a less accurate version of TestBoundingBox, but faster. Can have more false positives.
// Doesn't support indexing.
bool TestBoundingBoxFast(const float *cullMatrix, const void *vdata, const void *idata, int vertexCount, const VertexDecoder *dec, u32 vertType, ClipInfoFlags *clipInfoFlags);
bool TestBoundingBoxThrough(GEPrimitiveType prim, const void *vdata, const void *idata, int vertexCount, const VertexDecoder *dec, u32 vertType, int *bytesRead, ClipInfoFlags *flags);
bool EstimateThroughPrimSafeSize(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, u32 vertType, int *safeWidth, int *safeHeight);
void FlushPartialDecode() {
DecodeVerts(dec_, decoded_);
}
void FlushSkin() {
if (dec_ && dec_->skinInDecode) {
FlushPartialDecode();
}
}
int ExtendNonIndexedPrim(const uint32_t *cmd, const uint32_t *stall, const VertexDecoder *dec, u32 vertTypeID, bool clockwise, int *bytesRead, bool isTriangle, ClipInfoFlags clipInfoFlags);
bool SubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, u32 vertTypeID, bool clockwise, int *bytesRead, ClipInfoFlags clipInfoFlags);
void SkipPrim(GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, int *bytesRead);
template<class Surface>
void SubmitCurve(const void *control_points, const void *indices, Surface &surface, u32 vertType, int *bytesRead, const char *scope);
static void ClearSplineBezierWeights();
bool CanUseHardwareTransform(int prim) const;
std::vector<std::string> DebugGetVertexLoaderIDs();
std::string DebugGetVertexLoaderString(std::string_view id, DebugShaderStringType stringType);
virtual void NotifyConfigChanged();
bool DescribeCodePtr(const u8 *ptr, std::string &name) const;
int GetNumDrawCalls() const {
return numDrawVerts_;
}
VertexDecoder *GetVertexDecoder(u32 vertTypeID) {
VertexDecoder *dec;
if (decoderMap_.Get(vertTypeID, &dec))
return dec;
dec = new VertexDecoder();
_assert_(dec);
dec->SetVertexType(vertTypeID, decOptions_, decJitCache_);
decoderMap_.Insert(vertTypeID, dec);
return dec;
}
void AssertEmpty() {
_dbg_assert_(numDrawVerts_ == 0 && numDrawInds_ == 0);
}
// temporary hack
uint8_t *GetTempSpace() {
return decoded_ + 12 * 65536;
}
void FlushQueuedDepth();
protected:
bool CheckClipFlags(bool useHwTransform) const;
void DecodeVerts(const VertexDecoder *dec, u8 *dest);
int DecodeInds();
// Whether an indexed draw can share the previous draw's vertex decode, by widening its index range.
bool CanExtendDecode(const void *verts, const void *inds, const VertexDecoder *dec) const {
return inds && numDrawVerts_ > decodeVertsCounter_ && drawVerts_[numDrawVerts_ - 1].verts == verts && !dec->skinInDecode;
}
int ComputeNumVertsToDecode() const;
void ApplyFramebufferRead(FBOTexState *fboTexState);
void InitDepthRaster();
void ShutdownDepthRaster();
void DepthRasterSubmitRaw(GEPrimitiveType prim, const VertexDecoder *dec, uint32_t vertTypeID, int vertexCount);
void DepthRasterPredecoded(GEPrimitiveType prim, const void *inVerts, int numDecoded, const VertexDecoder *dec, int vertexCount);
bool CalculateDepthDraw(DepthDraw *draw, GEPrimitiveType prim, int vertexCount, int numDecoded);
static inline int IndexSize(u32 vtype) {
const u32 indexType = (vtype & GE_VTYPE_IDX_MASK);
if (indexType == GE_VTYPE_IDX_16BIT) {
return 2;
} else if (indexType == GE_VTYPE_IDX_32BIT) {
return 4;
}
return 1;
}
inline void UpdateEverUsedEqualDepth(GEComparison comp) {
switch (comp) {
case GE_COMP_EQUAL:
everUsedExactEqualDepth_ = true;
everUsedEqualDepth_ = true;
break;
case GE_COMP_NOTEQUAL:
case GE_COMP_LEQUAL:
case GE_COMP_GEQUAL:
everUsedEqualDepth_ = true;
break;
default:
break;
}
}
inline void ResetAfterDrawInline() {
gpuStats.perFrame.numFlushes++;
gpuStats.perFrame.numDrawCalls += numDrawInds_;
gpuStats.perFrame.numVertexDecodes += numDrawVerts_;
gpuStats.perFrame.numVertsSubmitted += vertexCountInDrawCalls_;
gpuStats.perFrame.numVertsDecoded += numDecodedVerts_;
indexGen.Reset();
numDecodedVerts_ = 0;
numDrawVerts_ = 0;
numDrawInds_ = 0;
vertexCountInDrawCalls_ = 0;
expandedVertsInDrawCalls_ = 0;
numVertsToDecode_ = 0;
decodeIndsCounter_ = 0;
decodeVertsCounter_ = 0;
seenPrims_ = 0;
anyCCWOrIndexed_ = false;
gstate_c.vertexFullAlpha = true;
clipInfoFlags_ = {};
// Now seems as good a time as any to reset the min/max coords, which we may examine later.
gstate_c.vertBounds.minU = 512;
gstate_c.vertBounds.minV = 512;
gstate_c.vertBounds.maxU = 0;
gstate_c.vertBounds.maxV = 0;
}
inline bool CollectedPureDraw() const {
// TODO: Do something faster.
if (useDepthRaster_) {
return false;
}
switch (seenPrims_) {
case 1 << GE_PRIM_TRIANGLE_STRIP:
return !anyCCWOrIndexed_ && numDrawInds_ == 1;
case 1 << GE_PRIM_LINES:
case 1 << GE_PRIM_POINTS:
case 1 << GE_PRIM_TRIANGLES:
return !anyCCWOrIndexed_;
default:
return false;
}
}
inline void DecodeIndsAndGetData(GEPrimitiveType *prim, int *numVerts, int *maxIndex, bool *useElements, bool forceIndexed) {
if (!forceIndexed && CollectedPureDraw()) {
*prim = drawInds_[0].prim;
*numVerts = numDecodedVerts_;
*maxIndex = numDecodedVerts_;
*useElements = false;
} else {
int vertexCount = DecodeInds();
*numVerts = vertexCount;
*maxIndex = numDecodedVerts_;
*prim = IndexGenerator::GeneralPrim((GEPrimitiveType)drawInds_[0].prim);
*useElements = true;
}
}
inline int RemainingIndices(const uint16_t *inds) const {
return DECODED_INDEX_BUFFER_SIZE / sizeof(uint16_t) - (inds - decIndex_);
}
bool useHWTransform_ = false;
// Used to prevent unnecessary flushing in softgpu.
bool flushOnParams_ = true;
// Set once a equal depth test is encountered.
bool everUsedEqualDepth_ = false;
bool everUsedExactEqualDepth_ = false;
// The draw context's invalidation callback is installed from BeginFrame, on the emu thread. The draw
// engine is created on the loader thread, while the UI thread may already be rendering and calling it.
bool invalidationCallbackInstalled_ = false;
// Vertex collector buffers
u8 *decoded_ = nullptr;
u16 *decIndex_ = nullptr;
// Separate from decoded_, which can hold decoded vertices that haven't been flushed yet.
u8 *bboxScratch_ = nullptr;
// Cached vertex decoders
DenseHashMap<u32, VertexDecoder *> decoderMap_;
VertexDecoderJitCache *decJitCache_ = nullptr;
VertexDecoderOptions decOptions_{};
TransformedVertex *transformed_ = nullptr;
TransformedVertex *transformedExpanded_ = nullptr;
// Defer all vertex decoding to a "Flush" (except when skinning, when we decode per draw)
struct DeferredVerts {
const void *verts;
UVScale uvScale;
u32 vertexCount;
u16 indexLowerBound;
u16 indexUpperBound;
};
struct DeferredInds {
const void *inds;
u32 vertexCount;
u8 vertDecodeIndex; // index into the drawVerts_ array to look up the vertexOffset.
u8 indexType;
GEPrimitiveType prim;
bool clockwise;
u16 offset;
};
enum { MAX_DEFERRED_DRAW_VERTS = 128 }; // If you change this to more than 256, change type of DeferredInds::vertDecodeIndex.
enum { MAX_DEFERRED_DRAW_INDS = 512 }; // Monster Hunter spams indexed calls that we end up merging.
DeferredVerts drawVerts_[MAX_DEFERRED_DRAW_VERTS];
uint32_t drawVertexOffsets_[MAX_DEFERRED_DRAW_VERTS];
// A cull mode change flushes, so all draws in a batch share this.
bool BatchTrianglesReversed() const { return numDrawInds_ > 0 && !drawInds_[0].clockwise; }
DeferredInds drawInds_[MAX_DEFERRED_DRAW_INDS];
const VertexDecoder *dec_ = nullptr;
u32 lastVType_ = -1; // corresponds to dec_. Could really just pick it out of dec_...
int numDrawVerts_ = 0;
int numDrawInds_ = 0;
int vertexCountInDrawCalls_ = 0;
// How many vertices software transform expands the queued points, lines and rectangles to (4 per
// point, line or rectangle). Must stay <= VERTEX_BUFFER_MAX, or the expansion drops the whole draw.
int expandedVertsInDrawCalls_ = 0;
// How many vertices DecodeVerts will produce for the queued draws. Must stay <= VERTEX_BUFFER_MAX.
int numVertsToDecode_ = 0;
int decodeVertsCounter_ = 0;
int decodeIndsCounter_ = 0;
int seenPrims_ = 0;
bool anyCCWOrIndexed_ = 0;
bool anyIndexed_ = 0;
// Vertex collector state
IndexGenerator indexGen;
int numDecodedVerts_ = 0;
GEPrimitiveType prevPrim_ = GE_PRIM_INVALID;
// Shader blending state
bool fboTexBound_ = false;
// Sometimes, unusual situations mean we need to reset dirty flags after state calc finishes.
uint64_t dirtyRequiresRecheck_ = 0;
ComputedPipelineState pipelineState_{};
GPUCommon *gpuCommon_ = nullptr;
// Software depth raster
bool useDepthRaster_ = false;
float *depthTransformed_ = nullptr;
int *depthScreenVerts_ = nullptr;
uint16_t *depthIndices_ = nullptr;
// Depth tracking
ClipInfoFlags clipInfoFlags_{};
ClipInfoFlags lastClipInfoFlags_{}; // Flags at the last flush. For dirtying.
// Queue
int depthVertexCount_ = 0;
int depthIndexCount_ = 0;
std::vector<DepthDraw> depthDraws_;
double rasterTimeStart_ = 0.0;
bool lastUseHwTransform_ = true;
};