diff --git a/GPU/Common/DepthRaster.cpp b/GPU/Common/DepthRaster.cpp index 0878f0dd3b..c14aaf260f 100644 --- a/GPU/Common/DepthRaster.cpp +++ b/GPU/Common/DepthRaster.cpp @@ -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); diff --git a/GPU/Common/DepthRaster.h b/GPU/Common/DepthRaster.h index d2baa4f1e0..7372ef951f 100644 --- a/GPU/Common/DepthRaster.h +++ b/GPU/Common/DepthRaster.h @@ -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); diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index 2535a5912e..97aa11d6ae 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -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 -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(cullMatrix, vdata, nullptr, vertexCount, dec, flags); - case GE_VTYPE_POS_16BIT: return ::TestBoundingBoxFast(cullMatrix, vdata, nullptr, vertexCount, dec, flags); - case GE_VTYPE_POS_FLOAT: return ::TestBoundingBoxFast(cullMatrix, vdata, nullptr, vertexCount, dec, flags); + case GE_VTYPE_POS_8BIT: return ::TestBoundingBoxFast(cullMatrix, vdata, nullptr, vertexCount, dec, cpuCullSupported, flags); + case GE_VTYPE_POS_16BIT: return ::TestBoundingBoxFast(cullMatrix, vdata, nullptr, vertexCount, dec, cpuCullSupported, flags); + case GE_VTYPE_POS_FLOAT: return ::TestBoundingBoxFast(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(cullMatrix, vdata, idata, vertexCount, dec, flags); - case GE_VTYPE_POS_16BIT: return ::TestBoundingBoxFast(cullMatrix, vdata, idata, vertexCount, dec, flags); - case GE_VTYPE_POS_FLOAT: return ::TestBoundingBoxFast(cullMatrix, vdata, idata, vertexCount, dec, flags); + case GE_VTYPE_POS_8BIT: return ::TestBoundingBoxFast(cullMatrix, vdata, idata, vertexCount, dec, cpuCullSupported, flags); + case GE_VTYPE_POS_16BIT: return ::TestBoundingBoxFast(cullMatrix, vdata, idata, vertexCount, dec, cpuCullSupported, flags); + case GE_VTYPE_POS_FLOAT: return ::TestBoundingBoxFast(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(cullMatrix, vdata, idata, vertexCount, dec, flags); - case GE_VTYPE_POS_16BIT: return ::TestBoundingBoxFast(cullMatrix, vdata, idata, vertexCount, dec, flags); - case GE_VTYPE_POS_FLOAT: return ::TestBoundingBoxFast(cullMatrix, vdata, idata, vertexCount, dec, flags); + case GE_VTYPE_POS_8BIT: return ::TestBoundingBoxFast(cullMatrix, vdata, idata, vertexCount, dec, cpuCullSupported, flags); + case GE_VTYPE_POS_16BIT: return ::TestBoundingBoxFast(cullMatrix, vdata, idata, vertexCount, dec, cpuCullSupported, flags); + case GE_VTYPE_POS_FLOAT: return ::TestBoundingBoxFast(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(cullMatrix, vdata, idata, vertexCount, dec, flags); - case GE_VTYPE_POS_16BIT: return ::TestBoundingBoxFast(cullMatrix, vdata, idata, vertexCount, dec, flags); - case GE_VTYPE_POS_FLOAT: return ::TestBoundingBoxFast(cullMatrix, vdata, idata, vertexCount, dec, flags); + case GE_VTYPE_POS_8BIT: return ::TestBoundingBoxFast(cullMatrix, vdata, idata, vertexCount, dec, cpuCullSupported, flags); + case GE_VTYPE_POS_16BIT: return ::TestBoundingBoxFast(cullMatrix, vdata, idata, vertexCount, dec, cpuCullSupported, flags); + case GE_VTYPE_POS_FLOAT: return ::TestBoundingBoxFast(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. diff --git a/GPU/Common/DrawEngineCommon.h b/GPU/Common/DrawEngineCommon.h index c528ba23a8..7e68bdaeef 100644 --- a/GPU/Common/DrawEngineCommon.h +++ b/GPU/Common/DrawEngineCommon.h @@ -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 vertClass_; + std::vector clipPos_; + std::vector clipInds_; // Cached vertex decoders DenseHashMap decoderMap_; diff --git a/GPU/D3D11/DrawEngineD3D11.cpp b/GPU/D3D11/DrawEngineD3D11.cpp index 7b656fdb86..c90d19924c 100644 --- a/GPU/D3D11/DrawEngineD3D11.cpp +++ b/GPU/D3D11/DrawEngineD3D11.cpp @@ -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"); diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index 3b05d01408..a6760b018b 100644 --- a/GPU/GLES/DrawEngineGLES.cpp +++ b/GPU/GLES/DrawEngineGLES.cpp @@ -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"); diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index d380503c78..79c7086c56 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -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++; diff --git a/GPU/GPUDefinitions.h b/GPU/GPUDefinitions.h index c640be960b..70ea4986a1 100644 --- a/GPU/GPUDefinitions.h +++ b/GPU/GPUDefinitions.h @@ -215,6 +215,7 @@ enum class ClipInfoFlags { DepthClampFragment = 16, MinMaxZClip = 32, MinMaxZDiscard = 64, + CpuCull = 128, }; ENUM_CLASS_BITOPS(ClipInfoFlags); diff --git a/GPU/Metal/DrawEngineMetal.h b/GPU/Metal/DrawEngineMetal.h index 9f42f7b073..cb69e41d6d 100644 --- a/GPU/Metal/DrawEngineMetal.h +++ b/GPU/Metal/DrawEngineMetal.h @@ -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 GetDepthStencilState(const MetalDepthStencilKey &key); bool ApplyState(const Pipeline *pipeline, bool overrideStencilRef, uint8_t stencilRef); diff --git a/GPU/Metal/DrawEngineMetal.mm b/GPU/Metal/DrawEngineMetal.mm index 9098dd60e4..45c0010334 100644 --- a/GPU/Metal/DrawEngineMetal.mm +++ b/GPU/Metal/DrawEngineMetal.mm @@ -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 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++; diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 163420f617..f65569f46b 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -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++;