Determine the clip flags directly when culling, simplifying the code.

This commit is contained in:
Henrik Rydgård
2026-05-30 13:03:39 +02:00
parent 2a5c2fa477
commit 44ece6dfa2
17 changed files with 144 additions and 136 deletions
+53 -50
View File
@@ -157,7 +157,7 @@ void DrawEngineCommon::DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex
bool clockwise = !gstate.isCullEnabled() || gstate.getCullMode() == cullMode;
VertexDecoder *dec = GetVertexDecoder(vertTypeID);
SubmitPrim(&temp[0], nullptr, prim, vertexCount, dec, vertTypeID, clockwise, &bytesRead, BoundingDepths());
SubmitPrim(&temp[0], nullptr, prim, vertexCount, dec, vertTypeID, clockwise, &bytesRead, clipInfoFlags_);
Flush();
if (!prevThrough) {
@@ -330,20 +330,19 @@ bool DrawEngineCommon::TestBoundingBox(const void *vdata, const void *inds, int
}
// This optionally culls collections of points against the four side planes, and always computes the min and max of Z and W.
// The result of that can be used to determine if we need to drop down to software transform+clip or we can hand
//
// The result of that is then used to determine if we need to drop down to software transform+clip or we can hand
// off to hardware, with whatever capabilities are available.
//
// NOTE: This doesn't handle through-mode or indexing (morph or skinning can be handled if they're implemented in software during decode).
template<u32 posFmt>
static bool TestBoundingBoxFast(const float *worldViewProj, const void *vdata, int vertexCount, const VertexDecoder *dec, u8 *decoded, BoundingDepths *depths) {
static bool TestBoundingBoxFast(const float *worldViewProj, const void *vdata, int vertexCount, const VertexDecoder *dec, u8 *decoded, ClipInfoFlags *clipInfoFlags) {
Mat4F32 worldViewProjMat(worldViewProj);
alignas(16) static const float planesXYData[4] = { 1, -1, 1, -1 };
Vec4F32 planesXY = Vec4F32::LoadAligned(planesXYData);
Vec4S32 insideMaskXY = Vec4S32::Zero();
Vec4S32 insideMaskZ = Vec4S32::Zero(); // Note: This does some duplicate computation. We could avoid it on ARM32 using Vec2S32 but not really worth it.
Vec4S32 anyOutsideMaskZ = Vec4S32::Zero();
float minProjZ = FLT_MAX;
float maxProjZ = -FLT_MAX;
// Used to reduce the Z precision. This effectively implements the small offsets where Z can be very slightly outside -1..1.
// In reality we should probably affect X and Y too, but meh.
@@ -353,9 +352,11 @@ static bool TestBoundingBoxFast(const float *worldViewProj, const void *vdata, i
const int offset = dec->posoff;
const s8 *data = (const s8 *)vdata + offset;
const float vpZOffset = gstate.getViewportZCenter();
const float vpZScale = gstate.getViewportZScale();
float minProjZ = FLT_MAX;
float maxProjZ = -FLT_MAX;
for (int i = 0; i < vertexCount; i++, data += stride) {
Vec4F32 objPos;
switch (posFmt) {
@@ -392,23 +393,47 @@ static bool TestBoundingBoxFast(const float *worldViewProj, const void *vdata, i
return false;
}
depths->valid = true;
depths->hitClipSpaceZW = AnyCompareBitsSet(anyOutsideMaskZ);
const float vpZOffset = gstate.getViewportZCenter();
minProjZ += vpZOffset;
maxProjZ += vpZOffset;
ClipInfoFlags flags = ClipInfoFlags::Valid;
// If the W=-Z plane was intersected, here we can go through the vertices again, and check for X/Y bounds for range culling.
// However! We need to find a valid way to do so by "backprojecting" the range culling into clip space, which may be a little tricky.
//
// If nothing is outside the box, the "inversion" cases (vertices hit the boundary after clipping like Flatout, Sengoku Cannon)
// cannot happen, and soft clipping is only needed if the viewport is smaller than the valid Z range.
//
// Alternatively, we just do a compat flag for the affected games until we can solve this.
// Before checking later, we apply a viewport to the projZ, so we can later compare against minZ/maxZ or the outer bounds.
// But to save operations we don't do it here.
depths->minProjZ = minProjZ + vpZOffset;
depths->maxProjZ = maxProjZ + vpZOffset;
if (needFragmentMinMaxClipping() && (minProjZ < gstate.getDepthRangeMin() || maxProjZ > gstate.getDepthRangeMax())) {
if (gstate_c.Use(GPU_USE_CLIP_DISTANCE)) {
flags |= ClipInfoFlags::MinMaxZDiscard;
} else {
flags |= ClipInfoFlags::MinMaxZClip;
}
}
if (AnyCompareBitsSet(anyOutsideMaskZ) || !gstate_c.Use(GPU_USE_CULL_DISTANCE)) {
// Some vertices were outside the Z clipping planes. Clip againt Z=-W in software (and do culling, too).
// TODO: With a compat flag for Flatout/Sengoku, we'll be able to avoid this in many cases.
flags |= ClipInfoFlags::SoftClipCull;
}
if (needFragmentDepthClamp() && (minProjZ < 0 || maxProjZ > 65535)) {
if (gstate_c.Use(GPU_USE_DEPTH_CLAMP)) {
flags |= ClipInfoFlags::DepthClamp;
} else {
flags |= ClipInfoFlags::DepthClampFragment;
}
}
*clipInfoFlags = flags;
return true;
}
bool DrawEngineCommon::TestBoundingBoxFast(const float *cullMatrix, const void *vdata, int vertexCount, const VertexDecoder *dec, u32 vertType, BoundingDepths *depths) {
bool DrawEngineCommon::TestBoundingBoxFast(const float *cullMatrix, const void *vdata, int vertexCount, const VertexDecoder *dec, u32 vertType, ClipInfoFlags *flags) {
// 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)) {
@@ -419,11 +444,11 @@ bool DrawEngineCommon::TestBoundingBoxFast(const float *cullMatrix, const void *
switch (vertType & GE_VTYPE_POS_MASK) {
case GE_VTYPE_POS_8BIT:
return ::TestBoundingBoxFast<GE_VTYPE_POS_8BIT>(cullMatrix, vdata, vertexCount, dec, decoded_, depths);
return ::TestBoundingBoxFast<GE_VTYPE_POS_8BIT>(cullMatrix, vdata, vertexCount, dec, decoded_, flags);
case GE_VTYPE_POS_16BIT:
return ::TestBoundingBoxFast<GE_VTYPE_POS_16BIT>(cullMatrix, vdata, vertexCount, dec, decoded_, depths);
return ::TestBoundingBoxFast<GE_VTYPE_POS_16BIT>(cullMatrix, vdata, vertexCount, dec, decoded_, flags);
case GE_VTYPE_POS_FLOAT:
return ::TestBoundingBoxFast<GE_VTYPE_POS_FLOAT>(cullMatrix, vdata, vertexCount, dec, decoded_, depths);
return ::TestBoundingBoxFast<GE_VTYPE_POS_FLOAT>(cullMatrix, vdata, vertexCount, dec, decoded_, flags);
default:
// Shouldn't end up here with the checks outside this function.
_dbg_assert_(false);
@@ -431,35 +456,6 @@ bool DrawEngineCommon::TestBoundingBoxFast(const float *cullMatrix, const void *
}
}
bool DrawEngineCommon::CheckBoundingDepths(bool useHWTransform) const {
if (useHWTransform && boundingDepths_.valid) {
if (boundingDepths_.hitClipSpaceZW) {
// Revert to software transform so we can clip more accurately.
//
// This is only really needed for two known games: Flatout (water) and Sengoku Cannon (pink geometry). But there may
// be some more.
return false;
}
if (needFragmentMinMaxClipping()) {
if ((boundingDepths_.minProjZ < gstate.getDepthRangeMin() || boundingDepths_.maxProjZ > gstate.getDepthRangeMax())) {
// Revert to software transform so we can clamp more accurately.
return false;
}
}
if (needFragmentDepthClamp()) {
if ((boundingDepths_.minProjZ < 0 || boundingDepths_.maxProjZ > 65535)) {
// Revert to software transform so we can clamp more accurately.
return false;
}
}
// Also handle clamping in software if it's not supported in hardware (or always?)
return true;
}
return useHWTransform;
}
// 2D bounding box test against scissor. No indexing yet.
// Only supports non-indexed draws with float positions. TODO: Add more float formats.
bool DrawEngineCommon::TestBoundingBoxThrough(const void *vdata, int vertexCount, const VertexDecoder *dec, u32 vertType, int *bytesRead) {
@@ -603,9 +599,10 @@ int DrawEngineCommon::ComputeNumVertsToDecode() const {
}
// Takes a list of consecutive PRIM opcodes, and extends the current draw call to include them.
// This is just a performance optimization.
int DrawEngineCommon::ExtendNonIndexedPrim(const uint32_t *cmd, const uint32_t *stall, const VertexDecoder *dec, u32 vertTypeID, bool clockwise, int *bytesRead, bool isTriangle, const BoundingDepths &depths) {
boundingDepths_.Merge(depths);
// This is just a performance optimization. NOTE: This isn't compatible with really accurate culling,
// unless we refactor things a bit.
int DrawEngineCommon::ExtendNonIndexedPrim(const uint32_t *cmd, const uint32_t *stall, const VertexDecoder *dec, u32 vertTypeID, bool clockwise, int *bytesRead, bool isTriangle, ClipInfoFlags clipInfoFlags) {
clipInfoFlags_ |= clipInfoFlags;
const uint32_t *start = cmd;
int prevDrawVerts = numDrawVerts_ - 1;
@@ -674,12 +671,18 @@ void DrawEngineCommon::SkipPrim(GEPrimitiveType prim, int vertexCount, const Ver
}
// vertTypeID is the vertex type but with the UVGen mode smashed into the top bits.
bool DrawEngineCommon::SubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, u32 vertTypeID, bool clockwise, int *bytesRead, const BoundingDepths &depths) {
bool DrawEngineCommon::SubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, u32 vertTypeID, bool clockwise, int *bytesRead, ClipInfoFlags clipInfoFlags) {
if (!indexGen.PrimCompatible(prevPrim_, prim) || numDrawVerts_ >= MAX_DEFERRED_DRAW_VERTS || numDrawInds_ >= MAX_DEFERRED_DRAW_INDS || vertexCountInDrawCalls_ + vertexCount > VERTEX_BUFFER_MAX) {
Flush();
}
boundingDepths_.Merge(depths);
// TODO: Flush on clipinfoflags change?
clipInfoFlags_ |= clipInfoFlags;
// if (clipInfoFlags_ != clipInfoFlags) {
// Flush();
// }
// clipInfoFlags_ = clipInfoFlags;
_dbg_assert_(numDrawVerts_ < MAX_DEFERRED_DRAW_VERTS);
_dbg_assert_(numDrawInds_ < MAX_DEFERRED_DRAW_INDS);
+16 -26
View File
@@ -73,25 +73,15 @@ struct alignas(16) Plane8 {
float Test(int i, const float f[3]) const { return x[i] * f[0] + y[i] * f[1] + z[i] * f[2] + w[i]; }
};
struct BoundingDepths {
bool valid = false;
bool hitClipSpaceZW = false;
float minProjZ = FLT_MAX;
float maxProjZ = -FLT_MAX;
void Merge(const BoundingDepths &other) {
if (!valid) {
*this = other;
return;
}
hitClipSpaceZW |= other.hitClipSpaceZW;
if (other.minProjZ < minProjZ) {
minProjZ = other.minProjZ;
}
if (other.maxProjZ > maxProjZ) {
maxProjZ = other.maxProjZ;
}
}
enum class ClipInfoFlags {
Valid = 1,
SoftClipCull = 2,
DepthClamp = 8,
DepthClampFragment = 16,
MinMaxZClip = 32,
MinMaxZDiscard = 64,
};
ENUM_CLASS_BITOPS(ClipInfoFlags);
class DrawEngineCommon {
public:
@@ -115,9 +105,9 @@ public:
// This would seem to be unnecessary now, but is still required for splines/beziers to work in the software backend since SubmitPrim
// is different. Should probably refactor that.
// Note that vertTypeID should be computed using GetVertTypeID().
virtual void DispatchSubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertTypeID, bool clockwise, int *bytesRead, const BoundingDepths &depths) {
virtual void DispatchSubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertTypeID, bool clockwise, int *bytesRead, ClipInfoFlags clipInfoFlags) {
VertexDecoder *dec = GetVertexDecoder(vertTypeID);
SubmitPrim(verts, inds, prim, vertexCount, dec, vertTypeID, clockwise, bytesRead, depths);
SubmitPrim(verts, inds, prim, vertexCount, dec, vertTypeID, clockwise, bytesRead, clipInfoFlags);
}
virtual void DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode, bool continuation);
@@ -126,7 +116,7 @@ public:
// This is a less accurate version of TestBoundingBox, but faster. Can have more false positives.
// Doesn't support indexing.
bool TestBoundingBoxFast(const float *cullMatrix, const void *vdata, int vertexCount, const VertexDecoder *dec, u32 vertType, BoundingDepths *depth);
bool TestBoundingBoxFast(const float *cullMatrix, const void *vdata, int vertexCount, const VertexDecoder *dec, u32 vertType, ClipInfoFlags *clipInfoFlags);
bool TestBoundingBoxThrough(const void *vdata, int vertexCount, const VertexDecoder *dec, u32 vertType, int *bytesRead);
bool EstimateThroughPrimSafeSize(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, u32 vertType, int *safeWidth, int *safeHeight);
@@ -140,8 +130,8 @@ public:
}
}
int ExtendNonIndexedPrim(const uint32_t *cmd, const uint32_t *stall, const VertexDecoder *dec, u32 vertTypeID, bool clockwise, int *bytesRead, bool isTriangle, const BoundingDepths &depths);
bool SubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, u32 vertTypeID, bool clockwise, int *bytesRead, const BoundingDepths &depths);
int ExtendNonIndexedPrim(const uint32_t *cmd, const uint32_t *stall, const VertexDecoder *dec, u32 vertTypeID, bool clockwise, int *bytesRead, bool isTriangle, ClipInfoFlags clipInfoFlags);
bool SubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, u32 vertTypeID, bool clockwise, int *bytesRead, ClipInfoFlags clipInfoFlags);
void SkipPrim(GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, int *bytesRead);
template<class Surface>
@@ -186,7 +176,7 @@ public:
protected:
virtual bool UpdateUseHWTessellation(bool enabled) const { return enabled; }
bool CheckBoundingDepths(bool useHwTransform) const;
bool CheckClipFlags(bool useHwTransform) const;
void DecodeVerts(const VertexDecoder *dec, u8 *dest);
int DecodeInds();
@@ -246,7 +236,7 @@ protected:
seenPrims_ = 0;
anyCCWOrIndexed_ = false;
gstate_c.vertexFullAlpha = true;
boundingDepths_ = BoundingDepths();
clipInfoFlags_ = {};
// Now seems as good a time as any to reset the min/max coords, which we may examine later.
gstate_c.vertBounds.minU = 512;
@@ -379,7 +369,7 @@ protected:
uint16_t *depthIndices_ = nullptr;
// Depth tracking
BoundingDepths boundingDepths_;
ClipInfoFlags clipInfoFlags_{};
// Queue
int depthVertexCount_ = 0;
+12 -14
View File
@@ -12,6 +12,7 @@
#include "GPU/Common/GPUStateUtils.h"
#include "GPU/Common/ShaderId.h"
#include "GPU/Common/VertexDecoderCommon.h"
#include "GPU/Common/DrawEngineCommon.h" // Just for ClipInfoFlags
std::string VertexShaderDesc(const VShaderID &id) {
std::stringstream desc;
@@ -70,7 +71,7 @@ std::string VertexShaderDesc(const VShaderID &id) {
return desc.str();
}
void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode) {
void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode, ClipInfoFlags clipInfoFlags) {
const bool isModeThrough = (vertType & GE_VTYPE_THROUGH) != 0;
const bool isSoftwareFallback = !isModeThrough && !useHWTransform && g_Config.bHardwareTransform;
bool doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear();
@@ -164,16 +165,13 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform,
}
id.SetBit(VS_BIT_NORM_REVERSE_TESS, gstate.isPatchNormalsReversed());
}
} else { // !useHwTransform
// Various conditions that require per-pixel depth manipulation (very expensive!)
if (!isModeThrough) {
if (needFragmentDepthClamp()) {
id.SetBit(VS_BIT_FS_DEPTH_CLAMP);
}
if (needFragmentMinMaxClipping()) {
id.SetBit(VS_BIT_FS_MINMAX_DISCARD);
}
}
}
if (clipInfoFlags & ClipInfoFlags::DepthClampFragment) {
id.SetBit(VS_BIT_FS_DEPTH_CLAMP);
}
if (clipInfoFlags & ClipInfoFlags::MinMaxZDiscard) {
id.SetBit(VS_BIT_FS_MINMAX_DISCARD);
}
id.SetBit(VS_BIT_FLATSHADE, doFlatShading);
@@ -301,7 +299,7 @@ inline u32 SanitizeBlendMode(GEBlendMode mode) {
// Here we must take all the bits of the gstate that determine what the fragment shader will
// look like, and concatenate them together into an ID.
void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pipelineState, const Draw::Bugs &bugs, bool useHwTransform) {
void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pipelineState, const Draw::Bugs &bugs, bool useHwTransform, ClipInfoFlags clipInfoFlags) {
FShaderID id;
bool isModeThrough = gstate.isModeThrough();
@@ -309,10 +307,10 @@ void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pip
// draws that needs this and use software transform as the fallback for them. That logic will have to change if we change that.
// NOTE: This check MUST be identical to the one in ComputeVertexShaderID, otherwise we might get mismatches between VS and FS and end up with no shader at all.
if (!useHwTransform && !isModeThrough) {
if (needFragmentDepthClamp()) {
if (clipInfoFlags & ClipInfoFlags::DepthClampFragment) {
id.SetBit(FS_BIT_DEPTH_CLAMP);
}
if (needFragmentMinMaxClipping()) {
if (clipInfoFlags & ClipInfoFlags::MinMaxZDiscard) {
id.SetBit(FS_BIT_MINMAX_DISCARD);
}
}
+5 -4
View File
@@ -7,12 +7,13 @@
#include "Common/CommonFuncs.h"
#include "GPU/GPUState.h"
enum class ClipInfoFlags;
// Shared ID checks for when the vertex and fragment shaders (and host code) need to coordinate.
// NOTE: Both of these assume non - through - mode.Don't check these if in through mode.
// NOTE: Both of these assume non-through-mode. Don't check these if in through mode.
inline bool needFragmentMinMaxClipping() {
return gstate.getDepthRangeMin() != 0 && gstate.getDepthRangeMax() != 0xFFFF && !gstate_c.Use(GPU_USE_CLIP_DISTANCE);
return gstate.getDepthRangeMin() != 0 && gstate.getDepthRangeMax() != 0xFFFF;
}
inline bool needFragmentDepthClamp() {
@@ -261,13 +262,13 @@ namespace Draw {
class Bugs;
}
void ComputeVertexShaderID(VShaderID *id, u32 vertType, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode);
void ComputeVertexShaderID(VShaderID *id, u32 vertType, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode, ClipInfoFlags clipInfoFlags);
// Generates a compact string that describes the shader. Useful in a list to get an overview
// of the current flora of shaders.
std::string VertexShaderDesc(const VShaderID &id);
struct ComputedPipelineState;
void ComputeFragmentShaderID(FShaderID *id, const ComputedPipelineState &pipelineState, const Draw::Bugs &bugs, bool useHwTransform);
void ComputeFragmentShaderID(FShaderID *id, const ComputedPipelineState &pipelineState, const Draw::Bugs &bugs, bool useHwTransform, ClipInfoFlags clipInfoFlags);
std::string FragmentShaderDesc(const FShaderID &id);
// For sanity checking.
+6 -5
View File
@@ -498,7 +498,7 @@ void DrawEngineCommon::SubmitCurve(const void *control_points, const void *indic
SimpleBufferManager managedBuf(decoded_, DECODED_VERTEX_BUFFER_SIZE / 2);
int num_points = surface.num_points_u * surface.num_points_v;
const int num_points = surface.num_points_u * surface.num_points_v;
u16 index_lower_bound = 0;
u16 index_upper_bound = num_points - 1;
IndexConverter ConvertIndex(vertType, indices);
@@ -523,7 +523,7 @@ void DrawEngineCommon::SubmitCurve(const void *control_points, const void *indic
return;
}
u32 origVertType = vertType;
const u32 origVertType = vertType;
vertType = ::NormalizeVertices(simplified_control_points, temp_buffer, (u8 *)control_points, index_lower_bound, index_upper_bound, origVDecoder, vertType);
VertexDecoder *vdecoder = GetVertexDecoder(vertType);
@@ -547,7 +547,7 @@ void DrawEngineCommon::SubmitCurve(const void *control_points, const void *indic
output.indices = decIndex_;
output.count = 0;
int maxVerts = DECODED_VERTEX_BUFFER_SIZE / 2 / vertexSize;
const int maxVerts = DECODED_VERTEX_BUFFER_SIZE / 2 / vertexSize;
surface.Init(maxVerts);
@@ -576,14 +576,15 @@ void DrawEngineCommon::SubmitCurve(const void *control_points, const void *indic
vertTypeID = GetVertTypeID(vertTypeWithIndex16, gstate.getUVGenMode(), applySkinInDecode_);
int generatedBytesRead;
if (output.count) {
BoundingDepths depths;
DispatchSubmitPrim(output.vertices, output.indices, PatchPrimToPrim(surface.primType), output.count, vertTypeID, true, &generatedBytesRead, depths);
ClipInfoFlags flags{}; // Don't need any special processing.
DispatchSubmitPrim(output.vertices, output.indices, PatchPrimToPrim(surface.primType), output.count, vertTypeID, true, &generatedBytesRead, flags);
}
if (flushOnParams_)
Flush();
if (origVertType & GE_VTYPE_TC_MASK) {
// If analysis says this is uninitialized, it's wrong.
gstate_c.uv = prevUVScale;
}
}
+5 -3
View File
@@ -294,7 +294,9 @@ void DrawEngineD3D11::Flush() {
// Always use software for flat shading to fix the provoking index.
bool tess = gstate_c.submitType == SubmitType::HW_BEZIER || gstate_c.submitType == SubmitType::HW_SPLINE;
bool useHWTransform = CanUseHardwareTransform(prim) && (tess || gstate.getShadeMode() != GE_SHADE_FLAT);
useHWTransform = CheckBoundingDepths(useHWTransform);
if (clipInfoFlags_ & ClipInfoFlags::SoftClipCull) {
useHWTransform = false;
}
if (useHWTransform != lastUseHwTransform_) {
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE);
@@ -333,7 +335,7 @@ void DrawEngineD3D11::Flush() {
D3D11VertexShader *vshader;
D3D11FragmentShader *fshader;
shaderManager_->GetShaders(prim, dec_->VertexType(), &vshader, &fshader, pipelineState_, useHWTransform, useHWTessellation_, decOptions_.expandAllWeightsToFloat, applySkinInDecode_);
shaderManager_->GetShaders(prim, dec_->VertexType(), &vshader, &fshader, pipelineState_, useHWTransform, useHWTessellation_, decOptions_.expandAllWeightsToFloat, applySkinInDecode_, clipInfoFlags_);
ID3D11InputLayout *inputLayout;
SetupDecFmtForDraw(vshader, dec_->GetDecVtxFmt(), dec_->VertexType(), &inputLayout);
context_->PSSetShader(fshader->GetShader(), nullptr, 0);
@@ -447,7 +449,7 @@ void DrawEngineD3D11::Flush() {
if (action == SW_DRAW_INDEXED) {
D3D11VertexShader *vshader;
D3D11FragmentShader *fshader;
shaderManager_->GetShaders(prim, swDec->VertexType(), &vshader, &fshader, pipelineState_, false, false, decOptions_.expandAllWeightsToFloat, true);
shaderManager_->GetShaders(prim, swDec->VertexType(), &vshader, &fshader, pipelineState_, false, false, decOptions_.expandAllWeightsToFloat, true, clipInfoFlags_);
context_->PSSetShader(fshader->GetShader(), nullptr, 0);
context_->VSSetShader(vshader->GetShader(), nullptr, 0);
shaderManager_->UpdateUniforms(framebufferManager_->UseBufferedRendering());
+3 -3
View File
@@ -186,20 +186,20 @@ void ShaderManagerD3D11::BindUniforms() {
context_->PSSetConstantBuffers(0, 1, ps_cbs);
}
void ShaderManagerD3D11::GetShaders(int prim, u32 vertexType, D3D11VertexShader **vshader, D3D11FragmentShader **fshader, const ComputedPipelineState &pipelineState, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode) {
void ShaderManagerD3D11::GetShaders(int prim, u32 vertexType, D3D11VertexShader **vshader, D3D11FragmentShader **fshader, const ComputedPipelineState &pipelineState, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode, ClipInfoFlags clipInfoFlags) {
VShaderID VSID;
FShaderID FSID;
if (gstate_c.IsDirty(DIRTY_VERTEXSHADER_STATE)) {
gstate_c.Clean(DIRTY_VERTEXSHADER_STATE);
ComputeVertexShaderID(&VSID, vertexType, useHWTransform, useHWTessellation, weightsAsFloat, useSkinInDecode);
ComputeVertexShaderID(&VSID, vertexType, useHWTransform, useHWTessellation, weightsAsFloat, useSkinInDecode, clipInfoFlags);
} else {
VSID = lastVSID_;
}
if (gstate_c.IsDirty(DIRTY_FRAGMENTSHADER_STATE)) {
gstate_c.Clean(DIRTY_FRAGMENTSHADER_STATE);
ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), useHWTransform);
ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), useHWTransform, clipInfoFlags);
} else {
FSID = lastFSID_;
}
+3 -1
View File
@@ -81,12 +81,14 @@ protected:
class D3D11PushBuffer;
enum class ClipInfoFlags;
class ShaderManagerD3D11 : public ShaderManagerCommon {
public:
ShaderManagerD3D11(Draw::DrawContext *draw, ID3D11Device *device, ID3D11DeviceContext *context, D3D_FEATURE_LEVEL featureLevel);
~ShaderManagerD3D11();
void GetShaders(int prim, u32 vertexType, D3D11VertexShader **vshader, D3D11FragmentShader **fshader, const ComputedPipelineState &pipelineState, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode);
void GetShaders(int prim, u32 vertexType, D3D11VertexShader **vshader, D3D11FragmentShader **fshader, const ComputedPipelineState &pipelineState, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode, ClipInfoFlags clipInfoFlags);
void ClearShaders() override;
void DirtyLastShader() override;
+6 -4
View File
@@ -254,14 +254,16 @@ void DrawEngineGLES::Flush() {
GEPrimitiveType prim = prevPrim_;
bool useHWTransform = CanUseHardwareTransform(prim);
useHWTransform = CheckBoundingDepths(useHWTransform);
if (clipInfoFlags_ & ClipInfoFlags::SoftClipCull) {
useHWTransform = false;
}
if (useHWTransform != lastUseHwTransform_) {
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE);
lastUseHwTransform_ = useHWTransform;
}
Shader *vshader = shaderManager_->ApplyVertexShader(useHWTransform, useHWTessellation_, dec_->VertexType(), decOptions_.expandAllWeightsToFloat, applySkinInDecode_ || !useHWTransform, &vsid);
Shader *vshader = shaderManager_->ApplyVertexShader(useHWTransform, useHWTessellation_, dec_->VertexType(), decOptions_.expandAllWeightsToFloat, applySkinInDecode_ || !useHWTransform, clipInfoFlags_, &vsid);
useHWTransform = vshader->UseHWTransform(); // In case shader compilation failed and it fell back. However, this can no longer really happen... Need to fix this.
@@ -313,7 +315,7 @@ void DrawEngineGLES::Flush() {
ApplyDrawState(prim);
ApplyDrawStateLate(false, 0);
LinkedShader *program = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_, true);
LinkedShader *program = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_, true, clipInfoFlags_);
GLRInputLayout *inputLayout = SetupDecFmtForDraw(dec_->GetDecVtxFmt());
if (useElements) {
render_->DrawIndexed(inputLayout,
@@ -399,7 +401,7 @@ void DrawEngineGLES::Flush() {
ApplyDrawState(prim);
ApplyDrawStateLate(result.setStencil, result.stencilValue);
LinkedShader *linked = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_, false);
LinkedShader *linked = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_, false, clipInfoFlags_);
if (!linked) {
// Not much we can do here. Let's skip drawing.
goto bail;
+5 -5
View File
@@ -764,10 +764,10 @@ Shader *ShaderManagerGLES::CompileVertexShader(VShaderID VSID) {
return new Shader(render_, codeBuffer_, desc, params);
}
Shader *ShaderManagerGLES::ApplyVertexShader(bool useHWTransform, bool useHWTessellation, u32 vertexType, bool weightsAsFloat, bool useSkinInDecode, VShaderID *VSID) {
Shader *ShaderManagerGLES::ApplyVertexShader(bool useHWTransform, bool useHWTessellation, u32 vertexType, bool weightsAsFloat, bool useSkinInDecode, ClipInfoFlags clipInfoFlags, VShaderID *VSID) {
if (gstate_c.IsDirty(DIRTY_VERTEXSHADER_STATE)) {
gstate_c.Clean(DIRTY_VERTEXSHADER_STATE);
ComputeVertexShaderID(VSID, vertexType, useHWTransform, useHWTessellation, weightsAsFloat, useSkinInDecode);
ComputeVertexShaderID(VSID, vertexType, useHWTransform, useHWTessellation, weightsAsFloat, useSkinInDecode, clipInfoFlags);
} else {
*VSID = lastVSID_;
}
@@ -800,7 +800,7 @@ Shader *ShaderManagerGLES::ApplyVertexShader(bool useHWTransform, bool useHWTess
// Can still work with software transform.
VShaderID vsidTemp;
ComputeVertexShaderID(&vsidTemp, vertexType, false, false, weightsAsFloat, true);
ComputeVertexShaderID(&vsidTemp, vertexType, false, false, weightsAsFloat, true, clipInfoFlags);
vs = CompileVertexShader(vsidTemp);
}
@@ -808,7 +808,7 @@ Shader *ShaderManagerGLES::ApplyVertexShader(bool useHWTransform, bool useHWTess
return vs;
}
LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState, bool useHwTransform) {
LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState, bool useHwTransform, ClipInfoFlags clipInfoFlags) {
uint64_t dirty = gstate_c.GetDirtyUniforms();
if (dirty) {
if (lastShader_)
@@ -820,7 +820,7 @@ LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs,
FShaderID FSID;
if (gstate_c.IsDirty(DIRTY_FRAGMENTSHADER_STATE)) {
gstate_c.Clean(DIRTY_FRAGMENTSHADER_STATE);
ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), useHwTransform);
ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), useHwTransform, clipInfoFlags);
} else {
FSID = lastFSID_;
}
+4 -2
View File
@@ -161,6 +161,8 @@ private:
class VertexDecoder;
enum class ClipInfoFlags;
class ShaderManagerGLES : public ShaderManagerCommon {
public:
ShaderManagerGLES(Draw::DrawContext *draw);
@@ -170,8 +172,8 @@ public:
// This is the old ApplyShader split into two parts, because of annoying information dependencies.
// If you call ApplyVertexShader, you MUST call ApplyFragmentShader soon afterwards.
Shader *ApplyVertexShader(bool useHWTransform, bool useHWTessellation, u32 vertexType, bool weightsAsFloat, bool useSkinInDecode, VShaderID *VSID);
LinkedShader *ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState, bool useHWTransform);
Shader *ApplyVertexShader(bool useHWTransform, bool useHWTessellation, u32 vertexType, bool weightsAsFloat, bool useSkinInDecode, ClipInfoFlags clipInfoFlags, VShaderID *VSID);
LinkedShader *ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState, bool useHWTransform, ClipInfoFlags clipInfoFlags);
void DeviceLost() override;
void DeviceRestore(Draw::DrawContext *draw) override;
+10 -10
View File
@@ -989,7 +989,7 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
// This is the check from #21678 for Evangelion JO.
// TODO: Make this less specific.
if (gstate.isModeThrough() && gstate.isTextureMapEnabled() && gstate.getColorMask() != 0xFFFFFFFF &&
gstate.getScissorX1() == 0 && gstate.getScissorY1() == 0 && Memory::IsVRAMAddress(gstate.getFrameBufAddress())) {
gstate.getScissorX1() == 0 && gstate.getScissorY1() == 0 && Memory::IsVRAMAddress(gstate.getFrameBufAddress())) {
int safeWidth;
int safeHeight;
// First-frame readback can happen before queued draws reach backend transform.
@@ -1014,7 +1014,7 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
#define MAX_CULL_CHECK_COUNT 2500
// For now, turn off culling on platforms where we don't have SIMD bounding box tests, like RISC-V.
// For now, turn off culling on platforms where we don't have SIMD bounding box tests, like RISC-V.
#if PPSSPP_ARCH(ARM_NEON) || PPSSPP_ARCH(SSE2)
#define PASSES_CULLING ((vertexType & (GE_VTYPE_THROUGH_MASK | GE_VTYPE_MORPHCOUNT_MASK | GE_VTYPE_WEIGHT_MASK | GE_VTYPE_IDX_MASK)) || count > MAX_CULL_CHECK_COUNT)
@@ -1025,13 +1025,13 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
#endif
BoundingDepths depths;
ClipInfoFlags flags{};
// If certain conditions are true, do frustum culling.
bool passCulling = PASSES_CULLING;
if (!passCulling) {
// Do software culling.
if (drawEngineCommon_->TestBoundingBoxFast(gstate_c.cullMatrix, verts, count, decoder, vertexType, &depths)) {
if (drawEngineCommon_->TestBoundingBoxFast(gstate_c.cullMatrix, verts, count, decoder, vertexType, &flags)) {
passCulling = true;
} else {
gpuStats.perFrame.numCulledDraws++;
@@ -1044,7 +1044,7 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
// Cuts down on checking, while not losing that much efficiency.
bool onePassed = false;
if (passCulling) {
if (!drawEngineCommon_->SubmitPrim(verts, inds, prim, count, decoder, vertTypeID, true, &bytesRead, depths)) {
if (!drawEngineCommon_->SubmitPrim(verts, inds, prim, count, decoder, vertTypeID, true, &bytesRead, flags)) {
canExtend = false;
}
onePassed = true;
@@ -1090,9 +1090,9 @@ 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.
// are consecutive in memory. We also ignore culling here which is not ideal.
_dbg_assert_((vertexType & GE_VTYPE_IDX_MASK) == GE_VTYPE_IDX_NONE);
int commandsExecuted = drawEngineCommon_->ExtendNonIndexedPrim(src, stall, decoder, vertTypeID, clockwise, &bytesRead, isTriangle, depths);
int commandsExecuted = drawEngineCommon_->ExtendNonIndexedPrim(src, stall, decoder, vertTypeID, clockwise, &bytesRead, isTriangle, flags);
if (!commandsExecuted) {
goto bail;
}
@@ -1118,18 +1118,18 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
}
bool passCulling = onePassed || PASSES_CULLING;
BoundingDepths depths;
ClipInfoFlags flags{};
if (!passCulling) {
// Do software culling.
_dbg_assert_((vertexType & GE_VTYPE_IDX_MASK) == GE_VTYPE_IDX_NONE);
if (drawEngineCommon_->TestBoundingBoxFast(gstate_c.cullMatrix, verts, count, decoder, vertexType, &depths)) {
if (drawEngineCommon_->TestBoundingBoxFast(gstate_c.cullMatrix, verts, count, decoder, vertexType, &flags)) {
passCulling = true;
} else {
gpuStats.perFrame.numCulledDraws++;
}
}
if (passCulling) {
if (!drawEngineCommon_->SubmitPrim(verts, inds, newPrim, count, decoder, vertTypeID, clockwise, &bytesRead, depths)) {
if (!drawEngineCommon_->SubmitPrim(verts, inds, newPrim, count, decoder, vertTypeID, clockwise, &bytesRead, flags)) {
canExtend = false;
}
// As soon as one passes, assume we don't need to check the rest of this batch.
+1 -1
View File
@@ -68,7 +68,7 @@ void SoftwareDrawEngine::Flush() {
transformUnit.Flush(gpuCommon_, "debug");
}
void SoftwareDrawEngine::DispatchSubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertTypeID, bool clockwise, int *bytesRead, const BoundingDepths &depths) {
void SoftwareDrawEngine::DispatchSubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertTypeID, bool clockwise, int *bytesRead, ClipInfoFlags clipInfoFlags) {
_assert_msg_(clockwise, "Mixed cull mode not supported.");
transformUnit.SubmitPrimitive(verts, inds, prim, vertexCount, vertTypeID, bytesRead, this);
}
+3 -1
View File
@@ -161,6 +161,8 @@ private:
friend SoftwareVertexReader;
};
enum class ClipInfoFlags;
class SoftwareDrawEngine : public DrawEngineCommon {
public:
SoftwareDrawEngine();
@@ -171,7 +173,7 @@ public:
void NotifyConfigChanged() override;
void Flush() override;
void DispatchSubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertType, bool clockwise, int *bytesRead, const BoundingDepths &depths) override;
void DispatchSubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, u32 vertTypeID, bool clockwise, int *bytesRead, ClipInfoFlags clipInfoFlags) override;
void DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode, bool continuation) override;
VertexDecoder *FindVertexDecoder(u32 vtype);
+6 -3
View File
@@ -241,8 +241,11 @@ void DrawEngineVulkan::Flush() {
provokingVertexOk = true;
}
bool useHWTransform = CanUseHardwareTransform(prim) && provokingVertexOk;
useHWTransform = CheckBoundingDepths(useHWTransform);
if (clipInfoFlags_ & ClipInfoFlags::SoftClipCull) {
useHWTransform = false;
}
// Is this still needed?
if (useHWTransform != lastUseHwTransform_) {
// Need to re-evaluate software transform fallbacks.
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE);
@@ -302,7 +305,7 @@ void DrawEngineVulkan::Flush() {
VulkanVertexShader *vshader = nullptr;
VulkanFragmentShader *fshader = nullptr;
shaderManager_->GetShaders(prim, dec_->VertexType(), &vshader, &fshader, pipelineState_, true, useHWTessellation_, decOptions_.expandAllWeightsToFloat, applySkinInDecode_);
shaderManager_->GetShaders(prim, dec_->VertexType(), &vshader, &fshader, pipelineState_, true, useHWTessellation_, decOptions_.expandAllWeightsToFloat, applySkinInDecode_, clipInfoFlags_);
_dbg_assert_msg_(vshader->UseHWTransform(), "Bad vshader");
VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(renderManager, pipelineLayout_, pipelineKey_, &dec_->decFmt, vshader, fshader, true, 0, framebufferManager_->GetMSAALevel(), false);
if (!pipeline || !pipeline->pipeline) {
@@ -470,7 +473,7 @@ void DrawEngineVulkan::Flush() {
VulkanVertexShader *vshader = nullptr;
VulkanFragmentShader *fshader = nullptr;
shaderManager_->GetShaders(prim, swDec->VertexType(), &vshader, &fshader, pipelineState_, false, false, decOptions_.expandAllWeightsToFloat, true);
shaderManager_->GetShaders(prim, swDec->VertexType(), &vshader, &fshader, pipelineState_, false, false, decOptions_.expandAllWeightsToFloat, true, clipInfoFlags_);
_dbg_assert_msg_(!vshader->UseHWTransform(), "Bad vshader");
VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(renderManager, pipelineLayout_, pipelineKey_, &swDec->decFmt, vshader, fshader, false, 0, framebufferManager_->GetMSAALevel(), false);
if (!pipeline || !pipeline->pipeline) {
+3 -3
View File
@@ -244,7 +244,7 @@ uint64_t ShaderManagerVulkan::UpdateUniforms(bool useBufferedRendering) {
return dirty;
}
void ShaderManagerVulkan::GetShaders(int prim, u32 vertexType, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, const ComputedPipelineState &pipelineState, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode) {
void ShaderManagerVulkan::GetShaders(int prim, u32 vertexType, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, const ComputedPipelineState &pipelineState, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode, ClipInfoFlags clipInfoFlags) {
VulkanContext *vulkan = (VulkanContext *)draw_->GetNativeObject(Draw::NativeObject::CONTEXT);
VShaderID VSID;
@@ -254,7 +254,7 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertexType, VulkanVertexShade
if (gstate_c.IsDirty(DIRTY_VERTEXSHADER_STATE)) {
gstate_c.Clean(DIRTY_VERTEXSHADER_STATE);
recomputedVS = true;
ComputeVertexShaderID(&VSID, vertexType, useHWTransform, useHWTessellation, weightsAsFloat, useSkinInDecode);
ComputeVertexShaderID(&VSID, vertexType, useHWTransform, useHWTessellation, weightsAsFloat, useSkinInDecode, clipInfoFlags);
if (VSID == lastVSID_) {
_dbg_assert_(lastVShader_ != nullptr);
vs = lastVShader_;
@@ -284,7 +284,7 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertexType, VulkanVertexShade
VulkanFragmentShader *fs = nullptr;
if (gstate_c.IsDirty(DIRTY_FRAGMENTSHADER_STATE)) {
gstate_c.Clean(DIRTY_FRAGMENTSHADER_STATE);
ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), useHWTransform);
ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), useHWTransform, clipInfoFlags);
recomputedFS = true;
if (FSID == lastFSID_) {
_dbg_assert_(lastFShader_ != nullptr);
+3 -1
View File
@@ -92,6 +92,8 @@ struct Uniforms {
UB_VS_Bones ub_bones{};
};
enum class ClipInfoFlags;
class ShaderManagerVulkan : public ShaderManagerCommon {
public:
ShaderManagerVulkan(Draw::DrawContext *draw);
@@ -100,7 +102,7 @@ public:
void DeviceLost() override;
void DeviceRestore(Draw::DrawContext *draw) override;
void GetShaders(int prim, u32 vertexType, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, const ComputedPipelineState &pipelineState, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode);
void GetShaders(int prim, u32 vertexType, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, const ComputedPipelineState &pipelineState, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode, ClipInfoFlags clipInfoFlags);
void ClearShaders() override;
void DirtyLastShader() override;