mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-12 17:19:28 +01:00
GPU: Clip and cull draws crossing the Z planes on the CPU, keeping hardware transform
Where the GPU can't clip and cull like the GE (no clip or cull distances, the near plane not on the minimum Z, or a game flagged CorrectCullAfterClip), a draw whose bounding box crosses a Z plane used to go to software transform. Mixing that with hardware transform on other passes over the same surface made their depth tests fail on PowerVR. Now the CPU decodes such a draw to RAM, clips its triangles at the near plane like the GE (into one triangle or two), drops those entirely beyond the far plane, and range culls the vertices clipping makes. The vertices, including the new ones interpolated from the decoded ones, then go through hardware transform like the rest. Lit draws, flat shading and normal-based texgen aren't linear in the decoded vertices, so they're left to the clip distance where there is one, and culled only when culled whole. Without clip distances, lit draws still take software transform. Regression risk: draws that cross a Z plane on GPUs without clip or cull distances, and the flagged games, now take a new path, on every backend. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
39b7e8dc2f
commit
e1ba8145db
11 files changed
+434
-83
No files matched your search
@@ -305,12 +305,9 @@ void DecodeAndTransformForDepthRaster(float *dest, const float *worldviewproj, c
|
||||
}
|
||||
}
|
||||
|
||||
void TransformPredecodedForDepthRaster(float *dest, const float *worldviewproj, const void *decodedVertexData, const VertexDecoder *dec, int count) {
|
||||
// TODO: Ditch skinned and morphed prims for now since we don't have a fast way to skin without running the full decoder.
|
||||
_dbg_assert_((dec->VertexType() & (GE_VTYPE_WEIGHT_MASK | GE_VTYPE_MORPHCOUNT_MASK)) == 0);
|
||||
|
||||
int vertexStride = dec->GetDecVtxFmt().stride;
|
||||
int offset = dec->GetDecVtxFmt().posoff;
|
||||
void TransformPredecodedForDepthRaster(float *dest, const float *worldviewproj, const void *decodedVertexData, const DecVtxFormat &fmt, int count) {
|
||||
int vertexStride = fmt.stride;
|
||||
int offset = fmt.posoff;
|
||||
|
||||
Mat4F32 mat(worldviewproj);
|
||||
|
||||
|
||||
@@ -55,13 +55,14 @@ struct DepthDraw {
|
||||
// So, we can be quite inaccurate without any issues, and skip a lot of functionality.
|
||||
|
||||
class VertexDecoder;
|
||||
struct DecVtxFormat;
|
||||
struct TransformedVertex;
|
||||
|
||||
// maxOutCount is the capacity of each of tx/ty/tz, in elements.
|
||||
int DepthRasterClipIndexedTriangles(int *tx, int *ty, float *tz, const float *transformed, const uint16_t *indexBuffer, const DepthDraw &draw, const DepthScissor scissor, int maxOutCount);
|
||||
int DepthRasterClipIndexedRectangles(int *tx, int *ty, float *tz, const float *transformed, const uint16_t *indexBuffer, const DepthDraw &draw, const DepthScissor scissor);
|
||||
void DecodeAndTransformForDepthRaster(float *dest, const float *worldviewproj, const void *vertexData, int indexLowerBound, int indexUpperBound, const VertexDecoder *dec, u32 vertTypeID);
|
||||
void TransformPredecodedForDepthRaster(float *dest, const float *worldviewproj, const void *decodedVertexData, const VertexDecoder *dec, int count);
|
||||
void TransformPredecodedForDepthRaster(float *dest, const float *worldviewproj, const void *decodedVertexData, const DecVtxFormat &fmt, int count);
|
||||
void ConvertPredecodedThroughForDepthRaster(float *dest, const void *decodedVertexData, const VertexDecoder *dec, int count);
|
||||
void DepthRasterScreenVerts(uint16_t *depth, int depthStride, const int *tx, const int *ty, const float *tz, int count, const DepthDraw &draw, const DepthScissor scissor, bool lowQ);
|
||||
|
||||
|
||||
+291
-17
@@ -394,6 +394,13 @@ static inline Vec4F32 DivideExact(Vec4F32 a, Vec4F32 b) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// Whether ClipCullPrims can clip every triangle of a draw with this state, not needing the clip distance.
|
||||
static bool CpuCanClipAll() {
|
||||
const GETexMapMode uvGen = gstate.getUVGenMode();
|
||||
const bool normalTexGen = uvGen == GE_TEXMAP_ENVIRONMENT_MAP || (uvGen == GE_TEXMAP_TEXTURE_MATRIX && gstate.getUVProjMode() == GE_PROJMAP_NORMALIZED_NORMAL);
|
||||
return gstate.getShadeMode() != GE_SHADE_FLAT && !(gstate.isTextureMapEnabled() && normalTexGen);
|
||||
}
|
||||
|
||||
// This optionally culls collections of points against the six planes, and always computes the min and max of Z and W.
|
||||
//
|
||||
// The result of that is then used to determine if we need to drop down to software transform+clip or we can hand
|
||||
@@ -401,7 +408,7 @@ static inline Vec4F32 DivideExact(Vec4F32 a, Vec4F32 b) {
|
||||
//
|
||||
// NOTE: This doesn't handle through-mode (morph or skinning can be handled if they're implemented in software during decode).
|
||||
template<u32 posFmt, u32 idxFmt>
|
||||
static bool TestBoundingBoxFast(const float *cullMatrix, const void *vdata, const void *idata, int vertexCount, const VertexDecoder *dec, ClipInfoFlags *clipInfoFlags) {
|
||||
static bool TestBoundingBoxFast(const float *cullMatrix, const void *vdata, const void *idata, int vertexCount, const VertexDecoder *dec, bool cpuCullSupported, ClipInfoFlags *clipInfoFlags) {
|
||||
// Four vertices at a time, one per lane. Past the end, the last vertex fills the lanes, which changes nothing.
|
||||
// The matrix stays in four registers, multiplied by lane. Splatted, it took sixteen, and was reloaded from the
|
||||
// stack every iteration.
|
||||
@@ -526,7 +533,19 @@ static bool TestBoundingBoxFast(const float *cullMatrix, const void *vdata, cons
|
||||
// Ninja Impact's grass, whose depth then matches the ground's).
|
||||
const bool clipPlanesDoIt = gstate_c.viewportZPlanesOnClipPlanes && gstate.isDepthClipEnabled();
|
||||
const bool hardwareClipsZ = gstate_c.Use(GPU_USE_CLIP_DISTANCE) && (clipPlanesDoIt || (gstate_c.Use(GPU_USE_CULL_DISTANCE) && gstate_c.viewportNearPlaneMatchesOutput));
|
||||
if (AnyCompareBitsSet(anyOutsideZ) && (!hardwareClipsZ || PSP_CoreParameter().compat.flags().CorrectCullAfterClip)) {
|
||||
// The GPU clips and culls like the GE then, except for range culling the parts clipping makes, which the games
|
||||
// flagged CorrectCullAfterClip need.
|
||||
const bool correctCullAfterClip = PSP_CoreParameter().compat.flags().CorrectCullAfterClip;
|
||||
const bool gpuDoesZ = hardwareClipsZ && !correctCullAfterClip;
|
||||
// Otherwise the CPU clips at the near plane and culls like the GE, including the range culling after clipping, and
|
||||
// the vertices stay on hardware transform, the same math as the other passes over the surface: mixing in software
|
||||
// transform made those depth tests fail on PowerVR. What it can't clip is left to the clip distance.
|
||||
const bool cpuCulls = cpuCullSupported && !gpuDoesZ && (!clipPlanesDoIt || correctCullAfterClip) && gstate.isDepthClipEnabled() && (gstate_c.Use(GPU_USE_CLIP_DISTANCE) || (CpuCanClipAll() && !gstate.isLightingEnabled()));
|
||||
if (cpuCulls) {
|
||||
if (AnyCompareBitsSet(anyOutsideZ)) {
|
||||
flags |= ClipInfoFlags::CpuCull;
|
||||
}
|
||||
} else if (AnyCompareBitsSet(anyOutsideZ) && !gpuDoesZ) {
|
||||
// Some vertices were outside the Z clipping planes. Clip againt Z=-W in software (and do culling, too).
|
||||
// Without cull distances the shader can't cull, and triangles entirely beyond the far plane would be
|
||||
// depth clamped and drawn (the stands over the pitch in FIFA Soccer). Without clip distances it can't
|
||||
@@ -557,6 +576,7 @@ static bool TestBoundingBoxFast(const float *cullMatrix, const void *vdata, cons
|
||||
return true;
|
||||
}
|
||||
|
||||
// Never CpuCull or SoftClipCull: without the bounding box test, nothing says the draw crosses a Z plane.
|
||||
ClipInfoFlags DrawEngineCommon::UncheckedClipInfoFlags() {
|
||||
ClipInfoFlags flags{};
|
||||
if (gstate.isModeThrough() || gstate.isModeClear()) {
|
||||
@@ -572,7 +592,7 @@ ClipInfoFlags DrawEngineCommon::UncheckedClipInfoFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
bool DrawEngineCommon::TestBoundingBoxFast(const float *cullMatrix, const void *vdata, const void *idata, int vertexCount, const VertexDecoder *dec, u32 vertType, ClipInfoFlags *flags) {
|
||||
bool DrawEngineCommon::TestBoundingBoxFast(const float *cullMatrix, const void *vdata, const void *idata, int vertexCount, const VertexDecoder *dec, u32 vertType, ClipInfoFlags *flags, bool cpuCullSupported) {
|
||||
// Although this may lead to drawing that shouldn't happen, the viewport is more complex on VR.
|
||||
// Let's always say objects are within bounds.
|
||||
if (gstate_c.Use(GPU_USE_VIRTUAL_REALITY)) {
|
||||
@@ -586,36 +606,36 @@ bool DrawEngineCommon::TestBoundingBoxFast(const float *cullMatrix, const void *
|
||||
switch (vertType & GE_VTYPE_IDX_MASK) {
|
||||
case GE_VTYPE_IDX_NONE:
|
||||
switch (vertType & GE_VTYPE_POS_MASK) {
|
||||
case GE_VTYPE_POS_8BIT: return ::TestBoundingBoxFast<GE_VTYPE_POS_8BIT, GE_VTYPE_IDX_NONE>(cullMatrix, vdata, nullptr, vertexCount, dec, flags);
|
||||
case GE_VTYPE_POS_16BIT: return ::TestBoundingBoxFast<GE_VTYPE_POS_16BIT, GE_VTYPE_IDX_NONE>(cullMatrix, vdata, nullptr, vertexCount, dec, flags);
|
||||
case GE_VTYPE_POS_FLOAT: return ::TestBoundingBoxFast<GE_VTYPE_POS_FLOAT, GE_VTYPE_IDX_NONE>(cullMatrix, vdata, nullptr, vertexCount, dec, flags);
|
||||
case GE_VTYPE_POS_8BIT: return ::TestBoundingBoxFast<GE_VTYPE_POS_8BIT, GE_VTYPE_IDX_NONE>(cullMatrix, vdata, nullptr, vertexCount, dec, cpuCullSupported, flags);
|
||||
case GE_VTYPE_POS_16BIT: return ::TestBoundingBoxFast<GE_VTYPE_POS_16BIT, GE_VTYPE_IDX_NONE>(cullMatrix, vdata, nullptr, vertexCount, dec, cpuCullSupported, flags);
|
||||
case GE_VTYPE_POS_FLOAT: return ::TestBoundingBoxFast<GE_VTYPE_POS_FLOAT, GE_VTYPE_IDX_NONE>(cullMatrix, vdata, nullptr, vertexCount, dec, cpuCullSupported, flags);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GE_VTYPE_IDX_8BIT:
|
||||
switch (vertType & GE_VTYPE_POS_MASK) {
|
||||
case GE_VTYPE_POS_8BIT: return ::TestBoundingBoxFast<GE_VTYPE_POS_8BIT, GE_VTYPE_IDX_8BIT>(cullMatrix, vdata, idata, vertexCount, dec, flags);
|
||||
case GE_VTYPE_POS_16BIT: return ::TestBoundingBoxFast<GE_VTYPE_POS_16BIT, GE_VTYPE_IDX_8BIT>(cullMatrix, vdata, idata, vertexCount, dec, flags);
|
||||
case GE_VTYPE_POS_FLOAT: return ::TestBoundingBoxFast<GE_VTYPE_POS_FLOAT, GE_VTYPE_IDX_8BIT>(cullMatrix, vdata, idata, vertexCount, dec, flags);
|
||||
case GE_VTYPE_POS_8BIT: return ::TestBoundingBoxFast<GE_VTYPE_POS_8BIT, GE_VTYPE_IDX_8BIT>(cullMatrix, vdata, idata, vertexCount, dec, cpuCullSupported, flags);
|
||||
case GE_VTYPE_POS_16BIT: return ::TestBoundingBoxFast<GE_VTYPE_POS_16BIT, GE_VTYPE_IDX_8BIT>(cullMatrix, vdata, idata, vertexCount, dec, cpuCullSupported, flags);
|
||||
case GE_VTYPE_POS_FLOAT: return ::TestBoundingBoxFast<GE_VTYPE_POS_FLOAT, GE_VTYPE_IDX_8BIT>(cullMatrix, vdata, idata, vertexCount, dec, cpuCullSupported, flags);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GE_VTYPE_IDX_16BIT:
|
||||
switch (vertType & GE_VTYPE_POS_MASK) {
|
||||
case GE_VTYPE_POS_8BIT: return ::TestBoundingBoxFast<GE_VTYPE_POS_8BIT, GE_VTYPE_IDX_16BIT>(cullMatrix, vdata, idata, vertexCount, dec, flags);
|
||||
case GE_VTYPE_POS_16BIT: return ::TestBoundingBoxFast<GE_VTYPE_POS_16BIT, GE_VTYPE_IDX_16BIT>(cullMatrix, vdata, idata, vertexCount, dec, flags);
|
||||
case GE_VTYPE_POS_FLOAT: return ::TestBoundingBoxFast<GE_VTYPE_POS_FLOAT, GE_VTYPE_IDX_16BIT>(cullMatrix, vdata, idata, vertexCount, dec, flags);
|
||||
case GE_VTYPE_POS_8BIT: return ::TestBoundingBoxFast<GE_VTYPE_POS_8BIT, GE_VTYPE_IDX_16BIT>(cullMatrix, vdata, idata, vertexCount, dec, cpuCullSupported, flags);
|
||||
case GE_VTYPE_POS_16BIT: return ::TestBoundingBoxFast<GE_VTYPE_POS_16BIT, GE_VTYPE_IDX_16BIT>(cullMatrix, vdata, idata, vertexCount, dec, cpuCullSupported, flags);
|
||||
case GE_VTYPE_POS_FLOAT: return ::TestBoundingBoxFast<GE_VTYPE_POS_FLOAT, GE_VTYPE_IDX_16BIT>(cullMatrix, vdata, idata, vertexCount, dec, cpuCullSupported, flags);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GE_VTYPE_IDX_32BIT:
|
||||
switch (vertType & GE_VTYPE_POS_MASK) {
|
||||
case GE_VTYPE_POS_8BIT: return ::TestBoundingBoxFast<GE_VTYPE_POS_8BIT, GE_VTYPE_IDX_32BIT>(cullMatrix, vdata, idata, vertexCount, dec, flags);
|
||||
case GE_VTYPE_POS_16BIT: return ::TestBoundingBoxFast<GE_VTYPE_POS_16BIT, GE_VTYPE_IDX_32BIT>(cullMatrix, vdata, idata, vertexCount, dec, flags);
|
||||
case GE_VTYPE_POS_FLOAT: return ::TestBoundingBoxFast<GE_VTYPE_POS_FLOAT, GE_VTYPE_IDX_32BIT>(cullMatrix, vdata, idata, vertexCount, dec, flags);
|
||||
case GE_VTYPE_POS_8BIT: return ::TestBoundingBoxFast<GE_VTYPE_POS_8BIT, GE_VTYPE_IDX_32BIT>(cullMatrix, vdata, idata, vertexCount, dec, cpuCullSupported, flags);
|
||||
case GE_VTYPE_POS_16BIT: return ::TestBoundingBoxFast<GE_VTYPE_POS_16BIT, GE_VTYPE_IDX_32BIT>(cullMatrix, vdata, idata, vertexCount, dec, cpuCullSupported, flags);
|
||||
case GE_VTYPE_POS_FLOAT: return ::TestBoundingBoxFast<GE_VTYPE_POS_FLOAT, GE_VTYPE_IDX_32BIT>(cullMatrix, vdata, idata, vertexCount, dec, cpuCullSupported, flags);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -815,6 +835,260 @@ int DrawEngineCommon::ComputeNumVertsToDecode() const {
|
||||
return sum;
|
||||
}
|
||||
|
||||
// DrawEngineCommon::vertClass_ bits.
|
||||
enum : u8 {
|
||||
VERT_BEHIND_NEAR = 1,
|
||||
VERT_BEYOND_FAR = 2,
|
||||
};
|
||||
|
||||
// Where the edge from the inside vertex a crosses the near plane to the outside vertex b, like the software
|
||||
// transform's clipper computes it.
|
||||
static float NearPlaneT(const float *a, const float *b) {
|
||||
const float denominator = (b[2] - a[2]) + (b[3] - a[3]);
|
||||
const float t = fabsf(denominator) > 0.000001f ? (-a[2] - a[3]) / denominator : 0.0f;
|
||||
return std::clamp(t, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
static bool NearPointOutsideRange(const float *a, const float *b, float t) {
|
||||
const float x = a[0] + (b[0] - a[0]) * t;
|
||||
const float y = a[1] + (b[1] - a[1]) * t;
|
||||
const float w = a[3] + (b[3] - a[3]) * t;
|
||||
const float sx = x * gstate.getViewportXScale() / w + gstate.getViewportXCenter();
|
||||
const float sy = y * gstate.getViewportYScale() / w + gstate.getViewportYCenter();
|
||||
return !(sx >= 0.0f && sy >= 0.0f && sx < 4096.0f && sy < 4096.0f);
|
||||
}
|
||||
|
||||
static void LerpComponent(u8 fmt, u8 *dst, const u8 *a, const u8 *b, float t) {
|
||||
int count = 0;
|
||||
switch (fmt) {
|
||||
case DEC_FLOAT_1: case DEC_FLOAT_2: case DEC_FLOAT_3: case DEC_FLOAT_4:
|
||||
count = fmt - DEC_FLOAT_1 + 1;
|
||||
for (int i = 0; i < count; i++) {
|
||||
const float fa = ((const float *)a)[i], fb = ((const float *)b)[i];
|
||||
((float *)dst)[i] = fa + (fb - fa) * t;
|
||||
}
|
||||
break;
|
||||
case DEC_S8_3:
|
||||
for (int i = 0; i < 3; i++) {
|
||||
const float fa = (s8)a[i], fb = (s8)b[i];
|
||||
dst[i] = (u8)(s8)roundf(fa + (fb - fa) * t);
|
||||
}
|
||||
break;
|
||||
case DEC_S16_3:
|
||||
for (int i = 0; i < 3; i++) {
|
||||
const float fa = ((const s16 *)a)[i], fb = ((const s16 *)b)[i];
|
||||
((s16 *)dst)[i] = (s16)roundf(fa + (fb - fa) * t);
|
||||
}
|
||||
break;
|
||||
case DEC_U8_1: case DEC_U8_2: case DEC_U8_3: case DEC_U8_4:
|
||||
count = fmt - DEC_U8_1 + 1;
|
||||
for (int i = 0; i < count; i++) {
|
||||
dst[i] = (u8)roundf(a[i] + (b[i] - a[i]) * t);
|
||||
}
|
||||
break;
|
||||
case DEC_U16_1: case DEC_U16_2: case DEC_U16_3: case DEC_U16_4:
|
||||
count = fmt - DEC_U16_1 + 1;
|
||||
for (int i = 0; i < count; i++) {
|
||||
const float fa = ((const u16 *)a)[i], fb = ((const u16 *)b)[i];
|
||||
((u16 *)dst)[i] = (u16)roundf(fa + (fb - fa) * t);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DrawEngineCommon::CpuClip DrawEngineCommon::BeginCpuClip(bool useHWTransform) {
|
||||
CpuClip clip;
|
||||
clip.fmt = dec_->GetDecVtxFmt();
|
||||
clip.active = useHWTransform && (clipInfoFlags_ & ClipInfoFlags::Valid) && (clipInfoFlags_ & ClipInfoFlags::CpuCull);
|
||||
return clip;
|
||||
}
|
||||
|
||||
void DrawEngineCommon::DecodeClipCull(CpuClip *clip, GEPrimitiveType *prim, int *vertexCount, int *maxIndex, bool *useElements) {
|
||||
// In decoded_, not the push buffer, which is too slow to read back.
|
||||
DecodeVerts(dec_, decoded_);
|
||||
DecodeIndsAndGetData(prim, vertexCount, maxIndex, useElements, true);
|
||||
clip->numVerts = numDecodedVerts_;
|
||||
*vertexCount = ClipCullPrims(*prim, decoded_, clip->fmt, &clip->numVerts, decIndex_, *vertexCount);
|
||||
*maxIndex = clip->numVerts;
|
||||
}
|
||||
|
||||
void DrawEngineCommon::DepthRasterSubmitHW(GEPrimitiveType prim, int vertexCount, const CpuClip &clip) {
|
||||
if (clip.active) {
|
||||
DepthRasterPredecoded(prim, decoded_, clip.numVerts, dec_, vertexCount, &clip.fmt);
|
||||
} else {
|
||||
DepthRasterSubmitRaw(prim, dec_, dec_->VertexType(), vertexCount);
|
||||
}
|
||||
}
|
||||
|
||||
int DrawEngineCommon::ClipCullPrims(GEPrimitiveType prim, u8 *verts, const DecVtxFormat &fmt, int *numVerts, u16 *inds, int indexCount) {
|
||||
// The indices are forced (DecodeIndsAndGetData), which makes strips and fans lists.
|
||||
int perPrim;
|
||||
switch (prim) {
|
||||
case GE_PRIM_TRIANGLES: perPrim = 3; break;
|
||||
case GE_PRIM_LINES: perPrim = 2; break;
|
||||
case GE_PRIM_POINTS: perPrim = 1; break;
|
||||
default: return indexCount;
|
||||
}
|
||||
|
||||
const float *m = gstate_c.worldviewproj;
|
||||
const int stride = fmt.stride;
|
||||
const int origVerts = *numVerts;
|
||||
// Grown, never shrunk, so they aren't cleared every time.
|
||||
if (clipPos_.size() < origVerts * 4) {
|
||||
clipPos_.resize(origVerts * 4);
|
||||
vertClass_.resize(origVerts);
|
||||
}
|
||||
const Mat4F32 mat(m);
|
||||
for (int i = 0; i < origVerts; i++) {
|
||||
// The decoded position is the last member, so the fourth lane reads into the next vertex, or the rest of
|
||||
// decoded_, and AsVec3ByMatrix44 ignores it.
|
||||
const float *pos = (const float *)(verts + i * stride + fmt.posoff);
|
||||
float *c = &clipPos_[i * 4];
|
||||
Vec4F32::Load(pos).AsVec3ByMatrix44(mat).Store(c);
|
||||
// The same as the far cull distance in the vertex shader. With w > 0 and w - z >= 0 it can't be negative, so
|
||||
// the division (in front of the far plane, mostly) is skipped there.
|
||||
const float farDist = c[3] - c[2];
|
||||
const bool beyondFar = (farDist < 0.0f || c[3] <= 0.0f) && farDist + 0.0000304f / c[3] < 0.0f;
|
||||
const bool behindNear = c[2] < -c[3];
|
||||
vertClass_[i] = (behindNear ? VERT_BEHIND_NEAR : 0) | (beyondFar ? VERT_BEYOND_FAR : 0);
|
||||
}
|
||||
|
||||
if (perPrim != 3) {
|
||||
int out = 0;
|
||||
for (int i = 0; i + perPrim <= indexCount; i += perPrim) {
|
||||
bool culled = true;
|
||||
for (int j = 0; j < perPrim; j++) {
|
||||
culled = culled && (vertClass_[inds[i + j]] & VERT_BEYOND_FAR);
|
||||
}
|
||||
if (culled) {
|
||||
continue;
|
||||
}
|
||||
for (int j = 0; j < perPrim; j++) {
|
||||
inds[out++] = inds[i + j];
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Triangles crossing the near plane get clipped here like the GE does it (ProcessTriangle in Clipper.cpp, from
|
||||
// gpu/probe exp43, exp44), into one triangle, or two, (p, a, b) and (p, b, n), with o behind the plane. Each is
|
||||
// then range culled on the vertices clipping made. The shader can't do that: it exempts the vertices behind the
|
||||
// near plane. The new vertices are interpolated from the decoded ones, so the GPU transforms them like the rest.
|
||||
// That's only right where the vertex outputs are linear in them: the GE interpolates the lit colors, so lit draws
|
||||
// are left to the GPU's clip distance, like normal-based texgen and flat shading (whose provoking colors would
|
||||
// have to move), culling only what is culled whole.
|
||||
const bool rangeCulling = gstate_c.Use(GPU_USE_VS_RANGE_CULLING) && gstate_c.submitType == SubmitType::DRAW;
|
||||
const bool canClip = CpuCanClipAll() && !gstate.isLightingEnabled();
|
||||
const bool reversed = BatchTrianglesReversed();
|
||||
const int maxVerts = std::min((int)VERTEX_BUFFER_MAX, (int)DECODED_VERTEX_BUFFER_SIZE / stride);
|
||||
const int maxInds = DECODED_INDEX_BUFFER_SIZE / sizeof(u16);
|
||||
int vertCount = origVerts;
|
||||
|
||||
auto makeVertex = [&](int in, int outside, float t) {
|
||||
u8 *dst = verts + vertCount * stride;
|
||||
const u8 *a = verts + in * stride;
|
||||
const u8 *b = verts + outside * stride;
|
||||
LerpComponent(DEC_FLOAT_3, dst + fmt.posoff, a + fmt.posoff, b + fmt.posoff, t);
|
||||
if (fmt.nrmfmt) LerpComponent(fmt.nrmfmt, dst + fmt.nrmoff, a + fmt.nrmoff, b + fmt.nrmoff, t);
|
||||
if (fmt.uvfmt) LerpComponent(fmt.uvfmt, dst + fmt.uvoff, a + fmt.uvoff, b + fmt.uvoff, t);
|
||||
if (fmt.c0fmt) LerpComponent(fmt.c0fmt, dst + fmt.c0off, a + fmt.c0off, b + fmt.c0off, t);
|
||||
if (fmt.c1fmt) LerpComponent(fmt.c1fmt, dst + fmt.c1off, a + fmt.c1off, b + fmt.c1off, t);
|
||||
if (fmt.w0fmt) LerpComponent(fmt.w0fmt, dst + fmt.w0off, a + fmt.w0off, b + fmt.w0off, t);
|
||||
if (fmt.w1fmt) LerpComponent(fmt.w1fmt, dst + fmt.w1off, a + fmt.w1off, b + fmt.w1off, t);
|
||||
return (u16)vertCount++;
|
||||
};
|
||||
|
||||
// A triangle becomes at most two.
|
||||
if ((int)clipInds_.size() < indexCount * 2) {
|
||||
clipInds_.resize(indexCount * 2);
|
||||
}
|
||||
u16 *out = clipInds_.data();
|
||||
int numOut = 0;
|
||||
auto emit = [&](const u16 *tri) {
|
||||
out[numOut++] = tri[0];
|
||||
out[numOut++] = tri[1];
|
||||
out[numOut++] = tri[2];
|
||||
};
|
||||
const u8 *vertClass = vertClass_.data();
|
||||
for (int i = 0; i + 3 <= indexCount; i += 3) {
|
||||
const u16 *tri = inds + i;
|
||||
const u8 c0 = vertClass[tri[0]], c1 = vertClass[tri[1]], c2 = vertClass[tri[2]];
|
||||
if (c0 & c1 & c2) {
|
||||
// All behind the near plane, or all beyond the far one.
|
||||
continue;
|
||||
}
|
||||
if (!((c0 | c1 | c2) & VERT_BEHIND_NEAR)) {
|
||||
emit(tri);
|
||||
continue;
|
||||
}
|
||||
const bool outside[3] = { (c0 & VERT_BEHIND_NEAR) != 0, (c1 & VERT_BEHIND_NEAR) != 0, (c2 & VERT_BEHIND_NEAR) != 0 };
|
||||
const int numOutside = (int)outside[0] + (int)outside[1] + (int)outside[2];
|
||||
|
||||
// The two vertices clipping makes, each from an inside vertex to an outside one.
|
||||
int from[2], to[2];
|
||||
if (numOutside == 1) {
|
||||
const int o = outside[0] ? 0 : (outside[1] ? 1 : 2);
|
||||
const int p = reversed ? (o + 1) % 3 : (o + 2) % 3;
|
||||
const int n = reversed ? (o + 2) % 3 : (o + 1) % 3;
|
||||
from[0] = tri[p]; to[0] = tri[o];
|
||||
from[1] = tri[n]; to[1] = tri[o];
|
||||
} else {
|
||||
const int in = !outside[0] ? 0 : (!outside[1] ? 1 : 2);
|
||||
from[0] = tri[in]; to[0] = tri[(in + 1) % 3];
|
||||
from[1] = tri[in]; to[1] = tri[(in + 2) % 3];
|
||||
}
|
||||
float t[2];
|
||||
bool madeOutside[2];
|
||||
for (int j = 0; j < 2; j++) {
|
||||
t[j] = NearPlaneT(&clipPos_[from[j] * 4], &clipPos_[to[j] * 4]);
|
||||
madeOutside[j] = rangeCulling && NearPointOutsideRange(&clipPos_[from[j] * 4], &clipPos_[to[j] * 4], t[j]);
|
||||
}
|
||||
|
||||
if (!canClip || vertCount + 2 > maxVerts) {
|
||||
// Leave the clipping to the GPU, culling only if every part would be culled: b is in both halves.
|
||||
if (!(numOutside == 1 ? madeOutside[1] : (madeOutside[0] || madeOutside[1]))) {
|
||||
emit(tri);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (numOutside == 2) {
|
||||
if (madeOutside[0] || madeOutside[1]) {
|
||||
continue;
|
||||
}
|
||||
const u16 a = makeVertex(from[0], to[0], t[0]);
|
||||
const u16 b = makeVertex(from[1], to[1], t[1]);
|
||||
const u16 sub[3] = { (u16)from[0], a, b };
|
||||
emit(sub);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (madeOutside[1]) {
|
||||
continue;
|
||||
}
|
||||
const u16 p = (u16)from[0], n = (u16)from[1];
|
||||
const u16 a = madeOutside[0] ? 0 : makeVertex(from[0], to[0], t[0]);
|
||||
const u16 b = makeVertex(from[1], to[1], t[1]);
|
||||
// Keep the winding we were given.
|
||||
const int first = reversed ? 2 : 1, second = reversed ? 1 : 2;
|
||||
if (!madeOutside[0]) {
|
||||
u16 sub[3];
|
||||
sub[0] = p; sub[first] = a; sub[second] = b;
|
||||
emit(sub);
|
||||
}
|
||||
u16 sub[3];
|
||||
sub[0] = p; sub[first] = b; sub[second] = n;
|
||||
emit(sub);
|
||||
}
|
||||
|
||||
numOut = std::min(numOut, maxInds - maxInds % 3);
|
||||
memcpy(inds, out, numOut * sizeof(u16));
|
||||
*numVerts = vertCount;
|
||||
return numOut;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Takes a list of consecutive PRIM opcodes, and extends the current draw call to include them.
|
||||
@@ -1331,7 +1605,7 @@ void DrawEngineCommon::DepthRasterSubmitRaw(GEPrimitiveType prim, const VertexDe
|
||||
CommitDepthDraw(draw, vertexCount, numDecoded);
|
||||
}
|
||||
|
||||
void DrawEngineCommon::DepthRasterPredecoded(GEPrimitiveType prim, const void *inVerts, int numDecoded, const VertexDecoder *dec, int vertexCount) {
|
||||
void DrawEngineCommon::DepthRasterPredecoded(GEPrimitiveType prim, const void *inVerts, int numDecoded, const VertexDecoder *dec, int vertexCount, const DecVtxFormat *fmt) {
|
||||
if (!gstate.isModeClear() && (!gstate.isDepthTestEnabled() || !gstate.isDepthWriteEnabled())) {
|
||||
return;
|
||||
}
|
||||
@@ -1355,7 +1629,7 @@ void DrawEngineCommon::DepthRasterPredecoded(GEPrimitiveType prim, const void *i
|
||||
}
|
||||
float worldviewproj[16];
|
||||
ComputeFinalProjMatrix().Store(worldviewproj);
|
||||
TransformPredecodedForDepthRaster(batch.transformed + 4 * draw.vertexOffset, worldviewproj, decoded_, dec, numDecoded);
|
||||
TransformPredecodedForDepthRaster(batch.transformed + 4 * draw.vertexOffset, worldviewproj, decoded_, fmt ? *fmt : dec->GetDecVtxFmt(), numDecoded);
|
||||
}
|
||||
|
||||
// Copy indices.
|
||||
|
||||
@@ -103,7 +103,8 @@ public:
|
||||
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.
|
||||
static bool TestBoundingBoxFast(const float *cullMatrix, const void *vdata, const void *idata, int vertexCount, const VertexDecoder *dec, u32 vertType, ClipInfoFlags *clipInfoFlags);
|
||||
// cpuCullSupported: the backend can take ClipInfoFlags::CpuCull for the draw (see CpuCullSupported()).
|
||||
static bool TestBoundingBoxFast(const float *cullMatrix, const void *vdata, const void *idata, int vertexCount, const VertexDecoder *dec, u32 vertType, ClipInfoFlags *clipInfoFlags, bool cpuCullSupported = false);
|
||||
bool TestBoundingBoxThrough(GEPrimitiveType prim, const void *vdata, const void *idata, int vertexCount, const VertexDecoder *dec, u32 vertType, int *bytesRead, ClipInfoFlags *flags);
|
||||
// For a draw too big or complex for TestBoundingBoxFast: the fallbacks it would need on a GPU that lacks
|
||||
// depth clamp or clip distances, assuming the worst, or nothing (not Valid) if none are needed.
|
||||
@@ -193,6 +194,23 @@ protected:
|
||||
}
|
||||
|
||||
int ComputeNumVertsToDecode() const;
|
||||
// A hardware transformed draw that the CPU clips and culls (ClipInfoFlags::CpuCull).
|
||||
struct CpuClip {
|
||||
bool active = false;
|
||||
int numVerts = 0;
|
||||
// Of the vertices the draw reads, in decoded_ when active.
|
||||
DecVtxFormat fmt;
|
||||
};
|
||||
CpuClip BeginCpuClip(bool useHWTransform);
|
||||
// For an active CpuClip: decodes the vertices into decoded_ and the indices into decIndex_, then clips and culls
|
||||
// them there. clip->numVerts vertices of clip->fmt are then in decoded_, to be pushed.
|
||||
void DecodeClipCull(CpuClip *clip, GEPrimitiveType *prim, int *vertexCount, int *maxIndex, bool *useElements);
|
||||
void DepthRasterSubmitHW(GEPrimitiveType prim, int vertexCount, const CpuClip &clip);
|
||||
|
||||
// With ClipInfoFlags::CpuCull, clips the triangles that cross the near plane, adding vertices to verts, and drops the
|
||||
// primitives the GE culls: those entirely beyond the far plane, and the clipped triangles outside the guardband.
|
||||
// verts are the decoded vertices the indices point into. Returns the new index count.
|
||||
int ClipCullPrims(GEPrimitiveType prim, u8 *verts, const DecVtxFormat &fmt, int *numVerts, u16 *inds, int indexCount);
|
||||
|
||||
void ApplyFramebufferRead(FBOTexState *fboTexState);
|
||||
// For BindFramebufferAsColorTexture, when the draw reads the target for shader blending.
|
||||
@@ -201,7 +219,7 @@ protected:
|
||||
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);
|
||||
void DepthRasterPredecoded(GEPrimitiveType prim, const void *inVerts, int numDecoded, const VertexDecoder *dec, int vertexCount, const DecVtxFormat *fmt = nullptr);
|
||||
bool CalculateDepthDraw(DepthDraw *draw, GEPrimitiveType prim, int vertexCount, int numDecoded);
|
||||
void CommitDepthDraw(const DepthDraw &draw, int vertexCount, int numDecoded);
|
||||
|
||||
@@ -301,6 +319,11 @@ protected:
|
||||
bool useHWTransform_ = false;
|
||||
// Used to prevent unnecessary flushing in softgpu.
|
||||
bool flushOnParams_ = true;
|
||||
// Whether the backend handles ClipInfoFlags::CpuCull in its hardware transform path.
|
||||
bool cpuCullSupported_ = false;
|
||||
public:
|
||||
bool CpuCullSupported() const { return cpuCullSupported_; }
|
||||
protected:
|
||||
// Whether SubmitCurve may queue its output as predecoded, rather than through DispatchSubmitPrim.
|
||||
bool curvesPredecoded_ = true;
|
||||
|
||||
@@ -317,6 +340,10 @@ protected:
|
||||
u16 *decIndex_ = nullptr;
|
||||
// Separate from decoded_, which can hold decoded vertices that haven't been flushed yet.
|
||||
u8 *bboxScratch_ = nullptr;
|
||||
// Per vertex of a CPU clipped draw: VERT_BEHIND_NEAR | VERT_BEYOND_FAR.
|
||||
std::vector<u8> vertClass_;
|
||||
std::vector<float> clipPos_;
|
||||
std::vector<u16> clipInds_;
|
||||
|
||||
// Cached vertex decoders
|
||||
DenseHashMap<u32, VertexDecoder *> decoderMap_;
|
||||
|
||||
@@ -72,6 +72,7 @@ DrawEngineD3D11::DrawEngineD3D11(Draw::DrawContext *draw, ID3D11Device *device,
|
||||
rasterCache_(4) {
|
||||
device1_ = (ID3D11Device1 *)draw->GetNativeObject(Draw::NativeObject::DEVICE_EX);
|
||||
decOptions_.expand8BitNormalsToFloat = true;
|
||||
cpuCullSupported_ = true;
|
||||
|
||||
InitDeviceObjects();
|
||||
}
|
||||
@@ -308,6 +309,8 @@ void DrawEngineD3D11::Flush() {
|
||||
lastUseHwTransform_ = useHWTransform;
|
||||
}
|
||||
|
||||
CpuClip cpuClip = BeginCpuClip(useHWTransform);
|
||||
|
||||
if (useHWTransform) {
|
||||
ID3D11Buffer *vb_ = nullptr;
|
||||
ID3D11Buffer *ib_ = nullptr;
|
||||
@@ -315,8 +318,13 @@ void DrawEngineD3D11::Flush() {
|
||||
int vertexCount;
|
||||
int maxIndex;
|
||||
bool useElements;
|
||||
DecodeVerts(dec_, decoded_);
|
||||
DecodeIndsAndGetData(&prim, &vertexCount, &maxIndex, &useElements, false);
|
||||
if (cpuClip.active) {
|
||||
DecodeClipCull(&cpuClip, &prim, &vertexCount, &maxIndex, &useElements);
|
||||
} else {
|
||||
DecodeVerts(dec_, decoded_);
|
||||
DecodeIndsAndGetData(&prim, &vertexCount, &maxIndex, &useElements, false);
|
||||
cpuClip.numVerts = numDecodedVerts_;
|
||||
}
|
||||
gpuStats.perFrame.numVertsDrawn += vertexCount;
|
||||
|
||||
bool hasColor = (lastVType_ & GE_VTYPE_COL_MASK) != GE_VTYPE_COL_NONE;
|
||||
@@ -347,7 +355,7 @@ void DrawEngineD3D11::Flush() {
|
||||
goto bail;
|
||||
}
|
||||
ID3D11InputLayout *inputLayout;
|
||||
if (FAILED(SetupDecFmtForDraw(vshader, dec_->GetDecVtxFmt(), dec_->VertexType(), &inputLayout))) {
|
||||
if (FAILED(SetupDecFmtForDraw(vshader, cpuClip.fmt, dec_->VertexType(), &inputLayout))) {
|
||||
// Can't draw without an input layout - the draw would silently do nothing anyway.
|
||||
goto bail;
|
||||
}
|
||||
@@ -357,13 +365,13 @@ void DrawEngineD3D11::Flush() {
|
||||
shaderManager_->BindUniforms();
|
||||
|
||||
context_->IASetInputLayout(inputLayout);
|
||||
UINT stride = dec_->GetDecVtxFmt().stride;
|
||||
UINT stride = cpuClip.fmt.stride;
|
||||
context_->IASetPrimitiveTopology(d3d11prim[prim]);
|
||||
|
||||
if (!vb_) {
|
||||
// Push!
|
||||
UINT vOffset;
|
||||
int vSize = numDecodedVerts_ * dec_->GetDecVtxFmt().stride;
|
||||
int vSize = cpuClip.numVerts * cpuClip.fmt.stride;
|
||||
uint8_t *vptr = pushVerts_->BeginPush(context_, &vOffset, vSize);
|
||||
if (!vptr) {
|
||||
goto bail;
|
||||
@@ -372,7 +380,7 @@ void DrawEngineD3D11::Flush() {
|
||||
pushVerts_->EndPush(context_);
|
||||
ID3D11Buffer *buf = pushVerts_->Buf();
|
||||
context_->IASetVertexBuffers(0, 1, &buf, &stride, &vOffset);
|
||||
if (useElements) {
|
||||
if (useElements && vertexCount > 0) {
|
||||
UINT iOffset;
|
||||
int iSize = 2 * vertexCount;
|
||||
uint8_t *iptr = pushInds_->BeginPush(context_, &iOffset, iSize);
|
||||
@@ -383,7 +391,7 @@ void DrawEngineD3D11::Flush() {
|
||||
pushInds_->EndPush(context_);
|
||||
context_->IASetIndexBuffer(pushInds_->Buf(), DXGI_FORMAT_R16_UINT, iOffset);
|
||||
context_->DrawIndexed(vertexCount, 0, 0);
|
||||
} else {
|
||||
} else if (!useElements) {
|
||||
context_->Draw(vertexCount, 0);
|
||||
}
|
||||
} else {
|
||||
@@ -397,7 +405,7 @@ void DrawEngineD3D11::Flush() {
|
||||
}
|
||||
}
|
||||
if (useDepthRaster_) {
|
||||
DepthRasterSubmitRaw(prim, dec_, dec_->VertexType(), vertexCount);
|
||||
DepthRasterSubmitHW(prim, vertexCount, cpuClip);
|
||||
}
|
||||
} else {
|
||||
PROFILE_THIS_SCOPE("soft");
|
||||
|
||||
+28
-16
@@ -51,6 +51,7 @@ enum {
|
||||
};
|
||||
|
||||
DrawEngineGLES::DrawEngineGLES(Draw::DrawContext *draw) : inputLayoutMap_(16), draw_(draw) {
|
||||
cpuCullSupported_ = true;
|
||||
render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
|
||||
|
||||
decOptions_.expand8BitNormalsToFloat = false;
|
||||
@@ -90,7 +91,6 @@ void DrawEngineGLES::InitDeviceObjects() {
|
||||
entries.push_back({ ATTR_COLOR1, 3, GL_UNSIGNED_BYTE, GL_TRUE, offsetof(TransformedVertex, color1_32) });
|
||||
entries.push_back({ ATTR_NORMAL, 1, GL_FLOAT, GL_FALSE, offsetof(TransformedVertex, fog) });
|
||||
softwareInputLayout_ = render_->CreateInputLayout(entries, stride);
|
||||
|
||||
}
|
||||
|
||||
void DrawEngineGLES::DestroyDeviceObjects() {
|
||||
@@ -269,6 +269,8 @@ void DrawEngineGLES::Flush() {
|
||||
uint32_t vertexBufferOffset = 0;
|
||||
uint32_t indexBufferOffset = 0;
|
||||
|
||||
CpuClip cpuClip = BeginCpuClip(useHWTransform);
|
||||
|
||||
Shader *vshader = shaderManager_->ApplyVertexShader(useHWTransform, dec_->VertexType(), clipInfoFlags_, &vsid);
|
||||
if (!vshader) {
|
||||
// Both the requested shader and the software transform fallback failed to compile.
|
||||
@@ -280,10 +282,19 @@ void DrawEngineGLES::Flush() {
|
||||
useHWTransform = vshader->UseHWTransform(); // In case shader compilation failed and it fell back.
|
||||
|
||||
if (useHWTransform) {
|
||||
if (lastVType_ & GE_VTYPE_WEIGHT_MASK) {
|
||||
// If software skinning, we're predecoding into "decoded". So make sure we're done, then push that content.
|
||||
DecodeVerts(dec_, decoded_);
|
||||
uint32_t size = numDecodedVerts_ * dec_->GetDecVtxFmt().stride;
|
||||
int vertexCount;
|
||||
int maxIndex;
|
||||
bool useElements;
|
||||
if (cpuClip.active || (lastVType_ & GE_VTYPE_WEIGHT_MASK)) {
|
||||
// If software skinning, or clipping on the CPU, we're predecoding into "decoded". So make sure we're done,
|
||||
// then push that content.
|
||||
if (cpuClip.active) {
|
||||
DecodeClipCull(&cpuClip, &prim, &vertexCount, &maxIndex, &useElements);
|
||||
} else {
|
||||
DecodeVerts(dec_, decoded_);
|
||||
cpuClip.numVerts = numDecodedVerts_;
|
||||
}
|
||||
uint32_t size = cpuClip.numVerts * cpuClip.fmt.stride;
|
||||
u8 *dest = (u8 *)frameData.pushVertex->Allocate(size, 4, &vertexBuffer, &vertexBufferOffset);
|
||||
memcpy(dest, decoded_, size);
|
||||
} else {
|
||||
@@ -293,13 +304,12 @@ void DrawEngineGLES::Flush() {
|
||||
DecodeVerts(dec_, dest);
|
||||
}
|
||||
|
||||
int vertexCount;
|
||||
int maxIndex;
|
||||
bool useElements;
|
||||
DecodeIndsAndGetData(&prim, &vertexCount, &maxIndex, &useElements, false);
|
||||
if (!cpuClip.active) {
|
||||
DecodeIndsAndGetData(&prim, &vertexCount, &maxIndex, &useElements, false);
|
||||
}
|
||||
gpuStats.perFrame.numVertsDrawn += vertexCount;
|
||||
|
||||
if (useElements) {
|
||||
if (useElements && vertexCount > 0) {
|
||||
uint32_t esz = sizeof(uint16_t) * vertexCount;
|
||||
void *dest = frameData.pushIndex->Allocate(esz, 2, &indexBuffer, &indexBufferOffset);
|
||||
// TODO: When we need to apply an index offset, we can apply it directly when copying the indices here.
|
||||
@@ -333,19 +343,21 @@ void DrawEngineGLES::Flush() {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
GLRInputLayout *inputLayout = SetupDecFmtForDraw(dec_->GetDecVtxFmt());
|
||||
GLRInputLayout *inputLayout = SetupDecFmtForDraw(cpuClip.fmt);
|
||||
if (useElements) {
|
||||
render_->DrawIndexed(inputLayout,
|
||||
vertexBuffer, vertexBufferOffset,
|
||||
indexBuffer, indexBufferOffset,
|
||||
glprim[prim], vertexCount, GL_UNSIGNED_SHORT, 1, maxIndex - 1);
|
||||
if (vertexCount > 0) {
|
||||
render_->DrawIndexed(inputLayout,
|
||||
vertexBuffer, vertexBufferOffset,
|
||||
indexBuffer, indexBufferOffset,
|
||||
glprim[prim], vertexCount, GL_UNSIGNED_SHORT, 1, maxIndex - 1);
|
||||
}
|
||||
} else {
|
||||
render_->Draw(
|
||||
inputLayout, vertexBuffer, vertexBufferOffset,
|
||||
glprim[prim], 0, vertexCount);
|
||||
}
|
||||
if (useDepthRaster_) {
|
||||
DepthRasterSubmitRaw(prim, dec_, dec_->VertexType(), vertexCount);
|
||||
DepthRasterSubmitHW(prim, vertexCount, cpuClip);
|
||||
}
|
||||
} else {
|
||||
PROFILE_THIS_SCOPE("soft");
|
||||
|
||||
+8
-3
@@ -576,6 +576,8 @@ u32 GPUCommonHW::CheckGPUFeatures() const {
|
||||
features |= GPU_USE_CLIP_DISTANCE;
|
||||
}
|
||||
|
||||
// With clip distances too, the GPU clips and culls draws crossing the Z planes, as long as the near plane is on
|
||||
// the min Z (see TestBoundingBoxFast). Otherwise the CPU does (DrawEngineCommon::ClipCullPrims).
|
||||
if (draw_->GetDeviceCaps().maxCullDistances >= 1) {
|
||||
features |= GPU_USE_CULL_DISTANCE;
|
||||
}
|
||||
@@ -1019,6 +1021,8 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
|
||||
}
|
||||
|
||||
// For now, turn off culling on platforms where we don't have SIMD bounding box tests, like RISC-V.
|
||||
// True means the bounding box test is skipped (through mode, morphing, skinning), not that the draw passed it.
|
||||
// Those get UncheckedClipInfoFlags(), so they're never clipped or culled on the CPU either.
|
||||
#if PPSSPP_ARCH(ARM_NEON) || PPSSPP_ARCH(SSE2)
|
||||
|
||||
#define PASSES_CULLING ((vertexType & (GE_VTYPE_THROUGH_MASK | GE_VTYPE_MORPHCOUNT_MASK | GE_VTYPE_WEIGHT_MASK)) != 0)
|
||||
@@ -1036,7 +1040,7 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
|
||||
}
|
||||
if (!passCulling && !discardLines) {
|
||||
// Do software culling.
|
||||
if (drawEngineCommon_->TestBoundingBoxFast(gstate_c.cullMatrix, verts, inds, count, decoder, vertexType, &flags)) {
|
||||
if (drawEngineCommon_->TestBoundingBoxFast(gstate_c.cullMatrix, verts, inds, count, decoder, vertexType, &flags, drawEngineCommon_->CpuCullSupported())) {
|
||||
passCulling = true;
|
||||
} else {
|
||||
gpuStats.perFrame.numCulledDraws++;
|
||||
@@ -1111,7 +1115,8 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
|
||||
bool clockwise = !gstate.isCullEnabled() || gstate.getCullMode() == cullMode;
|
||||
if (canExtend) {
|
||||
// Non-indexed draws can be cheaply merged if vertexAddr hasn't changed, that means the vertices
|
||||
// are consecutive in memory. We also ignore culling here which is not ideal.
|
||||
// are consecutive in memory. We also ignore culling here which is not ideal: the merged draws take
|
||||
// the first one's flags, so one crossing a Z plane after one that doesn't isn't clipped on the CPU.
|
||||
_dbg_assert_((vertexType & GE_VTYPE_IDX_MASK) == GE_VTYPE_IDX_NONE);
|
||||
const int maxVerts = Memory::MaxSizeAtAddress(gstate_c.vertexAddr) / decoder->VertexSize();
|
||||
int commandsExecuted = drawEngineCommon_->ExtendNonIndexedPrim(src, stall, decoder, vertTypeID, clockwise, &bytesRead, isTriangle, flags, maxVerts);
|
||||
@@ -1150,7 +1155,7 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
|
||||
}
|
||||
if (!passCulling) {
|
||||
// Do software culling.
|
||||
if (drawEngineCommon_->TestBoundingBoxFast(gstate_c.cullMatrix, verts, inds, count, decoder, vertexType, &flags)) {
|
||||
if (drawEngineCommon_->TestBoundingBoxFast(gstate_c.cullMatrix, verts, inds, count, decoder, vertexType, &flags, drawEngineCommon_->CpuCullSupported())) {
|
||||
passCulling = true;
|
||||
} else {
|
||||
gpuStats.perFrame.numCulledDraws++;
|
||||
|
||||
@@ -215,6 +215,7 @@ enum class ClipInfoFlags {
|
||||
DepthClampFragment = 16,
|
||||
MinMaxZClip = 32,
|
||||
MinMaxZDiscard = 64,
|
||||
CpuCull = 128,
|
||||
};
|
||||
ENUM_CLASS_BITOPS(ClipInfoFlags);
|
||||
|
||||
|
||||
@@ -134,7 +134,8 @@ private:
|
||||
uint32_t fragmentTextureMask;
|
||||
};
|
||||
|
||||
Pipeline *GetPipeline(const VShaderID &vsid, const FShaderID &fsid, bool useHWTransform);
|
||||
// hwFmt: the vertex format for hardware transform, null for software transform.
|
||||
Pipeline *GetPipeline(const VShaderID &vsid, const FShaderID &fsid, const DecVtxFormat *hwFmt);
|
||||
Pipeline *GetOrCreatePipeline(const MetalPipelineKey &key, const DecVtxFormat *decFmt, const MetalRenderTargetFormat &format);
|
||||
id<MTLDepthStencilState> GetDepthStencilState(const MetalDepthStencilKey &key);
|
||||
bool ApplyState(const Pipeline *pipeline, bool overrideStencilRef, uint8_t stencilRef);
|
||||
|
||||
@@ -144,6 +144,7 @@ static MTLVertexDescriptor *CreateVertexDescriptor(const DecVtxFormat *decFmt) {
|
||||
|
||||
DrawEngineMetal::DrawEngineMetal(Draw::DrawContext *draw) : draw_(draw), pipelines_(256) {
|
||||
decOptions_.expand8BitNormalsToFloat = false;
|
||||
cpuCullSupported_ = true;
|
||||
InitDeviceObjects();
|
||||
}
|
||||
|
||||
@@ -254,14 +255,14 @@ id<MTLDepthStencilState> DrawEngineMetal::GetDepthStencilState(const MetalDepthS
|
||||
return dss;
|
||||
}
|
||||
|
||||
DrawEngineMetal::Pipeline *DrawEngineMetal::GetPipeline(const VShaderID &vsid, const FShaderID &fsid, bool useHWTransform) {
|
||||
DrawEngineMetal::Pipeline *DrawEngineMetal::GetPipeline(const VShaderID &vsid, const FShaderID &fsid, const DecVtxFormat *hwFmt) {
|
||||
MetalPipelineKey key{};
|
||||
key.vsid = vsid;
|
||||
key.fsid = fsid;
|
||||
key.vtxFmtId = useHWTransform ? dec_->decFmt.id : SOFTWARE_TRANSFORM_VTXFMT;
|
||||
key.vtxFmtId = hwFmt ? hwFmt->id : SOFTWARE_TRANSFORM_VTXFMT;
|
||||
key.blend = blendKey_.value;
|
||||
key.targetFormat = rm_->CurTargetFormat().Key();
|
||||
return GetOrCreatePipeline(key, useHWTransform ? &dec_->decFmt : nullptr, rm_->CurTargetFormat());
|
||||
return GetOrCreatePipeline(key, hwFmt, rm_->CurTargetFormat());
|
||||
}
|
||||
|
||||
DrawEngineMetal::Pipeline *DrawEngineMetal::GetOrCreatePipeline(const MetalPipelineKey &key, const DecVtxFormat *decFmt, const MetalRenderTargetFormat &format) {
|
||||
@@ -675,6 +676,8 @@ void DrawEngineMetal::Flush() {
|
||||
lastUseHwTransform_ = useHWTransform;
|
||||
}
|
||||
|
||||
CpuClip cpuClip = BeginCpuClip(useHWTransform);
|
||||
|
||||
auto updateVertexFullAlpha = [&]() {
|
||||
bool hasColor = (lastVType_ & GE_VTYPE_COL_MASK) != GE_VTYPE_COL_NONE;
|
||||
if (gstate.isModeThrough()) {
|
||||
@@ -691,11 +694,11 @@ void DrawEngineMetal::Flush() {
|
||||
}
|
||||
VShaderID vsid;
|
||||
FShaderID fsid;
|
||||
shaderManager_->GetShaderIDs(prim, dec_->VertexType(), &vsid, &fsid, pipelineState_, hwTransform, clipInfoFlags_);
|
||||
shaderManager_->GetShaderIDs(prim, dec_->VertexType(), &vsid, &fsid, pipelineState_, hwTransform, hwTransform ? clipInfoFlags_ : clipInfoFlags_);
|
||||
// This might copy the framebuffer, which interrupts the render pass. So it goes before the pipeline lookup,
|
||||
// which depends on the render target.
|
||||
BindShaderBlendTex();
|
||||
lastPipeline_ = GetPipeline(vsid, fsid, hwTransform);
|
||||
lastPipeline_ = GetPipeline(vsid, fsid, hwTransform ? &cpuClip.fmt : nullptr);
|
||||
gstate_c.Clean(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE);
|
||||
gstate_c.Dirty(dirtyRequiresRecheck_);
|
||||
dirtyRequiresRecheck_ = 0;
|
||||
@@ -706,20 +709,27 @@ void DrawEngineMetal::Flush() {
|
||||
|
||||
if (useHWTransform) {
|
||||
MetalPushAlloc valloc;
|
||||
if (lastVType_ & GE_VTYPE_WEIGHT_MASK) {
|
||||
// If skinning, we're predecoding into "decoded". So make sure we're done, then push that content.
|
||||
DecodeVerts(dec_, decoded_);
|
||||
valloc = rm_->Push().Push(decoded_, numDecodedVerts_ * dec_->GetDecVtxFmt().stride, 16);
|
||||
int vertexCount;
|
||||
int maxIndex;
|
||||
bool useElements;
|
||||
if (cpuClip.active || (lastVType_ & GE_VTYPE_WEIGHT_MASK)) {
|
||||
// If skinning, or clipping on the CPU, we're predecoding into "decoded". So make sure we're done, then
|
||||
// push that content.
|
||||
if (cpuClip.active) {
|
||||
DecodeClipCull(&cpuClip, &prim, &vertexCount, &maxIndex, &useElements);
|
||||
} else {
|
||||
DecodeVerts(dec_, decoded_);
|
||||
cpuClip.numVerts = numDecodedVerts_;
|
||||
}
|
||||
valloc = rm_->Push().Push(decoded_, cpuClip.numVerts * cpuClip.fmt.stride, 16);
|
||||
} else {
|
||||
int vertsToDecode = ComputeNumVertsToDecode();
|
||||
valloc = rm_->Push().Allocate(vertsToDecode * dec_->GetDecVtxFmt().stride, 16);
|
||||
DecodeVerts(dec_, valloc.ptr);
|
||||
}
|
||||
|
||||
int vertexCount;
|
||||
int maxIndex;
|
||||
bool useElements;
|
||||
DecodeIndsAndGetData(&prim, &vertexCount, &maxIndex, &useElements, false);
|
||||
if (!cpuClip.active) {
|
||||
DecodeIndsAndGetData(&prim, &vertexCount, &maxIndex, &useElements, false);
|
||||
}
|
||||
gpuStats.perFrame.numVertsDrawn += vertexCount;
|
||||
updateVertexFullAlpha();
|
||||
|
||||
@@ -739,9 +749,11 @@ void DrawEngineMetal::Flush() {
|
||||
}
|
||||
BindResources(pipeline);
|
||||
MTLPrimitiveType mtlPrim = prim == GE_PRIM_TRIANGLE_STRIP ? MTLPrimitiveTypeTriangleStrip : MTLPrimitiveTypeTriangle;
|
||||
DrawPrimitives(mtlPrim, valloc.buffer, valloc.offset, vertexCount, useElements ? decIndex_ : nullptr);
|
||||
if (vertexCount > 0) {
|
||||
DrawPrimitives(mtlPrim, valloc.buffer, valloc.offset, vertexCount, useElements ? decIndex_ : nullptr);
|
||||
}
|
||||
if (useDepthRaster_) {
|
||||
DepthRasterSubmitRaw(prim, dec_, dec_->VertexType(), vertexCount);
|
||||
DepthRasterSubmitHW(prim, vertexCount, cpuClip);
|
||||
}
|
||||
} else {
|
||||
gpuStats.perFrame.numSoftTransformedDraws++;
|
||||
|
||||
@@ -51,6 +51,7 @@ enum {
|
||||
DrawEngineVulkan::DrawEngineVulkan(Draw::DrawContext *draw)
|
||||
: draw_(draw) {
|
||||
decOptions_.expand8BitNormalsToFloat = false;
|
||||
cpuCullSupported_ = true;
|
||||
}
|
||||
|
||||
void DrawEngineVulkan::InitDeviceObjects() {
|
||||
@@ -250,14 +251,25 @@ void DrawEngineVulkan::Flush() {
|
||||
// This could be avoided with the new dynamic state extensions, but not available enough on mobile.
|
||||
const bool forceIndexed = draw_->GetDeviceCaps().verySlowShaderCompiler;
|
||||
|
||||
CpuClip cpuClip = BeginCpuClip(useHWTransform);
|
||||
|
||||
if (useHWTransform) {
|
||||
uint32_t vbOffset;
|
||||
|
||||
VkBuffer vbuf = VK_NULL_HANDLE;
|
||||
if (lastVType_ & GE_VTYPE_WEIGHT_MASK) {
|
||||
// If skinning, we're predecoding into "decoded". So make sure we're done, then push that content.
|
||||
DecodeVerts(dec_, decoded_);
|
||||
VkDeviceSize size = numDecodedVerts_ * dec_->GetDecVtxFmt().stride;
|
||||
int vertexCount;
|
||||
int maxIndex;
|
||||
bool useElements;
|
||||
if (cpuClip.active || (lastVType_ & GE_VTYPE_WEIGHT_MASK)) {
|
||||
// If skinning, or clipping on the CPU, we're predecoding into "decoded". So make sure we're done, then
|
||||
// push that content.
|
||||
if (cpuClip.active) {
|
||||
DecodeClipCull(&cpuClip, &prim, &vertexCount, &maxIndex, &useElements);
|
||||
} else {
|
||||
DecodeVerts(dec_, decoded_);
|
||||
cpuClip.numVerts = numDecodedVerts_;
|
||||
}
|
||||
VkDeviceSize size = cpuClip.numVerts * cpuClip.fmt.stride;
|
||||
u8 *dest = (u8 *)pushVertex_->Allocate(size, 4, &vbuf, &vbOffset);
|
||||
memcpy(dest, decoded_, size);
|
||||
} else {
|
||||
@@ -268,10 +280,9 @@ void DrawEngineVulkan::Flush() {
|
||||
DecodeVerts(dec_, dest);
|
||||
}
|
||||
|
||||
int vertexCount;
|
||||
int maxIndex;
|
||||
bool useElements;
|
||||
DecodeIndsAndGetData(&prim, &vertexCount, &maxIndex, &useElements, false);
|
||||
if (!cpuClip.active) {
|
||||
DecodeIndsAndGetData(&prim, &vertexCount, &maxIndex, &useElements, false);
|
||||
}
|
||||
gpuStats.perFrame.numVertsDrawn += vertexCount;
|
||||
|
||||
bool hasColor = (lastVType_ & GE_VTYPE_COL_MASK) != GE_VTYPE_COL_NONE;
|
||||
@@ -306,7 +317,7 @@ void DrawEngineVulkan::Flush() {
|
||||
|
||||
shaderManager_->GetShaderIDs(prim, dec_->VertexType(), &vshaderID, &fshaderID, pipelineState_, true, clipInfoFlags_);
|
||||
_dbg_assert_msg_(vshaderID.Bit(VS_BIT_USE_HW_TRANSFORM) == true, "Bad vshader ID");
|
||||
VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(renderManager, shaderManager_, pipelineLayout_, pipelineKey_, &dec_->decFmt, vshaderID, fshaderID, true, 0, framebufferManager_->GetMSAALevel(), false);
|
||||
VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(renderManager, shaderManager_, pipelineLayout_, pipelineKey_, &cpuClip.fmt, vshaderID, fshaderID, true, 0, framebufferManager_->GetMSAALevel(), false);
|
||||
if (!pipeline || !pipeline->pipeline) {
|
||||
// Already logged, let's bail out.
|
||||
ResetAfterSkippedDraw();
|
||||
@@ -362,14 +373,16 @@ void DrawEngineVulkan::Flush() {
|
||||
baseUBOOffset, lightUBOOffset,
|
||||
};
|
||||
if (useElements) {
|
||||
VkBuffer ibuf;
|
||||
u32 ibOffset = (uint32_t)pushIndex_->Push(decIndex_, sizeof(uint16_t) * vertexCount, 4, &ibuf);
|
||||
renderManager->DrawIndexed(descSetIndex, ARRAY_SIZE(dynamicUBOOffsets), dynamicUBOOffsets, vbuf, vbOffset, ibuf, ibOffset, vertexCount, 1);
|
||||
if (vertexCount > 0) {
|
||||
VkBuffer ibuf;
|
||||
u32 ibOffset = (uint32_t)pushIndex_->Push(decIndex_, sizeof(uint16_t) * vertexCount, 4, &ibuf);
|
||||
renderManager->DrawIndexed(descSetIndex, ARRAY_SIZE(dynamicUBOOffsets), dynamicUBOOffsets, vbuf, vbOffset, ibuf, ibOffset, vertexCount, 1);
|
||||
}
|
||||
} else {
|
||||
renderManager->Draw(descSetIndex, ARRAY_SIZE(dynamicUBOOffsets), dynamicUBOOffsets, vbuf, vbOffset, vertexCount);
|
||||
}
|
||||
if (useDepthRaster_) {
|
||||
DepthRasterSubmitRaw(prim, dec_, dec_->VertexType(), vertexCount);
|
||||
DepthRasterSubmitHW(prim, vertexCount, cpuClip);
|
||||
}
|
||||
} else {
|
||||
gpuStats.perFrame.numSoftTransformedDraws++;
|
||||
|
||||
Reference in new issue
Block a user