mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-07-11 01:25:07 +02:00
Merge pull request #21715 from hrydgard/vp-refactor-2
Viewport and culling total rework
This commit is contained in:
+1
-2
@@ -2019,8 +2019,6 @@ set(GPU_SOURCES
|
||||
GPU/Common/FragmentShaderGenerator.h
|
||||
GPU/Common/VertexShaderGenerator.cpp
|
||||
GPU/Common/VertexShaderGenerator.h
|
||||
GPU/Common/GeometryShaderGenerator.cpp
|
||||
GPU/Common/GeometryShaderGenerator.h
|
||||
GPU/Common/FramebufferManagerCommon.cpp
|
||||
GPU/Common/FramebufferManagerCommon.h
|
||||
GPU/Common/GPUDebugInterface.cpp
|
||||
@@ -2093,6 +2091,7 @@ set(GPU_SOURCES
|
||||
GPU/GPUCommonHW.h
|
||||
GPU/GPUState.cpp
|
||||
GPU/GPUState.h
|
||||
GPU/GPUStateSIMDUtil.h
|
||||
GPU/Math3D.cpp
|
||||
GPU/Math3D.h
|
||||
GPU/Software/BinManager.cpp
|
||||
|
||||
@@ -36,6 +36,8 @@ const char * const hlsl_preamble_fs =
|
||||
"#define fract frac\n"
|
||||
"#define mod(x, y) fmod(x, y)\n"
|
||||
"#define inversesqrt rsqrt\n"
|
||||
"#define floatBitsToUint asuint\n"
|
||||
"#define uintBitsToFloat asfloat\n"
|
||||
"\n";
|
||||
|
||||
static const char * const hlsl_d3d11_preamble_fs =
|
||||
@@ -65,6 +67,8 @@ static const char * const hlsl_preamble_gs =
|
||||
"#define mediump\n"
|
||||
"#define highp\n"
|
||||
"#define inversesqrt rsqrt\n"
|
||||
"#define floatBitsToUint asuint\n"
|
||||
"#define uintBitsToFloat asfloat\n"
|
||||
"\n";
|
||||
|
||||
static const char * const vulkan_glsl_preamble_gs =
|
||||
@@ -92,6 +96,8 @@ static const char * const hlsl_preamble_vs =
|
||||
"#define fract frac\n"
|
||||
"#define mod(x, y) fmod(x, y)\n"
|
||||
"#define inversesqrt rsqrt\n"
|
||||
"#define floatBitsToUint asuint\n"
|
||||
"#define uintBitsToFloat asfloat\n"
|
||||
"\n";
|
||||
|
||||
static const char * const semanticNames[] = {
|
||||
|
||||
@@ -184,7 +184,7 @@ inline bool AnyZeroSignBit(Vec4S32 value) {
|
||||
return _mm_movemask_ps(_mm_castsi128_ps(value.v)) != 0xF;
|
||||
}
|
||||
|
||||
// These are for evaluating compare masks.
|
||||
// These are for evaluating compare masks. On some archs it might just check the upper bit.
|
||||
inline bool AllCompareBitsSet(Vec4S32 value) {
|
||||
return _mm_movemask_ps(_mm_castsi128_ps(value.v)) == 0xF;
|
||||
}
|
||||
|
||||
@@ -349,9 +349,11 @@ void ScreenManager::sendMessage(UIMessage message, const char *value) {
|
||||
|
||||
// NOTE: Changed this to send the message to all screens, instead of just the top one,
|
||||
// to allow EmuScreen to receive messages from popup menus. Hope this didn't break anything..
|
||||
for (const auto &iter : stack_) {
|
||||
if (iter.screen) {
|
||||
iter.screen->sendMessage(message, value);
|
||||
// NOTE: We do not use the iterator solution here, because the screen array may change by sendMessage!
|
||||
// Although if it does, that's very bad.
|
||||
for (int i = 0; i < stack_.size(); i++) {
|
||||
if (stack_[i].screen) {
|
||||
stack_[i].screen->sendMessage(message, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ public:
|
||||
void Layout() override;
|
||||
};
|
||||
|
||||
const float NONE = -FLT_MAX;
|
||||
constexpr float NONE = -FLT_MAX;
|
||||
|
||||
enum class Centering {
|
||||
None = 0,
|
||||
|
||||
@@ -99,7 +99,6 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) {
|
||||
CheckSetting(iniFile, gameID, "RequireBufferedRendering", &flags_.RequireBufferedRendering);
|
||||
CheckSetting(iniFile, gameID, "RequireBlockTransfer", &flags_.RequireBlockTransfer);
|
||||
CheckSetting(iniFile, gameID, "RequireDefaultCPUClock", &flags_.RequireDefaultCPUClock);
|
||||
CheckSetting(iniFile, gameID, "DisableAccurateDepth", &flags_.DisableAccurateDepth);
|
||||
CheckSetting(iniFile, gameID, "MGS2AcidHack", &flags_.MGS2AcidHack);
|
||||
CheckSetting(iniFile, gameID, "SonicRivalsHack", &flags_.SonicRivalsHack);
|
||||
CheckSetting(iniFile, gameID, "BlockTransferAllowCreateFB", &flags_.BlockTransferAllowCreateFB);
|
||||
|
||||
@@ -58,7 +58,6 @@ struct CompatFlags {
|
||||
bool RequireBufferedRendering;
|
||||
bool RequireBlockTransfer;
|
||||
bool RequireDefaultCPUClock;
|
||||
bool DisableAccurateDepth;
|
||||
bool MGS2AcidHack;
|
||||
bool SonicRivalsHack;
|
||||
bool BlockTransferAllowCreateFB;
|
||||
|
||||
+1
-1
@@ -284,7 +284,7 @@ public:
|
||||
|
||||
bool bSoftwareRendering;
|
||||
bool bSoftwareRenderingJit;
|
||||
bool bHardwareTransform; // only used in the GLES backend
|
||||
bool bHardwareTransform;
|
||||
bool bSoftwareSkinning;
|
||||
bool bVendorBugChecksEnabled;
|
||||
bool bUseGeometryShader;
|
||||
|
||||
+2
-2
@@ -569,7 +569,7 @@ static int sceGeGetMtx(int type, u32 matrixPtr) {
|
||||
if (!gpu || !gpu->GetMatrix24(GEMatrixType(type), dest, 0))
|
||||
return hleLogError(Log::sceGe, SCE_KERNEL_ERROR_INVALID_INDEX, "invalid matrix");
|
||||
|
||||
return hleLogInfo(Log::sceGe, 0);
|
||||
return hleLogDebug(Log::sceGe, 0);
|
||||
}
|
||||
|
||||
static u32 sceGeGetCmd(int cmd) {
|
||||
@@ -599,7 +599,7 @@ static u32 sceGeGetCmd(int cmd) {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return hleLogInfo(Log::sceGe, val);
|
||||
return hleLogDebug(Log::sceGe, val);
|
||||
}
|
||||
return hleLogError(Log::sceGe, SCE_KERNEL_ERROR_INVALID_INDEX);
|
||||
}
|
||||
|
||||
@@ -1219,6 +1219,7 @@ int sceKernelGetThreadExitStatus(SceUID threadID) {
|
||||
// Some return values don't deserve to be considered errors for logging.
|
||||
switch ((PSPErrorCode)status) {
|
||||
case SCE_KERNEL_ERROR_NOT_DORMANT:
|
||||
case SCE_KERNEL_ERROR_DORMANT:
|
||||
return hleLogDebug(Log::sceKernel, status);
|
||||
default:
|
||||
return hleLogDebugOrError(Log::sceKernel, status);
|
||||
|
||||
+1
-1
@@ -422,7 +422,7 @@ static u32 sceSasSetVolume(u32 core, int voiceNum, int leftVol, int rightVol, in
|
||||
bool overVolume = abs(leftVol) > PSP_SAS_VOL_MAX || abs(rightVol) > PSP_SAS_VOL_MAX;
|
||||
overVolume = overVolume || abs(effectLeftVol) > PSP_SAS_VOL_MAX || abs(effectRightVol) > PSP_SAS_VOL_MAX;
|
||||
if (overVolume) {
|
||||
return hleLogError(Log::sceSas, SCE_SAS_ERROR_INVALID_VOLUME);
|
||||
return hleLogWarning(Log::sceSas, SCE_SAS_ERROR_INVALID_VOLUME, "invalid volume");
|
||||
}
|
||||
|
||||
__SasDrain();
|
||||
|
||||
+126
-96
@@ -30,8 +30,10 @@
|
||||
#include "Core/Config.h"
|
||||
#include "GPU/GPUCommon.h"
|
||||
#include "GPU/Common/DrawEngineCommon.h"
|
||||
#include "GPU/GPUStateSIMDUtil.h"
|
||||
#include "GPU/Common/SplineCommon.h"
|
||||
#include "GPU/Common/DepthRaster.h"
|
||||
#include "GPU/Common/ShaderId.h"
|
||||
#include "GPU/Common/VertexDecoderCommon.h"
|
||||
#include "GPU/Common/SoftwareTransformCommon.h"
|
||||
#include "GPU/ge_constants.h"
|
||||
@@ -147,7 +149,7 @@ void DrawEngineCommon::DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex
|
||||
// Code checks this reg directly, not just the vtype ID.
|
||||
if (!prevThrough) {
|
||||
gstate.vertType |= GE_VTYPE_THROUGH;
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE);
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE);
|
||||
}
|
||||
|
||||
int bytesRead;
|
||||
@@ -155,12 +157,12 @@ 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);
|
||||
SubmitPrim(&temp[0], nullptr, prim, vertexCount, dec, vertTypeID, clockwise, &bytesRead, clipInfoFlags_);
|
||||
Flush();
|
||||
|
||||
if (!prevThrough) {
|
||||
gstate.vertType &= ~GE_VTYPE_THROUGH;
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE);
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,13 +188,16 @@ bool DrawEngineCommon::TestBoundingBox(const void *vdata, const void *inds, int
|
||||
|
||||
// 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))
|
||||
if (gstate_c.Use(GPU_USE_VIRTUAL_REALITY)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try to skip NormalizeVertices if it's pure positions. No need to bother with a vertex decoder
|
||||
// and a large vertex format.
|
||||
if ((vertType & 0xFFFFFF) == GE_VTYPE_POS_FLOAT && !inds) {
|
||||
memcpy(verts, vdata, sizeof(float) * 3 * vertexCount);
|
||||
// Most games that use bbox use floating point bboxes (Outrun, Tekken 6 etc).
|
||||
// memcpy(verts, vdata, sizeof(float) * 3 * vertexCount);
|
||||
verts = (float *)vdata;
|
||||
} else if ((vertType & 0xFFFFFF) == GE_VTYPE_POS_8BIT && !inds) {
|
||||
const s8 *vtx = (const s8 *)vdata;
|
||||
for (int i = 0; i < vertexCount * 3; i++) {
|
||||
@@ -305,7 +310,7 @@ bool DrawEngineCommon::TestBoundingBox(const void *vdata, const void *inds, int
|
||||
}
|
||||
}
|
||||
|
||||
int countToCheck = gstate.isDepthClampEnabled() ? 6 : 4;
|
||||
int countToCheck = gstate.isDepthClipEnabled() ? 6 : 4;
|
||||
#if 0
|
||||
// For debugging, the exclusive check. This should make it obvious where the culling borders are in screen space.
|
||||
for (int i = 0; i < countToCheck; i++) {
|
||||
@@ -324,36 +329,34 @@ bool DrawEngineCommon::TestBoundingBox(const void *vdata, const void *inds, int
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOTE: This doesn't handle through-mode, indexing, morph, or skinning.
|
||||
// TODO: For high vertex counts, we should just take the min/max of all the verts, and test the resulting six cube
|
||||
// corners. That way we can cull more draws quite cheaply.
|
||||
// We could take the min/max during the regular vertex decode, and just skip the draw call if it's trivially culled.
|
||||
// This would help games like Midnight Club (that one does a lot of out-of-bounds drawing) immensely.
|
||||
// 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 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) {
|
||||
// Simple, most common case.
|
||||
const int stride = dec->VertexSize();
|
||||
const int offset = dec->posoff;
|
||||
|
||||
// We only check the 4 sides. Near/far won't likely make a huge difference.
|
||||
// We test one vertex against 4 planes to get some SIMD. Vertices need to be transformed to world space
|
||||
// for testing, don't want to re-do that, so we have to use that "pivot" of the data.
|
||||
|
||||
// Planes are now fixed. Maybe we can use that to optimize.
|
||||
// x<-w x>w
|
||||
// y<-w y>w
|
||||
|
||||
// NOTE: The planes are the columns. We only need the first members.
|
||||
// x > -w -> x + w > 0
|
||||
// x < w -> -x + w > 0
|
||||
// y > -w -> y + w > 0
|
||||
// y < w -> -y + w > 0
|
||||
|
||||
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 insideMask = Vec4S32::Zero();
|
||||
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();
|
||||
|
||||
// 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.
|
||||
alignas(16) static const u32 vertexMaskData[4] = {0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFFFFFF};
|
||||
Vec4S32 vertexMask = Vec4S32::LoadAligned((const int *)vertexMaskData);
|
||||
const int stride = dec->VertexSize();
|
||||
const int offset = dec->posoff;
|
||||
const s8 *data = (const s8 *)vdata + offset;
|
||||
|
||||
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) {
|
||||
@@ -367,75 +370,87 @@ static bool TestBoundingBoxFast(const float *worldViewProj, const void *vdata, i
|
||||
objPos = Vec4F32::Load((const float *)data);
|
||||
break;
|
||||
}
|
||||
Vec4F32 clippos = objPos.AsVec3ByMatrix44(worldViewProjMat);
|
||||
Vec4F32 posXY = clippos.ShuffleXXYY();
|
||||
Vec4F32 posW = clippos.ShuffleWWWW();
|
||||
Vec4F32 planeDist = posXY * planesXY + posW;
|
||||
insideMask |= planeDist.CompareGe(Vec4F32::Zero());
|
||||
Vec4F32 clipPos = objPos.AsVec3ByMatrix44(worldViewProjMat) & vertexMask; // Not sure we should do the vertex mask thing.
|
||||
Vec4F32 posW = clipPos.ShuffleWWWW();
|
||||
Vec4F32 posXY = clipPos.ShuffleXXYY();
|
||||
Vec4F32 planeDistXY = posXY * planesXY + posW;
|
||||
insideMaskXY |= planeDistXY.CompareGe(Vec4F32::Zero());
|
||||
Vec4F32 posZ = clipPos.ShuffleZZZZ(); // This means that we compute the Z sides twice. Oh well.
|
||||
Vec4F32 planeDistZ = posZ * planesXY + posW;
|
||||
anyOutsideMaskZ |= planeDistZ.CompareLt(Vec4F32::Zero());
|
||||
insideMaskZ |= planeDistZ.CompareGe(Vec4F32::Zero());
|
||||
const float projZ = vpZScale * clipPos[2] / clipPos[3];
|
||||
if (projZ < minProjZ) {
|
||||
minProjZ = projZ;
|
||||
}
|
||||
if (projZ > maxProjZ) { // else ruins the minss/maxss optimization.
|
||||
maxProjZ = projZ;
|
||||
}
|
||||
}
|
||||
// At least one vertex is inside one of the planes.
|
||||
return AllCompareBitsSet(insideMask);
|
||||
|
||||
if (!AllCompareBitsSet(insideMaskXY) || !AllCompareBitsSet(insideMaskZ)) {
|
||||
// All vertices were outside one side of the clipping cube. We can skip the draw entirely.
|
||||
return false;
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
if (needFragmentMinMaxClipping() && (minProjZ < gstate.getDepthRangeMin() || maxProjZ > gstate.getDepthRangeMax())) {
|
||||
if (gstate_c.Use(GPU_USE_CLIP_DISTANCE)) {
|
||||
flags |= ClipInfoFlags::MinMaxZClip;
|
||||
} else {
|
||||
// Implement min/max in the fragment shader.
|
||||
flags |= ClipInfoFlags::MinMaxZDiscard;
|
||||
}
|
||||
}
|
||||
|
||||
if (AnyCompareBitsSet(anyOutsideMaskZ)) {
|
||||
// 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, unless
|
||||
// GPU_USE_CULL_DISTANCE is missing, in which case we need it for culling.
|
||||
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 *worldViewProj, const void *vdata, int vertexCount, const VertexDecoder *dec, u32 vertType) {
|
||||
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)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Modify the transform matrix to take the viewport into account before culling. This is not necessary
|
||||
// for most games, but there are games that rely on outside-viewport draws (such as Dante's Inferno)'s post
|
||||
// processing effects, and we don't want to cull those.
|
||||
// Potentially we should cache this transform matrix too, but hopefully this is not a bottleneck.
|
||||
// I guess we could also do this directly when computing worldviewproj...
|
||||
|
||||
const float vpXCenter = gstate.getViewportXCenter();
|
||||
const float vpYCenter = gstate.getViewportYCenter();
|
||||
const float vpXScale = gstate.getViewportXScale();
|
||||
const float vpYScale = gstate.getViewportYScale();
|
||||
const int scissorX2 = gstate.getScissorX2();
|
||||
const int scissorY2 = gstate.getScissorY2();
|
||||
|
||||
// Check for weird scaling that can make graphics extend beyond the viewport.
|
||||
// NOTE: These checks are not bullet proof.
|
||||
float mtx[16];
|
||||
if (vpXCenter != 2048.0f || vpYCenter != 2048.0f || vpXScale < ((scissorX2 + 1) >> 1) || vpYScale < ((scissorY2 + 1) >> 1)) {
|
||||
// Note that the PSP does not clip against the viewport.
|
||||
const Vec2f baseOffset = Vec2f(gstate.getOffsetX(), gstate.getOffsetY());
|
||||
// Region1 (rate) is used as an X1/Y1 here, matching PSP behavior.
|
||||
Vec2f minOffset = baseOffset + Vec2f(std::max(gstate.getRegionX1(), gstate.getScissorX1()), std::max(gstate.getRegionY1(), gstate.getScissorY1()));
|
||||
Vec2f maxOffset = baseOffset + Vec2f(std::min(gstate.getRegionX2(), gstate.getScissorX2()), std::min(gstate.getRegionY2(), gstate.getScissorY2()));
|
||||
|
||||
// Now let's apply the viewport to our scissor/region + offset range.
|
||||
Vec2f inverseViewportScale = Vec2f(1.0f / gstate.getViewportXScale(), 1.0f / gstate.getViewportYScale());
|
||||
Vec2f minViewport = (minOffset - Vec2f(gstate.getViewportXCenter(), gstate.getViewportYCenter())) * inverseViewportScale;
|
||||
Vec2f maxViewport = (maxOffset - Vec2f(gstate.getViewportXCenter(), gstate.getViewportYCenter())) * inverseViewportScale;
|
||||
|
||||
Vec2f viewportInvSize = Vec2f(1.0f / (maxViewport.x - minViewport.x), 1.0f / (maxViewport.y - minViewport.y));
|
||||
|
||||
Lin::Matrix4x4 applyViewport{};
|
||||
// Scale to the viewport's size.
|
||||
applyViewport.xx = 2.0f * viewportInvSize.x;
|
||||
applyViewport.yy = 2.0f * viewportInvSize.y;
|
||||
applyViewport.zz = 1.0f;
|
||||
applyViewport.ww = 1.0f;
|
||||
// And offset to the viewport's centers.
|
||||
applyViewport.wx = -(maxViewport.x + minViewport.x) * viewportInvSize.x;
|
||||
applyViewport.wy = -(maxViewport.y + minViewport.y) * viewportInvSize.y;
|
||||
|
||||
// TODO: Optimize.
|
||||
Matrix4ByMatrix4(mtx, worldViewProj, applyViewport.m);
|
||||
worldViewProj = mtx;
|
||||
} else if (vertexCount == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (vertType & GE_VTYPE_POS_MASK) {
|
||||
case GE_VTYPE_POS_8BIT:
|
||||
return ::TestBoundingBoxFast<GE_VTYPE_POS_8BIT>(worldViewProj, vdata, vertexCount, dec, decoded_);
|
||||
return ::TestBoundingBoxFast<GE_VTYPE_POS_8BIT>(cullMatrix, vdata, vertexCount, dec, decoded_, flags);
|
||||
case GE_VTYPE_POS_16BIT:
|
||||
return ::TestBoundingBoxFast<GE_VTYPE_POS_16BIT>(worldViewProj, vdata, vertexCount, dec, decoded_);
|
||||
return ::TestBoundingBoxFast<GE_VTYPE_POS_16BIT>(cullMatrix, vdata, vertexCount, dec, decoded_, flags);
|
||||
case GE_VTYPE_POS_FLOAT:
|
||||
return ::TestBoundingBoxFast<GE_VTYPE_POS_FLOAT>(worldViewProj, vdata, vertexCount, dec, decoded_);
|
||||
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);
|
||||
@@ -444,7 +459,7 @@ bool DrawEngineCommon::TestBoundingBoxFast(const float *worldViewProj, const voi
|
||||
}
|
||||
|
||||
// 2D bounding box test against scissor. No indexing yet.
|
||||
// Only supports non-indexed draws with float positions.
|
||||
// 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) {
|
||||
// Grab temp buffer space from large offsets in decoded_. Not exactly safe for large draws.
|
||||
if (vertexCount > 16) {
|
||||
@@ -467,8 +482,8 @@ bool DrawEngineCommon::TestBoundingBoxThrough(const void *vdata, int vertexCount
|
||||
bool allOutsideBottom = true;
|
||||
const float left = gstate.getScissorX1();
|
||||
const float top = gstate.getScissorY1();
|
||||
const float right = gstate.getScissorX2();
|
||||
const float bottom = gstate.getScissorY2();
|
||||
const float right = gstate.getScissorX2() + 1;
|
||||
const float bottom = gstate.getScissorY2() + 1;
|
||||
|
||||
switch (vertType & GE_VTYPE_POS_MASK) {
|
||||
case GE_VTYPE_POS_FLOAT:
|
||||
@@ -481,13 +496,13 @@ bool DrawEngineCommon::TestBoundingBoxThrough(const void *vdata, int vertexCount
|
||||
if (x >= left) {
|
||||
allOutsideLeft = false;
|
||||
}
|
||||
if (x <= right + 1) {
|
||||
if (x <= right) {
|
||||
allOutsideRight = false;
|
||||
}
|
||||
if (y >= top) {
|
||||
allOutsideTop = false;
|
||||
}
|
||||
if (y <= bottom + 1) {
|
||||
if (y <= bottom) {
|
||||
allOutsideBottom = false;
|
||||
}
|
||||
}
|
||||
@@ -585,9 +600,16 @@ int DrawEngineCommon::ComputeNumVertsToDecode() const {
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 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) {
|
||||
// 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) {
|
||||
if (clipInfoFlags & ClipInfoFlags::Valid) {
|
||||
clipInfoFlags_ |= clipInfoFlags;
|
||||
}
|
||||
|
||||
const uint32_t *start = cmd;
|
||||
int prevDrawVerts = numDrawVerts_ - 1;
|
||||
DeferredVerts &dv = drawVerts_[prevDrawVerts];
|
||||
@@ -635,7 +657,7 @@ int DrawEngineCommon::ExtendNonIndexedPrim(const uint32_t *cmd, const uint32_t *
|
||||
return cmd - start;
|
||||
}
|
||||
|
||||
void DrawEngineCommon::SkipPrim(GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, u32 vertTypeID, int *bytesRead) {
|
||||
void DrawEngineCommon::SkipPrim(GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, int *bytesRead) {
|
||||
if (!indexGen.PrimCompatible(prevPrim_, prim)) {
|
||||
Flush();
|
||||
}
|
||||
@@ -655,10 +677,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) {
|
||||
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();
|
||||
}
|
||||
|
||||
if (clipInfoFlags & ClipInfoFlags::Valid) {
|
||||
if (clipInfoFlags_ != clipInfoFlags) {
|
||||
Flush();
|
||||
}
|
||||
clipInfoFlags_ = clipInfoFlags;
|
||||
}
|
||||
|
||||
_dbg_assert_(numDrawVerts_ < MAX_DEFERRED_DRAW_VERTS);
|
||||
_dbg_assert_(numDrawInds_ < MAX_DEFERRED_DRAW_INDS);
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cfloat>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Data/Collections/Hashmaps.h"
|
||||
@@ -71,6 +73,16 @@ 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]; }
|
||||
};
|
||||
|
||||
enum class ClipInfoFlags {
|
||||
Valid = 1,
|
||||
SoftClipCull = 2,
|
||||
DepthClamp = 8,
|
||||
DepthClampFragment = 16,
|
||||
MinMaxZClip = 32,
|
||||
MinMaxZDiscard = 64,
|
||||
};
|
||||
ENUM_CLASS_BITOPS(ClipInfoFlags);
|
||||
|
||||
class DrawEngineCommon {
|
||||
public:
|
||||
DrawEngineCommon();
|
||||
@@ -93,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) {
|
||||
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);
|
||||
SubmitPrim(verts, inds, prim, vertexCount, dec, vertTypeID, clockwise, bytesRead, clipInfoFlags);
|
||||
}
|
||||
|
||||
virtual void DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode, bool continuation);
|
||||
@@ -104,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 *worldViewProj, const void *control_points, int vertexCount, const VertexDecoder *dec, u32 vertType);
|
||||
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);
|
||||
|
||||
@@ -118,9 +130,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
int ExtendNonIndexedPrim(const uint32_t *cmd, const uint32_t *stall, const VertexDecoder *dec, u32 vertTypeID, bool clockwise, int *bytesRead, bool isTriangle);
|
||||
bool SubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, u32 vertTypeID, bool clockwise, int *bytesRead);
|
||||
void SkipPrim(GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, u32 vertTypeID, int *bytesRead);
|
||||
int ExtendNonIndexedPrim(const uint32_t *cmd, const uint32_t *stall, const VertexDecoder *dec, u32 vertTypeID, bool clockwise, int *bytesRead, bool isTriangle, ClipInfoFlags clipInfoFlags);
|
||||
bool SubmitPrim(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, u32 vertTypeID, bool clockwise, int *bytesRead, ClipInfoFlags clipInfoFlags);
|
||||
void SkipPrim(GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, int *bytesRead);
|
||||
|
||||
template<class Surface>
|
||||
void SubmitCurve(const void *control_points, const void *indices, Surface &surface, u32 vertType, int *bytesRead, const char *scope);
|
||||
@@ -134,13 +146,6 @@ public:
|
||||
|
||||
virtual void NotifyConfigChanged();
|
||||
|
||||
bool EverUsedExactEqualDepth() const {
|
||||
return everUsedExactEqualDepth_;
|
||||
}
|
||||
void SetEverUsedExactEqualDepth(bool v) {
|
||||
everUsedExactEqualDepth_ = v;
|
||||
}
|
||||
|
||||
bool DescribeCodePtr(const u8 *ptr, std::string &name) const;
|
||||
int GetNumDrawCalls() const {
|
||||
return numDrawVerts_;
|
||||
@@ -171,6 +176,8 @@ public:
|
||||
protected:
|
||||
virtual bool UpdateUseHWTessellation(bool enabled) const { return enabled; }
|
||||
|
||||
bool CheckClipFlags(bool useHwTransform) const;
|
||||
|
||||
void DecodeVerts(const VertexDecoder *dec, u8 *dest);
|
||||
int DecodeInds();
|
||||
|
||||
@@ -229,6 +236,7 @@ protected:
|
||||
seenPrims_ = 0;
|
||||
anyCCWOrIndexed_ = false;
|
||||
gstate_c.vertexFullAlpha = true;
|
||||
clipInfoFlags_ = {};
|
||||
|
||||
// Now seems as good a time as any to reset the min/max coords, which we may examine later.
|
||||
gstate_c.vertBounds.minU = 512;
|
||||
@@ -360,10 +368,16 @@ protected:
|
||||
int *depthScreenVerts_ = nullptr;
|
||||
uint16_t *depthIndices_ = nullptr;
|
||||
|
||||
// Depth tracking
|
||||
ClipInfoFlags clipInfoFlags_{};
|
||||
ClipInfoFlags lastClipInfoFlags_{}; // Flags at the last flush. For dirtying.
|
||||
|
||||
// Queue
|
||||
int depthVertexCount_ = 0;
|
||||
int depthIndexCount_ = 0;
|
||||
std::vector<DepthDraw> depthDraws_;
|
||||
|
||||
double rasterTimeStart_ = 0.0;
|
||||
|
||||
bool lastUseHwTransform_ = true;
|
||||
};
|
||||
|
||||
@@ -58,6 +58,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
|
||||
bool highpTexcoord = false;
|
||||
bool enableFragmentTestCache = gstate_c.Use(GPU_USE_FRAGMENT_TEST_CACHE);
|
||||
|
||||
const bool fsMinmaxDiscard = id.Bit(FS_BIT_MINMAX_DISCARD);
|
||||
const bool fsDepthClamp = id.Bit(FS_BIT_DEPTH_CLAMP);
|
||||
|
||||
if (compat.gles) {
|
||||
// PowerVR needs highp to do the fog in MHU correctly.
|
||||
// Others don't, and some can't handle highp in the fragment shader.
|
||||
@@ -183,7 +186,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
|
||||
}
|
||||
|
||||
bool needFragCoord = readFramebufferTex || gstate_c.Use(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT);
|
||||
bool writeDepth = gstate_c.Use(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT) && !forceDepthWritesOff;
|
||||
bool writeDepth = (gstate_c.Use(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT) || fsDepthClamp) && !forceDepthWritesOff;
|
||||
|
||||
// TODO: We could have a separate mechanism to support more ops using the shader blending mechanism,
|
||||
// on hardware that can do proper bit math in fragment shaders.
|
||||
@@ -225,6 +228,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
|
||||
if (doTexture) {
|
||||
WRITE(p, "layout (location = 0) in highp vec3 v_texcoord;\n");
|
||||
}
|
||||
if (fsMinmaxDiscard || fsDepthClamp) {
|
||||
WRITE(p, "layout (location = 4) in highp vec2 v_zw;\n");
|
||||
}
|
||||
|
||||
if (enableAlphaTest && !alphaTestAgainstZero) {
|
||||
WRITE(p, "int roundAndScaleTo255i(in highp float x) { return int(floor(x * 255.0 + 0.5)); }\n");
|
||||
@@ -291,6 +297,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
|
||||
WRITE(p, " vec4 pixelPos : SV_POSITION;\n");
|
||||
}
|
||||
}
|
||||
if (fsMinmaxDiscard || fsDepthClamp) {
|
||||
WRITE(p, " vec2 v_zw : TEXCOORD2;\n");
|
||||
}
|
||||
WRITE(p, "};\n");
|
||||
|
||||
if (compat.shaderLanguage == HLSL_D3D11) {
|
||||
@@ -390,6 +399,13 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
|
||||
WRITE(p, "uniform float u_mipBias;\n");
|
||||
}
|
||||
|
||||
if (fsMinmaxDiscard) {
|
||||
*uniformMask |= DIRTY_RASTER_OFFSET; // they're updated together
|
||||
WRITE(p, "uniform vec2 u_minZmaxZ;\n");
|
||||
}
|
||||
|
||||
// Varyings
|
||||
|
||||
WRITE(p, "%s %s lowp vec4 v_color0;\n", shading, compat.varying_fs);
|
||||
if (lmode) {
|
||||
WRITE(p, "%s %s lowp vec3 v_color1;\n", shading, compat.varying_fs);
|
||||
@@ -402,6 +418,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
|
||||
if (doTexture) {
|
||||
WRITE(p, "%s %s vec3 v_texcoord;\n", compat.varying_fs, highpTexcoord ? "highp" : "mediump");
|
||||
}
|
||||
if (fsMinmaxDiscard || fsDepthClamp) {
|
||||
WRITE(p, "%s vec2 v_zw;\n", compat.varying_fs);
|
||||
}
|
||||
|
||||
if (!enableFragmentTestCache) {
|
||||
if (enableAlphaTest && !alphaTestAgainstZero) {
|
||||
@@ -499,6 +518,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
|
||||
if (doTexture) {
|
||||
WRITE(p, " vec3 v_texcoord = In.v_texcoord;\n");
|
||||
}
|
||||
if (fsMinmaxDiscard || fsDepthClamp) {
|
||||
WRITE(p, " vec2 v_zw = In.v_zw;\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Two things read from the old framebuffer - shader replacement blending and bit-level masking.
|
||||
@@ -1151,30 +1173,33 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
|
||||
WRITE(p, " %s = vec4(0.0, 0.0, 0.0, %s.z); // blue to alpha\n", compat.fragColor0, compat.fragColor0);
|
||||
}
|
||||
|
||||
if (gstate_c.Use(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT)) {
|
||||
DepthScaleFactors depthScale = GetDepthScaleFactors(gstate_c.UseFlags());
|
||||
if (fsDepthClamp || fsMinmaxDiscard) {
|
||||
WRITE(p, " highp float projZ = v_zw.x / v_zw.y;\n");
|
||||
}
|
||||
|
||||
const double scale = depthScale.ScaleU16();
|
||||
if (fsMinmaxDiscard) {
|
||||
// See the vertex shader generator for the explanation for this.
|
||||
WRITE(p, " float clipZ = floor(projZ * 0.5 + 0.5) * 2.0;\n");
|
||||
WRITE(p, " if (u_minZmaxZ.x > 0.0 && clipZ < u_minZmaxZ.x) DISCARD;\n");
|
||||
WRITE(p, " if (u_minZmaxZ.y < 65535.0 && clipZ > u_minZmaxZ.y) DISCARD;\n");
|
||||
}
|
||||
|
||||
WRITE(p, " highp float z = gl_FragCoord.z;\n");
|
||||
if (gstate_c.Use(GPU_USE_ACCURATE_DEPTH)) {
|
||||
// We center the depth with an offset, but only its fraction matters.
|
||||
// When (DepthSliceFactor() - 1) is odd, it will be 0.5, otherwise 0.
|
||||
if (((int)(depthScale.Scale() - 1.0f) & 1) == 1) {
|
||||
WRITE(p, " z = (floor((z * %f) - (1.0 / 2.0)) + (1.0 / 2.0)) * (1.0 / %f);\n", scale, scale);
|
||||
if (writeDepth) {
|
||||
if (gstate_c.Use(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT)) {
|
||||
if (fsDepthClamp) {
|
||||
WRITE(p, " gl_FragDepth = clamp(floor(projZ), 0.0, 65536.0) / 65536.0;\n");
|
||||
} else {
|
||||
WRITE(p, " z = floor(z * %f) * (1.0 / %f);\n", scale, scale);
|
||||
WRITE(p, " gl_FragDepth = floor(gl_FragCoord.z) / 65536.0;\n");
|
||||
}
|
||||
} else {
|
||||
WRITE(p, " z = (1.0 / 65535.0) * floor(z * 65535.0);\n");
|
||||
} else if (fsDepthClamp) {
|
||||
WRITE(p, " gl_FragDepth = clamp(projZ, 0.0, 65536.0) / 65536.0;\n");
|
||||
} else if (useDiscardStencilBugWorkaround) {
|
||||
// Adreno and some Mali drivers apply early frag tests even with discard in the shader,
|
||||
// when only stencil is used. The exact situation seems to vary by driver.
|
||||
// Writing depth prevents the bug for both vendors, even with depth_unchanged specified.
|
||||
// This doesn't make a ton of sense, but empirically does work.
|
||||
WRITE(p, " gl_FragDepth = gl_FragCoord.z;\n");
|
||||
}
|
||||
WRITE(p, " gl_FragDepth = z;\n");
|
||||
} else if (useDiscardStencilBugWorkaround) {
|
||||
// Adreno and some Mali drivers apply early frag tests even with discard in the shader,
|
||||
// when only stencil is used. The exact situation seems to vary by driver.
|
||||
// Writing depth prevents the bug for both vendors, even with depth_unchanged specified.
|
||||
// This doesn't make a ton of sense, but empirically does work.
|
||||
WRITE(p, " gl_FragDepth = gl_FragCoord.z;\n");
|
||||
}
|
||||
|
||||
if (compat.shaderLanguage == HLSL_D3D11) {
|
||||
|
||||
@@ -1037,7 +1037,7 @@ void FramebufferManagerCommon::NotifyRenderFramebufferCreated(VirtualFramebuffer
|
||||
|
||||
void FramebufferManagerCommon::NotifyRenderFramebufferUpdated(VirtualFramebuffer *vfb) {
|
||||
if (gstate_c.curRTWidth != vfb->width || gstate_c.curRTHeight != vfb->height) {
|
||||
gstate_c.Dirty(DIRTY_PROJTHROUGHMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE);
|
||||
gstate_c.Dirty(DIRTY_PROJTHROUGHMATRIX | DIRTY_VIEWPORTSCISSOR_STATE);
|
||||
}
|
||||
if (gstate_c.curRTRenderWidth != vfb->renderWidth || gstate_c.curRTRenderHeight != vfb->renderHeight) {
|
||||
gstate_c.Dirty(DIRTY_PROJMATRIX);
|
||||
@@ -1145,7 +1145,7 @@ void FramebufferManagerCommon::PerformWriteFormattedFromMemory(u32 addr, int siz
|
||||
const int bpp = BufferFormatBytesPerPixel(fmt);
|
||||
ResizeFramebufFBO(vfb, stride, size / (bpp * stride));
|
||||
// Resizing may change the viewport/etc.
|
||||
gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE);
|
||||
gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE);
|
||||
vfb->fb_stride = stride;
|
||||
// This might be a bit wider than necessary, but we'll redetect on next render.
|
||||
vfb->width = stride;
|
||||
@@ -2877,7 +2877,7 @@ void FramebufferManagerCommon::NotifyBlockTransferAfter(u32 dstBasePtr, int dstS
|
||||
dstRect.vfb->newHeight = std::max(dstRect.h, (int)dstRect.vfb->height);
|
||||
dstRect.vfb->lastFrameNewSize = gpuStats.totals.numFlips;
|
||||
// Resizing may change the viewport/etc.
|
||||
gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE);
|
||||
gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE);
|
||||
}
|
||||
DrawPixels(dstRect.vfb, static_cast<int>(dstX * dstXFactor), dstY, srcBase, dstRect.vfb->fb_format, static_cast<int>(srcStride * dstXFactor), static_cast<int>(dstRect.w_bytes / bpp * dstXFactor), dstRect.h, RASTER_COLOR, "BlockTransferCopy_DrawPixels");
|
||||
SetColorUpdated(dstRect.vfb, skipDrawReason);
|
||||
@@ -3115,12 +3115,7 @@ bool FramebufferManagerCommon::GetDepthbuffer(u32 fb_address, int fb_stride, u32
|
||||
|
||||
bool flipY = (GetGPUBackend() == GPUBackend::OPENGL && !useBufferedRendering_) ? true : false;
|
||||
|
||||
// Old code
|
||||
if (gstate_c.Use(GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT)) {
|
||||
buffer.Allocate(w, h, GPU_DBG_FORMAT_FLOAT_DIV_256, flipY);
|
||||
} else {
|
||||
buffer.Allocate(w, h, GPU_DBG_FORMAT_FLOAT, flipY);
|
||||
}
|
||||
buffer.Allocate(w, h, GPU_DBG_FORMAT_FLOAT, flipY);
|
||||
// No need to free on failure, that's the caller's job (it likely will reuse a buffer.)
|
||||
bool retval = draw_->CopyFramebufferToMemory(vfb->fbo, Draw::Aspect::DEPTH_BIT, 0, 0, w, h, Draw::DataFormat::D32F, buffer.GetData(), w, Draw::ReadbackMode::BLOCK, "GetDepthBuffer");
|
||||
if (!retval) {
|
||||
|
||||
@@ -216,6 +216,7 @@ struct GPUDebugVertex {
|
||||
|
||||
class StringWriter;
|
||||
|
||||
// TODO: This interface is somewhat pointless. GPUCommon could be the base class.
|
||||
class GPUDebugInterface {
|
||||
public:
|
||||
virtual ~GPUDebugInterface() = default;
|
||||
|
||||
+10
-256
@@ -518,77 +518,19 @@ ReplaceBlendType ReplaceBlendWithShader(GEBufferFormat bufferFormat) {
|
||||
return REPLACE_BLEND_STANDARD;
|
||||
}
|
||||
|
||||
static const float DEPTH_SLICE_FACTOR_HIGH = 4.0f;
|
||||
static const float DEPTH_SLICE_FACTOR_16BIT = 256.0f;
|
||||
|
||||
// The supported flag combinations. TODO: Maybe they should be distilled down into an enum.
|
||||
//
|
||||
// 0 - "Old"-style GL depth.
|
||||
// Or "Non-accurate depth" : effectively ignore minz / maxz. Map Z values based on viewport, which clamps.
|
||||
// This skews depth in many instances. Depth can be inverted in this mode if viewport says.
|
||||
// This is completely wrong, but works in some cases (probably because some game devs assumed it was how it worked)
|
||||
// and avoids some depth clamp issues.
|
||||
//
|
||||
// GPU_USE_ACCURATE_DEPTH:
|
||||
// Accurate depth: Z in the framebuffer matches the range of Z used on the PSP linearly in some way. We choose
|
||||
// a centered range, to simulate clamping by letting otherwise out-of-range pixels survive the 0 and 1 cutoffs.
|
||||
// Clip depth based on minz/maxz, and viewport is just a means to scale and center the value, not clipping or mapping to stored values.
|
||||
//
|
||||
// GPU_USE_ACCURATE_DEPTH | GPU_USE_DEPTH_CLAMP:
|
||||
// Variant of GPU_USE_ACCURATE_DEPTH, just the range is the nice and convenient 0-1 since we can use
|
||||
// hardware depth clamp. only viable in accurate depth mode, clamps depth and therefore uses the full 0-1 range. Using the full 0-1 range is not what accurate means, it's implied by depth clamp (which also means we're clamping.)
|
||||
//
|
||||
// GPU_USE_ACCURATE_DEPTH | GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT:
|
||||
// GPU_USE_ACCURATE_DEPTH | GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT | GPU_USE_DEPTH_CLAMP:
|
||||
// Only viable in accurate depth mode, means to use a range of the 24-bit depth values available
|
||||
// from the GPU to represent the 16-bit values the PSP had, to try to make everything round and
|
||||
// z-fight (close to) the same way as on hardware, cheaply (cheaper than rounding depth in fragment shader).
|
||||
// We automatically switch to this if Z tests for equality are used.
|
||||
// Depth clamp has no effect on the depth scaling here if set, though will still be enabled
|
||||
// and clamp wildly out of line values.
|
||||
//
|
||||
// Any other combinations of these particular flags are bogus (like for example a lonely GPU_USE_DEPTH_CLAMP).
|
||||
|
||||
// Currently obsolete. We may reintroduce squeezing 24-bit depth into a 16-bit range, although that
|
||||
// will mess with hardware depth clamp, so likely not worth it anymore.
|
||||
float DepthSliceFactor(u32 useFlags) {
|
||||
if (!(useFlags & GPU_USE_ACCURATE_DEPTH)) {
|
||||
// Old style depth.
|
||||
return 1.0f;
|
||||
}
|
||||
if (useFlags & GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT) {
|
||||
// Accurate depth but 16-bit resolution, so squish.
|
||||
return DEPTH_SLICE_FACTOR_16BIT;
|
||||
}
|
||||
if (useFlags & GPU_USE_DEPTH_CLAMP) {
|
||||
// Accurate depth, but we can use the full range since clamping is available.
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// Standard accurate depth.
|
||||
return DEPTH_SLICE_FACTOR_HIGH;
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// See class DepthScaleFactors for how to apply.
|
||||
DepthScaleFactors GetDepthScaleFactors(u32 useFlags) {
|
||||
if (!(useFlags & GPU_USE_ACCURATE_DEPTH)) {
|
||||
return DepthScaleFactors(0.0f, 65535.0f);
|
||||
}
|
||||
|
||||
if (useFlags & GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT) {
|
||||
const double offset = 0.5 * (DEPTH_SLICE_FACTOR_16BIT - 1.0) / DEPTH_SLICE_FACTOR_16BIT;
|
||||
// Use one bit for each value, rather than 1.0 / (65535.0 * 256.0).
|
||||
const double scale = 16777215.0;
|
||||
return DepthScaleFactors(offset, scale);
|
||||
} else if (useFlags & GPU_USE_DEPTH_CLAMP) {
|
||||
return DepthScaleFactors(0.0f, 65535.0f);
|
||||
} else {
|
||||
const double offset = 0.5f * (DEPTH_SLICE_FACTOR_HIGH - 1.0f) * (1.0f / DEPTH_SLICE_FACTOR_HIGH);
|
||||
return DepthScaleFactors(offset, (float)(DEPTH_SLICE_FACTOR_HIGH * 65535.0));
|
||||
}
|
||||
return DepthScaleFactors(0.0f, 65535.0f);
|
||||
}
|
||||
|
||||
void ConvertViewportAndScissor(const DisplayLayoutConfig &config, bool useBufferedRendering, float renderWidth, float renderHeight, int bufferWidth, int bufferHeight, ViewportAndScissor &out) {
|
||||
out.throughMode = gstate.isModeThrough();
|
||||
|
||||
float renderWidthFactor, renderHeightFactor;
|
||||
float renderX = 0.0f, renderY = 0.0f;
|
||||
float displayOffsetX, displayOffsetY;
|
||||
@@ -611,12 +553,6 @@ void ConvertViewportAndScissor(const DisplayLayoutConfig &config, bool useBuffer
|
||||
renderHeightFactor = renderHeight / 272.0f;
|
||||
}
|
||||
|
||||
// We take care negative offsets of in the projection matrix.
|
||||
// These come from split framebuffers (Killzone).
|
||||
// TODO: Might be safe to do get rid of this here and do the same for positive offsets?
|
||||
renderX = std::max(gstate_c.curRTOffsetX, 0);
|
||||
renderY = std::max(gstate_c.curRTOffsetY, 0);
|
||||
|
||||
// Scissor
|
||||
int scissorX1 = gstate.getScissorX1();
|
||||
int scissorY1 = gstate.getScissorY1();
|
||||
@@ -629,8 +565,8 @@ void ConvertViewportAndScissor(const DisplayLayoutConfig &config, bool useBuffer
|
||||
out.scissorW = 0;
|
||||
out.scissorH = 0;
|
||||
} else {
|
||||
out.scissorX = (renderX * renderWidthFactor) + displayOffsetX + scissorX1 * renderWidthFactor;
|
||||
out.scissorY = (renderY * renderHeightFactor) + displayOffsetY + scissorY1 * renderHeightFactor;
|
||||
out.scissorX = displayOffsetX + scissorX1 * renderWidthFactor;
|
||||
out.scissorY = displayOffsetY + scissorY1 * renderHeightFactor;
|
||||
out.scissorW = (scissorX2 - scissorX1) * renderWidthFactor;
|
||||
out.scissorH = (scissorY2 - scissorY1) * renderHeightFactor;
|
||||
}
|
||||
@@ -638,192 +574,10 @@ void ConvertViewportAndScissor(const DisplayLayoutConfig &config, bool useBuffer
|
||||
int curRTWidth = gstate_c.curRTWidth;
|
||||
int curRTHeight = gstate_c.curRTHeight;
|
||||
|
||||
float offsetX = gstate.getOffsetX();
|
||||
float offsetY = gstate.getOffsetY();
|
||||
|
||||
DepthScaleFactors depthScale = GetDepthScaleFactors(gstate_c.UseFlags());
|
||||
|
||||
if (out.throughMode) {
|
||||
// If renderX/renderY are offset to compensate for a split framebuffer,
|
||||
// applying the offset to the viewport isn't enough, since the viewport clips.
|
||||
// We need to apply either directly to the vertices, or to the "through" projection matrix.
|
||||
out.viewportX = renderX * renderWidthFactor + displayOffsetX;
|
||||
out.viewportY = renderY * renderHeightFactor + displayOffsetY;
|
||||
out.viewportW = curRTWidth * renderWidthFactor;
|
||||
out.viewportH = curRTHeight * renderHeightFactor;
|
||||
out.depthRangeMin = depthScale.EncodeFromU16(0.0f);
|
||||
out.depthRangeMax = depthScale.EncodeFromU16(65536.0f);
|
||||
} else {
|
||||
// These we can turn into a glViewport call, offset by offsetX and offsetY. Math after.
|
||||
float vpXScale = gstate.getViewportXScale();
|
||||
float vpXCenter = gstate.getViewportXCenter();
|
||||
float vpYScale = gstate.getViewportYScale();
|
||||
float vpYCenter = gstate.getViewportYCenter();
|
||||
|
||||
// The viewport transform appears to go like this:
|
||||
// Xscreen = -offsetX + vpXCenter + vpXScale * Xview
|
||||
// Yscreen = -offsetY + vpYCenter + vpYScale * Yview
|
||||
// Zscreen = vpZCenter + vpZScale * Zview
|
||||
|
||||
// The viewport is normally centered at 2048,2048 but can also be centered at other locations.
|
||||
// Offset is subtracted from the viewport center and is also set to values in those ranges, and is set so that the viewport will cover
|
||||
// the desired screen area ([0-480)x[0-272)), so 1808,1912.
|
||||
|
||||
// This means that to get the analogue glViewport we must:
|
||||
float vpX0 = vpXCenter - offsetX - fabsf(vpXScale);
|
||||
float vpY0 = vpYCenter - offsetY - fabsf(vpYScale);
|
||||
gstate_c.vpWidth = vpXScale * 2.0f;
|
||||
gstate_c.vpHeight = vpYScale * 2.0f;
|
||||
|
||||
float vpWidth = fabsf(gstate_c.vpWidth);
|
||||
float vpHeight = fabsf(gstate_c.vpHeight);
|
||||
|
||||
float left = renderX + vpX0;
|
||||
float top = renderY + vpY0;
|
||||
float right = left + vpWidth;
|
||||
float bottom = top + vpHeight;
|
||||
|
||||
out.widthScale = 1.0f;
|
||||
out.xOffset = 0.0f;
|
||||
out.heightScale = 1.0f;
|
||||
out.yOffset = 0.0f;
|
||||
|
||||
// If we're within the bounds, we want clipping the viewport way. So leave it be.
|
||||
{
|
||||
float overageLeft = std::max(-left, 0.0f);
|
||||
float overageRight = std::max(right - bufferWidth, 0.0f);
|
||||
|
||||
// Expand viewport to cover scissor region. The viewport doesn't clip on the PSP.
|
||||
if (right < scissorX2) {
|
||||
overageRight -= scissorX2 - right;
|
||||
}
|
||||
if (left > scissorX1) {
|
||||
overageLeft += scissorX1 - left;
|
||||
}
|
||||
|
||||
// Our center drifted by the difference in overages.
|
||||
float drift = overageRight - overageLeft;
|
||||
|
||||
if (overageLeft != 0.0f || overageRight != 0.0f) {
|
||||
left += overageLeft;
|
||||
right -= overageRight;
|
||||
|
||||
// Protect against the viewport being entirely outside the scissor.
|
||||
// Emit a tiny but valid viewport. Really, we should probably emit a flag to ignore draws.
|
||||
if (right <= left) {
|
||||
right = left + 1.0f;
|
||||
}
|
||||
|
||||
out.widthScale = vpWidth / (right - left);
|
||||
out.xOffset = drift / (right - left);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
float overageTop = std::max(-top, 0.0f);
|
||||
float overageBottom = std::max(bottom - bufferHeight, 0.0f);
|
||||
|
||||
// Expand viewport to cover scissor region. The viewport doesn't clip on the PSP.
|
||||
if (bottom < scissorY2) {
|
||||
overageBottom -= scissorY2 - bottom;
|
||||
}
|
||||
if (top > scissorY1) {
|
||||
overageTop += scissorY1 - top;
|
||||
}
|
||||
// Our center drifted by the difference in overages.
|
||||
float drift = overageBottom - overageTop;
|
||||
|
||||
if (overageTop != 0.0f || overageBottom != 0.0f) {
|
||||
top += overageTop;
|
||||
bottom -= overageBottom;
|
||||
|
||||
// Protect against the viewport being entirely outside the scissor.
|
||||
// Emit a tiny but valid viewport. Really, we should probably emit a flag to ignore draws.
|
||||
if (bottom <= top) {
|
||||
bottom = top + 1.0f;
|
||||
}
|
||||
|
||||
out.heightScale = vpHeight / (bottom - top);
|
||||
out.yOffset = drift / (bottom - top);
|
||||
}
|
||||
}
|
||||
|
||||
out.viewportX = left * renderWidthFactor + displayOffsetX;
|
||||
out.viewportY = top * renderHeightFactor + displayOffsetY;
|
||||
out.viewportW = (right - left) * renderWidthFactor;
|
||||
out.viewportH = (bottom - top) * renderHeightFactor;
|
||||
|
||||
// The depth viewport parameters are the same, but we handle it a bit differently.
|
||||
// When clipping is enabled, depth is clamped to [0, 65535]. And minz/maxz discard.
|
||||
// So, we apply the depth range as minz/maxz, and transform for the viewport.
|
||||
float vpZScale = gstate.getViewportZScale();
|
||||
float vpZCenter = gstate.getViewportZCenter();
|
||||
// TODO: This clip the entire draw if minz > maxz.
|
||||
float minz = gstate.getDepthRangeMin();
|
||||
float maxz = gstate.getDepthRangeMax();
|
||||
|
||||
if (gstate.isDepthClampEnabled() && (minz == 0 || maxz == 65535)) {
|
||||
// Here, we should "clamp." But clamping per fragment would be slow.
|
||||
// So, instead, we just increase the available range and hope.
|
||||
// If depthSliceFactor is 4, it means (75% / 2) of the depth lies in each direction.
|
||||
float fullDepthRange = 65535.0f * (depthScale.Scale() - 1.0f) * (1.0f / 2.0f);
|
||||
if (minz == 0) {
|
||||
minz -= fullDepthRange;
|
||||
}
|
||||
if (maxz == 65535) {
|
||||
maxz += fullDepthRange;
|
||||
}
|
||||
} else if (maxz == 65535) {
|
||||
// This means clamp isn't enabled, but we still want to allow values up to 65535.99.
|
||||
// If DepthSliceFactor() is 1.0, though, this would make out.depthRangeMax exceed 1.
|
||||
// Since that would clamp, it would make Z=1234 not match between draws when maxz changes.
|
||||
if (depthScale.Scale() > 1.0f)
|
||||
maxz = 65535.99f;
|
||||
}
|
||||
|
||||
// Okay. So, in our shader, -1 will map to minz, and +1 will map to maxz.
|
||||
float halfActualZRange = (maxz - minz) * (1.0f / 2.0f);
|
||||
out.depthScale = halfActualZRange < std::numeric_limits<float>::epsilon() ? 1.0f : vpZScale / halfActualZRange;
|
||||
// This adjusts the center from halfActualZRange to vpZCenter.
|
||||
out.zOffset = halfActualZRange < std::numeric_limits<float>::epsilon() ? 0.0f : (vpZCenter - (minz + halfActualZRange)) / halfActualZRange;
|
||||
|
||||
if (!gstate_c.Use(GPU_USE_ACCURATE_DEPTH)) {
|
||||
out.depthScale = 1.0f;
|
||||
out.zOffset = 0.0f;
|
||||
out.depthRangeMin = depthScale.EncodeFromU16(vpZCenter - vpZScale);
|
||||
out.depthRangeMax = depthScale.EncodeFromU16(vpZCenter + vpZScale);
|
||||
} else {
|
||||
out.depthRangeMin = depthScale.EncodeFromU16(minz);
|
||||
out.depthRangeMax = depthScale.EncodeFromU16(maxz);
|
||||
}
|
||||
|
||||
// OpenGL will clamp these for us anyway, and Direct3D will error if not clamped.
|
||||
// Of course, if this happens we've skewed out.depthScale/out.zOffset and may get z-fighting.
|
||||
out.depthRangeMin = std::max(out.depthRangeMin, 0.0f);
|
||||
out.depthRangeMax = std::min(out.depthRangeMax, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateCachedViewportState(const ViewportAndScissor &vpAndScissor) {
|
||||
if (vpAndScissor.throughMode)
|
||||
return;
|
||||
|
||||
bool scaleChanged = gstate_c.vpWidthScale != vpAndScissor.widthScale || gstate_c.vpHeightScale != vpAndScissor.heightScale;
|
||||
bool offsetChanged = gstate_c.vpXOffset != vpAndScissor.xOffset || gstate_c.vpYOffset != vpAndScissor.yOffset;
|
||||
bool depthChanged = gstate_c.vpDepthScale != vpAndScissor.depthScale || gstate_c.vpZOffset != vpAndScissor.zOffset;
|
||||
if (scaleChanged || offsetChanged || depthChanged) {
|
||||
gstate_c.vpWidthScale = vpAndScissor.widthScale;
|
||||
gstate_c.vpHeightScale = vpAndScissor.heightScale;
|
||||
gstate_c.vpDepthScale = vpAndScissor.depthScale;
|
||||
gstate_c.vpXOffset = vpAndScissor.xOffset;
|
||||
gstate_c.vpYOffset = vpAndScissor.yOffset;
|
||||
gstate_c.vpZOffset = vpAndScissor.zOffset;
|
||||
|
||||
gstate_c.Dirty(DIRTY_PROJMATRIX);
|
||||
if (depthChanged) {
|
||||
gstate_c.Dirty(DIRTY_DEPTHRANGE);
|
||||
}
|
||||
}
|
||||
out.viewportX = displayOffsetX;
|
||||
out.viewportY = displayOffsetY;
|
||||
out.viewportW = curRTWidth * renderWidthFactor;
|
||||
out.viewportH = curRTHeight * renderHeightFactor;
|
||||
}
|
||||
|
||||
static const BlendFactor genericALookup[11] = {
|
||||
|
||||
@@ -76,21 +76,11 @@ struct ViewportAndScissor {
|
||||
float viewportY;
|
||||
float viewportW;
|
||||
float viewportH;
|
||||
float depthRangeMin;
|
||||
float depthRangeMax;
|
||||
float widthScale;
|
||||
float heightScale;
|
||||
float depthScale;
|
||||
float xOffset;
|
||||
float yOffset;
|
||||
float zOffset;
|
||||
bool throughMode;
|
||||
};
|
||||
|
||||
// config is only used for non-buffered rendering.
|
||||
struct DisplayLayoutConfig;
|
||||
void ConvertViewportAndScissor(const DisplayLayoutConfig &config, bool useBufferedRendering, float renderWidth, float renderHeight, int bufferWidth, int bufferHeight, ViewportAndScissor &out);
|
||||
void UpdateCachedViewportState(const ViewportAndScissor &vpAndScissor);
|
||||
|
||||
// NOTE: See the .cpp file for detailed comment about how the use flags are interpreted.
|
||||
class DepthScaleFactors {
|
||||
|
||||
@@ -1,315 +0,0 @@
|
||||
// Copyright (c) 2012- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <locale.h>
|
||||
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/GPU/OpenGL/GLFeatures.h"
|
||||
#include "Common/GPU/ShaderWriter.h"
|
||||
#include "Common/GPU/thin3d.h"
|
||||
#include "Core/Config.h"
|
||||
#include "GPU/ge_constants.h"
|
||||
#include "GPU/GPUState.h"
|
||||
#include "GPU/Common/ShaderId.h"
|
||||
#include "GPU/Common/ShaderUniforms.h"
|
||||
#include "GPU/Common/GeometryShaderGenerator.h"
|
||||
|
||||
#undef WRITE
|
||||
|
||||
#define WRITE(p, ...) p.F(__VA_ARGS__)
|
||||
|
||||
// TODO: Could support VK_NV_geometry_shader_passthrough, though the hardware that supports
|
||||
// it is already pretty fast at geometry shaders..
|
||||
|
||||
|
||||
bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLanguageDesc &compat, const Draw::Bugs bugs, std::string *errorString) {
|
||||
std::vector<const char*> extensions;
|
||||
if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) {
|
||||
if (gl_extensions.EXT_gpu_shader4) {
|
||||
extensions.push_back("#extension GL_EXT_gpu_shader4 : enable");
|
||||
}
|
||||
}
|
||||
bool vertexRangeCulling = !id.Bit(GS_BIT_CURVE);
|
||||
bool clipClampedDepth = gstate_c.Use(GPU_USE_DEPTH_CLAMP);
|
||||
|
||||
ShaderWriter p(buffer, compat, ShaderStage::Geometry, extensions);
|
||||
|
||||
p.F("// %s\n", GeometryShaderDesc(id).c_str());
|
||||
|
||||
p.C("layout(triangles) in;\n");
|
||||
if (clipClampedDepth && vertexRangeCulling && !gstate_c.Use(GPU_USE_CLIP_DISTANCE)) {
|
||||
p.C("layout(triangle_strip, max_vertices = 12) out;\n");
|
||||
} else {
|
||||
p.C("layout(triangle_strip, max_vertices = 6) out;\n");
|
||||
}
|
||||
|
||||
if (compat.shaderLanguage == GLSL_VULKAN) {
|
||||
WRITE(p, "\n");
|
||||
WRITE(p, "layout (std140, set = 0, binding = 3) uniform baseVars {\n%s};\n", ub_baseStr);
|
||||
} else if (compat.shaderLanguage == HLSL_D3D11) {
|
||||
WRITE(p, "cbuffer base : register(b0) {\n%s};\n", ub_baseStr);
|
||||
}
|
||||
|
||||
std::vector<VaryingDef> varyings, outVaryings;
|
||||
|
||||
if (id.Bit(GS_BIT_DO_TEXTURE)) {
|
||||
varyings.push_back(VaryingDef{ "vec3", "v_texcoord", Draw::SEM_TEXCOORD0, 0, "highp" });
|
||||
outVaryings.push_back(VaryingDef{ "vec3", "v_texcoordOut", Draw::SEM_TEXCOORD0, 0, "highp" });
|
||||
}
|
||||
varyings.push_back(VaryingDef{ "vec4", "v_color0", Draw::SEM_COLOR0, 1, "lowp" });
|
||||
outVaryings.push_back(VaryingDef{ "vec4", "v_color0Out", Draw::SEM_COLOR0, 1, "lowp" });
|
||||
if (id.Bit(GS_BIT_LMODE)) {
|
||||
varyings.push_back(VaryingDef{ "vec3", "v_color1", Draw::SEM_COLOR1, 2, "lowp" });
|
||||
outVaryings.push_back(VaryingDef{ "vec3", "v_color1Out", Draw::SEM_COLOR1, 2, "lowp" });
|
||||
}
|
||||
varyings.push_back(VaryingDef{ "float", "v_fogdepth", Draw::SEM_TEXCOORD1, 3, "highp" });
|
||||
outVaryings.push_back(VaryingDef{ "float", "v_fogdepthOut", Draw::SEM_TEXCOORD1, 3, "highp" });
|
||||
|
||||
p.BeginGSMain(varyings, outVaryings);
|
||||
|
||||
// Apply culling.
|
||||
if (vertexRangeCulling) {
|
||||
p.C(" bool anyInside = false;\n");
|
||||
}
|
||||
// And apply manual clipping if necessary.
|
||||
if (!gstate_c.Use(GPU_USE_CLIP_DISTANCE)) {
|
||||
p.C(" float clip0[3];\n");
|
||||
if (clipClampedDepth) {
|
||||
p.C(" float clip1[3];\n");
|
||||
}
|
||||
}
|
||||
|
||||
p.C(" for (int i = 0; i < 3; i++) {\n"); // TODO: 3 or gl_in.length()? which will be faster?
|
||||
p.C(" vec4 outPos = gl_in[i].gl_Position;\n");
|
||||
p.C(" vec3 projPos = outPos.xyz / outPos.w;\n");
|
||||
|
||||
if (vertexRangeCulling) {
|
||||
p.C(" float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n");
|
||||
// Vertex range culling doesn't happen when Z clips, note sign of w is important.
|
||||
p.C(" if (u_cullRangeMin.w <= 0.0 || projZ * outPos.w > -outPos.w) {\n");
|
||||
const char *outMin = "projPos.x < u_cullRangeMin.x || projPos.y < u_cullRangeMin.y";
|
||||
const char *outMax = "projPos.x > u_cullRangeMax.x || projPos.y > u_cullRangeMax.y";
|
||||
p.F(" if ((%s) || (%s)) {\n", outMin, outMax);
|
||||
p.C(" return;\n"); // Cull!
|
||||
p.C(" }\n");
|
||||
p.C(" }\n");
|
||||
p.C(" if (u_cullRangeMin.w <= 0.0) {\n");
|
||||
p.C(" if (projPos.z < u_cullRangeMin.z || projPos.z > u_cullRangeMax.z) {\n");
|
||||
// When not clamping depth, cull the triangle of Z is outside the valid range (not based on clip Z.)
|
||||
p.C(" return;\n");
|
||||
p.C(" }\n");
|
||||
p.C(" } else {\n");
|
||||
p.C(" if (projPos.z >= u_cullRangeMin.z) { anyInside = true; }\n");
|
||||
p.C(" if (projPos.z <= u_cullRangeMax.z) { anyInside = true; }\n");
|
||||
p.C(" }\n");
|
||||
}
|
||||
|
||||
if (!gstate_c.Use(GPU_USE_CLIP_DISTANCE)) {
|
||||
// This is basically the same value as gl_ClipDistance would take, z + w.
|
||||
if (vertexRangeCulling) {
|
||||
// We add a small amount to prevent error as in #15816 (PSP Z is only 16-bit fixed point, anyway.)
|
||||
p.F(" clip0[i] = projZ * outPos.w + outPos.w + %f;\n", 0.0625 / 65536.0);
|
||||
} else {
|
||||
// Let's not complicate the code overly for this case. We'll clipClampedDepth.
|
||||
p.C(" clip0[i] = 0.0;\n");
|
||||
}
|
||||
|
||||
// This one does happen for rectangles.
|
||||
if (clipClampedDepth) {
|
||||
if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) {
|
||||
// On OpenGL/GLES, these values account for the -1 -> 1 range.
|
||||
p.C(" if (u_depthRange.y - u_depthRange.x >= 1.0) {\n");
|
||||
p.C(" clip1[i] = outPos.w + outPos.z;\n");
|
||||
} else {
|
||||
// Everywhere else, it's 0 -> 1, simpler.
|
||||
p.C(" if (u_depthRange.y >= 1.0) {\n");
|
||||
p.C(" clip1[i] = outPos.z;\n");
|
||||
}
|
||||
// This is similar, but for maxz when it's below 65535.0. -1/0 don't matter here.
|
||||
p.C(" } else if (u_depthRange.x + u_depthRange.y <= 65534.0) {\n");
|
||||
p.C(" clip1[i] = outPos.w - outPos.z;\n");
|
||||
p.C(" } else {\n");
|
||||
p.C(" clip1[i] = 0.0;\n");
|
||||
p.C(" }\n");
|
||||
}
|
||||
}
|
||||
|
||||
p.C(" } // for\n");
|
||||
|
||||
// Cull any triangle fully outside in the same direction when depth clamp enabled.
|
||||
// Basically simulate cull distances.
|
||||
if (vertexRangeCulling) {
|
||||
p.C(" if (u_cullRangeMin.w > 0.0 && !anyInside) {\n");
|
||||
p.C(" return;\n");
|
||||
p.C(" }\n");
|
||||
}
|
||||
|
||||
if (!gstate_c.Use(GPU_USE_CLIP_DISTANCE)) {
|
||||
// Clipping against one half-space cuts a triangle (17/27), culls (7/27), or creates two triangles (3/27).
|
||||
// We clip against two, so we can generate up to 4 triangles, a polygon with 6 points.
|
||||
p.C(" int indices[6];\n");
|
||||
p.C(" float factors[6];\n");
|
||||
p.C(" int ind = 0;\n");
|
||||
|
||||
// Pass 1 - clip against first half-space.
|
||||
p.C(" for (int i = 0; i < 3; i++) {\n");
|
||||
// First, use this vertex if it doesn't need clipping.
|
||||
p.C(" if (clip0[i] >= 0.0) {\n");
|
||||
p.C(" indices[ind] = i;\n");
|
||||
p.C(" factors[ind] = 0.0;\n");
|
||||
p.C(" ind++;\n");
|
||||
p.C(" }\n");
|
||||
|
||||
// Next, we generate an interpolated vertex if signs differ.
|
||||
p.C(" int inext = i == 2 ? 0 : i + 1;\n");
|
||||
p.C(" if (clip0[i] * clip0[inext] < 0.0) {\n");
|
||||
p.C(" float t = clip0[i] < 0.0 ? clip0[i] / (clip0[i] - clip0[inext]) : 1.0 - (clip0[inext] / (clip0[inext] - clip0[i]));\n");
|
||||
p.C(" indices[ind] = i;\n");
|
||||
p.C(" factors[ind] = t;\n");
|
||||
p.C(" ind++;\n");
|
||||
p.C(" }\n");
|
||||
|
||||
p.C(" }\n");
|
||||
|
||||
// Pass 2 - further clip against clamped Z.
|
||||
if (clipClampedDepth) {
|
||||
p.C(" int count0 = ind;\n");
|
||||
p.C(" int indices1[6];\n");
|
||||
p.C(" float factors1[6];\n");
|
||||
p.C(" ind = 0;\n");
|
||||
|
||||
// Let's start by interpolating the clip values.
|
||||
p.C(" float clip1after[4];\n");
|
||||
p.C(" for (int i = 0; i < count0; i++) {\n");
|
||||
p.C(" int idx = indices[i];\n");
|
||||
p.C(" float factor = factors[i];\n");
|
||||
p.C(" int next = idx == 2 ? 0 : idx + 1;\n");
|
||||
p.C(" clip1after[i] = mix(clip1[idx], clip1[next], factor);\n");
|
||||
p.C(" }\n");
|
||||
|
||||
// Alright, now time to clip, again.
|
||||
p.C(" for (int i = 0; i < count0; i++) {\n");
|
||||
// First, use this vertex if it doesn't need clipping.
|
||||
p.C(" if (clip1after[i] >= 0.0) {\n");
|
||||
p.C(" indices1[ind] = i;\n");
|
||||
p.C(" factors1[ind] = 0.0;\n");
|
||||
p.C(" ind++;\n");
|
||||
p.C(" }\n");
|
||||
|
||||
// Next, we generate an interpolated vertex if signs differ.
|
||||
p.C(" int inext = i == count0 - 1 ? 0 : i + 1;\n");
|
||||
p.C(" if (clip1after[i] * clip1after[inext] < 0.0) {\n");
|
||||
p.C(" float t = clip1after[i] < 0.0 ? clip1after[i] / (clip1after[i] - clip1after[inext]) : 1.0 - (clip1after[inext] / (clip1after[inext] - clip1after[i]));\n");
|
||||
p.C(" indices1[ind] = i;\n");
|
||||
p.C(" factors1[ind] = t;\n");
|
||||
p.C(" ind++;\n");
|
||||
p.C(" }\n");
|
||||
|
||||
p.C(" }\n");
|
||||
}
|
||||
|
||||
p.C(" if (ind < 3) {\n");
|
||||
p.C(" return;\n");
|
||||
p.C(" }\n");
|
||||
|
||||
p.C(" int idx;\n");
|
||||
p.C(" int next;\n");
|
||||
p.C(" float factor;\n");
|
||||
|
||||
auto emitIndex = [&](const char *which) {
|
||||
if (clipClampedDepth) {
|
||||
// We have to interpolate between four vertices.
|
||||
p.F(" idx = indices1[%s];\n", which);
|
||||
p.F(" factor = factors1[%s];\n", which);
|
||||
p.C(" next = idx == count0 - 1 ? 0 : idx + 1;\n");
|
||||
p.C(" gl_Position = mix(mix(gl_in[indices[idx]].gl_Position, gl_in[(indices[idx] + 1) % 3].gl_Position, factors[idx]), mix(gl_in[indices[next]].gl_Position, gl_in[(indices[next] + 1) % 3].gl_Position, factors[next]), factor);\n");
|
||||
for (size_t i = 0; i < varyings.size(); i++) {
|
||||
const VaryingDef &in = varyings[i];
|
||||
const VaryingDef &out = outVaryings[i];
|
||||
p.F(" %s = mix(mix(%s[indices[idx]], %s[(indices[idx] + 1) % 3], factors[idx]), mix(%s[indices[next]], %s[(indices[next] + 1) % 3], factors[next]), factor);\n", out.name, in.name, in.name, in.name, in.name);
|
||||
}
|
||||
} else {
|
||||
p.F(" idx = indices[%s];\n", which);
|
||||
p.F(" factor = factors[%s];\n", which);
|
||||
p.C(" next = idx == 2 ? 0 : idx + 1;\n");
|
||||
p.C(" gl_Position = mix(gl_in[idx].gl_Position, gl_in[next].gl_Position, factor);\n");
|
||||
for (size_t i = 0; i < varyings.size(); i++) {
|
||||
const VaryingDef &in = varyings[i];
|
||||
const VaryingDef &out = outVaryings[i];
|
||||
p.F(" %s = mix(%s[idx], %s[next], factor);\n", out.name, in.name, in.name);
|
||||
}
|
||||
}
|
||||
p.C(" EmitVertex();\n");
|
||||
};
|
||||
|
||||
// Alright, time to actually emit the first triangle.
|
||||
p.C(" for (int i = 0; i < 3; i++) {\n");
|
||||
emitIndex("i");
|
||||
p.C(" }\n");
|
||||
|
||||
// Did we end up with additional triangles? We'll do three points each for the rest.
|
||||
p.C(" for (int i = 3; i < ind; i++) {\n");
|
||||
p.C(" EndPrimitive();\n");
|
||||
|
||||
// Point one, always index zero.
|
||||
emitIndex("0");
|
||||
|
||||
// After that, one less than i (basically a triangle fan.)
|
||||
emitIndex("(i - 1)");
|
||||
|
||||
// And the new vertex itself.
|
||||
emitIndex("i");
|
||||
|
||||
p.C(" }\n");
|
||||
} else {
|
||||
const char *clipSuffix0 = compat.shaderLanguage == HLSL_D3D11 ? ".x" : "[0]";
|
||||
const char *clipSuffix1 = compat.shaderLanguage == HLSL_D3D11 ? ".y" : "[1]";
|
||||
|
||||
p.C(" for (int i = 0; i < 3; i++) {\n"); // TODO: 3 or gl_in.length()? which will be faster?
|
||||
p.C(" vec4 outPos = gl_in[i].gl_Position;\n");
|
||||
p.C(" vec3 projPos = outPos.xyz / outPos.w;\n");
|
||||
p.C(" float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n");
|
||||
if (clipClampedDepth) {
|
||||
// Copy the clip distance from the vertex shader.
|
||||
p.F(" gl_ClipDistance%s = gl_in[i].gl_ClipDistance%s;\n", clipSuffix0, clipSuffix0);
|
||||
p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w;\n", clipSuffix1);
|
||||
} else {
|
||||
// We shouldn't need to worry about rectangles-as-triangles here, since we don't use geometry shaders for that.
|
||||
// We add a small amount to prevent error as in #15816 (PSP Z is only 16-bit fixed point, anyway.)
|
||||
p.F(" gl_ClipDistance%s = projZ * outPos.w + outPos.w + %f;\n", clipSuffix0, 0.0625 / 65536.0);
|
||||
}
|
||||
p.C(" gl_Position = outPos;\n");
|
||||
if (gstate_c.Use(GPU_USE_CLIP_DISTANCE)) {
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < varyings.size(); i++) {
|
||||
const VaryingDef &in = varyings[i];
|
||||
const VaryingDef &out = outVaryings[i];
|
||||
p.F(" %s = %s[i];\n", out.name, in.name);
|
||||
}
|
||||
// Debug - null the red channel
|
||||
//p.C(" if (i == 0) v_color0Out.x = 0.0;\n");
|
||||
p.C(" EmitVertex();\n");
|
||||
p.C(" }\n");
|
||||
}
|
||||
|
||||
p.EndGSMain();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "GPU/Common/ShaderId.h"
|
||||
|
||||
bool GenerateGeometryShader(const GShaderID &id, char *buffer, const ShaderLanguageDesc &compat, const Draw::Bugs bugs, std::string *errorString);
|
||||
@@ -125,7 +125,6 @@ void IndexGenerator::AddStrip(int numVerts, int indexOffset, bool clockwise) {
|
||||
}
|
||||
}
|
||||
inds_ += numTris * 3;
|
||||
// wind doesn't need to be updated, an even number of triangles have been drawn.
|
||||
#elif PPSSPP_ARCH(ARM_NEON)
|
||||
uint16x8_t ibase8 = vdupq_n_u16(indexOffset);
|
||||
const u16 *offsets = clockwise ? offsets_clockwise : offsets_counter_clockwise;
|
||||
|
||||
+19
-14
@@ -68,7 +68,10 @@ enum : uint64_t {
|
||||
DIRTY_SHADERBLEND = 1ULL << 17, // Used only for in-shader blending.
|
||||
|
||||
DIRTY_UVSCALEOFFSET = 1ULL << 18,
|
||||
DIRTY_DEPTHRANGE = 1ULL << 19,
|
||||
// Free bit 19!
|
||||
|
||||
DIRTY_VIEWPORT_UNIFORMS = 1ULL << 20,
|
||||
// Free bit 20!
|
||||
|
||||
DIRTY_WORLDMATRIX = 1ULL << 21,
|
||||
DIRTY_VIEWMATRIX = 1ULL << 22,
|
||||
@@ -84,7 +87,7 @@ enum : uint64_t {
|
||||
|
||||
DIRTY_BEZIERSPLINE = 1ULL << 32,
|
||||
DIRTY_TEXCLAMP = 1ULL << 33,
|
||||
DIRTY_CULLRANGE = 1ULL << 34,
|
||||
// Free bit 34!
|
||||
|
||||
DIRTY_DEPAL = 1ULL << 35,
|
||||
DIRTY_COLORWRITEMASK = 1ULL << 36,
|
||||
@@ -93,34 +96,36 @@ enum : uint64_t {
|
||||
DIRTY_LIGHT_CONTROL = 1ULL << 38,
|
||||
DIRTY_TEX_ALPHA_MUL = 1ULL << 39,
|
||||
|
||||
DIRTY_RASTER_OFFSET = 1ULL << 40, // Also includes minZmaxZ.
|
||||
|
||||
// Bits 41-42 are free for new uniforms (although the mask below needs updating). Then we're really out and need to start merging.
|
||||
// Don't forget to update DIRTY_ALL_UNIFORMS when you start using them.
|
||||
|
||||
DIRTY_BONE_UNIFORMS = 0xFF000000ULL,
|
||||
|
||||
DIRTY_ALL_UNIFORMS = 0x0FFFFFFFFFFULL,
|
||||
DIRTY_ALL_UNIFORMS = 0x1FFFFFFFFFFULL,
|
||||
|
||||
// Other dirty elements that aren't uniforms
|
||||
|
||||
DIRTY_VIEW_PROJ_MATRIX = 1ULL << 40,
|
||||
DIRTY_WORLD_VIEW_PROJ_MATRIX = 1ULL << 41,
|
||||
// Free non-uniform bit 42.
|
||||
// Free non-uniform bit 43.
|
||||
DIRTY_FRAMEBUF = 1ULL << 44,
|
||||
DIRTY_TEXTURE_IMAGE = 1ULL << 45, // Means that the definition of the texture image has changed (address, stride etc), and we need to look up again.
|
||||
DIRTY_TEXTURE_PARAMS = 1ULL << 46,
|
||||
DIRTY_VIEW_PROJ_MATRIX = 1ULL << 42,
|
||||
DIRTY_WORLD_VIEW_PROJ_MATRIX = 1ULL << 43,
|
||||
DIRTY_CULL_MATRIX = 1ULL << 44, // This could also compute parameters to decide if we need hardware clipping, etc.
|
||||
DIRTY_FRAMEBUF = 1ULL << 45,
|
||||
DIRTY_TEXTURE_IMAGE = 1ULL << 46, // Means that the definition of the texture image has changed (address, stride etc), and we need to look up again.
|
||||
DIRTY_TEXTURE_PARAMS = 1ULL << 47,
|
||||
|
||||
// Render State
|
||||
DIRTY_BLEND_STATE = 1ULL << 47,
|
||||
DIRTY_DEPTHSTENCIL_STATE = 1ULL << 48,
|
||||
DIRTY_RASTER_STATE = 1ULL << 49,
|
||||
DIRTY_VIEWPORTSCISSOR_STATE = 1ULL << 50,
|
||||
DIRTY_VERTEXSHADER_STATE = 1ULL << 51,
|
||||
DIRTY_FRAGMENTSHADER_STATE = 1ULL << 52,
|
||||
DIRTY_GEOMETRYSHADER_STATE = 1ULL << 53,
|
||||
DIRTY_BLEND_STATE = 1ULL << 53,
|
||||
|
||||
// Note that the top 8 bits (54-63) cannot be dirtied through the commonCommandTable due to packing of other flags.
|
||||
|
||||
// Everything that's not uniforms. Use this after using thin3d.
|
||||
// TODO: Should we also add DIRTY_FRAMEBUF here? It kinda generally takes care of itself.
|
||||
DIRTY_ALL_RENDER_STATE = DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS,
|
||||
DIRTY_ALL_RENDER_STATE = DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS,
|
||||
|
||||
DIRTY_ALL = 0xFFFFFFFFFFFFFFFF
|
||||
};
|
||||
|
||||
+38
-53
@@ -12,11 +12,13 @@
|
||||
#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;
|
||||
desc << StringFromFormat("%08x:%08x ", id.d[1], id.d[0]);
|
||||
if (id.Bit(VS_BIT_IS_THROUGH)) desc << "THR ";
|
||||
if (id.Bit(VS_BIT_CLIP_ENABLE)) desc << "Clip ";
|
||||
if (id.Bit(VS_BIT_USE_HW_TRANSFORM)) desc << "HWX ";
|
||||
if (id.Bit(VS_BIT_HAS_COLOR)) desc << "C ";
|
||||
if (id.Bit(VS_BIT_HAS_TEXCOORD)) desc << "T ";
|
||||
@@ -35,6 +37,7 @@ std::string VertexShaderDesc(const VShaderID &id) {
|
||||
|
||||
if (uvgMode) desc << uvgModes[uvgMode];
|
||||
if (id.Bit(VS_BIT_ENABLE_BONES)) desc << "Bones:" << (id.Bits(VS_BIT_BONES, 3) + 1) << " ";
|
||||
|
||||
// Lights
|
||||
if (id.Bit(VS_BIT_LIGHTING_ENABLE)) {
|
||||
desc << "Light: ";
|
||||
@@ -59,15 +62,18 @@ std::string VertexShaderDesc(const VShaderID &id) {
|
||||
if (id.Bit(VS_BIT_HAS_TEXCOORD_TESS)) desc << "TessT ";
|
||||
if (id.Bit(VS_BIT_HAS_NORMAL_TESS)) desc << "TessN ";
|
||||
if (id.Bit(VS_BIT_NORM_REVERSE_TESS)) desc << "TessRevN ";
|
||||
if (id.Bit(VS_BIT_VERTEX_RANGE_CULLING)) desc << "Cull ";
|
||||
if (id.Bit(VS_BIT_VERTEX_RANGE_CULLING)) desc << "RangeCull ";
|
||||
|
||||
if (id.Bit(VS_BIT_SIMPLE_STEREO)) desc << "SimpleStereo ";
|
||||
if (id.Bit(VS_BIT_FS_MINMAX_DISCARD)) desc << "FSMinMax ";
|
||||
if (id.Bit(VS_BIT_FS_DEPTH_CLAMP)) desc << "FSDepthClamp ";
|
||||
|
||||
return desc.str();
|
||||
}
|
||||
|
||||
void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat, bool useSkinInDecode) {
|
||||
bool isModeThrough = (vertType & GE_VTYPE_THROUGH) != 0;
|
||||
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();
|
||||
bool doShadeMapping = doTexture && (gstate.getUVGenMode() == GE_TEXMAP_ENVIRONMENT_MAP);
|
||||
bool doFlatShading = gstate.getShadeMode() == GE_SHADE_FLAT && !gstate.isModeClear();
|
||||
@@ -91,6 +97,7 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform,
|
||||
id.SetBit(VS_BIT_LMODE, lmode);
|
||||
id.SetBit(VS_BIT_IS_THROUGH, isModeThrough);
|
||||
id.SetBit(VS_BIT_HAS_COLOR, vtypeHasColor);
|
||||
id.SetBit(VS_BIT_CLIP_ENABLE, gstate.isDepthClipEnabled());
|
||||
id.SetBit(VS_BIT_VERTEX_RANGE_CULLING, vertexRangeCulling);
|
||||
|
||||
if (!isModeThrough && gstate_c.Use(GPU_USE_SINGLE_PASS_STEREO)) {
|
||||
@@ -103,6 +110,7 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform,
|
||||
}
|
||||
|
||||
if (useHWTransform) {
|
||||
_dbg_assert_(!isModeThrough);
|
||||
id.SetBit(VS_BIT_USE_HW_TRANSFORM);
|
||||
id.SetBit(VS_BIT_HAS_NORMAL, vtypeHasNormal);
|
||||
|
||||
@@ -159,6 +167,13 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform,
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// These two bits cannot be combined, otherwise havoc occurs. We get reports that indicate this happened somehow... "ERROR: 0:14: 'u_proj' : undeclared identifier"
|
||||
@@ -264,6 +279,8 @@ std::string FragmentShaderDesc(const FShaderID &id) {
|
||||
if (id.Bit(FS_BIT_SAMPLE_ARRAY_TEXTURE)) desc << "TexArray ";
|
||||
if (id.Bit(FS_BIT_STEREO)) desc << "Stereo ";
|
||||
if (id.Bit(FS_BIT_USE_FRAMEBUFFER_FETCH)) desc << "(fetch)";
|
||||
if (id.Bit(FS_BIT_MINMAX_DISCARD)) desc << "FragMinMaxDiscard ";
|
||||
if (id.Bit(FS_BIT_DEPTH_CLAMP)) desc << "FragDepthClamp ";
|
||||
return desc.str();
|
||||
}
|
||||
|
||||
@@ -282,13 +299,26 @@ 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) {
|
||||
void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pipelineState, const Draw::Bugs &bugs, ClipInfoFlags clipInfoFlags) {
|
||||
FShaderID id;
|
||||
bool isModeThrough = gstate.isModeThrough();
|
||||
|
||||
// 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 (!isModeThrough) {
|
||||
if (clipInfoFlags & ClipInfoFlags::DepthClampFragment) {
|
||||
id.SetBit(FS_BIT_DEPTH_CLAMP);
|
||||
}
|
||||
if (clipInfoFlags & ClipInfoFlags::MinMaxZDiscard) {
|
||||
id.SetBit(FS_BIT_MINMAX_DISCARD);
|
||||
}
|
||||
} else {
|
||||
_dbg_assert_(0 == (clipInfoFlags & (ClipInfoFlags::DepthClampFragment | ClipInfoFlags::MinMaxZDiscard)));
|
||||
}
|
||||
|
||||
if (gstate.isModeClear()) {
|
||||
// We only need one clear shader, so let's ignore the rest of the bits.
|
||||
id.SetBit(FS_BIT_CLEARMODE);
|
||||
} else {
|
||||
bool isModeThrough = gstate.isModeThrough();
|
||||
bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled() && !isModeThrough;
|
||||
bool enableFog = gstate.isFogEnabled() && !isModeThrough;
|
||||
bool enableAlphaTest = gstate.isAlphaTestEnabled() && !IsAlphaTestTriviallyTrue();
|
||||
@@ -401,6 +431,9 @@ void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pip
|
||||
}
|
||||
}
|
||||
|
||||
// Various conditions that require per-pixel depth manipulation (very expensive!)
|
||||
bool needMinMaxClipping = gstate.getDepthRangeMin() != 0 && gstate.getDepthRangeMax() != 0xFFFF && !isModeThrough;
|
||||
|
||||
// Forcibly disable NEVER + depth-write on Mali.
|
||||
// TODO: Take this from computed depth test instead of directly from the gstate.
|
||||
// That will take more refactoring though.
|
||||
@@ -420,51 +453,3 @@ void ComputeFragmentShaderID(FShaderID *id_out, const ComputedPipelineState &pip
|
||||
|
||||
*id_out = id;
|
||||
}
|
||||
|
||||
std::string GeometryShaderDesc(const GShaderID &id) {
|
||||
std::stringstream desc;
|
||||
desc << StringFromFormat("%08x:%08x ", id.d[1], id.d[0]);
|
||||
if (id.Bit(GS_BIT_ENABLED)) desc << "ENABLED ";
|
||||
if (id.Bit(GS_BIT_DO_TEXTURE)) desc << "TEX ";
|
||||
if (id.Bit(GS_BIT_LMODE)) desc << "LM ";
|
||||
return desc.str();
|
||||
}
|
||||
|
||||
void ComputeGeometryShaderID(GShaderID *id_out, const Draw::Bugs &bugs, int prim) {
|
||||
GShaderID id;
|
||||
// Early out.
|
||||
if (!gstate_c.Use(GPU_USE_GS_CULLING)) {
|
||||
*id_out = id;
|
||||
return;
|
||||
}
|
||||
|
||||
bool isModeThrough = gstate.isModeThrough();
|
||||
bool isCurve = gstate_c.submitType != SubmitType::DRAW;
|
||||
bool isTriangle = prim == GE_PRIM_TRIANGLES || prim == GE_PRIM_TRIANGLE_FAN || prim == GE_PRIM_TRIANGLE_STRIP;
|
||||
|
||||
bool vertexRangeCulling = !isCurve;
|
||||
bool clipClampedDepth = gstate_c.Use(GPU_USE_DEPTH_CLAMP) && !gstate_c.Use(GPU_USE_CLIP_DISTANCE);
|
||||
|
||||
// Only use this for triangle primitives, and if we actually need it.
|
||||
if ((!vertexRangeCulling && !clipClampedDepth) || isModeThrough || !isTriangle) {
|
||||
*id_out = id;
|
||||
return;
|
||||
}
|
||||
|
||||
id.SetBit(GS_BIT_ENABLED, true);
|
||||
// Vertex range culling doesn't seem tno happen for spline/bezier, see #11692.
|
||||
id.SetBit(GS_BIT_CURVE, isCurve);
|
||||
|
||||
if (gstate.isModeClear()) {
|
||||
// No attribute bits.
|
||||
} else {
|
||||
bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled() && !isModeThrough;
|
||||
|
||||
id.SetBit(GS_BIT_LMODE, lmode);
|
||||
if (gstate.isTextureMapEnabled()) {
|
||||
id.SetBit(GS_BIT_DO_TEXTURE);
|
||||
}
|
||||
}
|
||||
|
||||
*id_out = id;
|
||||
}
|
||||
|
||||
+24
-45
@@ -5,6 +5,21 @@
|
||||
#include <cstdint>
|
||||
|
||||
#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.
|
||||
inline bool needFragmentMinMaxClipping() {
|
||||
return gstate.getDepthRangeMin() != 0 && gstate.getDepthRangeMax() != 0xFFFF;
|
||||
}
|
||||
|
||||
inline bool needFragmentDepthClamp() {
|
||||
// If gstate.isDepthClipEnabled is false, clamping does not happen, instead fragments are culled as normal.
|
||||
return (gstate.getDepthRangeMin() == 0 || gstate.getDepthRangeMax() == 0xFFFF) && gstate.isDepthClipEnabled();
|
||||
}
|
||||
|
||||
// VS_BIT_LIGHT_UBERSHADER indicates that some groups of these will be
|
||||
// sent to the shader and processed there. This cuts down the number of shaders ("ubershader approach").
|
||||
@@ -13,10 +28,10 @@ enum VShaderBit : uint8_t {
|
||||
VS_BIT_IS_THROUGH = 1,
|
||||
// bit 2 is free.
|
||||
VS_BIT_HAS_COLOR = 3,
|
||||
// bit 4 is free.
|
||||
VS_BIT_CLIP_ENABLE = 4,
|
||||
VS_BIT_VERTEX_RANGE_CULLING = 5,
|
||||
VS_BIT_SIMPLE_STEREO = 6,
|
||||
// 7 is free.
|
||||
// bit 7 is free,
|
||||
VS_BIT_USE_HW_TRANSFORM = 8,
|
||||
VS_BIT_HAS_NORMAL = 9, // conditioned on hw transform
|
||||
VS_BIT_NORM_REVERSE = 10,
|
||||
@@ -54,6 +69,8 @@ enum VShaderBit : uint8_t {
|
||||
VS_BIT_LIGHTING_ENABLE = 56,
|
||||
VS_BIT_WEIGHT_FMTSCALE = 57, // only two bits
|
||||
// 59 - 61 are free.
|
||||
VS_BIT_FS_MINMAX_DISCARD = 59, // Do min/max and/or depth clamp in the fragment shader. It just means we need to forward Z and W to the fragment shader.
|
||||
VS_BIT_FS_DEPTH_CLAMP = 60, // Do depth clamp in the fragment shader.
|
||||
VS_BIT_FLATSHADE = 62, // 1 bit
|
||||
VS_BIT_BEZIER = 63, // 1 bit
|
||||
// No more free
|
||||
@@ -83,7 +100,7 @@ enum FShaderBit : uint8_t {
|
||||
FS_BIT_COLOR_AGAINST_ZERO = 20,
|
||||
FS_BIT_ENABLE_FOG = 21, // Not used with FS_BIT_UBERSHADER
|
||||
FS_BIT_DO_TEXTURE_PROJ = 22,
|
||||
// 1 free bit
|
||||
FS_BIT_MINMAX_DISCARD = 23,
|
||||
FS_BIT_STENCIL_TO_ALPHA = 24, // 2 bits
|
||||
FS_BIT_REPLACE_ALPHA_WITH_STENCIL_TYPE = 26, // 4 bits (ReplaceAlphaType)
|
||||
FS_BIT_SIMULATE_LOGIC_OP_TYPE = 30, // 2 bits
|
||||
@@ -103,24 +120,14 @@ enum FShaderBit : uint8_t {
|
||||
FS_BIT_USE_FRAMEBUFFER_FETCH = 59,
|
||||
FS_BIT_UBERSHADER = 60,
|
||||
FS_BIT_DEPTH_TEST_NEVER = 61, // Only used on Mali. Set when depth == NEVER. We forcibly avoid writing to depth in this case, since it crashes the driver.
|
||||
FS_BIT_DEPTH_CLAMP = 62, // These both are connected to VS_BIT_MINMAX_DISCARD_OR_DEPTH_CLAMP in the vertex shader.
|
||||
// Free bit: 63
|
||||
};
|
||||
|
||||
static inline FShaderBit operator +(FShaderBit bit, int i) {
|
||||
return FShaderBit((int)bit + i);
|
||||
}
|
||||
|
||||
// Some of these bits are straight from FShaderBit, since they essentially enable attributes directly.
|
||||
enum GShaderBit : uint8_t {
|
||||
GS_BIT_ENABLED = 0, // If not set, we don't use a geo shader.
|
||||
GS_BIT_DO_TEXTURE = 1, // presence of texcoords
|
||||
GS_BIT_LMODE = 2,
|
||||
GS_BIT_CURVE = 3, // curve, which means don't do range culling.
|
||||
};
|
||||
|
||||
static inline GShaderBit operator +(GShaderBit bit, int i) {
|
||||
return GShaderBit((int)bit + i);
|
||||
}
|
||||
|
||||
struct ShaderID {
|
||||
ShaderID() {
|
||||
clear();
|
||||
@@ -251,46 +258,18 @@ struct FShaderID : ShaderID {
|
||||
}
|
||||
};
|
||||
|
||||
struct GShaderID : ShaderID {
|
||||
GShaderID() : ShaderID() {
|
||||
}
|
||||
|
||||
explicit GShaderID(ShaderID &src) {
|
||||
memcpy(d, src.d, sizeof(d));
|
||||
}
|
||||
|
||||
bool Bit(GShaderBit bit) const {
|
||||
return ShaderID::Bit((int)bit);
|
||||
}
|
||||
|
||||
int Bits(GShaderBit bit, int count) const {
|
||||
return ShaderID::Bits((int)bit, count);
|
||||
}
|
||||
|
||||
void SetBit(GShaderBit bit, bool value = true) {
|
||||
ShaderID::SetBit((int)bit, value);
|
||||
}
|
||||
|
||||
void SetBits(GShaderBit bit, int count, int value) {
|
||||
ShaderID::SetBits((int)bit, count, value);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
void ComputeFragmentShaderID(FShaderID *id, const ComputedPipelineState &pipelineState, const Draw::Bugs &bugs, ClipInfoFlags clipInfoFlags);
|
||||
std::string FragmentShaderDesc(const FShaderID &id);
|
||||
|
||||
void ComputeGeometryShaderID(GShaderID *id, const Draw::Bugs &bugs, int prim);
|
||||
std::string GeometryShaderDesc(const GShaderID &id);
|
||||
|
||||
// For sanity checking.
|
||||
bool FragmentIdNeedsFramebufferRead(const FShaderID &id);
|
||||
|
||||
+25
-101
@@ -6,7 +6,9 @@
|
||||
#include "Common/Data/Convert/SmallDataConvert.h"
|
||||
#include "Common/Math/lin/matrix4x4.h"
|
||||
#include "Common/Math/math_util.h"
|
||||
#include "Common/Math/CrossSIMD.h"
|
||||
#include "Common/Math/lin/vec3.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "GPU/GPUState.h"
|
||||
#include "GPU/Common/FramebufferManagerCommon.h"
|
||||
#include "GPU/Common/GPUStateUtils.h"
|
||||
@@ -14,76 +16,6 @@
|
||||
|
||||
using namespace Lin;
|
||||
|
||||
static void ConvertProjMatrixToZeroToOneDepth(Matrix4x4 &in) {
|
||||
const Vec3 trans(gstate_c.vpXOffset, gstate_c.vpYOffset, gstate_c.vpZOffset * 0.5f + 0.5f);
|
||||
const Vec3 scale(gstate_c.vpWidthScale, gstate_c.vpHeightScale, gstate_c.vpDepthScale * 0.5f);
|
||||
in.translateAndScale(trans, scale);
|
||||
}
|
||||
|
||||
void CalcCullRange(float minValues[4], float maxValues[4], bool flipViewport, bool hasNegZ) {
|
||||
// Account for the projection viewport adjustment when viewport is too large.
|
||||
auto reverseViewportX = [](float x) {
|
||||
float pspViewport = (x - gstate.getViewportXCenter()) * (1.0f / gstate.getViewportXScale());
|
||||
return (pspViewport * gstate_c.vpWidthScale) - gstate_c.vpXOffset;
|
||||
};
|
||||
auto reverseViewportY = [flipViewport](float y) {
|
||||
float heightScale = gstate_c.vpHeightScale;
|
||||
float yOffset = gstate_c.vpYOffset;
|
||||
if (flipViewport) {
|
||||
// For D3D11 and GLES non-buffered.
|
||||
heightScale = -heightScale;
|
||||
yOffset = -yOffset;
|
||||
}
|
||||
float pspViewport = (y - gstate.getViewportYCenter()) * (1.0f / gstate.getViewportYScale());
|
||||
return (pspViewport * heightScale) - yOffset;
|
||||
};
|
||||
auto transformZ = [hasNegZ](float z) {
|
||||
// Z culling ignores the viewport, so we just redo the projection matrix adjustments.
|
||||
if (hasNegZ) {
|
||||
return (z * gstate_c.vpDepthScale) + gstate_c.vpZOffset;
|
||||
}
|
||||
return (z * gstate_c.vpDepthScale * 0.5f) + gstate_c.vpZOffset * 0.5f + 0.5f;
|
||||
};
|
||||
auto sortPair = [](float a, float b) {
|
||||
return a > b ? std::make_pair(b, a) : std::make_pair(a, b);
|
||||
};
|
||||
|
||||
// The PSP seems to use 0.12.4 for X and Y, and 0.16.0 for Z.
|
||||
// Any vertex outside this range (unless depth clamp enabled) is discarded.
|
||||
auto x = sortPair(reverseViewportX(0.0f), reverseViewportX(4096.0f));
|
||||
auto y = sortPair(reverseViewportY(0.0f), reverseViewportY(4096.0f));
|
||||
auto z = sortPair(transformZ(-1.000030517578125f), transformZ(1.000030517578125f));
|
||||
// Since we have space in w, use it to pass the depth clamp flag. We also pass NAN for w "discard".
|
||||
float clampEnable = gstate.isDepthClampEnabled() ? 1.0f : 0.0f;
|
||||
|
||||
minValues[0] = x.first;
|
||||
minValues[1] = y.first;
|
||||
minValues[2] = z.first;
|
||||
minValues[3] = clampEnable;
|
||||
maxValues[0] = x.second;
|
||||
maxValues[1] = y.second;
|
||||
maxValues[2] = z.second + 1.0f / 65536.0f; // Adjustment needed due to some kind of rounding. See #17061
|
||||
maxValues[3] = NAN;
|
||||
}
|
||||
|
||||
// TODO: This will be removed later.
|
||||
static inline void FlipProjMatrix(Matrix4x4 & in) {
|
||||
const bool invertedY = gstate_c.vpHeight < 0;
|
||||
if (invertedY) {
|
||||
in[1] = -in[1];
|
||||
in[5] = -in[5];
|
||||
in[9] = -in[9];
|
||||
in[13] = -in[13];
|
||||
}
|
||||
const bool invertedX = gstate_c.vpWidth < 0;
|
||||
if (invertedX) {
|
||||
in[0] = -in[0];
|
||||
in[4] = -in[4];
|
||||
in[8] = -in[8];
|
||||
in[12] = -in[12];
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateRotation(float rotMatrix[4], bool useBufferedRendering) {
|
||||
if (!gstate_c.Use(GPU_USE_PRE_ROTATION) || useBufferedRendering) {
|
||||
rotMatrix[0] = 1.0f;
|
||||
@@ -100,7 +32,7 @@ void UpdateRotation(float rotMatrix[4], bool useBufferedRendering) {
|
||||
}
|
||||
}
|
||||
|
||||
void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipViewport, bool useBufferedRendering) {
|
||||
void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool useBufferedRendering) {
|
||||
if (dirtyUniforms & DIRTY_TEXENV) {
|
||||
Uint8x3ToFloat3(ub->texEnvColor, gstate.texenvcolor);
|
||||
}
|
||||
@@ -140,13 +72,7 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView
|
||||
}
|
||||
|
||||
if (dirtyUniforms & DIRTY_PROJMATRIX) {
|
||||
Matrix4x4 flippedMatrix;
|
||||
memcpy(&flippedMatrix, gstate.projMatrix, 16 * sizeof(float));
|
||||
|
||||
FlipProjMatrix(flippedMatrix);
|
||||
ConvertProjMatrixToZeroToOneDepth(flippedMatrix);
|
||||
CopyMatrix4x4(ub->proj, flippedMatrix.getReadPtr());
|
||||
|
||||
CopyMatrix4x4(ub->proj, gstate.projMatrix);
|
||||
ub->rotation = useBufferedRendering ? 0 : (float)g_display.rotation;
|
||||
}
|
||||
|
||||
@@ -159,8 +85,29 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView
|
||||
ub->rotation = useBufferedRendering ? 0 : (float)g_display.rotation;
|
||||
}
|
||||
|
||||
if (dirtyUniforms & DIRTY_RASTER_OFFSET) {
|
||||
ub->rasterOffset[0] = gstate.getOffsetX();
|
||||
ub->rasterOffset[1] = gstate.getOffsetY();
|
||||
ub->minZmaxZ[0] = (float)gstate.getDepthRangeMin();
|
||||
ub->minZmaxZ[1] = (float)gstate.getDepthRangeMax();
|
||||
|
||||
// test sine wave
|
||||
// ub->minZmaxZ[0] = (sin(time_now_d()) * 0.5f + 0.5f) * 65536.0;
|
||||
}
|
||||
|
||||
if (dirtyUniforms & DIRTY_VIEWPORT_UNIFORMS) {
|
||||
// TODO: This should be a couple of SIMD instructions.
|
||||
Vec4F32 vpScale = Vec4F32::LoadF24x3_DontCare(&gstate.viewportxscale);
|
||||
Vec4F32 vpOffset = Vec4F32::LoadF24x3_DontCare(&gstate.viewportxcenter);
|
||||
vpScale.Store(ub->vpScale);
|
||||
vpOffset.Store(ub->vpOffset);
|
||||
ub->NaN = std::numeric_limits<float>::quiet_NaN(); // Used in the shader for range culling.
|
||||
}
|
||||
|
||||
// Transform
|
||||
if (dirtyUniforms & DIRTY_WORLDMATRIX) {
|
||||
// TODO: We could change the shader to directly read these "malformed" matrices, but we'd
|
||||
// be doing the matrix multiplication manually.
|
||||
ConvertMatrix4x3To3x4Transposed(ub->world, gstate.worldMatrix);
|
||||
}
|
||||
if (dirtyUniforms & DIRTY_VIEWMATRIX) {
|
||||
@@ -236,29 +183,6 @@ void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipView
|
||||
}
|
||||
}
|
||||
|
||||
if (dirtyUniforms & DIRTY_DEPTHRANGE) {
|
||||
// Depth is [0, 1] mapping to [minz, maxz], not too hard.
|
||||
float vpZScale = gstate.getViewportZScale();
|
||||
float vpZCenter = gstate.getViewportZCenter();
|
||||
|
||||
// These are just the reverse of the formulas in GPUStateUtils.
|
||||
float halfActualZRange = InfToZero(gstate_c.vpDepthScale != 0.0f ? vpZScale / gstate_c.vpDepthScale : 0.0f);
|
||||
float inverseDepthScale = InfToZero(gstate_c.vpDepthScale != 0.0f ? 1.0f / gstate_c.vpDepthScale : 0.0f);
|
||||
|
||||
float minz = -((gstate_c.vpZOffset * halfActualZRange) - vpZCenter) - halfActualZRange;
|
||||
float viewZScale = halfActualZRange * 2.0f;
|
||||
float viewZCenter = minz;
|
||||
|
||||
ub->depthRange[0] = viewZScale;
|
||||
ub->depthRange[1] = viewZCenter;
|
||||
ub->depthRange[2] = gstate_c.vpZOffset * 0.5f + 0.5f;
|
||||
ub->depthRange[3] = 2.0f * inverseDepthScale;
|
||||
}
|
||||
|
||||
if (dirtyUniforms & DIRTY_CULLRANGE) {
|
||||
CalcCullRange(ub->cullRangeMin, ub->cullRangeMax, flipViewport, false);
|
||||
}
|
||||
|
||||
if (dirtyUniforms & DIRTY_BEZIERSPLINE) {
|
||||
ub->spline_counts = gstate_c.spline_num_points_u;
|
||||
}
|
||||
|
||||
+15
-16
@@ -10,8 +10,8 @@ enum : uint64_t {
|
||||
DIRTY_BASE_UNIFORMS =
|
||||
DIRTY_WORLDMATRIX | DIRTY_PROJTHROUGHMATRIX | DIRTY_VIEWMATRIX | DIRTY_TEXMATRIX | DIRTY_ALPHACOLORREF |
|
||||
DIRTY_PROJMATRIX | DIRTY_FOGCOLOR | DIRTY_FOGCOEF | DIRTY_TEXENV | DIRTY_TEX_ALPHA_MUL | DIRTY_STENCILREPLACEVALUE |
|
||||
DIRTY_ALPHACOLORMASK | DIRTY_SHADERBLEND | DIRTY_COLORWRITEMASK | DIRTY_UVSCALEOFFSET | DIRTY_TEXCLAMP | DIRTY_DEPTHRANGE | DIRTY_MATAMBIENTALPHA |
|
||||
DIRTY_BEZIERSPLINE | DIRTY_DEPAL,
|
||||
DIRTY_ALPHACOLORMASK | DIRTY_SHADERBLEND | DIRTY_COLORWRITEMASK | DIRTY_UVSCALEOFFSET | DIRTY_TEXCLAMP | DIRTY_MATAMBIENTALPHA |
|
||||
DIRTY_BEZIERSPLINE | DIRTY_DEPAL | DIRTY_VIEWPORT_UNIFORMS | DIRTY_RASTER_OFFSET,
|
||||
DIRTY_LIGHT_UNIFORMS =
|
||||
DIRTY_LIGHT_CONTROL | DIRTY_LIGHT0 | DIRTY_LIGHT1 | DIRTY_LIGHT2 | DIRTY_LIGHT3 |
|
||||
DIRTY_MATDIFFUSE | DIRTY_MATSPECULAR | DIRTY_MATEMISSIVE | DIRTY_AMBIENT,
|
||||
@@ -21,19 +21,19 @@ enum : uint64_t {
|
||||
// Every line here is a 4-float.
|
||||
struct alignas(16) UB_VS_FS_Base {
|
||||
float proj[16];
|
||||
float xywh[4]; // later, we could invert w and h here to avoid division.
|
||||
float view[12];
|
||||
float world[12];
|
||||
float tex[12];
|
||||
float xywh[4]; // later, we could invert w and h here to avoid division.
|
||||
float vpScale[3]; float NaN;
|
||||
float vpOffset[3]; float padding2;
|
||||
float rasterOffset[2]; float minZmaxZ[2];
|
||||
float uvScaleOffset[4];
|
||||
float depthRange[4];
|
||||
float matAmbient[4];
|
||||
float cullRangeMin[4];
|
||||
float cullRangeMax[4];
|
||||
uint32_t spline_counts; uint32_t depal_mask_shift_off_fmt; // 4 params packed into one.
|
||||
uint32_t colorWriteMask; float mipBias;
|
||||
// Fragment data
|
||||
float texNoAlpha; float texMul; float padding[2]; // this vec4 will hold ubershader stuff. We won't use integer flags in the fragment shader.
|
||||
float texNoAlpha; float texMul; float padding4[2]; // this vec4 will hold ubershader stuff. We won't use integer flags in the fragment shader.
|
||||
float fogColor[3]; uint32_t alphaColorRef;
|
||||
float texEnvColor[3]; uint32_t colorTestMask;
|
||||
float texClamp[4];
|
||||
@@ -41,21 +41,21 @@ struct alignas(16) UB_VS_FS_Base {
|
||||
float blendFixA[3]; float stencilReplaceValue;
|
||||
float blendFixB[3]; float rotation;
|
||||
// VR stuff is to go here, later. For normal drawing, we can then get away
|
||||
// with just uploading the first 448 bytes of the struct (up to and including fogCoef).
|
||||
// with just uploading the first X bytes of the struct (up to and including fogCoef).
|
||||
};
|
||||
static_assert(sizeof(UB_VS_FS_Base) == 432, "UB_VS_FS_Base should be 432 bytes");
|
||||
static_assert(sizeof(UB_VS_FS_Base) <= 432, "UB_VS_FS_Base should be 432 bytes");
|
||||
|
||||
static const char * const ub_baseStr =
|
||||
R"( mat4 u_proj;
|
||||
vec4 u_xywh;
|
||||
mat3x4 u_view;
|
||||
mat3x4 u_world;
|
||||
mat3x4 u_texmtx;
|
||||
vec4 u_xywh;
|
||||
vec3 u_vpScale; float u_NaN; // w = offsetX
|
||||
vec4 u_vpOffset; // w = offsetY
|
||||
vec2 u_rasterOffset; vec2 u_minZmaxZ;
|
||||
vec4 u_uvscaleoffset;
|
||||
vec4 u_depthRange;
|
||||
vec4 u_matambientalpha;
|
||||
vec4 u_cullRangeMin;
|
||||
vec4 u_cullRangeMax;
|
||||
uint u_spline_counts;
|
||||
uint u_depal_mask_shift_off_fmt;
|
||||
uint u_colorWriteMask;
|
||||
@@ -113,9 +113,8 @@ static const char * const ub_vs_bonesStr =
|
||||
R"( mat3x4 u_bone0; mat3x4 u_bone1; mat3x4 u_bone2; mat3x4 u_bone3; mat3x4 u_bone4; mat3x4 u_bone5; mat3x4 u_bone6; mat3x4 u_bone7; mat3x4 u_bone8;
|
||||
)";
|
||||
|
||||
void CalcCullRange(float minValues[4], float maxValues[4], bool flipViewport, bool hasNegZ);
|
||||
|
||||
void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool flipViewport, bool useBufferedRendering);
|
||||
// useBufferedRendering is only used to determine the rotation uniform.
|
||||
void BaseUpdateUniforms(UB_VS_FS_Base *ub, uint64_t dirtyUniforms, bool useBufferedRendering);
|
||||
void LightUpdateUniforms(UB_VS_Lights *ub, uint64_t dirtyUniforms);
|
||||
void BoneUpdateUniforms(UB_VS_Bones *ub, uint64_t dirtyUniforms);
|
||||
|
||||
|
||||
@@ -21,58 +21,53 @@
|
||||
#include "Common/CPUDetect.h"
|
||||
#include "Common/Math/math_util.h"
|
||||
#include "Common/GPU/OpenGL/GLFeatures.h"
|
||||
#include "Common/Math/CrossSIMD.h"
|
||||
#include "Core/Config.h"
|
||||
#include "Core/System.h"
|
||||
#include "GPU/GPUState.h"
|
||||
#include "GPU/Math3D.h"
|
||||
#include "GPU/GPUStateSIMDUtil.h"
|
||||
#include "GPU/Common/FramebufferManagerCommon.h"
|
||||
#include "GPU/Common/GPUStateUtils.h"
|
||||
#include "GPU/Common/SoftwareTransformCommon.h"
|
||||
#include "GPU/Common/TransformCommon.h"
|
||||
#include "GPU/Common/VertexDecoderCommon.h"
|
||||
#include "GPU/Common/DrawEngineCommon.h"
|
||||
#include "GPU/Software/Clipper.h"
|
||||
|
||||
// This is the software transform pipeline, which is necessary for supporting RECT
|
||||
// primitives correctly without geometry shaders, and may be easier to use for
|
||||
// debugging than the hardware transform pipeline.
|
||||
|
||||
// There's code here that simply expands transformed RECTANGLES into plain triangles.
|
||||
// Additionally, it performs some culling, clipping and clamping which it can do more accurately than the normal
|
||||
// hardware pipeline can.
|
||||
|
||||
// There's code here that simply expands transformed RECTANGLES into plain triangles, additionally LINEs and POINTs get expanded
|
||||
// to produce more PSP-like behavior.
|
||||
|
||||
// We're gonna have to keep software transforming RECTANGLES, unless we use a geom shader which we can't on OpenGL ES 2.0.
|
||||
// Usually, though, these primitives don't use lighting etc so it's no biggie performance wise, but it would be nice to get rid of
|
||||
// this code.
|
||||
|
||||
// Actually, if we find the camera-relative right and down vectors, it might even be possible to add the extra points in pre-transformed
|
||||
// space and thus make decent use of hardware transform.
|
||||
|
||||
// Actually again, single quads could be drawn more efficiently using GL_TRIANGLE_STRIP, no need to duplicate verts as for
|
||||
// GL_TRIANGLES. Still need to sw transform to compute the extra two corners though.
|
||||
//
|
||||
|
||||
// The verts are in the order: BR BL TL TR
|
||||
static void SwapUVs(TransformedVertex &a, TransformedVertex &b) {
|
||||
float tempu = a.u;
|
||||
float tempv = a.v;
|
||||
a.u = b.u;
|
||||
a.v = b.v;
|
||||
b.u = tempu;
|
||||
b.v = tempv;
|
||||
}
|
||||
|
||||
// 2 3 3 2 0 3 2 1
|
||||
// to to or
|
||||
// 1 0 0 1 1 2 3 0
|
||||
|
||||
// Note: 0 is BR and 2 is TL.
|
||||
|
||||
// The PSP has a funky mechanism where the UV direction of screen-space rectangles is decided by the relative positioning
|
||||
// of the two corners defining the rectangle.
|
||||
static void RotateUV(TransformedVertex v[4]) {
|
||||
const float x1 = v[2].x;
|
||||
const float x2 = v[0].x;
|
||||
const float y1 = v[2].y;
|
||||
const float y2 = v[0].y;
|
||||
|
||||
if ((x1 < x2 && y1 > y2) || (x1 > x2 && y1 < y2)) {
|
||||
SwapUVs(v[1], v[3]);
|
||||
float tempu = v[1].u;
|
||||
float tempv = v[1].v;
|
||||
v[1].u = v[3].u;
|
||||
v[1].v = v[3].v;
|
||||
v[3].u = tempu;
|
||||
v[3].v = tempv;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,35 +102,17 @@ static bool IsReallyAClear(const TransformedVertex *transformed, int numVerts, f
|
||||
}
|
||||
}
|
||||
|
||||
// The last vertical strip often extends outside the drawing area.
|
||||
if (transformed[numVerts - 1].x < x2)
|
||||
// The last vertical strip often extends outside the drawing area so we don't want an equality check.
|
||||
// But make sure it at least fully covers it.
|
||||
if (transformed[numVerts - 1].x < x2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SoftwareTransform::SetProjMatrix(const float mtx[14], bool invertedX, bool invertedY, const Lin::Vec3 &trans, const Lin::Vec3 &scale) {
|
||||
memcpy(&projMatrix_.m, mtx, 16 * sizeof(float));
|
||||
|
||||
if (invertedY) {
|
||||
projMatrix_.xy = -projMatrix_.xy;
|
||||
projMatrix_.yy = -projMatrix_.yy;
|
||||
projMatrix_.zy = -projMatrix_.zy;
|
||||
projMatrix_.wy = -projMatrix_.wy;
|
||||
}
|
||||
if (invertedX) {
|
||||
projMatrix_.xx = -projMatrix_.xx;
|
||||
projMatrix_.yx = -projMatrix_.yx;
|
||||
projMatrix_.zx = -projMatrix_.zx;
|
||||
projMatrix_.wx = -projMatrix_.wx;
|
||||
}
|
||||
|
||||
projMatrix_.translateAndScale(trans, scale);
|
||||
}
|
||||
|
||||
void SoftwareTransform::Transform(int prim, u32 vertType, const DecVtxFormat &decVtxFormat, int &numDecodedVerts, int vertsSize, int vertexCount, u16 *&inds, int indsSize, SoftwareTransformResult *result) {
|
||||
u8 *decoded = params_.decoded;
|
||||
TransformedVertex *transformed = params_.transformed;
|
||||
// At the end, this calls ProjectClipAndExpand which will expand rectangles as necessary, or apply culling.
|
||||
SoftwareTransformAction SoftwareTransform::Transform(SoftwareTransformParams ¶ms, int prim, u32 vertType, const DecVtxFormat &decVtxFormat, int &numDecodedVerts, int vertsSize, int vertexCount, u16 *&inds, int indsSize, SoftwareTransformResult *result) {
|
||||
bool throughmode = (vertType & GE_VTYPE_THROUGH_MASK) != 0;
|
||||
bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled();
|
||||
|
||||
@@ -156,15 +133,17 @@ void SoftwareTransform::Transform(int prim, u32 vertType, const DecVtxFormat &de
|
||||
float fog_end = getFloat24(gstate.fog1);
|
||||
float fog_slope = getFloat24(gstate.fog2);
|
||||
// Same fixup as in ShaderManagerGLES.cpp
|
||||
// Not really sure what a sensible value might be, but let's try 64k.
|
||||
constexpr float largeFogValue = 65535.0f;
|
||||
if (my_isnanorinf(fog_end)) {
|
||||
// Not really sure what a sensible value might be, but let's try 64k.
|
||||
fog_end = std::signbit(fog_end) ? -65535.0f : 65535.0f;
|
||||
fog_end = std::signbit(fog_end) ? -largeFogValue : largeFogValue;
|
||||
}
|
||||
if (my_isnanorinf(fog_slope)) {
|
||||
fog_slope = std::signbit(fog_slope) ? -65535.0f : 65535.0f;
|
||||
fog_slope = std::signbit(fog_slope) ? -largeFogValue : largeFogValue;
|
||||
}
|
||||
|
||||
VertexReader reader(decoded, decVtxFormat, vertType);
|
||||
TransformedVertex *transformed = params.transformed;
|
||||
VertexReader reader(params.decoded, decVtxFormat, vertType);
|
||||
if (throughmode) {
|
||||
const u32 materialAmbientRGBA = gstate.getMaterialAmbientRGBA();
|
||||
const bool hasColor = reader.hasColor0();
|
||||
@@ -172,7 +151,6 @@ void SoftwareTransform::Transform(int prim, u32 vertType, const DecVtxFormat &de
|
||||
for (int index = 0; index < numDecodedVerts; index++) {
|
||||
// Do not touch the coordinates or the colors. No lighting.
|
||||
reader.Goto(index);
|
||||
// TODO: Write to a flexible buffer, we don't always need all four components.
|
||||
TransformedVertex &vert = transformed[index];
|
||||
reader.ReadPosThrough(vert.pos);
|
||||
vert.pos_w = 1.0f;
|
||||
@@ -217,11 +195,11 @@ void SoftwareTransform::Transform(int prim, u32 vertType, const DecVtxFormat &de
|
||||
result->safeHeight = scissorY2;
|
||||
}
|
||||
}
|
||||
if (params_.allowClear && reallyAClear && gl_extensions.gpuVendor != GPU_VENDOR_IMGTEC) {
|
||||
if (params.allowClear && reallyAClear && gl_extensions.gpuVendor != GPU_VENDOR_IMGTEC) {
|
||||
// If alpha is not allowed to be separate, it must match for both depth/stencil and color. Vulkan requires this.
|
||||
bool alphaMatchesColor = gstate.isClearModeColorMask() == gstate.isClearModeAlphaMask();
|
||||
bool depthMatchesStencil = gstate.isClearModeAlphaMask() == gstate.isClearModeDepthMask();
|
||||
bool matchingComponents = params_.allowSeparateAlphaClear || (alphaMatchesColor && depthMatchesStencil);
|
||||
bool matchingComponents = params.allowSeparateAlphaClear || (alphaMatchesColor && depthMatchesStencil);
|
||||
bool stencilNotMasked = !gstate.isClearModeAlphaMask() || gstate.getStencilWriteMask() == 0x00;
|
||||
if (matchingComponents && stencilNotMasked) {
|
||||
DepthScaleFactors depthScale = GetDepthScaleFactors(gstate_c.UseFlags());
|
||||
@@ -229,12 +207,11 @@ void SoftwareTransform::Transform(int prim, u32 vertType, const DecVtxFormat &de
|
||||
float depth = depthScale.EncodeFromU16((float)(int)(transformed[1].z * 65535.0f));
|
||||
// Non-zero depth clears are unusual, but some drivers don't match drawn depth values to cleared values.
|
||||
// Games sometimes expect exact matches (see #12626, for example) for equal comparisons.
|
||||
if (!(params_.everUsedEqualDepth && gstate.isClearModeDepthMask() && result->depth > 0.0f && result->depth < 1.0f)) {
|
||||
if (!(params.everUsedEqualDepth && gstate.isClearModeDepthMask() && result->depth > 0.0f && result->depth < 1.0f)) {
|
||||
result->color = transformed[1].color0_32;
|
||||
result->depth = depth;
|
||||
result->action = SW_CLEAR;
|
||||
gpuStats.perFrame.numClears++;
|
||||
return;
|
||||
return SW_CLEAR;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,9 +233,13 @@ void SoftwareTransform::Transform(int prim, u32 vertType, const DecVtxFormat &de
|
||||
Vec3f worldnormal(0, 0, 1);
|
||||
reader.ReadPosNonThrough(pos);
|
||||
|
||||
float ruv[2] = { 0.0f, 0.0f };
|
||||
float ruv[2];
|
||||
if (reader.hasUV())
|
||||
reader.ReadUV(ruv);
|
||||
else {
|
||||
ruv[0] = 0.0f;
|
||||
ruv[1] = 0.0f;
|
||||
}
|
||||
|
||||
Vec4f unlitColor;
|
||||
if (reader.hasColor0())
|
||||
@@ -381,18 +362,22 @@ void SoftwareTransform::Transform(int prim, u32 vertType, const DecVtxFormat &de
|
||||
Vec3ByMatrix43(v, out, gstate.viewMatrix);
|
||||
fogCoef = (v[2] + fog_end) * fog_slope;
|
||||
|
||||
// TODO: Write to a flexible buffer, we don't always need all four components.
|
||||
Vec3ByMatrix44(transformed[index].pos, v, projMatrix_.m);
|
||||
// Then transform by the projection.
|
||||
Vec3ByMatrix44(transformed[index].pos, v, gstate.projMatrix);
|
||||
|
||||
transformed[index].fog = fogCoef;
|
||||
memcpy(&transformed[index].uv, uv, 3 * sizeof(float));
|
||||
transformed[index].color0_32 = c0.ToRGBA();
|
||||
transformed[index].color1_32 = c1.ToRGBA();
|
||||
|
||||
// Projection happens later in ProjectClipAndExpand.
|
||||
|
||||
// Vertex depth rounding is done in the shader, to simulate the 16-bit depth buffer.
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This doesn't seem to be a very good check, but let's leave it for now.
|
||||
|
||||
// Detect full screen "clears" that might not be so obvious, to set the safe size if possible.
|
||||
if (!result->setSafeSize && prim == GE_PRIM_RECTANGLES && numDecodedVerts == 2 && throughmode) {
|
||||
bool clearingColor = gstate.isModeClear() && (gstate.isClearModeColorMask() || gstate.isClearModeAlphaMask());
|
||||
@@ -409,28 +394,219 @@ void SoftwareTransform::Transform(int prim, u32 vertType, const DecVtxFormat &de
|
||||
}
|
||||
}
|
||||
|
||||
_dbg_assert_(result->action == SW_NOT_READY);
|
||||
|
||||
ProjectClipAndExpand(prim, vertexCount, vertType, inds, indsSize, numDecodedVerts, vertsSize, result);
|
||||
return ProjectClipAndExpand(params, prim, vertexCount, vertType, inds, indsSize, numDecodedVerts, vertsSize, result);
|
||||
}
|
||||
|
||||
void SoftwareTransform::ProjectClipAndExpand(int prim, int vertexCount, u32 vertType, u16 *&inds, int indsSize, int &numDecodedVerts, int vertsSize, SoftwareTransformResult *result) {
|
||||
TransformedVertex *transformed = params_.transformed;
|
||||
TransformedVertex *transformedExpanded = params_.transformedExpanded;
|
||||
// Modifies the vertices in-place. Applies viewport and projection.
|
||||
// TODO: SIMD.
|
||||
void SoftwareTransform::ProjectVertices(TransformedVertex *transformed, int vertexCount) {
|
||||
#if 0
|
||||
Lin::Vec3 vpOffset(gstate.getViewportXCenter(), gstate.getViewportYCenter(), gstate.getViewportZCenter());
|
||||
Lin::Vec3 vpScale(gstate.getViewportXScale(), gstate.getViewportYScale(), gstate.getViewportZScale());
|
||||
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
// Here we also need to do the perspective device and apply the viewport.
|
||||
// TODO: Move this to ProjectClipAndExpand.
|
||||
const float w = transformed[i].pos_w;
|
||||
const float recip = 1.0f / w;
|
||||
Lin::Vec3 xyz = vpOffset + vpScale.scaledBy(Lin::Vec3(transformed[i].x, transformed[i].y, transformed[i].z)) * recip;
|
||||
transformed[i].x = xyz.x;
|
||||
transformed[i].y = xyz.y;
|
||||
transformed[i].z = xyz.z;
|
||||
}
|
||||
#else
|
||||
const Vec4F32 vpOffset = GetViewportOffsetVec(gstate);
|
||||
const Vec4F32 vpScale = GetViewportScaleVec(gstate);
|
||||
// CrossSIMD implementation.
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
Vec4F32 xyzw = Vec4F32::Load(&transformed[i].x);
|
||||
Vec4F32 wRecip = Vec4F32::Splat(1.0f / transformed[i].pos_w);
|
||||
Vec4F32 projected = xyzw * vpScale * wRecip + vpOffset;
|
||||
// Now, we need to restore the W value as we'll still need it later.
|
||||
projected.WithLane3From(xyzw).Store(&transformed[i].x);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Helper to check if a vertex is inside the near plane (z >= -w)
|
||||
// We add a tiny epsilon to prevent floating-point precision issues at the exact boundary
|
||||
inline bool IsInsideNearPlane(const TransformedVertex& v) {
|
||||
return v.z >= -v.pos_w;
|
||||
}
|
||||
|
||||
inline bool IsInsideFarPlane(const TransformedVertex& v) {
|
||||
return v.z <= v.pos_w;
|
||||
}
|
||||
|
||||
// TODO: Use CrossSIMD, should help.
|
||||
inline void LerpTransformedVertex(TransformedVertex *dest, TransformedVertex &a, TransformedVertex &b, float t) {
|
||||
dest->x = a.x + (b.x - a.x) * t;
|
||||
dest->y = a.y + (b.y - a.y) * t;
|
||||
dest->z = a.z + (b.z - a.z) * t;
|
||||
dest->pos_w = a.pos_w + (b.pos_w - a.pos_w) * t;
|
||||
dest->u = a.u + (b.u - a.u) * t;
|
||||
dest->v = a.v + (b.v - a.v) * t;
|
||||
dest->uv_w = a.uv_w + (b.uv_w - a.uv_w) * t;
|
||||
dest->fog = a.fog + (b.fog - a.fog) * t;
|
||||
|
||||
// note: colorBlend is backwards.
|
||||
dest->color0_32 = colorBlend(b.color0_32, a.color0_32, t);
|
||||
dest->color1_32 = colorBlend(b.color1_32, a.color1_32, t);
|
||||
}
|
||||
|
||||
// Generated by Gemini, and adapted to fit.
|
||||
void ClipTrianglesAgainstNearPlane(
|
||||
TransformedVertex *transformed, int &transformedCount, int maxTransformed,
|
||||
u16 *indicesIn, int numIndicesIn,
|
||||
u16 *indicesOut, int &numIndicesOut, int maxIndicesOut
|
||||
) {
|
||||
int clipCount = 0;
|
||||
// Process one triangle (3 indices) at a time
|
||||
for (size_t i = 0; i < numIndicesIn; i += 3) {
|
||||
u16 idx0 = indicesIn[i];
|
||||
u16 idx1 = indicesIn[i + 1];
|
||||
u16 idx2 = indicesIn[i + 2];
|
||||
|
||||
TransformedVertex& v0 = transformed[idx0];
|
||||
TransformedVertex& v1 = transformed[idx1];
|
||||
TransformedVertex& v2 = transformed[idx2];
|
||||
|
||||
bool in0 = IsInsideNearPlane(v0);
|
||||
bool in1 = IsInsideNearPlane(v1);
|
||||
bool in2 = IsInsideNearPlane(v2);
|
||||
|
||||
bool inFar0 = IsInsideFarPlane(v0);
|
||||
bool inFar1 = IsInsideFarPlane(v1);
|
||||
bool inFar2 = IsInsideFarPlane(v2);
|
||||
|
||||
int insideCount = (in0 ? 1 : 0) + (in1 ? 1 : 0) + (in2 ? 1 : 0);
|
||||
int insideFarCount = (inFar0 ? 1 : 0) + (inFar1 ? 1 : 0) + (inFar2 ? 1 : 0);
|
||||
|
||||
// Case 1: Entirely visible
|
||||
if (insideCount == 3) {
|
||||
indicesOut[numIndicesOut++] = idx0;
|
||||
indicesOut[numIndicesOut++] = idx1;
|
||||
indicesOut[numIndicesOut++] = idx2;
|
||||
}
|
||||
// Case 2: Entirely clipped / behind near plane
|
||||
else if (insideCount == 0) {
|
||||
// Cull, no clipping needed.
|
||||
continue;
|
||||
}
|
||||
// Case 3: Entirely beyond far plane
|
||||
else if (insideFarCount == 0) {
|
||||
// All are beyond the far plane. Cull.
|
||||
continue;
|
||||
}
|
||||
// Case 3: Partially clipped
|
||||
else {
|
||||
clipCount++;
|
||||
|
||||
// We need to organize vertices cleanly to calculate intersections.
|
||||
// We will create a local polygon array of the inside/outside states.
|
||||
u16 triIdx[3] = {idx0, idx1, idx2};
|
||||
bool triIn[3] = {in0, in1, in2};
|
||||
|
||||
// Output generated vertex indices for this clipped polygon
|
||||
u16 polyIndices[4];
|
||||
int polyLength = 0;
|
||||
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
int next = (j + 1) % 3;
|
||||
|
||||
u16 currIdx = triIdx[j];
|
||||
u16 nextIdx = triIdx[next];
|
||||
|
||||
// If current vertex is inside, it stays a part of the output polygon
|
||||
if (triIn[j]) {
|
||||
polyIndices[polyLength++] = currIdx;
|
||||
}
|
||||
|
||||
// If we cross the clipping plane line (inside->outside or outside->inside)
|
||||
if (triIn[j] != triIn[next]) {
|
||||
/* const */ TransformedVertex& a = transformed[currIdx];
|
||||
/* const */ TransformedVertex& b = transformed[nextIdx];
|
||||
|
||||
// Find interpolation factor 't' where: z_interpolated = -w_interpolated
|
||||
// Lerp formulation:
|
||||
// z = a.z + t*(b.z - a.z)
|
||||
// w = a.w + t*(b.w - a.w)
|
||||
// Set z = -w => a.z + t*(b.z - a.z) = -(a.pos_w + t*(b.pos_w - a.pos_w))
|
||||
// Solve for t:
|
||||
float denominator = (b.z - a.z) + (b.pos_w - a.pos_w);
|
||||
float t = 0.0f;
|
||||
if (fabsf(denominator) > 0.000001f) {
|
||||
t = (-a.z - a.pos_w) / denominator;
|
||||
}
|
||||
// Clamp safely due to float precision
|
||||
if (t < 0.0f) t = 0.0f;
|
||||
if (t > 1.0f) t = 1.0f;
|
||||
|
||||
// Generate new vertex at the intersection point
|
||||
TransformedVertex newVertex;
|
||||
LerpTransformedVertex(&newVertex, const_cast<TransformedVertex&>(a), const_cast<TransformedVertex&>(b), t);
|
||||
|
||||
// Force exact intersection to eliminate precision creeping down the pipeline
|
||||
newVertex.z = -newVertex.pos_w;
|
||||
|
||||
// These can be used for debugging.
|
||||
// newVertex.color0_32 = 0xFFFF00FF;
|
||||
// a.color0_32 = 0xFFFF00FF;
|
||||
// b.color0_32 = 0xFFFF00FF;
|
||||
|
||||
// Append to global vertex buffer
|
||||
transformed[transformedCount++] = newVertex;
|
||||
u16 newIdx = static_cast<u16>(transformedCount - 1);
|
||||
polyIndices[polyLength++] = newIdx;
|
||||
}
|
||||
}
|
||||
|
||||
// Triangulate the resulting polygon array (will be either 3 or 4 vertices)
|
||||
if (polyLength == 3) {
|
||||
indicesOut[numIndicesOut++] = polyIndices[0];
|
||||
indicesOut[numIndicesOut++] = polyIndices[1];
|
||||
indicesOut[numIndicesOut++] = polyIndices[2];
|
||||
} else if (polyLength == 4) {
|
||||
// Triangle 1
|
||||
indicesOut[numIndicesOut++] = polyIndices[0];
|
||||
indicesOut[numIndicesOut++] = polyIndices[1];
|
||||
indicesOut[numIndicesOut++] = polyIndices[2];
|
||||
// Triangle 2
|
||||
indicesOut[numIndicesOut++] = polyIndices[0];
|
||||
indicesOut[numIndicesOut++] = polyIndices[2];
|
||||
indicesOut[numIndicesOut++] = polyIndices[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gpuStats.perFrame.numSoftClippedTriangles++;
|
||||
}
|
||||
|
||||
SoftwareTransformAction SoftwareTransform::ProjectClipAndExpand(SoftwareTransformParams ¶ms, int prim, int vertexCount, u32 vertType, u16 *&inds, int indsSize, int &numDecodedVerts, int vertsSize, SoftwareTransformResult *result) {
|
||||
TransformedVertex *transformed = params.transformed;
|
||||
TransformedVertex *transformedExpanded = params.transformedExpanded;
|
||||
bool throughmode = (vertType & GE_VTYPE_THROUGH_MASK) != 0;
|
||||
|
||||
// Step 2: expand and process primitives.
|
||||
result->drawBuffer = transformed;
|
||||
int numTrans = 0;
|
||||
|
||||
FramebufferManagerCommon *fbman = params_.fbman;
|
||||
FramebufferManagerCommon *fbman = params.fbman;
|
||||
bool useBufferedRendering = fbman->UseBufferedRendering();
|
||||
|
||||
// NOTE: ExpandRectanges/lines/etc should do clipping while they're at it.
|
||||
if (prim == GE_PRIM_RECTANGLES) {
|
||||
// TODO: We should cull rectangles outzide -W<Z<W here if *both* points are outside in the same direction,
|
||||
// like we do with triangles.
|
||||
|
||||
if (!throughmode) {
|
||||
ProjectVertices(transformed, numDecodedVerts);
|
||||
}
|
||||
|
||||
if (!ExpandRectangles(vertexCount, numDecodedVerts, vertsSize, inds, indsSize, transformed, transformedExpanded, numTrans, throughmode, &result->pixelMapped)) {
|
||||
result->drawNumTrans = 0;
|
||||
result->pixelMapped = false;
|
||||
return;
|
||||
return SW_CULLED;
|
||||
}
|
||||
result->drawBuffer = transformedExpanded;
|
||||
|
||||
@@ -448,65 +624,111 @@ void SoftwareTransform::ProjectClipAndExpand(int prim, int vertexCount, u32 vert
|
||||
}
|
||||
}
|
||||
} else if (prim == GE_PRIM_POINTS) {
|
||||
// TODO: We should cull points here if they are outside -W<Z<W.
|
||||
|
||||
if (!throughmode) {
|
||||
ProjectVertices(transformed, numDecodedVerts);
|
||||
}
|
||||
|
||||
result->pixelMapped = false;
|
||||
if (!ExpandPoints(vertexCount, numDecodedVerts, vertsSize, inds, indsSize, transformed, transformedExpanded, numTrans, throughmode)) {
|
||||
result->drawNumTrans = 0;
|
||||
return;
|
||||
return SW_CULLED;
|
||||
}
|
||||
result->drawBuffer = transformedExpanded;
|
||||
} else if (prim == GE_PRIM_LINES) {
|
||||
// TODO: We should cull rectangles outzide -W<Z<W here if *both* points are outside in the same direction,
|
||||
// like we do with triangles.
|
||||
|
||||
if (!throughmode) {
|
||||
ProjectVertices(transformed, numDecodedVerts);
|
||||
}
|
||||
|
||||
result->pixelMapped = false;
|
||||
if (!ExpandLines(vertexCount, numDecodedVerts, vertsSize, inds, indsSize, transformed, transformedExpanded, numTrans, throughmode)) {
|
||||
result->drawNumTrans = 0;
|
||||
return;
|
||||
return SW_CULLED;
|
||||
}
|
||||
result->drawBuffer = transformedExpanded;
|
||||
} else {
|
||||
// We can simply draw the unexpanded buffer.
|
||||
} else if (prim == GE_PRIM_TRIANGLES) {
|
||||
// Triangles. We can simply draw the unexpanded buffer, although we do also take the opportunity to perform culling.
|
||||
numTrans = vertexCount;
|
||||
result->pixelMapped = false;
|
||||
|
||||
// If we don't support custom cull in the shader, process it here.
|
||||
if (!gstate_c.Use(GPU_USE_CULL_DISTANCE) && vertexCount > 0 && !throughmode) {
|
||||
if (throughmode) {
|
||||
// Nothing to do, pass the vertices right through as-is. Well, we can go look for pixel mapping, but we don't do any culling or clipping.
|
||||
if (g_Config.bSmart2DTexFiltering && !gstate_c.textureIsVideo) {
|
||||
// We check some common cases for pixel mapping.
|
||||
// TODO: It's not really optimal that some previous step has removed the triangle strip.
|
||||
if (vertexCount <= 6 && prim == GE_PRIM_TRIANGLES) {
|
||||
// It's enough to check UV deltas vs pos deltas between vertex pairs:
|
||||
// 0-1 1-3 3-2 2-0. Maybe can even skip the last one. Probably some simple math can get us that sequence.
|
||||
// Unfortunately we need to reverse the previous UV scaling operation. Fortunately these are powers of two
|
||||
// so the operations are exact.
|
||||
bool pixelMapped = true;
|
||||
const u16 *indsIn = (const u16 *)inds;
|
||||
const float uscale = gstate_c.curTextureWidth;
|
||||
const float vscale = gstate_c.curTextureHeight;
|
||||
for (int t = 0; t < vertexCount; t += 3) {
|
||||
struct { int a; int b; } pairs[] = {{0, 1}, {1, 2}, {2, 0}};
|
||||
for (int i = 0; i < ARRAY_SIZE(pairs); i++) {
|
||||
int a = indsIn[t + pairs[i].a];
|
||||
int b = indsIn[t + pairs[i].b];
|
||||
float du = fabsf((transformed[a].u - transformed[b].u) * uscale);
|
||||
float dv = fabsf((transformed[a].v - transformed[b].v) * vscale);
|
||||
float dx = fabsf(transformed[a].x - transformed[b].x);
|
||||
float dy = fabsf(transformed[a].y - transformed[b].y);
|
||||
if (du != dx || dv != dy) {
|
||||
pixelMapped = false;
|
||||
}
|
||||
}
|
||||
if (!pixelMapped) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result->pixelMapped = pixelMapped;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Culling and clipping needs to be done here, it doesn't happen in the shader in the case of software transform.
|
||||
// However, fast culling should already have taken care of the Z<-W and Z>W culling, but we check for it on a per-triangle
|
||||
// basis here anyway.
|
||||
const u16 *indsIn = (const u16 *)inds;
|
||||
u16 *origInds = inds;
|
||||
|
||||
// TODO: We should either merge the two loops, or avoid the second loop if no culling is needed.
|
||||
|
||||
// Now, for each triangle, throw away the indices if:
|
||||
// - Depth clip/clamp on, and ALL verts are outside *in the same direction*.
|
||||
// - Depth clip/clamp off, and ANY vert is outside.
|
||||
|
||||
u16 *newInds = inds + vertexCount;
|
||||
u16 *indsOut = newInds;
|
||||
|
||||
float minZValue, maxZValue;
|
||||
CalcCullParams(minZValue, maxZValue);
|
||||
if (gstate.isDepthClipEnabled()) {
|
||||
const u16 *indsIn = (const u16 *)inds;
|
||||
int newIndexCount = 0;
|
||||
ClipTrianglesAgainstNearPlane(transformed, numDecodedVerts, 65536, inds, vertexCount, indsOut, newIndexCount, 65336);
|
||||
numTrans = newIndexCount;
|
||||
} else {
|
||||
std::vector<int> outsideZ;
|
||||
outsideZ.resize(vertexCount);
|
||||
|
||||
std::vector<int> outsideZ;
|
||||
outsideZ.resize(vertexCount);
|
||||
|
||||
// First, check inside/outside directions for each index.
|
||||
for (int i = 0; i < vertexCount; ++i) {
|
||||
float z = transformed[indsIn[i]].z / transformed[indsIn[i]].pos_w;
|
||||
if (z > maxZValue)
|
||||
outsideZ[i] = 1;
|
||||
else if (z < minZValue)
|
||||
outsideZ[i] = -1;
|
||||
else
|
||||
outsideZ[i] = 0;
|
||||
}
|
||||
|
||||
// Now, for each primitive type, throw away the indices if:
|
||||
// - Depth clamp on, and ALL verts are outside *in the same direction*.
|
||||
// - Depth clamp off, and ANY vert is outside.
|
||||
if (prim == GE_PRIM_TRIANGLES && gstate.isDepthClampEnabled()) {
|
||||
numTrans = 0;
|
||||
for (int i = 0; i < vertexCount - 2; i += 3) {
|
||||
if (outsideZ[i + 0] != 0 && outsideZ[i + 0] == outsideZ[i + 1] && outsideZ[i + 0] == outsideZ[i + 2]) {
|
||||
// All outside, and all the same direction. Nuke this triangle.
|
||||
continue;
|
||||
// First, check inside/outside directions for each index.
|
||||
// We are still in clip space here, so we can cull aggressively in Z.
|
||||
// TODO: This is so cheap now that we can probably avoid the buffer and just do the work below.
|
||||
for (int i = 0; i < vertexCount; ++i) {
|
||||
float z = transformed[indsIn[i]].z;
|
||||
float w = transformed[indsIn[i]].pos_w;
|
||||
if (z > w) {
|
||||
outsideZ[i] = 1;
|
||||
} else if (z < -w) {
|
||||
outsideZ[i] = -1;
|
||||
} else {
|
||||
outsideZ[i] = 0;
|
||||
}
|
||||
|
||||
memcpy(indsOut, indsIn + i, 3 * sizeof(uint16_t));
|
||||
indsOut += 3;
|
||||
numTrans += 3;
|
||||
}
|
||||
|
||||
inds = newInds;
|
||||
} else if (prim == GE_PRIM_TRIANGLES) {
|
||||
numTrans = 0;
|
||||
for (int i = 0; i < vertexCount - 2; i += 3) {
|
||||
if (outsideZ[i + 0] != 0 || outsideZ[i + 1] != 0 || outsideZ[i + 2] != 0) {
|
||||
@@ -518,70 +740,83 @@ void SoftwareTransform::ProjectClipAndExpand(int prim, int vertexCount, u32 vert
|
||||
indsOut += 3;
|
||||
numTrans += 3;
|
||||
}
|
||||
|
||||
inds = newInds;
|
||||
}
|
||||
} else if (throughmode && g_Config.bSmart2DTexFiltering && !gstate_c.textureIsVideo) {
|
||||
// We check some common cases for pixel mapping.
|
||||
// TODO: It's not really optimal that some previous step has removed the triangle strip.
|
||||
if (vertexCount <= 6 && prim == GE_PRIM_TRIANGLES) {
|
||||
// It's enough to check UV deltas vs pos deltas between vertex pairs:
|
||||
// 0-1 1-3 3-2 2-0. Maybe can even skip the last one. Probably some simple math can get us that sequence.
|
||||
// Unfortunately we need to reverse the previous UV scaling operation. Fortunately these are powers of two
|
||||
// so the operations are exact.
|
||||
bool pixelMapped = true;
|
||||
const u16 *indsIn = (const u16 *)inds;
|
||||
const float uscale = gstate_c.curTextureWidth;
|
||||
const float vscale = gstate_c.curTextureHeight;
|
||||
for (int t = 0; t < vertexCount; t += 3) {
|
||||
struct { int a; int b; } pairs[] = { {0, 1}, {1, 2}, {2, 0} };
|
||||
for (int i = 0; i < ARRAY_SIZE(pairs); i++) {
|
||||
int a = indsIn[t + pairs[i].a];
|
||||
int b = indsIn[t + pairs[i].b];
|
||||
float du = fabsf((transformed[a].u - transformed[b].u) * uscale);
|
||||
float dv = fabsf((transformed[a].v - transformed[b].v) * vscale);
|
||||
float dx = fabsf(transformed[a].x - transformed[b].x);
|
||||
float dy = fabsf(transformed[a].y - transformed[b].y);
|
||||
if (du != dx || dv != dy) {
|
||||
pixelMapped = false;
|
||||
|
||||
inds = newInds;
|
||||
|
||||
// Now that we're done culling and generating clipped vertices if needed (not yet implemented), we go ahead and project.
|
||||
ProjectVertices(transformed, numDecodedVerts);
|
||||
|
||||
#if 0
|
||||
// NOTE! This code is effectively obsolete now that we have implemented depth clamp in the fragment shader,
|
||||
// However, this can be an alternate partial solution for low-performance hardware in the future.
|
||||
|
||||
// Alright! Now, we can approximate Z-clamping, if the hardware lacks support for doing it for us.
|
||||
// Now, this can only be done exactly if all vertices in a triangle are beyond the far plane.
|
||||
// If not we technically need to cut it in two parts to clamp accurately.
|
||||
|
||||
// However, in most cases that matter (such as missing skies, etc), this is fine.
|
||||
// We could be aggressive and clamp every individual vertex, but this takes the safer (but not 100% safe) route and only clamps vertices
|
||||
// that are part of a triangle where all three are beyond the same plane.
|
||||
|
||||
const int maxZInt = gstate.getDepthRangeMax();
|
||||
// float maxZ = maxZInt / 65535.0f;
|
||||
const int minZInt = gstate.getDepthRangeMin();
|
||||
// float minZ = minZInt / 65535.0f;
|
||||
// We only need to clamp if minZ and maxZ aren't at the extreme in each direction, as otherwise
|
||||
// minZ and maxZ will cut things off.
|
||||
|
||||
if (gstate.isDepthClipEnabled() && (minZInt == 0 || maxZInt == 65535)) {
|
||||
for (int i = 0; i < numTrans - 2; i += 3) {
|
||||
TransformedVertex &v0 = transformed[newInds[i]];
|
||||
TransformedVertex &v1 = transformed[newInds[i + 1]];
|
||||
TransformedVertex &v2 = transformed[newInds[i + 2]];
|
||||
if (v0.x < 0.0f || v0.x > 4096.0f ||
|
||||
v1.x < 0.0f || v1.y > 4096.0f ||
|
||||
v2.x < 0.0f || v2.y > 4096.0f) {
|
||||
// If it's outside the viewport, we might as well skip the clamping, as it won't be visible anyway.
|
||||
// continue;
|
||||
}
|
||||
|
||||
if (minZInt == 0) {
|
||||
bool v0InFront = v0.z < 0.0f;
|
||||
bool v1InFront = v1.z < 0.0f;
|
||||
bool v2InFront = v2.z < 0.0f;
|
||||
if (v0InFront && v1InFront && v2InFront) {
|
||||
v0.z = 0.0f;
|
||||
v1.z = 0.0f;
|
||||
v2.z = 0.0f;
|
||||
}
|
||||
}
|
||||
if (!pixelMapped) {
|
||||
break;
|
||||
|
||||
if (maxZInt == 65535) {
|
||||
bool v0Beyond = v0.z >= 65535.0f;
|
||||
bool v1Beyond = v1.z >= 65535.0f;
|
||||
bool v2Beyond = v2.z >= 65535.0f;
|
||||
|
||||
if (v0Beyond && v1Beyond && v2Beyond) {
|
||||
v0.z = 65535.0f;
|
||||
v1.z = 65535.0f;
|
||||
v2.z = 65535.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
result->pixelMapped = pixelMapped;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
_dbg_assert_(false);
|
||||
}
|
||||
|
||||
if (gstate.isModeClear()) {
|
||||
gpuStats.perFrame.numClears++;
|
||||
}
|
||||
|
||||
result->action = SW_DRAW_INDEXED;
|
||||
result->drawNumTrans = numTrans;
|
||||
return SW_DRAW_INDEXED;
|
||||
}
|
||||
|
||||
void SoftwareTransform::CalcCullParams(float &minZValue, float &maxZValue) const {
|
||||
// The projected Z can be up to 0x3F8000FF, which is where this constant is from.
|
||||
// It seems like it may only maintain 15 mantissa bits (excluding implied.)
|
||||
maxZValue = 1.000030517578125f * gstate_c.vpDepthScale;
|
||||
minZValue = -maxZValue;
|
||||
// Scale and offset the Z appropriately, since we baked that into a projection transform.
|
||||
if (true) { // all backends are "use half z" now
|
||||
maxZValue = maxZValue * 0.5f + 0.5f + gstate_c.vpZOffset * 0.5f;
|
||||
minZValue = minZValue * 0.5f + 0.5f + gstate_c.vpZOffset * 0.5f;
|
||||
} else {
|
||||
maxZValue += gstate_c.vpZOffset;
|
||||
minZValue += gstate_c.vpZOffset;
|
||||
}
|
||||
// In case scale was negative, flip.
|
||||
if (minZValue > maxZValue)
|
||||
std::swap(minZValue, maxZValue);
|
||||
}
|
||||
|
||||
bool SoftwareTransform::ExpandRectangles(int vertexCount, int &numDecodedVerts, int vertsSize, u16 *&inds, int indsSize, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode, bool *pixelMappedExactly) const {
|
||||
bool SoftwareTransform::ExpandRectangles(int vertexCount, int &numDecodedVerts, int vertsSize, u16 *&inds, int indsSize, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode, bool *pixelMappedExactly) {
|
||||
// Before we start, do a sanity check - does the output fit?
|
||||
if ((vertexCount / 2) * 6 > indsSize) {
|
||||
// Won't fit, kill the draw.
|
||||
@@ -593,6 +828,7 @@ bool SoftwareTransform::ExpandRectangles(int vertexCount, int &numDecodedVerts,
|
||||
}
|
||||
|
||||
// Rectangles always need 2 vertices, disregard the last one if there's an odd number.
|
||||
|
||||
vertexCount = vertexCount & ~1;
|
||||
numTrans = 0;
|
||||
TransformedVertex *trans = &transformedExpanded[0];
|
||||
@@ -629,6 +865,14 @@ bool SoftwareTransform::ExpandRectangles(int vertexCount, int &numDecodedVerts,
|
||||
}
|
||||
}
|
||||
|
||||
float z = transVtxBR.z;
|
||||
// Apply Z clamping. It appears clipping does not affect rectangles, see #12058.
|
||||
if (z > 65535.0f) {
|
||||
z = 65535.0f;
|
||||
} else if (z < 0.0f) {
|
||||
z = 0.0f;
|
||||
}
|
||||
|
||||
// We have to turn the rectangle into two triangles, so 6 points.
|
||||
// This is 4 verts + 6 indices.
|
||||
|
||||
@@ -636,12 +880,14 @@ bool SoftwareTransform::ExpandRectangles(int vertexCount, int &numDecodedVerts,
|
||||
trans[0] = transVtxBR;
|
||||
trans[0].u = transVtxBR.u * uscale;
|
||||
trans[0].v = transVtxBR.v * vscale;
|
||||
trans[0].z = z;
|
||||
|
||||
// top right
|
||||
trans[1] = transVtxBR;
|
||||
trans[1].y = transVtxTL.y;
|
||||
trans[1].u = transVtxBR.u * uscale;
|
||||
trans[1].v = transVtxTL.v * vscale;
|
||||
trans[1].z = z;
|
||||
|
||||
// top left
|
||||
trans[2] = transVtxBR;
|
||||
@@ -649,12 +895,14 @@ bool SoftwareTransform::ExpandRectangles(int vertexCount, int &numDecodedVerts,
|
||||
trans[2].y = transVtxTL.y;
|
||||
trans[2].u = transVtxTL.u * uscale;
|
||||
trans[2].v = transVtxTL.v * vscale;
|
||||
trans[2].z = z;
|
||||
|
||||
// bottom left
|
||||
trans[3] = transVtxBR;
|
||||
trans[3].x = transVtxTL.x;
|
||||
trans[3].u = transVtxTL.u * uscale;
|
||||
trans[3].v = transVtxBR.v * vscale;
|
||||
trans[3].z = z;
|
||||
|
||||
// That's the four corners. Now process UV rotation.
|
||||
RotateUV(trans);
|
||||
@@ -730,10 +978,10 @@ bool SoftwareTransform::ExpandLines(int vertexCount, int &numDecodedVerts, int v
|
||||
u16 *newInds = inds + vertexCount;
|
||||
u16 *indsOut = newInds;
|
||||
|
||||
float dx = 1.0f * gstate_c.vpWidthScale * (1.0f / fabsf(gstate.getViewportXScale()));
|
||||
float dy = 1.0f * gstate_c.vpHeightScale * (1.0f / fabsf(gstate.getViewportYScale()));
|
||||
float du = 1.0f / gstate_c.curTextureWidth;
|
||||
float dv = 1.0f / gstate_c.curTextureHeight;
|
||||
float dx = 1.0f;
|
||||
float dy = 1.0f;
|
||||
float du = 1.0f;
|
||||
float dv = 1.0f;
|
||||
|
||||
if (throughmode) {
|
||||
dx = 1.0f;
|
||||
@@ -754,8 +1002,8 @@ bool SoftwareTransform::ExpandLines(int vertexCount, int &numDecodedVerts, int v
|
||||
const TransformedVertex &transVtx2 = transformed[indsIn[i + 1]];
|
||||
|
||||
// Okay, let's calculate the perpendicular.
|
||||
float horizontal = transVtx2.x * transVtx2.pos_w - transVtx1.x * transVtx1.pos_w;
|
||||
float vertical = transVtx2.y * transVtx2.pos_w - transVtx1.y * transVtx1.pos_w;
|
||||
float horizontal = transVtx2.x - transVtx1.x;
|
||||
float vertical = transVtx2.y - transVtx1.y;
|
||||
|
||||
Vec2f addWidth = Vec2f(-vertical, horizontal).Normalized();
|
||||
|
||||
@@ -763,13 +1011,13 @@ bool SoftwareTransform::ExpandLines(int vertexCount, int &numDecodedVerts, int v
|
||||
float yoff = addWidth.y * dy;
|
||||
|
||||
// bottom right
|
||||
trans[0].CopyFromWithOffset(transVtx2, xoff * transVtx2.pos_w, yoff * transVtx2.pos_w);
|
||||
trans[0].CopyFromWithOffset(transVtx2, xoff, yoff);
|
||||
// top right
|
||||
trans[1].CopyFromWithOffset(transVtx1, xoff * transVtx1.pos_w, yoff * transVtx1.pos_w);
|
||||
trans[1].CopyFromWithOffset(transVtx1, xoff, yoff);
|
||||
// top left
|
||||
trans[2].CopyFromWithOffset(transVtx1, -xoff * transVtx1.pos_w, -yoff * transVtx1.pos_w);
|
||||
trans[2].CopyFromWithOffset(transVtx1, -xoff, -yoff);
|
||||
// bottom left
|
||||
trans[3].CopyFromWithOffset(transVtx2, -xoff * transVtx2.pos_w, -yoff * transVtx2.pos_w);
|
||||
trans[3].CopyFromWithOffset(transVtx2, -xoff, -yoff);
|
||||
|
||||
// Triangle: BR-TR-TL
|
||||
indsOut[0] = i * 2 + 0;
|
||||
@@ -802,21 +1050,21 @@ bool SoftwareTransform::ExpandLines(int vertexCount, int &numDecodedVerts, int v
|
||||
const TransformedVertex &transVtxBL = (transVtxT.y != transVtxB.y || transVtxT.x > transVtxB.x) ? transVtxB : transVtxT;
|
||||
|
||||
// Okay, let's calculate the perpendicular.
|
||||
float horizontal = transVtxTL.x * transVtxTL.pos_w - transVtxBL.x * transVtxBL.pos_w;
|
||||
float vertical = transVtxTL.y * transVtxTL.pos_w - transVtxBL.y * transVtxBL.pos_w;
|
||||
float horizontal = transVtxTL.x - transVtxBL.x;
|
||||
float vertical = transVtxTL.y - transVtxBL.y;
|
||||
Vec2f addWidth = Vec2f(-vertical, horizontal).Normalized();
|
||||
|
||||
// bottom right
|
||||
trans[0] = transVtxBL;
|
||||
trans[0].x += addWidth.x * dx * trans[0].pos_w;
|
||||
trans[0].y += addWidth.y * dy * trans[0].pos_w;
|
||||
trans[0].x += addWidth.x * dx;
|
||||
trans[0].y += addWidth.y * dy;
|
||||
trans[0].u += addWidth.x * du * trans[0].uv_w;
|
||||
trans[0].v += addWidth.y * dv * trans[0].uv_w;
|
||||
|
||||
// top right
|
||||
trans[1] = transVtxTL;
|
||||
trans[1].x += addWidth.x * dx * trans[1].pos_w;
|
||||
trans[1].y += addWidth.y * dy * trans[1].pos_w;
|
||||
trans[1].x += addWidth.x * dx;
|
||||
trans[1].y += addWidth.y * dy;
|
||||
trans[1].u += addWidth.x * du * trans[1].uv_w;
|
||||
trans[1].v += addWidth.y * dv * trans[1].uv_w;
|
||||
|
||||
@@ -879,8 +1127,8 @@ bool SoftwareTransform::ExpandPoints(int vertexCount, int &maxIndex, int vertsSi
|
||||
|
||||
// Create the bottom right version.
|
||||
TransformedVertex transVtxBR = transVtxTL;
|
||||
transVtxBR.x += dx * transVtxTL.pos_w;
|
||||
transVtxBR.y += dy * transVtxTL.pos_w;
|
||||
transVtxBR.x += dx;
|
||||
transVtxBR.y += dy;
|
||||
transVtxBR.u += du * transVtxTL.uv_w;
|
||||
transVtxBR.v += dv * transVtxTL.uv_w;
|
||||
|
||||
@@ -1029,7 +1277,7 @@ u32 NormalizeVertices(SimpleVertex *sverts, u8 *bufPtr, const u8 *inPtr, int low
|
||||
}
|
||||
|
||||
// clip space to screen space
|
||||
Vec3f ClipToScreen(const Vec4f& coords) {
|
||||
static Vec3f ClipToScreen(const Vec4f& coords) {
|
||||
float xScale = gstate.getViewportXScale();
|
||||
float xCenter = gstate.getViewportXCenter();
|
||||
float yScale = gstate.getViewportYScale();
|
||||
|
||||
@@ -28,13 +28,12 @@ class FramebufferManagerCommon;
|
||||
class TextureCacheCommon;
|
||||
|
||||
enum SoftwareTransformAction {
|
||||
SW_NOT_READY,
|
||||
SW_DRAW_INDEXED,
|
||||
SW_CLEAR,
|
||||
SW_CULLED, // don't draw
|
||||
};
|
||||
|
||||
struct SoftwareTransformResult {
|
||||
SoftwareTransformAction action;
|
||||
u32 color;
|
||||
float depth;
|
||||
|
||||
@@ -68,23 +67,18 @@ void IndexBufferProvokingLastToFirst(int prim, u16 *inds, int indsSize);
|
||||
|
||||
class SoftwareTransform {
|
||||
public:
|
||||
SoftwareTransform(SoftwareTransformParams ¶ms) : params_(params) {}
|
||||
|
||||
void SetProjMatrix(const float mtx[14], bool invertedX, bool invertedY, const Lin::Vec3 &trans, const Lin::Vec3 &scale);
|
||||
void Transform(int prim, u32 vertexType, const DecVtxFormat &decVtxFormat, int &numDecodedVerts, int vertsSize, int vertexCount, u16 *&inds, int indsSize, SoftwareTransformResult *result);
|
||||
// indsSize is in indices, not bytes.
|
||||
// NOTE: In case of clipping, this might write extra vertices after params.transformed.
|
||||
static SoftwareTransformAction Transform(SoftwareTransformParams ¶ms, int prim, u32 vertexType, const DecVtxFormat &decVtxFormat, int &numDecodedVerts, int vertsSize, int vertexCount, u16 *&inds, int indsSize, SoftwareTransformResult *result);
|
||||
|
||||
protected:
|
||||
// NOTE: The viewport must be up to date!
|
||||
// indsSize is in indices, not bytes.
|
||||
void ProjectClipAndExpand(int prim, int vertexCount, u32 vertType, u16 *&inds, int indsSize, int &numDecodedVerts, int vertsSize, SoftwareTransformResult *result);
|
||||
static SoftwareTransformAction ProjectClipAndExpand(SoftwareTransformParams ¶ms, int prim, int vertexCount, u32 vertType, u16 *&inds, int indsSize, int &numDecodedVerts, int vertsSize, SoftwareTransformResult *result);
|
||||
|
||||
void CalcCullParams(float &minZValue, float &maxZValue) const;
|
||||
bool ExpandRectangles(int vertexCount, int &numDecodedVerts, int vertsSize, u16 *&inds, int indsSize, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode, bool *pixelMappedExactly) const;
|
||||
static void ProjectVertices(TransformedVertex *transformed, int vertexCount);
|
||||
static bool ExpandRectangles(int vertexCount, int &numDecodedVerts, int vertsSize, u16 *&inds, int indsSize, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode, bool *pixelMappedExactly);
|
||||
static bool ExpandLines(int vertexCount, int &numDecodedVerts, int vertsSize, u16 *&inds, int indsSize, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode) ;
|
||||
static bool ExpandPoints(int vertexCount, int &numDecodedVerts, int vertsSize, u16 *&inds, int indsSize, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int &numTrans, bool throughmode) ;
|
||||
|
||||
const SoftwareTransformParams ¶ms_;
|
||||
Lin::Matrix4x4 projMatrix_;
|
||||
};
|
||||
|
||||
class DrawEngineCommon;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -575,13 +575,16 @@ void DrawEngineCommon::SubmitCurve(const void *control_points, const void *indic
|
||||
|
||||
vertTypeID = GetVertTypeID(vertTypeWithIndex16, gstate.getUVGenMode(), applySkinInDecode_);
|
||||
int generatedBytesRead;
|
||||
if (output.count)
|
||||
DispatchSubmitPrim(output.vertices, output.indices, PatchPrimToPrim(surface.primType), output.count, vertTypeID, true, &generatedBytesRead);
|
||||
if (output.count) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,20 +131,29 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
bool highpFog = false;
|
||||
bool highpTexcoord = false;
|
||||
|
||||
const bool isModeThrough = id.Bit(VS_BIT_IS_THROUGH);
|
||||
const bool useHWTransform = id.Bit(VS_BIT_USE_HW_TRANSFORM);
|
||||
|
||||
const bool clipEnable = id.Bit(VS_BIT_CLIP_ENABLE) && !isModeThrough; // this is the PSP clip flag, which has some various consequences.
|
||||
const bool clipNearPlane = gstate_c.Use(GPU_USE_CLIP_DISTANCE) && useHWTransform;
|
||||
const bool clipMinMax = gstate_c.Use(GPU_USE_CLIP_DISTANCE) && !isModeThrough; // If clip planes are available, we want to use them for min/max. We skip the min/max culling in software transform (not yet implemented).
|
||||
|
||||
const bool rangeCulling = id.Bit(VS_BIT_VERTEX_RANGE_CULLING);
|
||||
const bool depthCullEnable = gstate_c.Use(GPU_USE_CULL_DISTANCE) && !isModeThrough && rangeCulling && useHWTransform; // Range culling is gated on draw type, we don't want to do this culling for splines apparently.
|
||||
|
||||
std::vector<const char*> extensions;
|
||||
extensions.reserve(6);
|
||||
if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) {
|
||||
if (gl_extensions.EXT_gpu_shader4) {
|
||||
extensions.push_back("#extension GL_EXT_gpu_shader4 : enable");
|
||||
}
|
||||
bool useClamp = gstate_c.Use(GPU_USE_DEPTH_CLAMP) && !id.Bit(VS_BIT_IS_THROUGH);
|
||||
if (gl_extensions.EXT_clip_cull_distance && (id.Bit(VS_BIT_VERTEX_RANGE_CULLING) || useClamp)) {
|
||||
if (gl_extensions.EXT_clip_cull_distance && (depthCullEnable || clipMinMax || clipNearPlane)) {
|
||||
extensions.push_back("#extension GL_EXT_clip_cull_distance : enable");
|
||||
}
|
||||
if (gl_extensions.APPLE_clip_distance && (id.Bit(VS_BIT_VERTEX_RANGE_CULLING) || useClamp)) {
|
||||
if (gl_extensions.APPLE_clip_distance && (clipMinMax || clipNearPlane)) {
|
||||
extensions.push_back("#extension GL_APPLE_clip_distance : enable");
|
||||
}
|
||||
if (gl_extensions.ARB_cull_distance && id.Bit(VS_BIT_VERTEX_RANGE_CULLING)) {
|
||||
if (gl_extensions.ARB_cull_distance && depthCullEnable) {
|
||||
extensions.push_back("#extension GL_ARB_cull_distance : enable");
|
||||
}
|
||||
}
|
||||
@@ -163,7 +172,6 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
|
||||
p.F("// %s\n", VertexShaderDesc(id).c_str());
|
||||
|
||||
bool isModeThrough = id.Bit(VS_BIT_IS_THROUGH);
|
||||
bool lmode = id.Bit(VS_BIT_LMODE);
|
||||
|
||||
GETexMapMode uvGenMode = static_cast<GETexMapMode>(id.Bits(VS_BIT_UVGEN_MODE, 2));
|
||||
@@ -175,9 +183,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
|
||||
bool flatBug = bugs.Has(Draw::Bugs::BROKEN_FLAT_IN_SHADER) && g_Config.bVendorBugChecksEnabled;
|
||||
bool needsZWHack = bugs.Has(Draw::Bugs::EQUAL_WZ_CORRUPTS_DEPTH) && g_Config.bVendorBugChecksEnabled;
|
||||
bool nanBug = bugs.Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL) && g_Config.bVendorBugChecksEnabled;
|
||||
|
||||
bool doFlatShading = id.Bit(VS_BIT_FLATSHADE) && !flatBug;
|
||||
|
||||
bool useHWTransform = id.Bit(VS_BIT_USE_HW_TRANSFORM);
|
||||
bool hasColor = id.Bit(VS_BIT_HAS_COLOR) || !useHWTransform;
|
||||
bool hasNormal = id.Bit(VS_BIT_HAS_NORMAL) && useHWTransform;
|
||||
bool hasTexcoord = id.Bit(VS_BIT_HAS_TEXCOORD) || !useHWTransform;
|
||||
@@ -213,6 +222,11 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
bool hasNormalTess = id.Bit(VS_BIT_HAS_NORMAL_TESS);
|
||||
bool flipNormalTess = id.Bit(VS_BIT_NORM_REVERSE_TESS);
|
||||
|
||||
// Should we do the min/max discard in the shader and/or or use depth clamping?
|
||||
// In both cases we need to just forward
|
||||
const bool fsMinmaxDiscard = id.Bit(VS_BIT_FS_MINMAX_DISCARD);
|
||||
const bool fsDepthClamp = id.Bit(VS_BIT_FS_DEPTH_CLAMP);
|
||||
|
||||
const char *shading = "";
|
||||
if (compat.glslES30 || compat.shaderLanguage == GLSL_VULKAN)
|
||||
shading = doFlatShading ? "flat " : "";
|
||||
@@ -236,10 +250,12 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
}
|
||||
bool texCoordInVec3 = false;
|
||||
|
||||
bool vertexRangeCulling = id.Bit(VS_BIT_VERTEX_RANGE_CULLING) && !isModeThrough;
|
||||
bool clipClampedDepth = !isModeThrough && gstate_c.Use(GPU_USE_DEPTH_CLAMP) && gstate_c.Use(GPU_USE_CLIP_DISTANCE);
|
||||
const char *clipClampedDepthSuffix = "[0]";
|
||||
const char *vertexRangeClipSuffix = clipClampedDepth ? "[1]" : "[0]";
|
||||
const char *minZClipPlaneSuffix = "[0]";
|
||||
const char *maxZClipPlaneSuffix = "[1]";
|
||||
const char *zClipPlaneSuffix = "[2]";
|
||||
|
||||
const char *cullDistanceNearSuffix = "[0]";
|
||||
const char *cullDistanceFarSuffix = "[1]";
|
||||
|
||||
if (compat.shaderLanguage == GLSL_VULKAN) {
|
||||
WRITE(p, "\n");
|
||||
@@ -287,6 +303,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
|
||||
WRITE(p, "layout (location = 3) out highp float v_fogdepth;\n");
|
||||
|
||||
if (fsMinmaxDiscard || fsDepthClamp) {
|
||||
WRITE(p, "layout (location = 4) out highp vec2 v_zw;\n");
|
||||
}
|
||||
|
||||
WRITE(p, "invariant gl_Position;\n");
|
||||
} else if (compat.shaderLanguage == HLSL_D3D11) {
|
||||
// Note: These two share some code after this hellishly large if/else.
|
||||
@@ -345,21 +365,23 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
}
|
||||
|
||||
WRITE(p, " float v_fogdepth : TEXCOORD1;\n");
|
||||
{
|
||||
WRITE(p, " vec4 gl_Position : SV_Position;\n");
|
||||
bool clipRange = vertexRangeCulling && gstate_c.Use(GPU_USE_CLIP_DISTANCE);
|
||||
if (clipClampedDepth && clipRange) {
|
||||
WRITE(p, " float2 gl_ClipDistance : SV_ClipDistance;\n");
|
||||
clipClampedDepthSuffix = ".x";
|
||||
vertexRangeClipSuffix = ".y";
|
||||
} else if (clipClampedDepth || clipRange) {
|
||||
WRITE(p, " float gl_ClipDistance : SV_ClipDistance;\n");
|
||||
clipClampedDepthSuffix = "";
|
||||
vertexRangeClipSuffix = "";
|
||||
}
|
||||
if (vertexRangeCulling && gstate_c.Use(GPU_USE_CULL_DISTANCE)) {
|
||||
WRITE(p, " float2 gl_CullDistance : SV_CullDistance0;\n");
|
||||
}
|
||||
if (fsMinmaxDiscard || fsDepthClamp) {
|
||||
WRITE(p, " vec2 v_zw : TEXCOORD2;\n");
|
||||
}
|
||||
// gl_Position must be last for D3D11.
|
||||
WRITE(p, " vec4 gl_Position : SV_Position;\n");
|
||||
minZClipPlaneSuffix = ".x";
|
||||
maxZClipPlaneSuffix = ".y";
|
||||
zClipPlaneSuffix = ".z";
|
||||
if (clipMinMax && clipNearPlane) {
|
||||
WRITE(p, " float3 gl_ClipDistance : SV_ClipDistance;\n");
|
||||
} else if (clipMinMax) {
|
||||
WRITE(p, " float2 gl_ClipDistance : SV_ClipDistance;\n");
|
||||
} else if (clipNearPlane) {
|
||||
_dbg_assert_(false); // not an allowed combination
|
||||
}
|
||||
if (depthCullEnable) {
|
||||
WRITE(p, " float2 gl_CullDistance : SV_CullDistance0;\n");
|
||||
}
|
||||
WRITE(p, "};\n");
|
||||
} else {
|
||||
@@ -378,7 +400,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
if (useHWTransform)
|
||||
WRITE(p, "%s vec3 position;\n", compat.attribute);
|
||||
else
|
||||
WRITE(p, "%s vec4 position;\n", compat.attribute); // need to pass the fog coord in w
|
||||
WRITE(p, "%s vec4 position;\n", compat.attribute); // XYZW clip space coordinate.
|
||||
*attrMask |= 1 << ATTR_POSITION;
|
||||
|
||||
if (useHWTransform && hasNormal) {
|
||||
@@ -409,20 +431,27 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
}
|
||||
}
|
||||
|
||||
if (isModeThrough) {
|
||||
WRITE(p, "uniform vec4 u_xywh;\n");
|
||||
*uniformMask |= DIRTY_PROJTHROUGHMATRIX;
|
||||
} else if (useHWTransform) {
|
||||
WRITE(p, "uniform vec4 u_xywh;\n");
|
||||
WRITE(p, "uniform float u_NaN;\n");
|
||||
*uniformMask |= DIRTY_PROJTHROUGHMATRIX;
|
||||
|
||||
WRITE(p, "uniform vec2 u_minZmaxZ;\n");
|
||||
*uniformMask |= DIRTY_RASTER_OFFSET; // this flag is shared with raster offset.
|
||||
|
||||
if (!isModeThrough) {
|
||||
WRITE(p, "uniform vec2 u_rasterOffset;\n");
|
||||
*uniformMask |= DIRTY_RASTER_OFFSET;
|
||||
}
|
||||
|
||||
if (useHWTransform) {
|
||||
WRITE(p, "uniform vec3 u_vpScale;\n");
|
||||
WRITE(p, "uniform vec3 u_vpOffset;\n");
|
||||
if (gstate_c.Use(GPU_USE_VIRTUAL_REALITY)) {
|
||||
WRITE(p, "uniform mat4 u_proj_lens;\n");
|
||||
}
|
||||
WRITE(p, "uniform mat4 u_proj;\n");
|
||||
*uniformMask |= DIRTY_PROJMATRIX;
|
||||
}
|
||||
|
||||
if (useHWTransform) {
|
||||
// When transforming by hardware, we need a great deal more uniforms...
|
||||
// TODO: Use 4x3 matrices where possible. Though probably doesn't matter much.
|
||||
*uniformMask |= DIRTY_VIEWPORT_UNIFORMS | DIRTY_PROJMATRIX;
|
||||
// TODO: Use 4x3 matrices where possible (world and view and maybe tex, not proj).
|
||||
WRITE(p, "uniform mat4 u_world;\n");
|
||||
WRITE(p, "uniform mat4 u_view;\n");
|
||||
*uniformMask |= DIRTY_WORLDMATRIX | DIRTY_VIEWMATRIX;
|
||||
@@ -494,13 +523,6 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
WRITE(p, "uniform highp vec2 u_fogcoef;\n");
|
||||
*uniformMask |= DIRTY_FOGCOEF;
|
||||
|
||||
if (!isModeThrough) {
|
||||
WRITE(p, "uniform highp vec4 u_depthRange;\n");
|
||||
WRITE(p, "uniform highp vec4 u_cullRangeMin;\n");
|
||||
WRITE(p, "uniform highp vec4 u_cullRangeMax;\n");
|
||||
*uniformMask |= DIRTY_DEPTHRANGE | DIRTY_CULLRANGE;
|
||||
}
|
||||
|
||||
WRITE(p, "%s%s lowp vec4 v_color0;\n", shading, compat.varying_vs);
|
||||
if (lmode) {
|
||||
WRITE(p, "%s%s lowp vec3 v_color1;\n", shading, compat.varying_vs);
|
||||
@@ -514,18 +536,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
} else {
|
||||
WRITE(p, "%s mediump float v_fogdepth;\n", compat.varying_vs);
|
||||
}
|
||||
}
|
||||
|
||||
// See comment above this function (GenerateVertexShader).
|
||||
if (!isModeThrough && gstate_c.Use(GPU_ROUND_DEPTH_TO_16BIT)) {
|
||||
// Apply the projection and viewport to get the Z buffer value, floor to integer, undo the viewport and projection.
|
||||
WRITE(p, "\nvec4 depthRoundZVP(vec4 v) {\n");
|
||||
WRITE(p, " float z = v.z / v.w;\n");
|
||||
WRITE(p, " z = z * u_depthRange.x + u_depthRange.y;\n");
|
||||
WRITE(p, " z = floor(z);\n");
|
||||
WRITE(p, " z = (z - u_depthRange.y) / u_depthRange.x;\n");
|
||||
WRITE(p, " return vec4(v.x, v.y, z * v.w, v.w);\n");
|
||||
WRITE(p, "}\n\n");
|
||||
if (fsMinmaxDiscard || fsDepthClamp) {
|
||||
WRITE(p, "%s highp vec2 v_zw;\n", compat.varying_vs);
|
||||
}
|
||||
}
|
||||
|
||||
// Hardware tessellation
|
||||
@@ -725,6 +739,8 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
}
|
||||
}
|
||||
|
||||
WRITE(p, " bool zClipped = false;\n");
|
||||
|
||||
if (!useHWTransform) {
|
||||
// Simple pass-through of vertex data to fragment shader
|
||||
if (texCoordInVec3) {
|
||||
@@ -745,20 +761,15 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
}
|
||||
WRITE(p, " %sv_fogdepth = fog;\n", compat.vsOutPrefix);
|
||||
if (isModeThrough) {
|
||||
// The proj_through matrix already has the rotation, if needed.
|
||||
// NOTE: In through mode, we can ignore W, it's always 1.0. However,
|
||||
// this transform will later be applied in all modes.
|
||||
WRITE(p, " vec4 outPos;\n");
|
||||
WRITE(p, " outPos.xy = ((position.xy - u_xywh.xy * position.w) / u_xywh.zw) * 2.0 - 1.0;\n");
|
||||
WRITE(p, " outPos.zw = position.zw;\n");
|
||||
// WRITE(p, " vec4 outPos = mul(u_proj_through, vec4(position.xyz, 1.0));\n");
|
||||
WRITE(p, " vec4 outPos = position;\n");
|
||||
WRITE(p, " outPos.z *= 65536.0;\n"); // TODO: This multiplication should be moved to the vertex decoders for through mode.
|
||||
} else {
|
||||
// The viewport is used in this case, so need to compensate for that.
|
||||
if (gstate_c.Use(GPU_ROUND_DEPTH_TO_16BIT)) {
|
||||
WRITE(p, " vec4 outPos = depthRoundZVP(position);\n");
|
||||
} else {
|
||||
WRITE(p, " vec4 outPos = position;\n");
|
||||
}
|
||||
// The viewport has already been applied here, along with the division.
|
||||
WRITE(p, " vec4 outPos = position;\n");
|
||||
}
|
||||
|
||||
if (fsMinmaxDiscard || fsDepthClamp) {
|
||||
WRITE(p, " %sv_zw = vec2(outPos.z * outPos.w, outPos.w);\n", compat.vsOutPrefix);
|
||||
}
|
||||
} else {
|
||||
// Step 1: World Transform / Skinning
|
||||
@@ -837,20 +848,39 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
}
|
||||
|
||||
// Final view and projection transforms.
|
||||
if (gstate_c.Use(GPU_ROUND_DEPTH_TO_16BIT)) {
|
||||
if (gstate_c.Use(GPU_USE_VIRTUAL_REALITY)) {
|
||||
WRITE(p, " vec4 outPos = depthRoundZVP(mul(u_proj_lens, viewPos));\n");
|
||||
WRITE(p, " vec4 orgPos = depthRoundZVP(mul(u_proj, viewPos));\n");
|
||||
} else {
|
||||
WRITE(p, " vec4 outPos = depthRoundZVP(mul(u_proj, viewPos));\n");
|
||||
}
|
||||
if (gstate_c.Use(GPU_USE_VIRTUAL_REALITY)) {
|
||||
WRITE(p, " vec4 outPos = mul(u_proj_lens, viewPos);\n");
|
||||
WRITE(p, " vec4 orgPos = mul(u_proj, viewPos);\n");
|
||||
} else {
|
||||
if (gstate_c.Use(GPU_USE_VIRTUAL_REALITY)) {
|
||||
WRITE(p, " vec4 outPos = mul(u_proj_lens, viewPos);\n");
|
||||
WRITE(p, " vec4 orgPos = mul(u_proj, viewPos);\n");
|
||||
} else {
|
||||
WRITE(p, " vec4 outPos = mul(u_proj, viewPos);\n");
|
||||
}
|
||||
WRITE(p, " vec4 outPos = mul(u_proj, viewPos);\n");
|
||||
}
|
||||
|
||||
// We're in clip space, so here we check for clipping. We check if the actual hardware will clip.
|
||||
// If so, we skip the "range culling" (x and y out-of-bounds checks) since they wouldn't have happened, most likely.
|
||||
// NOTE: There are some games that depend on clipping already having been done when checking the range culling.
|
||||
// We can't handle that here, we use the software transform pipeline for that.
|
||||
WRITE(p, " if (outPos.z < -outPos.w) {\n");
|
||||
WRITE(p, " zClipped = true;\n");
|
||||
WRITE(p, " }\n");
|
||||
// Then we actually add the clip plane.
|
||||
if (clipNearPlane) {
|
||||
WRITE(p, " %sgl_ClipDistance%s = outPos.z + outPos.w;\n", compat.vsOutPrefix, zClipPlaneSuffix);
|
||||
}
|
||||
|
||||
if (depthCullEnable) {
|
||||
// Before the viewport, discard any primitives that are fully outside the clipping volume in Z.
|
||||
// NOTE: We add a small offset to the clip distance to allow hex 0x3F8000XX (up to 1.0000304) where XX are arbitrary.
|
||||
WRITE(p, " %sgl_CullDistance%s = outPos.z + outPos.w + 0.0000304 / outPos.w;\n", compat.vsOutPrefix, cullDistanceNearSuffix);
|
||||
WRITE(p, " %sgl_CullDistance%s = outPos.w - outPos.z + 0.0000304 / outPos.w;\n", compat.vsOutPrefix, cullDistanceFarSuffix);
|
||||
}
|
||||
|
||||
// Perform the perspective projection and viewport transform. (We'll have to undo the division before passing the coordinate along).
|
||||
// In software transform mode, this is performed in on the CPU.
|
||||
WRITE(p, " float recip = 1.0 / outPos.w;\n");
|
||||
WRITE(p, " outPos.xyz = outPos.xyz * u_vpScale.xyz * recip + u_vpOffset.xyz;\n");
|
||||
|
||||
if (fsMinmaxDiscard || fsDepthClamp) {
|
||||
WRITE(p, " %sv_zw = vec2(outPos.z * outPos.w, outPos.w);\n", compat.vsOutPrefix);
|
||||
}
|
||||
|
||||
// TODO: Declare variables for dots for shade mapping if needed.
|
||||
@@ -1208,67 +1238,50 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
WRITE(p, " %sv_fogdepth = (viewPos.z + u_fogcoef.x) * u_fogcoef.y;\n", compat.vsOutPrefix);
|
||||
}
|
||||
|
||||
if (clipClampedDepth) {
|
||||
// This should clip against minz, but only when it's above zero.
|
||||
if (ShaderLanguageIsOpenGL(compat.shaderLanguage)) {
|
||||
// On OpenGL/GLES, these values account for the -1 -> 1 range.
|
||||
WRITE(p, " if (u_depthRange.y - u_depthRange.x >= 1.0) {\n");
|
||||
WRITE(p, " %sgl_ClipDistance%s = outPos.w + outPos.z;\n", compat.vsOutPrefix, clipClampedDepthSuffix);
|
||||
} else {
|
||||
// Everywhere else, it's 0 -> 1, simpler.
|
||||
WRITE(p, " if (u_depthRange.y >= 1.0) {\n");
|
||||
WRITE(p, " %sgl_ClipDistance%s = outPos.z;\n", compat.vsOutPrefix, clipClampedDepthSuffix);
|
||||
if (!isModeThrough) {
|
||||
// Cull against X and Y limits (unless the GPU has a certain driver bug).
|
||||
// It's not clear what the limits should be in through mode though, although I'm sure they exist.
|
||||
if (!nanBug && rangeCulling) {
|
||||
WRITE(p, " if (!zClipped && (outPos.x < 0.0 || outPos.y < 0.0 || outPos.x >= 4096.0 || outPos.y >= 4096.0 || outPos.w < -1.0)) {\n");
|
||||
// Discard the whole triangle by setting one vertex to NaN.
|
||||
WRITE(p, " outPos = vec4(u_NaN, u_NaN, u_NaN, u_NaN);\n");
|
||||
// TODO: We could just return here. But we can also just keep going to avoid branching, the NaNs will propagate.
|
||||
WRITE(p, " }\n");
|
||||
}
|
||||
// This is similar, but for maxz when it's below 65535.0. -1/0 don't matter here.
|
||||
WRITE(p, " } else if (u_depthRange.x + u_depthRange.y <= 65534.0) {\n");
|
||||
WRITE(p, " %sgl_ClipDistance%s = outPos.w - outPos.z;\n", compat.vsOutPrefix, clipClampedDepthSuffix);
|
||||
WRITE(p, " } else {\n");
|
||||
WRITE(p, " %sgl_ClipDistance%s = 0.0;\n", compat.vsOutPrefix, clipClampedDepthSuffix);
|
||||
WRITE(p, " }\n");
|
||||
|
||||
// Apply raster offset after the range culling.
|
||||
WRITE(p, " outPos.xy -= u_rasterOffset.xy;\n");
|
||||
}
|
||||
|
||||
if (vertexRangeCulling) {
|
||||
WRITE(p, " vec3 projPos = outPos.xyz / outPos.w;\n");
|
||||
WRITE(p, " float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n");
|
||||
// I think we should use min/max clipping for through-mode as well, right?
|
||||
if (clipMinMax) {
|
||||
// We use clipping, where available, to implement min/max Z.
|
||||
// 1.0 is used to disable the clip plane (should we generate more shaders instead? how costly are they?)
|
||||
|
||||
if (!bugs.Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) {
|
||||
// Vertex range culling always happens, even when Z clips - but since we let the host GPU clip
|
||||
// here afterwards, and never actually perform the clipping, we have to disable range culling here if we
|
||||
// detect clipping, otherwise we're checking unclipped coordinates.
|
||||
WRITE(p, " if (u_cullRangeMin.w <= 0.0 || projZ * outPos.w > -outPos.w) {\n");
|
||||
const char *outMin = "projPos.x < u_cullRangeMin.x || projPos.y < u_cullRangeMin.y";
|
||||
const char *outMax = "projPos.x > u_cullRangeMax.x || projPos.y > u_cullRangeMax.y";
|
||||
WRITE(p, " if ((%s) || (%s)) {\n", outMin, outMax);
|
||||
WRITE(p, " outPos.xyzw = u_cullRangeMax.wwww;\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " if (u_cullRangeMin.w <= 0.0) {\n");
|
||||
WRITE(p, " if (projPos.z < u_cullRangeMin.z || projPos.z > u_cullRangeMax.z) {\n");
|
||||
WRITE(p, " outPos.xyzw = u_cullRangeMax.wwww;\n");
|
||||
WRITE(p, " }\n");
|
||||
WRITE(p, " }\n");
|
||||
}
|
||||
// Note: outPos.z need to be multiplied by outPos.w to undo the division, which shouldn't be in effect here.
|
||||
// We should probably store the undivided outPos in a variable.
|
||||
|
||||
const char *cull0 = compat.shaderLanguage == HLSL_D3D11 ? ".x" : "[0]";
|
||||
const char *cull1 = compat.shaderLanguage == HLSL_D3D11 ? ".y" : "[1]";
|
||||
if (gstate_c.Use(GPU_USE_CLIP_DISTANCE)) {
|
||||
// TODO: Ignore triangles from GE_PRIM_RECTANGLES in transform mode, which should not clip to neg z.
|
||||
// We add a small amount to prevent error as in #15816 (PSP Z is only 16-bit fixed point, anyway.)
|
||||
WRITE(p, " %sgl_ClipDistance%s = projZ * outPos.w + outPos.w + %f;\n", compat.vsOutPrefix, vertexRangeClipSuffix, 0.0625 / 65536.0);
|
||||
}
|
||||
if (gstate_c.Use(GPU_USE_CULL_DISTANCE)) {
|
||||
// Cull any triangle fully outside in the same direction when depth clamp enabled.
|
||||
// We check u_depthRange in case depthScale was zero - in that case we can't work out the cull distance.
|
||||
WRITE(p, " if (u_cullRangeMin.w > 0.0 && u_depthRange.w != 0.0f) {\n");
|
||||
WRITE(p, " %sgl_CullDistance%s = projPos.z - u_cullRangeMin.z;\n", compat.vsOutPrefix, cull0);
|
||||
WRITE(p, " %sgl_CullDistance%s = u_cullRangeMax.z - projPos.z;\n", compat.vsOutPrefix, cull1);
|
||||
WRITE(p, " } else {\n");
|
||||
WRITE(p, " %sgl_CullDistance%s = 0.0;\n", compat.vsOutPrefix, cull0);
|
||||
WRITE(p, " %sgl_CullDistance%s = 0.0;\n", compat.vsOutPrefix, cull1);
|
||||
WRITE(p, " }\n");
|
||||
}
|
||||
// We round to nearest 15-bit value for the check - this seems to match some of [Unknown]'s test, and PSP GPU floats
|
||||
// often have a 15-bit mantissa.
|
||||
WRITE(p, " float clipZ = floor(outPos.z * 0.5 + 0.5) * 2.0;\n");
|
||||
|
||||
WRITE(p, " %sgl_ClipDistance%s = u_minZmaxZ.x > 0.0 ? (clipZ - u_minZmaxZ.x) * outPos.w : 1.0;\n", compat.vsOutPrefix, minZClipPlaneSuffix);
|
||||
WRITE(p, " %sgl_ClipDistance%s = u_minZmaxZ.y < 65535.0 ? (u_minZmaxZ.y - clipZ) * outPos.w : 1.0;\n", compat.vsOutPrefix, maxZClipPlaneSuffix);
|
||||
}
|
||||
|
||||
// Convert to NDC space, using the framebuffer offset and size stored in u_xywh.
|
||||
WRITE(p, " outPos.xy = ((outPos.xy - u_xywh.xy) / u_xywh.zw) * 2.0 - 1.0;\n");
|
||||
|
||||
if (gstate_c.Use(GPU_ROUND_DEPTH_TO_16BIT)) {
|
||||
WRITE(p, " outPos.z = float(int(outPos.z));\n");
|
||||
}
|
||||
|
||||
WRITE(p, " outPos.z = outPos.z / 65536.0;\n"); // Or 65536?
|
||||
|
||||
// Convert back to clip space coordinates. This is needed for all modern shader models.
|
||||
// After all our work in projected space, multiply xyz back with z to the get clip space position that the shader model wants.
|
||||
WRITE(p, " outPos.xyz *= outPos.w;\n");
|
||||
|
||||
if (compat.shaderLanguage == GLSL_VULKAN && gstate_c.Use(GPU_USE_PRE_ROTATION)) {
|
||||
// Apply rotation from the uniform.
|
||||
WRITE(p, " mat2 displayRotation = mat2(\n");
|
||||
@@ -1301,6 +1314,12 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
|
||||
WRITE(p, " %sgl_Position.y *= u_scaleY;\n", compat.vsOutPrefix);
|
||||
}
|
||||
|
||||
if (fsDepthClamp) {
|
||||
// Overwrite Z with a value that will not be clipped.
|
||||
// Then we will overwrite the Z in the fragment shader with the per-pixel value computed from the interpolated v_zw.
|
||||
WRITE(p, " %sgl_Position.z = (u_minZmaxZ.x + u_minZmaxZ.y) * 0.5 * (1.0 / 65536.0);\n", compat.vsOutPrefix, compat.vsOutPrefix);
|
||||
}
|
||||
|
||||
if (compat.depthMinusOneToOne) {
|
||||
// Convert from 0->1 to -1->1 depth range.
|
||||
WRITE(p, " %sgl_Position.z = %sgl_Position.z * 2.0 - %sgl_Position.w;\n", compat.vsOutPrefix, compat.vsOutPrefix, compat.vsOutPrefix);
|
||||
|
||||
@@ -294,6 +294,23 @@ 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);
|
||||
if (clipInfoFlags_ & ClipInfoFlags::Valid) {
|
||||
if (clipInfoFlags_ & ClipInfoFlags::SoftClipCull) {
|
||||
useHWTransform = false;
|
||||
}
|
||||
}
|
||||
if (clipInfoFlags_ != lastClipInfoFlags_) {
|
||||
ClipInfoFlags changed = (ClipInfoFlags)((u32)clipInfoFlags_ ^ (u32)lastClipInfoFlags_);
|
||||
if (changed & (ClipInfoFlags::DepthClampFragment | ClipInfoFlags::MinMaxZDiscard)) {
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE);
|
||||
}
|
||||
lastClipInfoFlags_ = clipInfoFlags_;
|
||||
}
|
||||
|
||||
if (useHWTransform != lastUseHwTransform_) {
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE);
|
||||
lastUseHwTransform_ = useHWTransform;
|
||||
}
|
||||
|
||||
if (useHWTransform) {
|
||||
ID3D11Buffer *vb_ = nullptr;
|
||||
@@ -327,7 +344,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);
|
||||
@@ -404,18 +421,6 @@ void DrawEngineD3D11::Flush() {
|
||||
IndexBufferProvokingLastToFirst(prim, inds, vertexCount);
|
||||
}
|
||||
|
||||
// We need correct viewport values in gstate_c already.
|
||||
if (gstate_c.IsDirty(DIRTY_VIEWPORTSCISSOR_STATE)) {
|
||||
ViewportAndScissor vpAndScissor;
|
||||
ConvertViewportAndScissor(
|
||||
framebufferManager_->GetDisplayLayoutConfigCopy(),
|
||||
framebufferManager_->UseBufferedRendering(),
|
||||
framebufferManager_->GetRenderWidth(), framebufferManager_->GetRenderHeight(),
|
||||
framebufferManager_->GetTargetBufferWidth(), framebufferManager_->GetTargetBufferHeight(),
|
||||
vpAndScissor);
|
||||
UpdateCachedViewportState(vpAndScissor);
|
||||
}
|
||||
|
||||
// At this point, rect and line primitives are still preserved as such. So, it's the best time to do software depth raster.
|
||||
// We could piggyback on the viewport transform below, but it gets complicated since it's different per-backend. Which we really
|
||||
// should clean up one day...
|
||||
@@ -434,13 +439,7 @@ void DrawEngineD3D11::Flush() {
|
||||
params.allowClear = true;
|
||||
params.allowSeparateAlphaClear = false; // D3D11 doesn't support separate alpha clears
|
||||
|
||||
SoftwareTransform swTransform(params);
|
||||
|
||||
const Lin::Vec3 trans(gstate_c.vpXOffset, gstate_c.vpYOffset, gstate_c.vpZOffset * 0.5f + 0.5f);
|
||||
const Lin::Vec3 scale(gstate_c.vpWidthScale, gstate_c.vpHeightScale, gstate_c.vpDepthScale * 0.5f);
|
||||
swTransform.SetProjMatrix(gstate.projMatrix, gstate_c.vpWidth < 0, gstate_c.vpHeight < 0, trans, scale);
|
||||
|
||||
swTransform.Transform(prim, swDec->VertexType(), swDec->GetDecVtxFmt(), numDecodedVerts_, VERTEX_BUFFER_MAX, vertexCount, inds, RemainingIndices(inds), &result);
|
||||
const SoftwareTransformAction action = SoftwareTransform::Transform(params, prim, swDec->VertexType(), swDec->GetDecVtxFmt(), numDecodedVerts_, VERTEX_BUFFER_MAX, vertexCount, inds, RemainingIndices(inds), &result);
|
||||
|
||||
if (result.setSafeSize)
|
||||
framebufferManager_->SetSafeSize(result.safeWidth, result.safeHeight);
|
||||
@@ -451,14 +450,15 @@ void DrawEngineD3D11::Flush() {
|
||||
textureCache_->ApplyTexture();
|
||||
gstate_c.pixelMapped = false;
|
||||
}
|
||||
|
||||
// Need to ApplyDrawState after ApplyTexture because depal can launch a render pass and that wrecks the state.
|
||||
ApplyDrawState(prim);
|
||||
ApplyDrawStateLate(result.setStencil, result.stencilValue);
|
||||
|
||||
if (result.action == SW_DRAW_INDEXED) {
|
||||
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());
|
||||
@@ -490,7 +490,7 @@ void DrawEngineD3D11::Flush() {
|
||||
pushInds_->EndPush(context_);
|
||||
context_->IASetIndexBuffer(pushInds_->Buf(), DXGI_FORMAT_R16_UINT, iOffset);
|
||||
context_->DrawIndexed(result.drawNumTrans, 0, 0);
|
||||
} else if (result.action == SW_CLEAR) {
|
||||
} else if (action == SW_CLEAR) {
|
||||
u32 clearColor = result.color;
|
||||
float clearDepth = result.depth;
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/CommonWindows.h"
|
||||
|
||||
#include <d3d11.h>
|
||||
#include <d3d11_1.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
@@ -82,10 +82,6 @@ GPU_D3D11::~GPU_D3D11() {
|
||||
u32 GPU_D3D11::CheckGPUFeatures() const {
|
||||
u32 features = GPUCommonHW::CheckGPUFeatures();
|
||||
|
||||
// Accurate depth is required because the Direct3D API does not support inverse Z.
|
||||
// So we cannot incorrectly use the viewport transform as the depth range on Direct3D.
|
||||
features |= GPU_USE_ACCURATE_DEPTH;
|
||||
|
||||
features |= GPU_USE_TEXTURE_FLOAT;
|
||||
features |= GPU_USE_INSTANCE_RENDERING;
|
||||
features |= GPU_USE_TEXTURE_LOD_CONTROL;
|
||||
@@ -123,7 +119,6 @@ void GPU_D3D11::BeginHostFrame(const DisplayLayoutConfig &config) {
|
||||
shaderManager_->DirtyLastShader();
|
||||
|
||||
framebufferManager_->BeginFrame(config);
|
||||
gstate_c.Dirty(DIRTY_PROJTHROUGHMATRIX);
|
||||
|
||||
if (gstate_c.useFlagsChanged) {
|
||||
// TODO: It'd be better to recompile them in the background, probably?
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonWindows.h"
|
||||
#include <d3d11.h>
|
||||
|
||||
#include "GPU/GPUCommonHW.h"
|
||||
|
||||
@@ -157,7 +157,7 @@ uint64_t ShaderManagerD3D11::UpdateUniforms(bool useBufferedRendering) {
|
||||
if (dirty != 0) {
|
||||
D3D11_MAPPED_SUBRESOURCE map;
|
||||
if (dirty & DIRTY_BASE_UNIFORMS) {
|
||||
BaseUpdateUniforms(&ub_base, dirty, true, useBufferedRendering);
|
||||
BaseUpdateUniforms(&ub_base, dirty, useBufferedRendering);
|
||||
context_->Map(push_base.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &map);
|
||||
memcpy(map.pData, &ub_base, sizeof(ub_base));
|
||||
context_->Unmap(push_base.Get(), 0);
|
||||
@@ -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());
|
||||
ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), clipInfoFlags);
|
||||
} else {
|
||||
FSID = lastFSID_;
|
||||
}
|
||||
@@ -222,6 +222,7 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertexType, D3D11VertexShader
|
||||
VertexShaderFlags flags;
|
||||
GenerateVertexShader(VSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &attrMask, &uniformMask, &flags, &genErrorString);
|
||||
_assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "VS length error: %d", (int)strlen(codeBuffer_));
|
||||
// OutputDebugStringA(codeBuffer_);
|
||||
vs = new D3D11VertexShader(device_, featureLevel_, VSID, codeBuffer_, useHWTransform);
|
||||
vsCache_[VSID] = vs;
|
||||
} else {
|
||||
@@ -238,6 +239,7 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertexType, D3D11VertexShader
|
||||
FragmentShaderFlags flags;
|
||||
GenerateFragmentShader(FSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &uniformMask, &flags, &genErrorString);
|
||||
_assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "FS length error: %d", (int)strlen(codeBuffer_));
|
||||
// OutputDebugStringA(codeBuffer_);
|
||||
fs = new D3D11FragmentShader(device_, featureLevel_, FSID, codeBuffer_, useHWTransform);
|
||||
fsCache_[FSID] = fs;
|
||||
} else {
|
||||
@@ -246,6 +248,11 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertexType, D3D11VertexShader
|
||||
|
||||
lastFSID_ = FSID;
|
||||
|
||||
_dbg_assert_(FSID.Bit(FS_BIT_FLATSHADE) == VSID.Bit(VS_BIT_FLATSHADE));
|
||||
_dbg_assert_(FSID.Bit(FS_BIT_LMODE) == VSID.Bit(VS_BIT_LMODE));
|
||||
_dbg_assert_(FSID.Bit(FS_BIT_MINMAX_DISCARD) == VSID.Bit(VS_BIT_FS_MINMAX_DISCARD));
|
||||
_dbg_assert_(FSID.Bit(FS_BIT_DEPTH_CLAMP) == VSID.Bit(VS_BIT_FS_DEPTH_CLAMP));
|
||||
|
||||
lastVShader_ = vs;
|
||||
lastFShader_ = fs;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Common/CommonWindows.h"
|
||||
#include <d3d11.h>
|
||||
#include <d3d11_1.h>
|
||||
|
||||
@@ -221,14 +222,16 @@ void DrawEngineD3D11::ApplyDrawState(int prim) {
|
||||
bool wantCull = !gstate.isModeClear() && prim != GE_PRIM_RECTANGLES && prim > GE_PRIM_LINE_STRIP && gstate.isCullEnabled();
|
||||
keys_.raster.cullMode = wantCull ? (gstate.getCullMode() ? D3D11_CULL_FRONT : D3D11_CULL_BACK) : D3D11_CULL_NONE;
|
||||
|
||||
// In D3D11, depth clamping is on by default, we have to enable clipping. This is the opposite from the
|
||||
// other APIs.
|
||||
if (gstate.isModeClear() || gstate.isModeThrough()) {
|
||||
// TODO: Might happen in clear mode if not through...
|
||||
keys_.raster.depthClipEnable = 1;
|
||||
} else {
|
||||
if (gstate.getDepthRangeMin() == 0 || gstate.getDepthRangeMax() == 65535) {
|
||||
// TODO: Still has a bug where we clamp to depth range if one is not the full range.
|
||||
// But the alternate is not clamping in either direction...
|
||||
keys_.raster.depthClipEnable = !gstate.isDepthClampEnabled() || !gstate_c.Use(GPU_USE_DEPTH_CLAMP);
|
||||
// We get some extra clamping behavior if clipping is enabled.
|
||||
const bool clamp = gstate.isDepthClipEnabled() && gstate_c.Use(GPU_USE_DEPTH_CLAMP);
|
||||
keys_.raster.depthClipEnable = !clamp;
|
||||
} else {
|
||||
// We just want to clip in this case, the clamp would be clipped anyway.
|
||||
keys_.raster.depthClipEnable = 1;
|
||||
@@ -331,21 +334,14 @@ void DrawEngineD3D11::ApplyDrawState(int prim) {
|
||||
framebufferManager_->GetRenderWidth(), framebufferManager_->GetRenderHeight(),
|
||||
framebufferManager_->GetTargetBufferWidth(), framebufferManager_->GetTargetBufferHeight(),
|
||||
vpAndScissor);
|
||||
UpdateCachedViewportState(vpAndScissor);
|
||||
|
||||
float depthMin = vpAndScissor.depthRangeMin;
|
||||
float depthMax = vpAndScissor.depthRangeMax;
|
||||
|
||||
if (depthMin < 0.0f) depthMin = 0.0f;
|
||||
if (depthMax > 1.0f) depthMax = 1.0f;
|
||||
|
||||
Draw::Viewport &vp = dynState_.viewport;
|
||||
vp.TopLeftX = vpAndScissor.viewportX;
|
||||
vp.TopLeftY = vpAndScissor.viewportY;
|
||||
vp.Width = vpAndScissor.viewportW;
|
||||
vp.Height = vpAndScissor.viewportH;
|
||||
vp.MinDepth = depthMin;
|
||||
vp.MaxDepth = depthMax;
|
||||
vp.MinDepth = 0.0f;
|
||||
vp.MaxDepth = 1.0f;
|
||||
|
||||
D3D11_RECT &scissor = dynState_.scissor;
|
||||
scissor.left = vpAndScissor.scissorX;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "Common/CommonWindows.h"
|
||||
#include <d3d11.h>
|
||||
|
||||
#include "Common/GPU/thin3d.h"
|
||||
|
||||
// TODO: Do this more progressively. No need to compute the entire state if the entire state hasn't changed.
|
||||
|
||||
struct D3D11BlendKey {
|
||||
union {
|
||||
uint64_t value;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <cstring>
|
||||
#include <cfloat>
|
||||
|
||||
#include "Common/CommonWindows.h"
|
||||
#include <d3d11.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ static constexpr GECmdInfo geCmdInfo[] = {
|
||||
{ GE_CMD_LIGHTENABLE1, "light1_on", GECmdFormat::FLAG, "Light 1 enable", CMD_FMT_FLAG, GE_CMD_LIGHTINGENABLE },
|
||||
{ GE_CMD_LIGHTENABLE2, "light2_on", GECmdFormat::FLAG, "Light 2 enable", CMD_FMT_FLAG, GE_CMD_LIGHTINGENABLE },
|
||||
{ GE_CMD_LIGHTENABLE3, "light3_on", GECmdFormat::FLAG, "Light 3 enable", CMD_FMT_FLAG, GE_CMD_LIGHTINGENABLE },
|
||||
{ GE_CMD_DEPTHCLAMPENABLE, "zclamp_on", GECmdFormat::FLAG, "Depth clamp enable", CMD_FMT_FLAG},
|
||||
{ GE_CMD_DEPTHCLIPENABLE, "zclamp_on", GECmdFormat::FLAG, "Depth clamp enable", CMD_FMT_FLAG},
|
||||
{ GE_CMD_CULLFACEENABLE, "cull_on", GECmdFormat::FLAG, "Cullface enable", CMD_FMT_FLAG},
|
||||
{ GE_CMD_TEXTUREMAPENABLE, "tex_on", GECmdFormat::FLAG, "Texture enable", CMD_FMT_FLAG},
|
||||
{ GE_CMD_FOGENABLE, "fog_on", GECmdFormat::FLAG, "Fog enable", CMD_FMT_FLAG},
|
||||
@@ -306,7 +306,7 @@ static constexpr GECmdAlias geCmdAliases[] = {
|
||||
{ GE_CMD_LIGHTENABLE1, { "light1enable" } },
|
||||
{ GE_CMD_LIGHTENABLE2, { "light2enable" } },
|
||||
{ GE_CMD_LIGHTENABLE3, { "light3enable" } },
|
||||
{ GE_CMD_DEPTHCLAMPENABLE, { "zclampenable", "depthclamp_on", "depthclampenable" } },
|
||||
{ GE_CMD_DEPTHCLIPENABLE, { "zclampenable", "depthclamp_on", "depthclampenable" } },
|
||||
{ GE_CMD_CULLFACEENABLE, { "cullenable", "cullface_on", "cullfaceenable" } },
|
||||
{ GE_CMD_TEXTUREMAPENABLE, { "texenable", "texture_on", "textureenable" } },
|
||||
{ GE_CMD_FOGENABLE, { "fogenable" } },
|
||||
|
||||
+28
-22
@@ -253,14 +253,35 @@ void DrawEngineGLES::Flush() {
|
||||
|
||||
GEPrimitiveType prim = prevPrim_;
|
||||
|
||||
Shader *vshader = shaderManager_->ApplyVertexShader(CanUseHardwareTransform(prim), useHWTessellation_, dec_->VertexType(), decOptions_.expandAllWeightsToFloat, applySkinInDecode_ || !CanUseHardwareTransform(prim), &vsid);
|
||||
bool useHWTransform = CanUseHardwareTransform(prim);
|
||||
if (clipInfoFlags_ & ClipInfoFlags::Valid) {
|
||||
if (clipInfoFlags_ & ClipInfoFlags::SoftClipCull) {
|
||||
useHWTransform = false;
|
||||
}
|
||||
}
|
||||
if (clipInfoFlags_ != lastClipInfoFlags_) {
|
||||
ClipInfoFlags changed = (ClipInfoFlags)((u32)clipInfoFlags_ ^ (u32)lastClipInfoFlags_);
|
||||
if (changed & (ClipInfoFlags::DepthClampFragment | ClipInfoFlags::MinMaxZDiscard)) {
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE);
|
||||
}
|
||||
lastClipInfoFlags_ = clipInfoFlags_;
|
||||
}
|
||||
|
||||
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, 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.
|
||||
|
||||
GLRBuffer *vertexBuffer = nullptr;
|
||||
GLRBuffer *indexBuffer = nullptr;
|
||||
uint32_t vertexBufferOffset = 0;
|
||||
uint32_t indexBufferOffset = 0;
|
||||
|
||||
if (vshader->UseHWTransform()) {
|
||||
if (useHWTransform) {
|
||||
if (applySkinInDecode_ && (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_);
|
||||
@@ -303,7 +324,7 @@ void DrawEngineGLES::Flush() {
|
||||
ApplyDrawState(prim);
|
||||
ApplyDrawStateLate(false, 0);
|
||||
|
||||
LinkedShader *program = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_, framebufferManager_->UseBufferedRendering());
|
||||
LinkedShader *program = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_, clipInfoFlags_);
|
||||
GLRInputLayout *inputLayout = SetupDecFmtForDraw(dec_->GetDecVtxFmt());
|
||||
if (useElements) {
|
||||
render_->DrawIndexed(inputLayout,
|
||||
@@ -340,17 +361,6 @@ void DrawEngineGLES::Flush() {
|
||||
gpuStats.perFrame.numUncachedVertsDrawn += vertexCount;
|
||||
prim = IndexGenerator::GeneralPrim((GEPrimitiveType)drawInds_[0].prim);
|
||||
|
||||
// We need correct viewport values in gstate_c already.
|
||||
if (gstate_c.IsDirty(DIRTY_VIEWPORTSCISSOR_STATE)) {
|
||||
ConvertViewportAndScissor(
|
||||
framebufferManager_->GetDisplayLayoutConfigCopy(),
|
||||
framebufferManager_->UseBufferedRendering(),
|
||||
framebufferManager_->GetRenderWidth(), framebufferManager_->GetRenderHeight(),
|
||||
framebufferManager_->GetTargetBufferWidth(), framebufferManager_->GetTargetBufferHeight(),
|
||||
vpAndScissor_);
|
||||
UpdateCachedViewportState(vpAndScissor_);
|
||||
}
|
||||
|
||||
int maxIndex = numDecodedVerts_;
|
||||
|
||||
// TODO: Split up into multiple draw calls for GLES 2.0 where you can't guarantee support for more than 0x10000 verts.
|
||||
@@ -381,12 +391,8 @@ void DrawEngineGLES::Flush() {
|
||||
params.allowClear = true; // Clear in OpenGL respects scissor rects, so we'll use it.
|
||||
params.allowSeparateAlphaClear = true;
|
||||
|
||||
SoftwareTransform swTransform(params);
|
||||
const SoftwareTransformAction action = SoftwareTransform::Transform(params, prim, swDec->VertexType(), swDec->GetDecVtxFmt(), numDecodedVerts_, VERTEX_BUFFER_MAX, vertexCount, inds, RemainingIndices(inds), &result);
|
||||
|
||||
const Lin::Vec3 trans(gstate_c.vpXOffset, gstate_c.vpYOffset, gstate_c.vpZOffset);
|
||||
const Lin::Vec3 scale(gstate_c.vpWidthScale, gstate_c.vpHeightScale, gstate_c.vpDepthScale);
|
||||
swTransform.SetProjMatrix(gstate.projMatrix, gstate_c.vpWidth < 0, gstate_c.vpHeight < 0, trans, scale);
|
||||
swTransform.Transform(prim, swDec->VertexType(), swDec->GetDecVtxFmt(), numDecodedVerts_, VERTEX_BUFFER_MAX, vertexCount, inds, RemainingIndices(inds), &result);
|
||||
if (result.setSafeSize)
|
||||
framebufferManager_->SetSafeSize(result.safeWidth, result.safeHeight);
|
||||
|
||||
@@ -404,19 +410,19 @@ void DrawEngineGLES::Flush() {
|
||||
ApplyDrawState(prim);
|
||||
ApplyDrawStateLate(result.setStencil, result.stencilValue);
|
||||
|
||||
LinkedShader *linked = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_, framebufferManager_->UseBufferedRendering());
|
||||
LinkedShader *linked = shaderManager_->ApplyFragmentShader(vsid, vshader, pipelineState_, clipInfoFlags_);
|
||||
if (!linked) {
|
||||
// Not much we can do here. Let's skip drawing.
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (result.action == SW_DRAW_INDEXED) {
|
||||
if (action == SW_DRAW_INDEXED) {
|
||||
vertexBufferOffset = (uint32_t)frameData.pushVertex->Push(result.drawBuffer, numDecodedVerts_ * sizeof(TransformedVertex), 4, &vertexBuffer);
|
||||
indexBufferOffset = (uint32_t)frameData.pushIndex->Push(inds, sizeof(uint16_t) * result.drawNumTrans, 2, &indexBuffer);
|
||||
render_->DrawIndexed(
|
||||
softwareInputLayout_, vertexBuffer, vertexBufferOffset, indexBuffer, indexBufferOffset,
|
||||
glprim[prim], result.drawNumTrans, GL_UNSIGNED_SHORT);
|
||||
} else if (result.action == SW_CLEAR) {
|
||||
} else if (action == SW_CLEAR) {
|
||||
u32 clearColor = result.color;
|
||||
float clearDepth = result.depth;
|
||||
|
||||
|
||||
@@ -95,9 +95,6 @@ GPU_GLES::GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
|
||||
File::IOFile f(shaderCachePath_, "rb");
|
||||
if (f.IsOpen()) {
|
||||
if (shaderManagerGL_->LoadCacheFlags(f, &drawEngine_)) {
|
||||
if (drawEngineCommon_->EverUsedExactEqualDepth()) {
|
||||
sawExactEqualDepth_ = true;
|
||||
}
|
||||
gstate_c.SetUseFlags(CheckGPUFeatures());
|
||||
// We're compiling now, clear if they changed.
|
||||
gstate_c.useFlagsChanged = false;
|
||||
|
||||
@@ -106,6 +106,10 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs,
|
||||
queries.push_back({ &u_proj, "u_proj" });
|
||||
queries.push_back({ &u_proj_lens, "u_proj_lens" });
|
||||
queries.push_back({ &u_xywh, "u_xywh" });
|
||||
queries.push_back({ &u_vpScale, "u_vpScale" });
|
||||
queries.push_back({ &u_vpOffset, "u_vpOffset"});
|
||||
queries.push_back({ &u_rasterOffset, "u_rasterOffset" });
|
||||
queries.push_back({ &u_minZmaxZ, "u_minZmaxZ" });
|
||||
queries.push_back({ &u_texenv, "u_texenv" });
|
||||
queries.push_back({ &u_fogcolor, "u_fogcolor" });
|
||||
queries.push_back({ &u_fogcoef, "u_fogcoef" });
|
||||
@@ -116,6 +120,7 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs,
|
||||
queries.push_back({ &u_blendFixA, "u_blendFixA" });
|
||||
queries.push_back({ &u_blendFixB, "u_blendFixB" });
|
||||
queries.push_back({ &u_fbotexSize, "u_fbotexSize" });
|
||||
queries.push_back({ &u_NaN, "u_NaN" });
|
||||
|
||||
// Transform
|
||||
queries.push_back({ &u_view, "u_view" });
|
||||
@@ -126,9 +131,6 @@ LinkedShader::LinkedShader(GLRenderManager *render, VShaderID VSID, Shader *vs,
|
||||
numBones = TranslateNumBones(VSID.Bits(VS_BIT_BONES, 3) + 1);
|
||||
else
|
||||
numBones = 0;
|
||||
queries.push_back({ &u_depthRange, "u_depthRange" });
|
||||
queries.push_back({ &u_cullRangeMin, "u_cullRangeMin" });
|
||||
queries.push_back({ &u_cullRangeMax, "u_cullRangeMax" });
|
||||
|
||||
// These two are only used for VR, but let's always query them for simplicity.
|
||||
queries.push_back({ &u_scaleX, "u_scaleX" });
|
||||
@@ -308,12 +310,6 @@ static void SetMatrix4x3(GLRenderManager *render, GLint *uniform, const float *m
|
||||
render->SetUniformM4x4(uniform, m4x4);
|
||||
}
|
||||
|
||||
static void ConvertProjMatrixToZeroToOneDepth(Matrix4x4 &in) {
|
||||
const Vec3 trans(gstate_c.vpXOffset, gstate_c.vpYOffset, gstate_c.vpZOffset * 0.5f + 0.5f);
|
||||
const Vec3 scale(gstate_c.vpWidthScale, gstate_c.vpHeightScale, gstate_c.vpDepthScale * 0.5f);
|
||||
in.translateAndScale(trans, scale);
|
||||
}
|
||||
|
||||
static inline void FlipProjMatrix(Matrix4x4 &in) {
|
||||
const bool invertedY = gstate_c.vpHeight < 0;
|
||||
if (invertedY) {
|
||||
@@ -377,7 +373,7 @@ void LinkedShader::use(const ShaderID &VSID) const {
|
||||
// Note that we no longer track attr masks here - we do it for the input layouts instead.
|
||||
}
|
||||
|
||||
void LinkedShader::UpdateUniforms(const ShaderID &vsid, bool useBufferedRendering, const ShaderLanguageDesc &shaderLanguage) {
|
||||
void LinkedShader::UpdateUniforms(const ShaderID &vsid, const ShaderLanguageDesc &shaderLanguage) {
|
||||
u64 dirty = dirtyUniforms & availableUniforms;
|
||||
dirtyUniforms = 0;
|
||||
|
||||
@@ -427,20 +423,12 @@ void LinkedShader::UpdateUniforms(const ShaderID &vsid, bool useBufferedRenderin
|
||||
UpdateVRProjection(gstate.projMatrix, vrProjection.m);
|
||||
}
|
||||
UpdateVRParams(gstate.projMatrix);
|
||||
|
||||
FlipProjMatrix(vrProjection);
|
||||
ConvertProjMatrixToZeroToOneDepth(vrProjection);
|
||||
|
||||
render_->SetUniformM4x4(&u_proj_lens, vrProjection.m);
|
||||
}
|
||||
|
||||
Matrix4x4 flippedMatrix;
|
||||
memcpy(&flippedMatrix, gstate.projMatrix, 16 * sizeof(float));
|
||||
|
||||
FlipProjMatrix(flippedMatrix);
|
||||
ConvertProjMatrixToZeroToOneDepth(flippedMatrix);
|
||||
|
||||
render_->SetUniformM4x4(&u_proj, flippedMatrix.m);
|
||||
Matrix4x4 matrix;
|
||||
memcpy(&matrix, gstate.projMatrix, 16 * sizeof(float));
|
||||
render_->SetUniformM4x4(&u_proj, matrix.m);
|
||||
}
|
||||
if (dirty & DIRTY_PROJTHROUGHMATRIX) {
|
||||
float xywh[4];
|
||||
@@ -449,6 +437,8 @@ void LinkedShader::UpdateUniforms(const ShaderID &vsid, bool useBufferedRenderin
|
||||
xywh[2] = (float)gstate_c.curRTWidth;
|
||||
xywh[3] = (float)gstate_c.curRTHeight;
|
||||
SetFloatUniform4(render_, &u_xywh, xywh);
|
||||
float nan = std::numeric_limits<float>::quiet_NaN();
|
||||
render_->SetUniformF1(&u_NaN, nan);
|
||||
}
|
||||
if (dirty & DIRTY_TEXENV) {
|
||||
SetColorUniform3(render_, &u_texenv, gstate.texenvcolor);
|
||||
@@ -577,31 +567,33 @@ void LinkedShader::UpdateUniforms(const ShaderID &vsid, bool useBufferedRenderin
|
||||
if (dirty & DIRTY_TEXMATRIX) {
|
||||
SetMatrix4x3(render_, &u_texmtx, gstate.tgenMatrix);
|
||||
}
|
||||
if (dirty & DIRTY_DEPTHRANGE) {
|
||||
// Since depth is [-1, 1] mapping to [minz, maxz], this is easyish.
|
||||
float vpZScale = gstate.getViewportZScale();
|
||||
float vpZCenter = gstate.getViewportZCenter();
|
||||
|
||||
// These are just the reverse of the formulas in GPUStateUtils.
|
||||
float halfActualZRange = InfToZero(gstate_c.vpDepthScale != 0.0f ? vpZScale / gstate_c.vpDepthScale : 0.0f);
|
||||
float inverseDepthScale = InfToZero(gstate_c.vpDepthScale != 0.0f ? 1.0f / gstate_c.vpDepthScale : 0.0f);
|
||||
float minz = -((gstate_c.vpZOffset * halfActualZRange) - vpZCenter) - halfActualZRange;
|
||||
float viewZScale = halfActualZRange;
|
||||
float viewZCenter = minz + halfActualZRange;
|
||||
|
||||
if (!gstate_c.Use(GPU_USE_ACCURATE_DEPTH)) {
|
||||
viewZScale = vpZScale;
|
||||
viewZCenter = vpZCenter;
|
||||
}
|
||||
|
||||
float data[4] = { viewZScale, viewZCenter, gstate_c.vpZOffset, inverseDepthScale };
|
||||
SetFloatUniform4(render_, &u_depthRange, data);
|
||||
if (dirty & DIRTY_RASTER_OFFSET) {
|
||||
float offset[2] = {
|
||||
gstate.getOffsetX(),
|
||||
gstate.getOffsetY(),
|
||||
};
|
||||
render_->SetUniformF(&u_rasterOffset, 2, offset);
|
||||
float minZmaxZ[2] = {
|
||||
(float)gstate.getDepthRangeMin(),
|
||||
(float)gstate.getDepthRangeMax(),
|
||||
};
|
||||
render_->SetUniformF(&u_minZmaxZ, 2, minZmaxZ);
|
||||
}
|
||||
if (dirty & DIRTY_CULLRANGE) {
|
||||
float minValues[4], maxValues[4];
|
||||
CalcCullRange(minValues, maxValues, !useBufferedRendering, true);
|
||||
SetFloatUniform4(render_, &u_cullRangeMin, minValues);
|
||||
SetFloatUniform4(render_, &u_cullRangeMax, maxValues);
|
||||
|
||||
if (dirty & DIRTY_VIEWPORT_UNIFORMS) {
|
||||
float center[3] = {
|
||||
gstate.getViewportXCenter(),
|
||||
gstate.getViewportYCenter(),
|
||||
gstate.getViewportZCenter(),
|
||||
};
|
||||
float scale[3] = {
|
||||
gstate.getViewportXScale(),
|
||||
gstate.getViewportYScale(),
|
||||
gstate.getViewportZScale(),
|
||||
};
|
||||
render_->SetUniformF(&u_vpOffset, 3, center);
|
||||
render_->SetUniformF(&u_vpScale, 3, scale);
|
||||
}
|
||||
|
||||
if (dirty & DIRTY_STENCILREPLACEVALUE) {
|
||||
@@ -772,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_;
|
||||
}
|
||||
@@ -808,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);
|
||||
}
|
||||
|
||||
@@ -816,7 +808,7 @@ Shader *ShaderManagerGLES::ApplyVertexShader(bool useHWTransform, bool useHWTess
|
||||
return vs;
|
||||
}
|
||||
|
||||
LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState, bool useBufferedRendering) {
|
||||
LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs, const ComputedPipelineState &pipelineState, ClipInfoFlags clipInfoFlags) {
|
||||
uint64_t dirty = gstate_c.GetDirtyUniforms();
|
||||
if (dirty) {
|
||||
if (lastShader_)
|
||||
@@ -828,13 +820,13 @@ 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());
|
||||
ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), clipInfoFlags);
|
||||
} else {
|
||||
FSID = lastFSID_;
|
||||
}
|
||||
|
||||
if (lastVShaderSame_ && FSID == lastFSID_) {
|
||||
lastShader_->UpdateUniforms(VSID, useBufferedRendering, draw_->GetShaderLanguageDesc());
|
||||
lastShader_->UpdateUniforms(VSID, draw_->GetShaderLanguageDesc());
|
||||
return lastShader_;
|
||||
}
|
||||
|
||||
@@ -870,6 +862,8 @@ LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs,
|
||||
if (ls == nullptr) {
|
||||
_dbg_assert_(FSID.Bit(FS_BIT_LMODE) == VSID.Bit(VS_BIT_LMODE));
|
||||
_dbg_assert_(FSID.Bit(FS_BIT_FLATSHADE) == VSID.Bit(VS_BIT_FLATSHADE));
|
||||
_dbg_assert_(FSID.Bit(FS_BIT_MINMAX_DISCARD) == VSID.Bit(VS_BIT_FS_MINMAX_DISCARD));
|
||||
_dbg_assert_(FSID.Bit(FS_BIT_DEPTH_CLAMP) == VSID.Bit(VS_BIT_FS_DEPTH_CLAMP));
|
||||
|
||||
if (vs == nullptr || fs == nullptr) {
|
||||
// Can't draw. This shouldn't really happen (but can happen if fragment shader generation fails)
|
||||
@@ -884,7 +878,7 @@ LinkedShader *ShaderManagerGLES::ApplyFragmentShader(VShaderID VSID, Shader *vs,
|
||||
} else {
|
||||
ls->use(VSID);
|
||||
}
|
||||
ls->UpdateUniforms(VSID, useBufferedRendering, draw_->GetShaderLanguageDesc());
|
||||
ls->UpdateUniforms(VSID, draw_->GetShaderLanguageDesc());
|
||||
|
||||
lastShader_ = ls;
|
||||
return ls;
|
||||
@@ -965,11 +959,11 @@ std::string ShaderManagerGLES::DebugGetShaderString(std::string id, DebugShaderT
|
||||
// as sometimes these features might have an effect on the ID bits.
|
||||
|
||||
enum class CacheDetectFlags {
|
||||
EQUAL_DEPTH = 1,
|
||||
EQUAL_DEPTH_UNUSED = 1,
|
||||
};
|
||||
|
||||
#define CACHE_HEADER_MAGIC 0x83277592
|
||||
#define CACHE_VERSION 38
|
||||
#define CACHE_VERSION 39
|
||||
|
||||
struct CacheHeader {
|
||||
uint32_t magic;
|
||||
@@ -990,10 +984,6 @@ bool ShaderManagerGLES::LoadCacheFlags(File::IOFile &f, DrawEngineGLES *drawEngi
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((header.detectFlags & (uint32_t)CacheDetectFlags::EQUAL_DEPTH) != 0) {
|
||||
drawEngine->SetEverUsedExactEqualDepth(true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1153,6 +1143,9 @@ void ShaderManagerGLES::SaveCache(const Path &filename, DrawEngineGLES *drawEngi
|
||||
if (linkedShaderCache_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: We shouldn't save the cache if we're just running a frame dump.
|
||||
|
||||
INFO_LOG(Log::G3D, "Saving the shader cache to '%s'", filename.c_str());
|
||||
FILE *f = File::OpenCFile(filename, "wb");
|
||||
if (!f) {
|
||||
@@ -1163,8 +1156,6 @@ void ShaderManagerGLES::SaveCache(const Path &filename, DrawEngineGLES *drawEngi
|
||||
header.magic = CACHE_HEADER_MAGIC;
|
||||
header.version = CACHE_VERSION;
|
||||
header.detectFlags = 0;
|
||||
if (drawEngine->EverUsedExactEqualDepth())
|
||||
header.detectFlags |= (uint32_t)CacheDetectFlags::EQUAL_DEPTH;
|
||||
header.useFlags = gstate_c.GetUseFlags();
|
||||
header.numVertexShaders = GetNumVertexShaders();
|
||||
header.numFragmentShaders = GetNumFragmentShaders();
|
||||
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
~LinkedShader();
|
||||
|
||||
void use(const ShaderID &VSID) const;
|
||||
void UpdateUniforms(const ShaderID &VSID, bool useBufferedRendering, const ShaderLanguageDesc &shaderLanguage);
|
||||
void UpdateUniforms(const ShaderID &VSID, const ShaderLanguageDesc &shaderLanguage);
|
||||
void Delete();
|
||||
|
||||
GLRenderManager *render_;
|
||||
@@ -61,16 +61,19 @@ public:
|
||||
int u_proj;
|
||||
int u_proj_lens;
|
||||
int u_xywh;
|
||||
int u_vpScale;
|
||||
int u_vpOffset;
|
||||
int u_texenv;
|
||||
int u_view;
|
||||
int u_texmtx;
|
||||
int u_world;
|
||||
int u_depthRange; // x,y = viewport xscale/xcenter. z,w=clipping minz/maxz (?)
|
||||
int u_cullRangeMin;
|
||||
int u_cullRangeMax;
|
||||
int u_mipBias;
|
||||
int u_scaleX;
|
||||
int u_scaleY;
|
||||
int u_NaN;
|
||||
|
||||
int u_rasterOffset;
|
||||
int u_minZmaxZ;
|
||||
|
||||
#ifdef USE_BONE_ARRAY
|
||||
int u_bone; // array, size is numBones
|
||||
@@ -158,6 +161,8 @@ private:
|
||||
|
||||
class VertexDecoder;
|
||||
|
||||
enum class ClipInfoFlags;
|
||||
|
||||
class ShaderManagerGLES : public ShaderManagerCommon {
|
||||
public:
|
||||
ShaderManagerGLES(Draw::DrawContext *draw);
|
||||
@@ -167,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 useBufferedRendering);
|
||||
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, ClipInfoFlags clipInfoFlags);
|
||||
|
||||
void DeviceLost() override;
|
||||
void DeviceRestore(Draw::DrawContext *draw) override;
|
||||
|
||||
@@ -223,9 +223,8 @@ void DrawEngineGLES::ApplyDrawState(int prim) {
|
||||
depthClampEnable = false;
|
||||
} else {
|
||||
if (gstate.getDepthRangeMin() == 0 || gstate.getDepthRangeMax() == 65535) {
|
||||
// TODO: Still has a bug where we clamp to depth range if one is not the full range.
|
||||
// But the alternate is not clamping in either direction...
|
||||
depthClampEnable = gstate.isDepthClampEnabled() && gstate_c.Use(GPU_USE_DEPTH_CLAMP);
|
||||
// We get some extra clamping behavior if clipping is enabled.
|
||||
depthClampEnable = gstate.isDepthClipEnabled() && gstate_c.Use(GPU_USE_DEPTH_CLAMP);
|
||||
} else {
|
||||
// We just want to clip in this case, the clamp would be clipped anyway.
|
||||
depthClampEnable = false;
|
||||
@@ -280,13 +279,11 @@ void DrawEngineGLES::ApplyDrawState(int prim) {
|
||||
framebufferManager_->GetRenderWidth(), framebufferManager_->GetRenderHeight(),
|
||||
framebufferManager_->GetTargetBufferWidth(), framebufferManager_->GetTargetBufferHeight(),
|
||||
vpAndScissor_);
|
||||
UpdateCachedViewportState(vpAndScissor_);
|
||||
|
||||
renderManager->SetScissor(GLRect2D{ vpAndScissor_.scissorX, vpAndScissor_.scissorY, vpAndScissor_.scissorW, vpAndScissor_.scissorH });
|
||||
renderManager->SetViewport({
|
||||
vpAndScissor_.viewportX, vpAndScissor_.viewportY,
|
||||
vpAndScissor_.viewportW, vpAndScissor_.viewportH,
|
||||
vpAndScissor_.depthRangeMin, vpAndScissor_.depthRangeMax });
|
||||
vpAndScissor_.viewportW, vpAndScissor_.viewportH, 0.0f, 1.0f });
|
||||
}
|
||||
|
||||
gstate_c.Clean(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_BLEND_STATE);
|
||||
|
||||
@@ -68,6 +68,27 @@ inline unsigned int toFloat24(float f) {
|
||||
return i >> 8;
|
||||
}
|
||||
|
||||
// TODO: Not completely sure about the exact mechanics here, or where we need to use this.
|
||||
// Useful to experiment with.
|
||||
inline float roundToFloat24(float f) {
|
||||
unsigned int i;
|
||||
memcpy(&i, &f, 4);
|
||||
i &= 0xFFFFFF00;
|
||||
i += 0x80; // TODO: Subtract for negative numbers?
|
||||
float retval;
|
||||
memcpy(&retval, &i, 4);
|
||||
return retval;
|
||||
}
|
||||
|
||||
inline float truncateToFloat24(float f) {
|
||||
unsigned int i;
|
||||
memcpy(&i, &f, 4);
|
||||
i &= 0xFFFFFF00;
|
||||
float retval;
|
||||
memcpy(&retval, &i, 4);
|
||||
return retval;
|
||||
}
|
||||
|
||||
// TODO: Possibly use macros to disable expensive parts of stats tracking in release builds.
|
||||
struct GPUStatsPerFrame {
|
||||
// Per frame statistics
|
||||
@@ -77,6 +98,8 @@ struct GPUStatsPerFrame {
|
||||
int numDrawSyncs;
|
||||
int numListSyncs;
|
||||
int numFlushes;
|
||||
int numSoftTransformedDraws;
|
||||
int numSoftClippedTriangles;
|
||||
int numBBOXJumps;
|
||||
int numVertsSubmitted;
|
||||
int numVertsDecoded;
|
||||
@@ -119,6 +142,7 @@ struct GPUStatsPerFrame {
|
||||
};
|
||||
|
||||
struct GPUStatsTotals {
|
||||
// Flip count. Doesn't really belong here.
|
||||
int numFlips;
|
||||
};
|
||||
|
||||
|
||||
+1
-2
@@ -260,7 +260,6 @@
|
||||
<ClInclude Include="Common\TextureReplacer.h" />
|
||||
<ClInclude Include="Common\TextureShaderCommon.h" />
|
||||
<ClInclude Include="Common\Draw2D.h" />
|
||||
<ClInclude Include="Common\GeometryShaderGenerator.h" />
|
||||
<ClInclude Include="Common\ReinterpretFramebuffer.h" />
|
||||
<ClInclude Include="Common\DepalettizeShaderCommon.h" />
|
||||
<ClInclude Include="Common\DrawEngineCommon.h" />
|
||||
@@ -333,6 +332,7 @@
|
||||
<ClInclude Include="GPUCommonHW.h" />
|
||||
<ClInclude Include="GPUDefinitions.h" />
|
||||
<ClInclude Include="GPUState.h" />
|
||||
<ClInclude Include="GPUStateSIMDUtil.h" />
|
||||
<ClInclude Include="Math3D.h" />
|
||||
<ClInclude Include="Software\BinManager.h" />
|
||||
<ClInclude Include="Software\Clipper.h" />
|
||||
@@ -364,7 +364,6 @@
|
||||
<ClCompile Include="Common\TextureReplacer.cpp" />
|
||||
<ClCompile Include="Common\TextureShaderCommon.cpp" />
|
||||
<ClCompile Include="Common\Draw2D.cpp" />
|
||||
<ClCompile Include="Common\GeometryShaderGenerator.cpp" />
|
||||
<ClCompile Include="Common\ReinterpretFramebuffer.cpp" />
|
||||
<ClCompile Include="Common\DepalettizeShaderCommon.cpp" />
|
||||
<ClCompile Include="Common\DrawEngineCommon.cpp" />
|
||||
|
||||
@@ -243,9 +243,6 @@
|
||||
<ClInclude Include="Debugger\GECommandTable.h">
|
||||
<Filter>Debugger</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Common\GeometryShaderGenerator.h">
|
||||
<Filter>Common</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GPUCommonHW.h">
|
||||
<Filter>Common</Filter>
|
||||
</ClInclude>
|
||||
@@ -264,6 +261,9 @@
|
||||
<ClInclude Include="Common\DepthRaster.h">
|
||||
<Filter>Common</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GPUStateSIMDUtil.h">
|
||||
<Filter>Common</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Math3D.cpp">
|
||||
@@ -494,9 +494,6 @@
|
||||
<ClCompile Include="Debugger\GECommandTable.cpp">
|
||||
<Filter>Debugger</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Common\GeometryShaderGenerator.cpp">
|
||||
<Filter>Common</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Common\DepthBufferCommon.cpp">
|
||||
<Filter>Common</Filter>
|
||||
</ClCompile>
|
||||
|
||||
+63
-3
@@ -1355,7 +1355,7 @@ void GPUCommon::FlushImm() {
|
||||
gstate.textureMapEnable = (GE_CMD_TEXTUREMAPENABLE << 24) | (int)texturing;
|
||||
gstate.fogEnable = (GE_CMD_FOGENABLE << 24) | (int)fog;
|
||||
gstate.ditherEnable = (GE_CMD_DITHERENABLE << 24) | (int)dither;
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_UVSCALEOFFSET | DIRTY_CULLRANGE);
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_UVSCALEOFFSET);
|
||||
}
|
||||
|
||||
drawEngineCommon_->DispatchSubmitImm(immPrim_, immBuffer_, immCount_, cullMode, immFirstSent_);
|
||||
@@ -1370,7 +1370,7 @@ void GPUCommon::FlushImm() {
|
||||
gstate.textureMapEnable = (GE_CMD_TEXTUREMAPENABLE << 24) | (int)prevTexturing;
|
||||
gstate.fogEnable = (GE_CMD_FOGENABLE << 24) | (int)prevFog;
|
||||
gstate.ditherEnable = (GE_CMD_DITHERENABLE << 24) | (int)prevDither;
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_UVSCALEOFFSET | DIRTY_CULLRANGE);
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_UVSCALEOFFSET);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2203,6 +2203,66 @@ void GPUCommon::UpdateMatrixProducts() {
|
||||
float world[4 * 4];
|
||||
ConvertMatrix4x3To4x4(world, gstate.worldMatrix);
|
||||
Matrix4ByMatrix4(gstate_c.worldviewproj, world, gstate_c.viewproj);
|
||||
gstate_c.Clean(DIRTY_WORLD_VIEW_PROJ_MATRIX);
|
||||
}
|
||||
if (gstate_c.IsDirty(DIRTY_CULL_MATRIX)) {
|
||||
// Modify the transform matrix to take the viewport into account before culling. This is not necessary
|
||||
// for most games, but there are games that rely on outside-viewport draws (such as Dante's Inferno)'s post
|
||||
// processing effects, and we don't want to cull those.
|
||||
// Potentially we should cache this transform matrix too, but hopefully this is not a bottleneck.
|
||||
// I guess we could also do this directly when computing worldviewproj...
|
||||
|
||||
const float vpXCenter = gstate.getViewportXCenter();
|
||||
const float vpYCenter = gstate.getViewportYCenter();
|
||||
const float vpXScale = gstate.getViewportXScale();
|
||||
const float vpYScale = gstate.getViewportYScale();
|
||||
const int scissorX2 = gstate.getScissorX2();
|
||||
const int scissorY2 = gstate.getScissorY2();
|
||||
|
||||
// Check for weird scaling that can make graphics extend beyond the viewport.
|
||||
// NOTE: These checks are not bullet proof.
|
||||
if (vpXCenter != 2048.0f || vpYCenter != 2048.0f || vpXScale < ((scissorX2 + 1) >> 1) || fabsf(vpYScale) < ((scissorY2 + 1) >> 1)) {
|
||||
// Note that the PSP does not clip against the viewport.
|
||||
const Vec2f baseOffset = Vec2f(gstate.getOffsetX(), gstate.getOffsetY());
|
||||
// Region1 (rate) is used as an X1/Y1 here, matching PSP behavior. ???
|
||||
_dbg_assert_(gstate.getRegionX1() != 0x100);
|
||||
_dbg_assert_(gstate.getRegionY1() != 0x100);
|
||||
Vec2f minOffset = baseOffset + Vec2f(std::max(gstate.getRegionX1(), gstate.getScissorX1()), std::max(gstate.getRegionY1(), gstate.getScissorY1()));
|
||||
Vec2f maxOffset = baseOffset + Vec2f(std::min(gstate.getRegionX2(), gstate.getScissorX2()), std::min(gstate.getRegionY2(), gstate.getScissorY2()));
|
||||
|
||||
// Now let's apply the viewport to our scissor/region + offset range.
|
||||
Vec2f inverseViewportScale = Vec2f(1.0f / gstate.getViewportXScale(), 1.0f / gstate.getViewportYScale());
|
||||
Vec2f minViewport = (minOffset - Vec2f(gstate.getViewportXCenter(), gstate.getViewportYCenter())) * inverseViewportScale;
|
||||
Vec2f maxViewport = (maxOffset - Vec2f(gstate.getViewportXCenter(), gstate.getViewportYCenter())) * inverseViewportScale;
|
||||
|
||||
Vec2f viewportInvSize = Vec2f(1.0f / (maxViewport.x - minViewport.x), 1.0f / (maxViewport.y - minViewport.y));
|
||||
|
||||
Lin::Matrix4x4 applyViewport{};
|
||||
// Scale to the viewport's size.
|
||||
applyViewport.xx = 2.0f * viewportInvSize.x;
|
||||
applyViewport.yy = 2.0f * viewportInvSize.y;
|
||||
applyViewport.zz = 1.0f;
|
||||
applyViewport.ww = 1.0f;
|
||||
// And offset to the viewport's centers.
|
||||
applyViewport.wx = -(maxViewport.x + minViewport.x) * viewportInvSize.x;
|
||||
applyViewport.wy = -(maxViewport.y + minViewport.y) * viewportInvSize.y;
|
||||
|
||||
// TODO: Optimize. It's possible to scale/offset a matrix in a quicker way.
|
||||
Matrix4ByMatrix4(gstate_c.cullMatrix, gstate_c.worldviewproj, applyViewport.m);
|
||||
} else {
|
||||
// No funny business, just use worldviewproj for culling.
|
||||
memcpy(gstate_c.cullMatrix, gstate_c.worldviewproj, sizeof(float) * 16);
|
||||
}
|
||||
|
||||
// Now, check the Z range. If the viewport matches the limits of Z, we can avoid the need to do near clipping in many cases
|
||||
// since the host hardware will take care of it automatically.
|
||||
const float absZScale = fabsf(gstate.getViewportZScale());
|
||||
const float zCenter = gstate.getViewportZCenter();
|
||||
const float zMin = gstate.getDepthRangeMin();
|
||||
const float frontPlane = zCenter - absZScale;
|
||||
if (frontPlane == zMin || frontPlane == zMin + 1.0f) {
|
||||
gstate_c.viewportNearPlaneMatchesOutput = true;
|
||||
}
|
||||
}
|
||||
// It's just a bit operation, cheaper to clean all three together.
|
||||
gstate_c.Clean(DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX);
|
||||
}
|
||||
|
||||
+2
-2
@@ -280,14 +280,14 @@ protected:
|
||||
void SetDrawType(DrawType type, GEPrimitiveType prim) {
|
||||
if (type != lastDraw_) {
|
||||
// We always flush when drawing splines/beziers so no need to do so here
|
||||
gstate_c.Dirty(DIRTY_UVSCALEOFFSET | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE);
|
||||
gstate_c.Dirty(DIRTY_UVSCALEOFFSET | DIRTY_VERTEXSHADER_STATE);
|
||||
lastDraw_ = type;
|
||||
}
|
||||
// Prim == RECTANGLES can cause CanUseHardwareTransform to flip, so we need to dirty.
|
||||
// Also, culling may be affected so dirty the raster state.
|
||||
if (IsTrianglePrim(prim) != IsTrianglePrim(lastPrim_)) {
|
||||
Flush();
|
||||
gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE);
|
||||
gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE);
|
||||
lastPrim_ = prim;
|
||||
}
|
||||
}
|
||||
|
||||
+67
-89
@@ -65,7 +65,7 @@ const CommonCommandTableEntry commonCommandTable[] = {
|
||||
|
||||
// Changes that dirty the framebuffer
|
||||
{ GE_CMD_FRAMEBUFPTR, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS },
|
||||
{ GE_CMD_FRAMEBUFWIDTH, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE },
|
||||
{ GE_CMD_FRAMEBUFWIDTH, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE },
|
||||
{ GE_CMD_FRAMEBUFPIXFORMAT, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_FRAGMENTSHADER_STATE },
|
||||
{ GE_CMD_ZBUFPTR, FLAG_FLUSHBEFOREONCHANGE },
|
||||
{ GE_CMD_ZBUFWIDTH, FLAG_FLUSHBEFOREONCHANGE },
|
||||
@@ -75,8 +75,8 @@ const CommonCommandTableEntry commonCommandTable[] = {
|
||||
{ GE_CMD_FOG2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FOGCOEF },
|
||||
|
||||
// These affect the fragment shader so need flushing.
|
||||
{ GE_CMD_CLEARMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE },
|
||||
{ GE_CMD_TEXTUREMAPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE },
|
||||
{ GE_CMD_CLEARMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE },
|
||||
{ GE_CMD_TEXTUREMAPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE },
|
||||
{ GE_CMD_FOGENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAGMENTSHADER_STATE },
|
||||
{ GE_CMD_TEXMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE },
|
||||
{ GE_CMD_TEXSHADELS, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
|
||||
@@ -90,7 +90,7 @@ const CommonCommandTableEntry commonCommandTable[] = {
|
||||
|
||||
// These change the vertex shader so need flushing.
|
||||
{ GE_CMD_REVERSENORMAL, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
|
||||
{ GE_CMD_LIGHTINGENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE },
|
||||
{ GE_CMD_LIGHTINGENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE },
|
||||
{ GE_CMD_LIGHTENABLE0, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
|
||||
{ GE_CMD_LIGHTENABLE1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
|
||||
{ GE_CMD_LIGHTENABLE2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
|
||||
@@ -102,7 +102,7 @@ const CommonCommandTableEntry commonCommandTable[] = {
|
||||
{ GE_CMD_MATERIALUPDATE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
|
||||
|
||||
// These change all shaders so need flushing.
|
||||
{ GE_CMD_LIGHTMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE },
|
||||
{ GE_CMD_LIGHTMODE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE },
|
||||
|
||||
{ GE_CMD_TEXFILTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS },
|
||||
{ GE_CMD_TEXWRAP, FLAG_FLUSHBEFOREONCHANGE, DIRTY_TEXTURE_PARAMS | DIRTY_FRAGMENTSHADER_STATE },
|
||||
@@ -192,27 +192,27 @@ const CommonCommandTableEntry commonCommandTable[] = {
|
||||
{ GE_CMD_ANTIALIASENABLE, FLAG_FLUSHBEFOREONCHANGE },
|
||||
|
||||
// Viewport.
|
||||
{ GE_CMD_OFFSETX, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE },
|
||||
{ GE_CMD_OFFSETY, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE },
|
||||
{ GE_CMD_VIEWPORTXSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE },
|
||||
{ GE_CMD_VIEWPORTYSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE },
|
||||
{ GE_CMD_VIEWPORTXCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE },
|
||||
{ GE_CMD_VIEWPORTYCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE },
|
||||
{ GE_CMD_VIEWPORTZSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_DEPTHRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE },
|
||||
{ GE_CMD_VIEWPORTZCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULLRANGE | DIRTY_DEPTHRANGE | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE },
|
||||
{ GE_CMD_DEPTHCLAMPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE | DIRTY_RASTER_STATE },
|
||||
{ GE_CMD_OFFSETX, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_OFFSET },
|
||||
{ GE_CMD_OFFSETY, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_OFFSET },
|
||||
{ GE_CMD_VIEWPORTXSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULL_MATRIX | DIRTY_VIEWPORT_UNIFORMS | DIRTY_VIEWPORTSCISSOR_STATE }, // GetFramebufferHeuristicInputs still uses viewport scale
|
||||
{ GE_CMD_VIEWPORTYSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULL_MATRIX | DIRTY_VIEWPORT_UNIFORMS | DIRTY_VIEWPORTSCISSOR_STATE },
|
||||
{ GE_CMD_VIEWPORTXCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULL_MATRIX | DIRTY_VIEWPORT_UNIFORMS },
|
||||
{ GE_CMD_VIEWPORTYCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULL_MATRIX | DIRTY_VIEWPORT_UNIFORMS },
|
||||
{ GE_CMD_VIEWPORTZSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULL_MATRIX | DIRTY_VIEWPORT_UNIFORMS },
|
||||
{ GE_CMD_VIEWPORTZCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULL_MATRIX | DIRTY_VIEWPORT_UNIFORMS },
|
||||
|
||||
// Z clip
|
||||
{ GE_CMD_MINZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHRANGE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE },
|
||||
{ GE_CMD_MAXZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHRANGE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE },
|
||||
// Z range and clipping
|
||||
{ GE_CMD_DEPTHCLIPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE },
|
||||
{ GE_CMD_MINZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE | DIRTY_RASTER_OFFSET | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE },
|
||||
{ GE_CMD_MAXZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE | DIRTY_RASTER_OFFSET | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE },
|
||||
|
||||
// Region
|
||||
{ GE_CMD_REGION1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE },
|
||||
{ GE_CMD_REGION2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE },
|
||||
{ GE_CMD_REGION1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE },
|
||||
{ GE_CMD_REGION2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULL_MATRIX | DIRTY_VIEWPORTSCISSOR_STATE },
|
||||
|
||||
// Scissor
|
||||
{ GE_CMD_SCISSOR1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE },
|
||||
{ GE_CMD_SCISSOR2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE },
|
||||
{ GE_CMD_SCISSOR1, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULL_MATRIX | DIRTY_VIEWPORTSCISSOR_STATE },
|
||||
{ GE_CMD_SCISSOR2, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_CULL_MATRIX | DIRTY_VIEWPORTSCISSOR_STATE },
|
||||
|
||||
// Lighting base colors
|
||||
{ GE_CMD_AMBIENTCOLOR, FLAG_FLUSHBEFOREONCHANGE, DIRTY_AMBIENT },
|
||||
@@ -504,14 +504,6 @@ void GPUCommonHW::UpdateCmdInfo() {
|
||||
}
|
||||
}
|
||||
|
||||
void GPUCommonHW::BeginHostFrame(const DisplayLayoutConfig &config) {
|
||||
GPUCommon::BeginHostFrame(config);
|
||||
if (drawEngineCommon_->EverUsedExactEqualDepth() && !sawExactEqualDepth_) {
|
||||
sawExactEqualDepth_ = true;
|
||||
gstate_c.SetUseFlags(CheckGPUFeatures());
|
||||
}
|
||||
}
|
||||
|
||||
void GPUCommonHW::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) {
|
||||
framebufferManager_->SetDisplayFramebuffer(framebuf, stride, format);
|
||||
NotifyDisplay(framebuf, stride, format);
|
||||
@@ -593,7 +585,9 @@ u32 GPUCommonHW::CheckGPUFeatures() const {
|
||||
features |= GPU_USE_BLEND_MINMAX;
|
||||
}
|
||||
|
||||
if (draw_->GetDeviceCaps().maxClipDistances >= 2) {
|
||||
// The next three (clipDistance, cullDistance, depthClamp) are the critical features that let us correctly emulate PSP behavior without fallbacks.
|
||||
// This is except for the case of clipped polygons still reaching outside the guardband, where we still need to software-clip if detected.
|
||||
if (draw_->GetDeviceCaps().maxClipDistances >= 3) {
|
||||
features |= GPU_USE_CLIP_DISTANCE;
|
||||
}
|
||||
|
||||
@@ -601,22 +595,14 @@ u32 GPUCommonHW::CheckGPUFeatures() const {
|
||||
features |= GPU_USE_CULL_DISTANCE;
|
||||
}
|
||||
|
||||
if (draw_->GetDeviceCaps().depthClampSupported) {
|
||||
features |= GPU_USE_DEPTH_CLAMP;
|
||||
}
|
||||
|
||||
if (draw_->GetDeviceCaps().textureDepthSupported) {
|
||||
features |= GPU_USE_DEPTH_TEXTURE;
|
||||
}
|
||||
|
||||
if (draw_->GetDeviceCaps().depthClampSupported) {
|
||||
// Some backends always do GPU_USE_ACCURATE_DEPTH, but it's required for depth clamp.
|
||||
features |= GPU_USE_DEPTH_CLAMP | GPU_USE_ACCURATE_DEPTH;
|
||||
}
|
||||
|
||||
bool canClipOrCull = draw_->GetDeviceCaps().maxClipDistances >= 2 || draw_->GetDeviceCaps().maxCullDistances >= 1;
|
||||
bool canDiscardVertex = !draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL);
|
||||
if ((canClipOrCull || canDiscardVertex) && !g_Config.bDisableRangeCulling) {
|
||||
// We'll dynamically use the parts that are supported, to reduce artifacts as much as possible.
|
||||
features |= GPU_USE_VS_RANGE_CULLING;
|
||||
}
|
||||
|
||||
if (draw_->GetDeviceCaps().framebufferFetchSupported) {
|
||||
features |= GPU_USE_FRAMEBUFFER_FETCH;
|
||||
features |= GPU_USE_SHADER_BLENDING; // doesn't matter if we are buffered or not here.
|
||||
@@ -634,16 +620,15 @@ u32 GPUCommonHW::CheckGPUFeatures() const {
|
||||
features |= GPU_USE_CLEAR_RAM_HACK;
|
||||
}
|
||||
|
||||
// Even without depth clamp, force accurate depth on for some games that break without it.
|
||||
if (PSP_CoreParameter().compat.flags().DepthRangeHack) {
|
||||
features |= GPU_USE_ACCURATE_DEPTH;
|
||||
}
|
||||
|
||||
// Some backends will turn this off again in the calling function.
|
||||
if (g_Config.bUberShaderFragment) {
|
||||
features |= GPU_USE_FRAGMENT_UBERSHADER;
|
||||
}
|
||||
|
||||
if (!draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) {
|
||||
features |= GPU_USE_VS_RANGE_CULLING;
|
||||
}
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
@@ -652,24 +637,9 @@ u32 GPUCommonHW::CheckGPUFeaturesLate(u32 features) const {
|
||||
bool prefer24 = draw_->GetDeviceCaps().preferredDepthBufferFormat == Draw::DataFormat::D24_S8;
|
||||
bool prefer16 = draw_->GetDeviceCaps().preferredDepthBufferFormat == Draw::DataFormat::D16;
|
||||
if (!prefer16) {
|
||||
if (sawExactEqualDepth_ && (features & GPU_USE_ACCURATE_DEPTH) != 0 && !PSP_CoreParameter().compat.flags().ForceMaxDepthResolution) {
|
||||
// Exact equal tests tend to have issues unless we use the PSP's depth range.
|
||||
// We use 24-bit depth virtually everwhere, the fallback is just for safety.
|
||||
if (prefer24)
|
||||
features |= GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT;
|
||||
else
|
||||
features |= GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT;
|
||||
} else if (!g_Config.bHighQualityDepth && (features & GPU_USE_ACCURATE_DEPTH) != 0) {
|
||||
features |= GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT;
|
||||
} else if (PSP_CoreParameter().compat.flags().PixelDepthRounding) {
|
||||
if (prefer24 && (features & GPU_USE_ACCURATE_DEPTH) != 0) {
|
||||
// Here we can simulate a 16 bit depth buffer by scaling.
|
||||
// Note that the depth buffer is fixed point, not floating, so dividing by 256 is pretty good.
|
||||
features |= GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT;
|
||||
} else {
|
||||
// Use fragment rounding on where available otherwise.
|
||||
features |= GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT;
|
||||
}
|
||||
if (PSP_CoreParameter().compat.flags().PixelDepthRounding) {
|
||||
// Use fragment rounding on where available otherwise.
|
||||
features |= GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT;
|
||||
} else if (PSP_CoreParameter().compat.flags().VertexDepthRounding) {
|
||||
features |= GPU_ROUND_DEPTH_TO_16BIT;
|
||||
}
|
||||
@@ -883,7 +853,7 @@ void GPUCommonHW::Execute_VertexType(u32 op, u32 diff) {
|
||||
|
||||
if (diff & GE_VTYPE_THROUGH_MASK) {
|
||||
// Switching between through and non-through, we need to invalidate a bunch of stuff.
|
||||
gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_CULLRANGE);
|
||||
gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -903,8 +873,9 @@ void GPUCommonHW::Execute_VertexTypeSkinning(u32 op, u32 diff) {
|
||||
}
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE);
|
||||
}
|
||||
|
||||
if (diff & GE_VTYPE_THROUGH_MASK)
|
||||
gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_CULLRANGE);
|
||||
gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_FRAGMENTSHADER_STATE);
|
||||
}
|
||||
|
||||
void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
|
||||
@@ -1014,8 +985,10 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
|
||||
uint32_t vertTypeID = GetVertTypeID(vertexType, gstate.getUVGenMode(), g_Config.bSoftwareSkinning);
|
||||
VertexDecoder *decoder = drawEngineCommon_->GetVertexDecoder(vertTypeID);
|
||||
|
||||
// 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.
|
||||
@@ -1038,9 +1011,9 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
|
||||
}
|
||||
}
|
||||
|
||||
#define MAX_CULL_CHECK_COUNT 6
|
||||
#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)
|
||||
@@ -1051,11 +1024,13 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
|
||||
|
||||
#endif
|
||||
|
||||
ClipInfoFlags flags{};
|
||||
|
||||
// If certain conditions are true, do frustum culling.
|
||||
bool passCulling = PASSES_CULLING;
|
||||
if (!passCulling) {
|
||||
// Do software culling.
|
||||
if (drawEngineCommon_->TestBoundingBoxFast(gstate_c.worldviewproj, verts, count, decoder, vertexType)) {
|
||||
if (drawEngineCommon_->TestBoundingBoxFast(gstate_c.cullMatrix, verts, count, decoder, vertexType, &flags)) {
|
||||
passCulling = true;
|
||||
} else {
|
||||
gpuStats.perFrame.numCulledDraws++;
|
||||
@@ -1068,13 +1043,13 @@ 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)) {
|
||||
if (!drawEngineCommon_->SubmitPrim(verts, inds, prim, count, decoder, vertTypeID, true, &bytesRead, flags)) {
|
||||
canExtend = false;
|
||||
}
|
||||
onePassed = true;
|
||||
} else {
|
||||
// Still need to advance bytesRead.
|
||||
drawEngineCommon_->SkipPrim(prim, count, decoder, vertTypeID, &bytesRead);
|
||||
drawEngineCommon_->SkipPrim(prim, count, decoder, &bytesRead);
|
||||
canExtend = false;
|
||||
}
|
||||
|
||||
@@ -1114,9 +1089,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);
|
||||
int commandsExecuted = drawEngineCommon_->ExtendNonIndexedPrim(src, stall, decoder, vertTypeID, clockwise, &bytesRead, isTriangle, flags);
|
||||
if (!commandsExecuted) {
|
||||
goto bail;
|
||||
}
|
||||
@@ -1142,24 +1117,25 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
|
||||
}
|
||||
|
||||
bool passCulling = onePassed || PASSES_CULLING;
|
||||
ClipInfoFlags flags{};
|
||||
if (!passCulling) {
|
||||
// Do software culling.
|
||||
_dbg_assert_((vertexType & GE_VTYPE_IDX_MASK) == GE_VTYPE_IDX_NONE);
|
||||
if (drawEngineCommon_->TestBoundingBoxFast(gstate_c.worldviewproj, verts, count, decoder, vertexType)) {
|
||||
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)) {
|
||||
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.
|
||||
onePassed = true;
|
||||
} else {
|
||||
// Still need to advance bytesRead.
|
||||
drawEngineCommon_->SkipPrim(newPrim, count, decoder, vertTypeID, &bytesRead);
|
||||
drawEngineCommon_->SkipPrim(newPrim, count, decoder, &bytesRead);
|
||||
canExtend = false;
|
||||
}
|
||||
AdvanceVerts(vertexType, count, bytesRead);
|
||||
@@ -1364,7 +1340,7 @@ void GPUCommonHW::Execute_Bezier(u32 op, u32 diff) {
|
||||
SetDrawType(DRAW_BEZIER, PatchPrimToPrim(surface.primType));
|
||||
|
||||
// We need to dirty UVSCALEOFFSET here because we look at the submit type when setting that uniform.
|
||||
gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_UVSCALEOFFSET);
|
||||
gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_UVSCALEOFFSET);
|
||||
if (drawEngineCommon_->CanUseHardwareTessellation(surface.primType)) {
|
||||
gstate_c.submitType = SubmitType::HW_BEZIER;
|
||||
if (gstate_c.spline_num_points_u != surface.num_points_u) {
|
||||
@@ -1379,7 +1355,7 @@ void GPUCommonHW::Execute_Bezier(u32 op, u32 diff) {
|
||||
gstate_c.UpdateUVScaleOffset();
|
||||
drawEngineCommon_->SubmitCurve(control_points, indices, surface, gstate.vertType, &bytesRead, "bezier");
|
||||
|
||||
gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_UVSCALEOFFSET);
|
||||
gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_UVSCALEOFFSET);
|
||||
gstate_c.submitType = SubmitType::DRAW;
|
||||
|
||||
// After drawing, we advance pointers - see SubmitPrim which does the same.
|
||||
@@ -1444,7 +1420,7 @@ void GPUCommonHW::Execute_Spline(u32 op, u32 diff) {
|
||||
SetDrawType(DRAW_SPLINE, PatchPrimToPrim(surface.primType));
|
||||
|
||||
// We need to dirty UVSCALEOFFSET here because we look at the submit type when setting that uniform.
|
||||
gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_UVSCALEOFFSET);
|
||||
gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_UVSCALEOFFSET);
|
||||
if (drawEngineCommon_->CanUseHardwareTessellation(surface.primType)) {
|
||||
gstate_c.submitType = SubmitType::HW_SPLINE;
|
||||
if (gstate_c.spline_num_points_u != surface.num_points_u) {
|
||||
@@ -1459,7 +1435,7 @@ void GPUCommonHW::Execute_Spline(u32 op, u32 diff) {
|
||||
gstate_c.UpdateUVScaleOffset();
|
||||
drawEngineCommon_->SubmitCurve(control_points, indices, surface, gstate.vertType, &bytesRead, "spline");
|
||||
|
||||
gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE | DIRTY_UVSCALEOFFSET);
|
||||
gstate_c.Dirty(DIRTY_RASTER_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_UVSCALEOFFSET);
|
||||
gstate_c.submitType = SubmitType::DRAW;
|
||||
|
||||
// After drawing, we advance pointers - see SubmitPrim which does the same.
|
||||
@@ -1541,7 +1517,7 @@ void GPUCommonHW::Execute_WorldMtxNum(u32 op, u32 diff) {
|
||||
if (dst[i] != newVal) {
|
||||
Flush();
|
||||
dst[i] = newVal;
|
||||
gstate_c.Dirty(DIRTY_WORLDMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX);
|
||||
gstate_c.Dirty(DIRTY_WORLDMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX);
|
||||
}
|
||||
if (++i >= end) {
|
||||
break;
|
||||
@@ -1564,7 +1540,7 @@ void GPUCommonHW::Execute_WorldMtxData(u32 op, u32 diff) {
|
||||
if (num < 12 && newVal != ((const u32 *)gstate.worldMatrix)[num]) {
|
||||
Flush();
|
||||
((u32 *)gstate.worldMatrix)[num] = newVal;
|
||||
gstate_c.Dirty(DIRTY_WORLDMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX);
|
||||
gstate_c.Dirty(DIRTY_WORLDMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX);
|
||||
}
|
||||
num++;
|
||||
gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (num & 0x00FFFFFF);
|
||||
@@ -1594,7 +1570,7 @@ void GPUCommonHW::Execute_ViewMtxNum(u32 op, u32 diff) {
|
||||
if (dst[i] != newVal) {
|
||||
Flush();
|
||||
dst[i] = newVal;
|
||||
gstate_c.Dirty(DIRTY_VIEWMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX);
|
||||
gstate_c.Dirty(DIRTY_VIEWMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX) ;
|
||||
}
|
||||
if (++i >= end) {
|
||||
break;
|
||||
@@ -1617,7 +1593,7 @@ void GPUCommonHW::Execute_ViewMtxData(u32 op, u32 diff) {
|
||||
if (num < 12 && newVal != ((const u32 *)gstate.viewMatrix)[num]) {
|
||||
Flush();
|
||||
((u32 *)gstate.viewMatrix)[num] = newVal;
|
||||
gstate_c.Dirty(DIRTY_VIEWMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX);
|
||||
gstate_c.Dirty(DIRTY_VIEWMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX);
|
||||
}
|
||||
num++;
|
||||
gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (num & 0x00FFFFFF);
|
||||
@@ -1647,7 +1623,7 @@ void GPUCommonHW::Execute_ProjMtxNum(u32 op, u32 diff) {
|
||||
if (dst[i] != newVal) {
|
||||
Flush();
|
||||
dst[i] = newVal;
|
||||
gstate_c.Dirty(DIRTY_PROJMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX);
|
||||
gstate_c.Dirty(DIRTY_PROJMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX);
|
||||
}
|
||||
if (++i >= end) {
|
||||
break;
|
||||
@@ -1670,7 +1646,7 @@ void GPUCommonHW::Execute_ProjMtxData(u32 op, u32 diff) {
|
||||
if (num < 16 && newVal != ((const u32 *)gstate.projMatrix)[num]) {
|
||||
Flush();
|
||||
((u32 *)gstate.projMatrix)[num] = newVal;
|
||||
gstate_c.Dirty(DIRTY_PROJMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX);
|
||||
gstate_c.Dirty(DIRTY_PROJMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX);
|
||||
}
|
||||
num++;
|
||||
if (num <= 16)
|
||||
@@ -1831,7 +1807,7 @@ void GPUCommonHW::FormatGPUStatsCommon(StringWriter &w) {
|
||||
w.F(
|
||||
"DL processing time: %0.2f ms, %d drawsync, %d listsync\n"
|
||||
"Draw: %d (%d dec, %d culled), flushes %d, clears %d, bbox jumps %d\n"
|
||||
"Vertices: %d dec: %d drawn: %d\n"
|
||||
"%d soft. Vertices: %d dec: %d drawn: %d clipped tris: %d\n"
|
||||
"FBOs active: %d (evaluations: %d, created %d)\n"
|
||||
"Textures: %d, dec: %d, invalidated: %d, hashed: %d kB, clut %d\n"
|
||||
"readbacks %d (%d non-block), upload %d (cached %d), depal %d\n"
|
||||
@@ -1850,9 +1826,11 @@ void GPUCommonHW::FormatGPUStatsCommon(StringWriter &w) {
|
||||
gpuStats.perFrame.numFlushes,
|
||||
gpuStats.perFrame.numClears,
|
||||
gpuStats.perFrame.numBBOXJumps,
|
||||
gpuStats.perFrame.numSoftTransformedDraws,
|
||||
gpuStats.perFrame.numVertsSubmitted,
|
||||
gpuStats.perFrame.numVertsDecoded,
|
||||
gpuStats.perFrame.numUncachedVertsDrawn,
|
||||
gpuStats.perFrame.numSoftClippedTriangles,
|
||||
(int)framebufferManager_->NumVFBs(),
|
||||
gpuStats.perFrame.numFramebufferEvaluations,
|
||||
gpuStats.perFrame.numFBOsCreated,
|
||||
|
||||
@@ -19,8 +19,6 @@ public:
|
||||
void DeviceLost() override;
|
||||
void DeviceRestore(Draw::DrawContext *draw) override;
|
||||
|
||||
void BeginHostFrame(const DisplayLayoutConfig &config) override;
|
||||
|
||||
u32 CheckGPUFeatures() const override;
|
||||
|
||||
// From GPUDebugInterface.
|
||||
@@ -107,7 +105,6 @@ protected:
|
||||
u32 CheckGPUFeaturesLate(u32 features) const;
|
||||
|
||||
int msaaLevel_ = 0;
|
||||
bool sawExactEqualDepth_ = false;
|
||||
ShaderManagerCommon *shaderManager_ = nullptr;
|
||||
bool curFramebufferDirty_ = false;
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <cstring>
|
||||
|
||||
#include "Common/Common.h"
|
||||
#include "Common/Data/Color/RGBAUtil.h"
|
||||
#include "Core/MemMap.h"
|
||||
|
||||
|
||||
|
||||
+2
-1
@@ -358,8 +358,9 @@ void GPUStateCache::DoState(PointerWrap &p) {
|
||||
Do(p, savedContextVersion);
|
||||
}
|
||||
|
||||
if (p.GetMode() == PointerWrap::MODE_READ)
|
||||
if (p.GetMode() == PointerWrap::MODE_READ) {
|
||||
gstate_c.Dirty(DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX);
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr const char * g_gpuUseFlagNames[32] = {
|
||||
|
||||
+14
-7
@@ -27,6 +27,7 @@
|
||||
#include "GPU/ge_constants.h"
|
||||
#include "GPU/Common/ShaderCommon.h"
|
||||
#include "Common/Math/SIMDHeaders.h"
|
||||
#include "Common/Math/lin/vec3.h"
|
||||
|
||||
class PointerWrap;
|
||||
|
||||
@@ -61,7 +62,7 @@ struct GPUgstate {
|
||||
region2,
|
||||
lightingEnable,
|
||||
lightEnable[4],
|
||||
depthClampEnable,
|
||||
depthClipEnable,
|
||||
cullfaceEnable,
|
||||
textureMapEnable, // 0x1E GE_CMD_TEXTUREMAPENABLE
|
||||
fogEnable,
|
||||
@@ -388,16 +389,20 @@ struct GPUgstate {
|
||||
int getRegionX2() const { return (region2 & 0x3FF); }
|
||||
int getRegionY2() const { return (region2 >> 10) & 0x3FF; }
|
||||
|
||||
bool isDepthClampEnabled() const { return depthClampEnable & 1; }
|
||||
bool isDepthClipEnabled() const { return depthClipEnable & 1; }
|
||||
|
||||
// Note that the X1/Y1/Z1 here does not mean the upper-left corner, but half the dimensions. X2/Y2/Z2 are the center.
|
||||
float getViewportXScale() const { return getFloat24(viewportxscale); }
|
||||
float getViewportYScale() const { return getFloat24(viewportyscale); }
|
||||
float getViewportZScale() const { return getFloat24(viewportzscale); }
|
||||
// TODO: Rename to offset?
|
||||
float getViewportXCenter() const { return getFloat24(viewportxcenter); }
|
||||
float getViewportYCenter() const { return getFloat24(viewportycenter); }
|
||||
float getViewportZCenter() const { return getFloat24(viewportzcenter); }
|
||||
|
||||
Lin::Vec3 getViewportScale() const { return Lin::Vec3(getViewportXScale(), getViewportYScale(), getViewportZScale()); }
|
||||
Lin::Vec3 getViewportOffset() const { return Lin::Vec3(getViewportXCenter(), getViewportYCenter(), getViewportZCenter()); }
|
||||
|
||||
// Fixed 12.4 point.
|
||||
int getOffsetX16() const { return offsetx & 0xFFFF; }
|
||||
int getOffsetY16() const { return offsety & 0xFFFF; }
|
||||
@@ -453,14 +458,14 @@ struct UVScale {
|
||||
float uOff, vOff;
|
||||
};
|
||||
|
||||
#define FLAG_BIT(x) (1 << x)
|
||||
#define FLAG_BIT(x) (u32)(1U << x)
|
||||
|
||||
// These flags are mainly to make sure that we make decisions on code path in a single
|
||||
// location. Sometimes we need to take things into account in multiple places, it helps
|
||||
// to centralize into flags like this. They're also fast to check since the cache line
|
||||
// will be hot.
|
||||
// NOTE: Do not forget to update the string array at the end of GPUState.cpp!
|
||||
enum {
|
||||
enum : u32 {
|
||||
GPU_USE_DUALSOURCE_BLEND = FLAG_BIT(0),
|
||||
GPU_USE_LIGHT_UBERSHADER = FLAG_BIT(1),
|
||||
GPU_USE_FRAGMENT_TEST_CACHE = FLAG_BIT(2),
|
||||
@@ -478,11 +483,11 @@ enum {
|
||||
GPU_USE_DEPTH_CLAMP = FLAG_BIT(14),
|
||||
GPU_USE_TEXTURE_LOD_CONTROL = FLAG_BIT(15),
|
||||
GPU_USE_DEPTH_TEXTURE = FLAG_BIT(16),
|
||||
GPU_USE_ACCURATE_DEPTH = FLAG_BIT(17),
|
||||
GPU_USE_GS_CULLING = FLAG_BIT(18), // Geometry shader
|
||||
// Free bit: 17
|
||||
// Free bit: 18
|
||||
GPU_USE_FRAMEBUFFER_ARRAYS = FLAG_BIT(19),
|
||||
GPU_USE_FRAMEBUFFER_FETCH = FLAG_BIT(20),
|
||||
GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT = FLAG_BIT(21),
|
||||
// Free bit: 21,
|
||||
GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT = FLAG_BIT(22),
|
||||
GPU_ROUND_DEPTH_TO_16BIT = FLAG_BIT(23), // Can be disabled either per game or if we use a real 16-bit depth buffer
|
||||
GPU_USE_CLIP_DISTANCE = FLAG_BIT(24),
|
||||
@@ -662,6 +667,8 @@ public:
|
||||
// We recompute viewproj when view or proj changes, and worldviewproj when world, view, or proj changes.
|
||||
float viewproj[16];
|
||||
float worldviewproj[16];
|
||||
float cullMatrix[16];
|
||||
bool viewportNearPlaneMatchesOutput;
|
||||
|
||||
KnownVertexBounds vertBounds;
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common/Math/CrossSIMD.h"
|
||||
#include "GPU/GPUState.h"
|
||||
|
||||
inline Vec4F32 GetViewportOffsetVec(const GPUgstate &gstate) {
|
||||
return Vec4F32::LoadF24x3_DontCare(&gstate.viewportxcenter);
|
||||
}
|
||||
inline Vec4F32 GetViewportScaleVec(const GPUgstate &gstate) {
|
||||
return Vec4F32::LoadF24x3_DontCare(&gstate.viewportxscale);
|
||||
}
|
||||
+1
-1
@@ -323,7 +323,7 @@ void GeDisassembleOp(u32 pc, u32 op, u32 prev, char *buffer, int bufsize) {
|
||||
}
|
||||
break;
|
||||
|
||||
case GE_CMD_DEPTHCLAMPENABLE:
|
||||
case GE_CMD_DEPTHCLIPENABLE:
|
||||
snprintf(buffer, bufsize, "Depth clamp enable: %i", data);
|
||||
break;
|
||||
|
||||
|
||||
+16
-16
@@ -1176,26 +1176,26 @@ inline void ConvertMatrix4x3To4x4Transposed(float *m4x4, const float *m4x3) {
|
||||
// 4567
|
||||
// 89AB
|
||||
// Don't see a way to SIMD that. Should be pretty fast anyway.
|
||||
inline void ConvertMatrix4x3To3x4Transposed(float *m4x4, const float *m4x3) {
|
||||
inline void ConvertMatrix4x3To3x4Transposed(float *m3x4, const float *m4x3) {
|
||||
#if PPSSPP_ARCH(ARM_NEON)
|
||||
// vld3q is a perfect match here!
|
||||
float32x4x3_t packed = vld3q_f32(m4x3);
|
||||
vst1q_f32(m4x4, packed.val[0]);
|
||||
vst1q_f32(m4x4 + 4, packed.val[1]);
|
||||
vst1q_f32(m4x4 + 8, packed.val[2]);
|
||||
vst1q_f32(m3x4, packed.val[0]);
|
||||
vst1q_f32(m3x4 + 4, packed.val[1]);
|
||||
vst1q_f32(m3x4 + 8, packed.val[2]);
|
||||
#else
|
||||
m4x4[0] = m4x3[0];
|
||||
m4x4[1] = m4x3[3];
|
||||
m4x4[2] = m4x3[6];
|
||||
m4x4[3] = m4x3[9];
|
||||
m4x4[4] = m4x3[1];
|
||||
m4x4[5] = m4x3[4];
|
||||
m4x4[6] = m4x3[7];
|
||||
m4x4[7] = m4x3[10];
|
||||
m4x4[8] = m4x3[2];
|
||||
m4x4[9] = m4x3[5];
|
||||
m4x4[10] = m4x3[8];
|
||||
m4x4[11] = m4x3[11];
|
||||
m3x4[0] = m4x3[0];
|
||||
m3x4[1] = m4x3[3];
|
||||
m3x4[2] = m4x3[6];
|
||||
m3x4[3] = m4x3[9];
|
||||
m3x4[4] = m4x3[1];
|
||||
m3x4[5] = m4x3[4];
|
||||
m3x4[6] = m4x3[7];
|
||||
m3x4[7] = m4x3[10];
|
||||
m3x4[8] = m4x3[2];
|
||||
m3x4[9] = m4x3[5];
|
||||
m3x4[10] = m4x3[8];
|
||||
m3x4[11] = m4x3[11];
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@ void ProcessRect(const ClipVertexData &v0, const ClipVertexData &v1, BinManager
|
||||
CheckOutsideZ(v1.clippos, outsidePos, outsideNeg);
|
||||
|
||||
// With depth clamp off, we discard the rectangle if even one vert is outside.
|
||||
if (outsidePos + outsideNeg > 0 && !gstate.isDepthClampEnabled())
|
||||
if (outsidePos + outsideNeg > 0 && !gstate.isDepthClipEnabled())
|
||||
return;
|
||||
// With it on, both must be outside in the same direction.
|
||||
else if (outsidePos >= 2 || outsideNeg >= 2)
|
||||
@@ -288,7 +288,7 @@ void ProcessLine(const ClipVertexData &v0, const ClipVertexData &v1, BinManager
|
||||
CheckOutsideZ(v1.clippos, outsidePos, outsideNeg);
|
||||
|
||||
// With depth clamp off, we discard the line if even one vert is outside.
|
||||
if (outsidePos + outsideNeg > 0 && !gstate.isDepthClampEnabled())
|
||||
if (outsidePos + outsideNeg > 0 && !gstate.isDepthClipEnabled())
|
||||
return;
|
||||
// With it on, both must be outside in the same direction.
|
||||
else if (outsidePos >= 2 || outsideNeg >= 2)
|
||||
@@ -332,7 +332,7 @@ void ProcessTriangle(const ClipVertexData &v0, const ClipVertexData &v1, const C
|
||||
CheckOutsideZ(v2.clippos, outsidePos, outsideNeg);
|
||||
|
||||
// With depth clamp off, we discard the triangle if even one vert is outside.
|
||||
if (outsidePos + outsideNeg > 0 && !gstate.isDepthClampEnabled())
|
||||
if (outsidePos + outsideNeg > 0 && !gstate.isDepthClipEnabled())
|
||||
return;
|
||||
// With it on, all three must be outside in the same direction.
|
||||
else if (outsidePos >= 3 || outsideNeg >= 3)
|
||||
|
||||
@@ -31,4 +31,4 @@ void ProcessLine(const ClipVertexData &v0, const ClipVertexData &v1, BinManager
|
||||
void ProcessTriangle(const ClipVertexData &v0, const ClipVertexData &v1, const ClipVertexData &v2, const ClipVertexData &provoking, BinManager &binner);
|
||||
void ProcessRect(const ClipVertexData &v0, const ClipVertexData &v1, BinManager &binner);
|
||||
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -228,7 +228,7 @@ const SoftwareCommandTableEntry softgpuCommandTable[] = {
|
||||
{ GE_CMD_VIEWPORTYCENTER, 0, SoftDirty::TRANSFORM_VIEWPORT },
|
||||
{ GE_CMD_VIEWPORTZSCALE, 0, SoftDirty::TRANSFORM_VIEWPORT },
|
||||
{ GE_CMD_VIEWPORTZCENTER, 0, SoftDirty::TRANSFORM_VIEWPORT },
|
||||
{ GE_CMD_DEPTHCLAMPENABLE, 0, SoftDirty::TRANSFORM_BASIC },
|
||||
{ GE_CMD_DEPTHCLIPENABLE, 0, SoftDirty::TRANSFORM_BASIC },
|
||||
|
||||
// Z clipping.
|
||||
{ GE_CMD_MINZ, 0, SoftDirty::PIXEL_BASIC | SoftDirty::PIXEL_CACHED },
|
||||
@@ -1030,10 +1030,6 @@ void SoftGPU::Execute_FramebufFormat(u32 op, u32 diff) {
|
||||
drawEngine_->transformUnit.Flush(this, "framebuf");
|
||||
}
|
||||
|
||||
void SoftGPU::Execute_BoundingBox(u32 op, u32 diff) {
|
||||
GPUCommon::Execute_BoundingBox(op, diff);
|
||||
}
|
||||
|
||||
void SoftGPU::Execute_ZbufPtr(u32 op, u32 diff) {
|
||||
// We assume depthbuf.data won't change while we're drawing.
|
||||
if (diff) {
|
||||
|
||||
@@ -189,9 +189,6 @@ public:
|
||||
// Overridden to change flushing behavior.
|
||||
void Execute_Call(u32 op, u32 diff);
|
||||
|
||||
// Overridden for a dirty flag change.
|
||||
void Execute_BoundingBox(u32 op, u32 diff);
|
||||
|
||||
void Execute_WorldMtxNum(u32 op, u32 diff);
|
||||
void Execute_ViewMtxNum(u32 op, u32 diff);
|
||||
void Execute_ProjMtxNum(u32 op, u32 diff);
|
||||
|
||||
@@ -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) {
|
||||
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);
|
||||
}
|
||||
@@ -153,8 +153,6 @@ WorldCoords TransformUnit::ModelToWorldNormal(const ModelCoords &coords) {
|
||||
|
||||
template <bool depthClamp, bool alwaysCheckRange>
|
||||
static ScreenCoords ClipToScreenInternal(Vec3f scaled, const ClipCoords &coords, bool *outside_range_flag) {
|
||||
ScreenCoords ret;
|
||||
|
||||
// Account for rounding for X and Y.
|
||||
// TODO: Validate actual rounding range.
|
||||
constexpr float SCREEN_BOUND = 4095.0f + (15.5f / 16.0f);
|
||||
@@ -197,7 +195,7 @@ static inline ScreenCoords ClipToScreenInternal(const ClipCoords &coords, bool *
|
||||
float y = coords.y * yScale / coords.w + yCenter;
|
||||
float z = coords.z * zScale / coords.w + zCenter;
|
||||
|
||||
if (gstate.isDepthClampEnabled()) {
|
||||
if (gstate.isDepthClipEnabled()) {
|
||||
return ClipToScreenInternal<true, true>(Vec3f(x, y, z), coords, outside_range_flag);
|
||||
}
|
||||
return ClipToScreenInternal<false, true>(Vec3f(x, y, z), coords, outside_range_flag);
|
||||
@@ -315,7 +313,7 @@ void ComputeTransformState(TransformState *state, const VertexReader &vreader) {
|
||||
state->screenAdd = Vec3f(gstate.getViewportXCenter(), gstate.getViewportYCenter(), gstate.getViewportZCenter());
|
||||
}
|
||||
|
||||
if (gstate.isDepthClampEnabled())
|
||||
if (gstate.isDepthClipEnabled())
|
||||
state->roundToScreen = &ClipToScreenInternal<true, false>;
|
||||
else
|
||||
state->roundToScreen = &ClipToScreenInternal<false, false>;
|
||||
|
||||
@@ -43,10 +43,9 @@ enum class CullType {
|
||||
OFF = 2,
|
||||
};
|
||||
|
||||
struct ScreenCoords
|
||||
{
|
||||
ScreenCoords() {}
|
||||
ScreenCoords(int x, int y, u16 z) : x(x), y(y), z(z) {}
|
||||
struct ScreenCoords {
|
||||
ScreenCoords() = default;
|
||||
ScreenCoords(int _x, int _y, u16 _z) : x(_x), y(_y), z(_z) {}
|
||||
|
||||
int x;
|
||||
int y;
|
||||
@@ -54,24 +53,21 @@ struct ScreenCoords
|
||||
|
||||
Vec2<int> xy() const { return Vec2<int>(x, y); }
|
||||
|
||||
ScreenCoords operator * (const float t) const
|
||||
{
|
||||
ScreenCoords operator * (const float t) const {
|
||||
return ScreenCoords((int)(x * t), (int)(y * t), (u16)(z * t));
|
||||
}
|
||||
|
||||
ScreenCoords operator / (const int t) const
|
||||
{
|
||||
ScreenCoords operator / (const int t) const {
|
||||
return ScreenCoords(x / t, y / t, z / t);
|
||||
}
|
||||
|
||||
ScreenCoords operator + (const ScreenCoords& oth) const
|
||||
{
|
||||
ScreenCoords operator + (const ScreenCoords& oth) const {
|
||||
return ScreenCoords(x + oth.x, y + oth.y, z + oth.z);
|
||||
}
|
||||
};
|
||||
|
||||
struct DrawingCoords {
|
||||
DrawingCoords() {}
|
||||
DrawingCoords() = default;
|
||||
DrawingCoords(s16 x, s16 y) : x(x), y(y) {}
|
||||
|
||||
s16 x;
|
||||
@@ -165,6 +161,8 @@ private:
|
||||
friend SoftwareVertexReader;
|
||||
};
|
||||
|
||||
enum class ClipInfoFlags;
|
||||
|
||||
class SoftwareDrawEngine : public DrawEngineCommon {
|
||||
public:
|
||||
SoftwareDrawEngine();
|
||||
@@ -175,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) 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);
|
||||
|
||||
@@ -241,6 +241,27 @@ void DrawEngineVulkan::Flush() {
|
||||
provokingVertexOk = true;
|
||||
}
|
||||
bool useHWTransform = CanUseHardwareTransform(prim) && provokingVertexOk;
|
||||
if (clipInfoFlags_ & ClipInfoFlags::Valid) {
|
||||
if (clipInfoFlags_ & ClipInfoFlags::SoftClipCull) {
|
||||
useHWTransform = false;
|
||||
}
|
||||
}
|
||||
if (clipInfoFlags_ != lastClipInfoFlags_) {
|
||||
ClipInfoFlags changed = (ClipInfoFlags)((u32)clipInfoFlags_ ^ (u32)lastClipInfoFlags_);
|
||||
if (changed & (ClipInfoFlags::DepthClampFragment | ClipInfoFlags::MinMaxZDiscard)) {
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_RASTER_STATE);
|
||||
}
|
||||
lastClipInfoFlags_ = clipInfoFlags_;
|
||||
}
|
||||
|
||||
// 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);
|
||||
lastUseHwTransform_ = useHWTransform;
|
||||
}
|
||||
|
||||
// TODO: Here we can check depths_ to see if we need to fall back to software transform for clipping.
|
||||
|
||||
// The optimization to avoid indexing isn't really worth it on Vulkan since it means creating more pipelines.
|
||||
// This could be avoided with the new dynamic state extensions, but not available enough on mobile.
|
||||
@@ -285,28 +306,27 @@ void DrawEngineVulkan::Flush() {
|
||||
sampler = nullSampler_;
|
||||
}
|
||||
|
||||
if (!lastPipeline_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE) || prim != lastPrim_) {
|
||||
if (!lastPipeline_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE) || prim != lastPrim_) {
|
||||
if (prim != lastPrim_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE)) {
|
||||
ConvertStateToVulkanKey(*framebufferManager_, shaderManager_, prim, pipelineKey_, dynState_);
|
||||
}
|
||||
|
||||
VulkanVertexShader *vshader = nullptr;
|
||||
VulkanFragmentShader *fshader = nullptr;
|
||||
VulkanGeometryShader *gshader = nullptr;
|
||||
|
||||
shaderManager_->GetShaders(prim, dec_->VertexType(), &vshader, &fshader, &gshader, 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, gshader, true, 0, framebufferManager_->GetMSAALevel(), false);
|
||||
VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(renderManager, pipelineLayout_, pipelineKey_, &dec_->decFmt, vshader, fshader, true, 0, framebufferManager_->GetMSAALevel(), false);
|
||||
if (!pipeline || !pipeline->pipeline) {
|
||||
// Already logged, let's bail out.
|
||||
ResetAfterDraw();
|
||||
ResetAfterSkippedDraw();
|
||||
return;
|
||||
}
|
||||
BindShaderBlendTex(); // This might cause copies so important to do before BindPipeline.
|
||||
|
||||
if (!renderManager->BindPipeline(pipeline->pipeline, pipeline->pipelineFlags, pipelineLayout_)) {
|
||||
renderManager->ReportBadStateForDraw();
|
||||
ResetAfterDraw();
|
||||
ResetAfterSkippedDraw();
|
||||
return;
|
||||
}
|
||||
if (pipeline != lastPipeline_) {
|
||||
@@ -375,6 +395,8 @@ void DrawEngineVulkan::Flush() {
|
||||
DepthRasterSubmitRaw(prim, dec_, dec_->VertexType(), vertexCount);
|
||||
}
|
||||
} else {
|
||||
gpuStats.perFrame.numSoftTransformedDraws++;
|
||||
|
||||
PROFILE_THIS_SCOPE("soft");
|
||||
const VertexDecoder *swDec = dec_;
|
||||
if (swDec->nweights != 0) {
|
||||
@@ -400,20 +422,6 @@ void DrawEngineVulkan::Flush() {
|
||||
|
||||
// At this point, the output is always an indexed triangle/line/point list, no strips/fans.
|
||||
|
||||
// We need to update the viewport early because it's checked for flipping in SoftwareTransform.
|
||||
// We don't have a "DrawStateEarly" in vulkan, so...
|
||||
// TODO: Probably should eventually refactor this and feed the vp size into SoftwareTransform directly (Unknown's idea).
|
||||
if (gstate_c.IsDirty(DIRTY_VIEWPORTSCISSOR_STATE)) {
|
||||
ViewportAndScissor vpAndScissor;
|
||||
ConvertViewportAndScissor(
|
||||
framebufferManager_->GetDisplayLayoutConfigCopy(),
|
||||
framebufferManager_->UseBufferedRendering(),
|
||||
framebufferManager_->GetRenderWidth(), framebufferManager_->GetRenderHeight(),
|
||||
framebufferManager_->GetTargetBufferWidth(), framebufferManager_->GetTargetBufferHeight(),
|
||||
vpAndScissor);
|
||||
UpdateCachedViewportState(vpAndScissor);
|
||||
}
|
||||
|
||||
// At this point, rect and line primitives are still preserved as such. So, it's the best time to do software depth raster.
|
||||
// We could piggyback on the viewport transform below, but it gets complicated since it's different per-backend. Which we really
|
||||
// should clean up one day...
|
||||
@@ -444,19 +452,14 @@ void DrawEngineVulkan::Flush() {
|
||||
params.allowSeparateAlphaClear = false;
|
||||
params.everUsedEqualDepth = everUsedEqualDepth_;
|
||||
|
||||
SoftwareTransform swTransform(params);
|
||||
|
||||
const Lin::Vec3 trans(gstate_c.vpXOffset, gstate_c.vpYOffset, gstate_c.vpZOffset * 0.5f + 0.5f);
|
||||
const Lin::Vec3 scale(gstate_c.vpWidthScale, gstate_c.vpHeightScale, gstate_c.vpDepthScale * 0.5f);
|
||||
swTransform.SetProjMatrix(gstate.projMatrix, gstate_c.vpWidth < 0, gstate_c.vpHeight < 0, trans, scale);
|
||||
swTransform.Transform(prim, swDec->VertexType(), swDec->GetDecVtxFmt(), numDecodedVerts_, VERTEX_BUFFER_MAX, vertexCount, inds, RemainingIndices(inds), &result);
|
||||
const SoftwareTransformAction action = SoftwareTransform::Transform(params, prim, swDec->VertexType(), swDec->GetDecVtxFmt(), numDecodedVerts_, VERTEX_BUFFER_MAX, vertexCount, inds, RemainingIndices(inds), &result);
|
||||
|
||||
if (result.setSafeSize)
|
||||
framebufferManager_->SetSafeSize(result.safeWidth, result.safeHeight);
|
||||
|
||||
// Only here, where we know whether to clear or to draw primitives, should we actually set the current framebuffer! Because that gives use the opportunity
|
||||
// to use a "pre-clear" render pass, for high efficiency on tilers.
|
||||
if (result.action == SW_DRAW_INDEXED) {
|
||||
if (action == SW_DRAW_INDEXED) {
|
||||
if (textureNeedsApply) {
|
||||
gstate_c.pixelMapped = result.pixelMapped;
|
||||
gstate_c.dstSquared = false;
|
||||
@@ -471,28 +474,27 @@ void DrawEngineVulkan::Flush() {
|
||||
gstate_c.Dirty(DIRTY_BLEND_STATE);
|
||||
}
|
||||
}
|
||||
if (!lastPipeline_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE) || prim != lastPrim_) {
|
||||
if (!lastPipeline_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE) || prim != lastPrim_) {
|
||||
if (prim != lastPrim_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE)) {
|
||||
ConvertStateToVulkanKey(*framebufferManager_, shaderManager_, prim, pipelineKey_, dynState_);
|
||||
}
|
||||
|
||||
VulkanVertexShader *vshader = nullptr;
|
||||
VulkanFragmentShader *fshader = nullptr;
|
||||
VulkanGeometryShader *gshader = nullptr;
|
||||
|
||||
shaderManager_->GetShaders(prim, swDec->VertexType(), &vshader, &fshader, &gshader, 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, gshader, false, 0, framebufferManager_->GetMSAALevel(), false);
|
||||
VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(renderManager, pipelineLayout_, pipelineKey_, &swDec->decFmt, vshader, fshader, false, 0, framebufferManager_->GetMSAALevel(), false);
|
||||
if (!pipeline || !pipeline->pipeline) {
|
||||
// Already logged, let's bail out.
|
||||
ResetAfterDraw();
|
||||
ResetAfterSkippedDraw();
|
||||
return;
|
||||
}
|
||||
BindShaderBlendTex(); // This might cause copies so super important to do before BindPipeline.
|
||||
|
||||
if (!renderManager->BindPipeline(pipeline->pipeline, pipeline->pipelineFlags, pipelineLayout_)) {
|
||||
renderManager->ReportBadStateForDraw();
|
||||
ResetAfterDraw();
|
||||
ResetAfterSkippedDraw();
|
||||
return;
|
||||
}
|
||||
if (pipeline != lastPipeline_) {
|
||||
@@ -544,7 +546,7 @@ void DrawEngineVulkan::Flush() {
|
||||
u32 vbOffset = (uint32_t)pushVertex_->Push(result.drawBuffer, numDecodedVerts_ * sizeof(TransformedVertex), 4, &vbuf);
|
||||
u32 ibOffset = (uint32_t)pushIndex_->Push(inds, sizeof(short) * result.drawNumTrans, 4, &ibuf);
|
||||
renderManager->DrawIndexed(descSetIndex, ARRAY_SIZE(dynamicUBOOffsets), dynamicUBOOffsets, vbuf, vbOffset, ibuf, ibOffset, result.drawNumTrans, 1);
|
||||
} else if (result.action == SW_CLEAR) {
|
||||
} else if (action == SW_CLEAR) {
|
||||
// Note: we won't get here if the clear is alpha but not color, or color but not alpha.
|
||||
bool clearColor = gstate.isClearModeColorMask();
|
||||
bool clearAlpha = gstate.isClearModeAlphaMask(); // and stencil
|
||||
@@ -575,7 +577,7 @@ void DrawEngineVulkan::Flush() {
|
||||
gpuCommon_->NotifyFlush();
|
||||
}
|
||||
|
||||
void DrawEngineVulkan::ResetAfterDraw() {
|
||||
void DrawEngineVulkan::ResetAfterSkippedDraw() {
|
||||
indexGen.Reset();
|
||||
numDecodedVerts_ = 0;
|
||||
numDrawVerts_ = 0;
|
||||
|
||||
@@ -165,7 +165,7 @@ private:
|
||||
|
||||
void UpdateUBOs();
|
||||
|
||||
NO_INLINE void ResetAfterDraw();
|
||||
NO_INLINE void ResetAfterSkippedDraw();
|
||||
|
||||
Draw::DrawContext *draw_;
|
||||
|
||||
|
||||
@@ -122,9 +122,6 @@ void GPU_Vulkan::LoadCache(const Path &filename) {
|
||||
}
|
||||
if (result) {
|
||||
// Reload use flags in case LoadCacheFlags() changed them.
|
||||
if (drawEngineCommon_->EverUsedExactEqualDepth()) {
|
||||
sawExactEqualDepth_ = true;
|
||||
}
|
||||
gstate_c.SetUseFlags(CheckGPUFeatures());
|
||||
result = shaderManagerVulkan_->LoadCache(f);
|
||||
if (!result) {
|
||||
@@ -207,44 +204,6 @@ u32 GPU_Vulkan::CheckGPUFeatures() const {
|
||||
|
||||
VulkanContext *vulkan = (VulkanContext *)draw_->GetNativeObject(Draw::NativeObject::CONTEXT);
|
||||
|
||||
// Could simplify this, but it's good as documentation.
|
||||
switch (vulkan->GetPhysicalDeviceProperties().properties.vendorID) {
|
||||
case VULKAN_VENDOR_AMD:
|
||||
// Accurate depth is required on AMD (due to reverse-Z driver bug) so we ignore the compat flag to disable it on those. See #9545
|
||||
features |= GPU_USE_ACCURATE_DEPTH;
|
||||
break;
|
||||
case VULKAN_VENDOR_QUALCOMM:
|
||||
// Accurate depth is required on Adreno too (seems to also have a reverse-Z driver bug).
|
||||
features |= GPU_USE_ACCURATE_DEPTH;
|
||||
break;
|
||||
case VULKAN_VENDOR_ARM:
|
||||
{
|
||||
// This check is probably not exactly accurate. But old drivers had problems with reverse-Z, just like AMD and Qualcomm.
|
||||
|
||||
// NOTE: Galaxy S8 has version 16 but still seems to have some problems with accurate depth.
|
||||
|
||||
// TODO: Move this check to thin3d_vulkan.
|
||||
|
||||
bool driverTooOld = IsHashMaliDriverVersion(vulkan->GetPhysicalDeviceProperties().properties)
|
||||
|| VK_VERSION_MAJOR(vulkan->GetPhysicalDeviceProperties().properties.driverVersion) < 14;
|
||||
|
||||
if (!PSP_CoreParameter().compat.flags().DisableAccurateDepth || driverTooOld) {
|
||||
features |= GPU_USE_ACCURATE_DEPTH;
|
||||
} else {
|
||||
features &= ~GPU_USE_ACCURATE_DEPTH;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VULKAN_VENDOR_IMGTEC:
|
||||
// We ignore the disable flag on IMGTec. Another reverse-Z bug (plus, not really any reason to bother). See #17044
|
||||
features |= GPU_USE_ACCURATE_DEPTH;
|
||||
break;
|
||||
default:
|
||||
// On other GPUs we'll just assume we don't need inaccurate depth, leaving ARM Mali as the odd one out.
|
||||
features |= GPU_USE_ACCURATE_DEPTH;
|
||||
break;
|
||||
}
|
||||
|
||||
if (vulkan->SupportsPreRotation() && g_Config.bSkipBufferEffects) {
|
||||
features |= GPU_USE_PRE_ROTATION;
|
||||
}
|
||||
@@ -259,18 +218,6 @@ u32 GPU_Vulkan::CheckGPUFeatures() const {
|
||||
features |= GPU_USE_VERTEX_TEXTURE_FETCH;
|
||||
features |= GPU_USE_TEXTURE_FLOAT;
|
||||
|
||||
// Fall back to geometry shader culling if we can't do vertex range culling.
|
||||
// Checking accurate depth here because the old depth path is uncommon and not well tested for this.
|
||||
if (draw_->GetDeviceCaps().geometryShaderSupported && (features & GPU_USE_ACCURATE_DEPTH) != 0) {
|
||||
const bool useGeometry = g_Config.bUseGeometryShader && !draw_->GetBugs().Has(Draw::Bugs::GEOMETRY_SHADERS_SLOW_OR_BROKEN);
|
||||
const bool vertexSupported = draw_->GetDeviceCaps().maxClipDistances >= 2 && draw_->GetDeviceCaps().maxCullDistances >= 1;
|
||||
if (useGeometry && (!vertexSupported || (features & GPU_USE_VS_RANGE_CULLING) == 0)) {
|
||||
// Switch to culling via the geometry shader if not fully supported in vertex.
|
||||
features |= GPU_USE_GS_CULLING;
|
||||
features &= ~GPU_USE_VS_RANGE_CULLING;
|
||||
}
|
||||
}
|
||||
|
||||
if (!draw_->GetBugs().Has(Draw::Bugs::PVR_BAD_16BIT_TEXFORMATS)) {
|
||||
// These are VULKAN_4444_FORMAT and friends.
|
||||
// Note that we are now using the correct set of formats - the only cases where some may be missing
|
||||
@@ -288,12 +235,6 @@ u32 GPU_Vulkan::CheckGPUFeatures() const {
|
||||
if (g_Config.bStereoRendering && draw_->GetDeviceCaps().multiViewSupported) {
|
||||
features |= GPU_USE_SINGLE_PASS_STEREO;
|
||||
features |= GPU_USE_SIMPLE_STEREO_PERSPECTIVE;
|
||||
|
||||
if (features & GPU_USE_GS_CULLING) {
|
||||
// Many devices that support stereo and GS don't support GS during stereo.
|
||||
features &= ~GPU_USE_GS_CULLING;
|
||||
features |= GPU_USE_VS_RANGE_CULLING;
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to workaround #17386
|
||||
|
||||
@@ -186,7 +186,7 @@ static std::string CutFromMain(const std::string &str) {
|
||||
|
||||
static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager, VkPipelineCache pipelineCache,
|
||||
VKRPipelineLayout *layout, PipelineFlags pipelineFlags, VkSampleCountFlagBits sampleCount, const VulkanPipelineRasterStateKey &key,
|
||||
const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, VulkanGeometryShader *gs, bool useHwTransform, u32 variantBitmask, bool cacheLoad) {
|
||||
const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform, u32 variantBitmask, bool cacheLoad) {
|
||||
_assert_(fs && vs);
|
||||
|
||||
if (!fs || !fs->GetModule()) {
|
||||
@@ -197,10 +197,6 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager,
|
||||
ERROR_LOG(Log::G3D, "Vertex shader missing in CreateVulkanPipeline");
|
||||
return nullptr;
|
||||
}
|
||||
if (gs && !gs->GetModule()) {
|
||||
ERROR_LOG(Log::G3D, "Geometry shader missing in CreateVulkanPipeline");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VulkanPipeline *vulkanPipeline = new VulkanPipeline();
|
||||
vulkanPipeline->desc = new VKRGraphicsPipelineDesc();
|
||||
@@ -209,7 +205,7 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager,
|
||||
|
||||
desc->fragmentShader = fs->GetModule();
|
||||
desc->vertexShader = vs->GetModule();
|
||||
desc->geometryShader = gs ? gs->GetModule() : nullptr;
|
||||
desc->geometryShader = nullptr;
|
||||
|
||||
PROFILE_THIS_SCOPE("pipelinebuild");
|
||||
bool useBlendConstant = false;
|
||||
@@ -290,9 +286,6 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager,
|
||||
|
||||
desc->fragmentShaderSource = fs->GetShaderString(SHADER_STRING_SOURCE_CODE);
|
||||
desc->vertexShaderSource = vs->GetShaderString(SHADER_STRING_SOURCE_CODE);
|
||||
if (gs) {
|
||||
desc->geometryShaderSource = gs->GetShaderString(SHADER_STRING_SOURCE_CODE);
|
||||
}
|
||||
|
||||
_dbg_assert_(key.topology != VK_PRIMITIVE_TOPOLOGY_POINT_LIST);
|
||||
_dbg_assert_(key.topology != VK_PRIMITIVE_TOPOLOGY_LINE_LIST);
|
||||
@@ -344,9 +337,6 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager,
|
||||
if (useBlendConstant) {
|
||||
pipelineFlags |= PipelineFlags::USES_BLEND_CONSTANT;
|
||||
}
|
||||
if (gs) {
|
||||
pipelineFlags |= PipelineFlags::USES_GEOMETRY_SHADER;
|
||||
}
|
||||
if (dss.depthTestEnable || dss.stencilTestEnable) {
|
||||
pipelineFlags |= PipelineFlags::USES_DEPTH_STENCIL;
|
||||
}
|
||||
@@ -354,7 +344,7 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager,
|
||||
return vulkanPipeline;
|
||||
}
|
||||
|
||||
VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VulkanRenderManager *renderManager, VKRPipelineLayout *layout, const VulkanPipelineRasterStateKey &rasterKey, const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, VulkanGeometryShader *gs, bool useHwTransform, u32 variantBitmask, int multiSampleLevel, bool cacheLoad) {
|
||||
VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VulkanRenderManager *renderManager, VKRPipelineLayout *layout, const VulkanPipelineRasterStateKey &rasterKey, const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform, u32 variantBitmask, int multiSampleLevel, bool cacheLoad) {
|
||||
if (!pipelineCache_) {
|
||||
VkPipelineCacheCreateInfo pc{ VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO };
|
||||
VkResult res = vkCreatePipelineCache(vulkan_->GetDevice(), &pc, nullptr, &pipelineCache_);
|
||||
@@ -367,7 +357,6 @@ VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VulkanRenderManager *
|
||||
key.useHWTransform = useHwTransform;
|
||||
key.vShader = vs->GetModule();
|
||||
key.fShader = fs->GetModule();
|
||||
key.gShader = gs ? gs->GetModule() : VK_NULL_HANDLE;
|
||||
key.vtxFmtId = useHwTransform ? decFmt->id : 0;
|
||||
|
||||
VulkanPipeline *pipeline;
|
||||
@@ -390,7 +379,7 @@ VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VulkanRenderManager *
|
||||
|
||||
pipeline = CreateVulkanPipeline(
|
||||
renderManager, pipelineCache_, layout, pipelineFlags, sampleCount,
|
||||
rasterKey, decFmt, vs, fs, gs, useHwTransform, variantBitmask, cacheLoad);
|
||||
rasterKey, decFmt, vs, fs, useHwTransform, variantBitmask, cacheLoad);
|
||||
|
||||
// If the above failed, we got a null pipeline. We still insert it to keep track.
|
||||
pipelines_.Insert(key, pipeline);
|
||||
@@ -592,14 +581,10 @@ std::string VulkanPipelineKey::GetDescription(DebugShaderStringType stringType,
|
||||
// More detailed description of all the parts of the pipeline.
|
||||
VkShaderModule fsModule = this->fShader->BlockUntilReady();
|
||||
VkShaderModule vsModule = this->vShader->BlockUntilReady();
|
||||
VkShaderModule gsModule = this->gShader ? this->gShader->BlockUntilReady() : VK_NULL_HANDLE;
|
||||
|
||||
std::stringstream str;
|
||||
str << "VS: " << VertexShaderDesc(shaderManager->GetVertexShaderFromModule(vsModule)->GetID()) << std::endl;
|
||||
str << "FS: " << FragmentShaderDesc(shaderManager->GetFragmentShaderFromModule(fsModule)->GetID()) << std::endl;
|
||||
if (gsModule) {
|
||||
str << "GS: " << GeometryShaderDesc(shaderManager->GetGeometryShaderFromModule(gsModule)->GetID()) << std::endl;
|
||||
}
|
||||
str << GetRasterStateDesc(true);
|
||||
return str.str();
|
||||
}
|
||||
@@ -622,7 +607,7 @@ struct StoredVulkanPipelineKey {
|
||||
VulkanPipelineRasterStateKey raster;
|
||||
VShaderID vShaderID;
|
||||
FShaderID fShaderID;
|
||||
GShaderID gShaderID;
|
||||
FShaderID gShaderID; // keep the file format compatible
|
||||
uint32_t vtxFmtId;
|
||||
uint32_t variants;
|
||||
bool useHWTransform; // TODO: Still needed?
|
||||
@@ -673,13 +658,7 @@ void PipelineManagerVulkan::SavePipelineCache(FILE *file, bool saveRawPipelineCa
|
||||
return;
|
||||
VulkanVertexShader *vshader = shaderManager->GetVertexShaderFromModule(pkey.vShader->BlockUntilReady());
|
||||
VulkanFragmentShader *fshader = shaderManager->GetFragmentShaderFromModule(pkey.fShader->BlockUntilReady());
|
||||
VulkanGeometryShader *gshader = nullptr;
|
||||
if (pkey.gShader) {
|
||||
gshader = shaderManager->GetGeometryShaderFromModule(pkey.gShader->BlockUntilReady());
|
||||
if (!gshader)
|
||||
failed = true;
|
||||
}
|
||||
if (!vshader || !fshader || failed) {
|
||||
if (!vshader || !fshader) {
|
||||
failed = true;
|
||||
return;
|
||||
}
|
||||
@@ -689,7 +668,7 @@ void PipelineManagerVulkan::SavePipelineCache(FILE *file, bool saveRawPipelineCa
|
||||
key.useHWTransform = pkey.useHWTransform;
|
||||
key.fShaderID = fshader->GetID();
|
||||
key.vShaderID = vshader->GetID();
|
||||
key.gShaderID = gshader ? gshader->GetID() : GShaderID();
|
||||
key.gShaderID = {};
|
||||
key.variants = value->GetVariantsBitmask();
|
||||
if (key.useHWTransform) {
|
||||
// NOTE: This is not a vtype, but a decoded vertex format.
|
||||
@@ -804,13 +783,6 @@ bool PipelineManagerVulkan::LoadPipelineCache(FILE *file, bool loadRawPipelineCa
|
||||
|
||||
VulkanVertexShader *vs = shaderManager->GetVertexShaderFromID(key.vShaderID);
|
||||
VulkanFragmentShader *fs = shaderManager->GetFragmentShaderFromID(key.fShaderID);
|
||||
VulkanGeometryShader *gs = shaderManager->GetGeometryShaderFromID(key.gShaderID);
|
||||
if (!vs || !fs || (!gs && key.gShaderID.Bit(GS_BIT_ENABLED))) {
|
||||
// We just ignore this one, it'll get created later if needed.
|
||||
// Probably some useFlags mismatch.
|
||||
WARN_LOG(Log::G3D, "Failed to find vs or fs in pipeline %d in cache, skipping pipeline", (int)i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Avoid creating multisampled shaders if it's not enabled, as that results in an invalid combination.
|
||||
// Note that variantsToBuild is NOT directly a RenderPassType! instead, it's a collection of (1 << RenderPassType).
|
||||
@@ -826,7 +798,7 @@ bool PipelineManagerVulkan::LoadPipelineCache(FILE *file, bool loadRawPipelineCa
|
||||
DecVtxFormat fmt;
|
||||
fmt.InitializeFromID(key.vtxFmtId);
|
||||
VulkanPipeline *pipeline = GetOrCreatePipeline(
|
||||
rm, layout, key.raster, key.useHWTransform ? &fmt : 0, vs, fs, gs, key.useHWTransform, variantsToBuild, multiSampleLevel, true);
|
||||
rm, layout, key.raster, key.useHWTransform ? &fmt : 0, vs, fs, key.useHWTransform, variantsToBuild, multiSampleLevel, true);
|
||||
if (!pipeline) {
|
||||
pipelineCreateFailCount += 1;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ struct VulkanPipelineKey {
|
||||
VKRRenderPass *renderPass;
|
||||
Promise<VkShaderModule> *vShader;
|
||||
Promise<VkShaderModule> *fShader;
|
||||
Promise<VkShaderModule> *gShader;
|
||||
uint32_t vtxFmtId;
|
||||
bool useHWTransform;
|
||||
|
||||
@@ -88,7 +87,8 @@ public:
|
||||
~PipelineManagerVulkan();
|
||||
|
||||
// variantMask is only used when loading pipelines from cache.
|
||||
VulkanPipeline *GetOrCreatePipeline(VulkanRenderManager *renderManager, VKRPipelineLayout *layout, const VulkanPipelineRasterStateKey &rasterKey, const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, VulkanGeometryShader *gs, bool useHwTransform, u32 variantMask, int multiSampleLevel, bool cacheLoad);
|
||||
VulkanPipeline *GetOrCreatePipeline(VulkanRenderManager *renderManager, VKRPipelineLayout *layout, const VulkanPipelineRasterStateKey &rasterKey, const DecVtxFormat *decFmt,
|
||||
VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform, u32 variantMask, int multiSampleLevel, bool cacheLoad);
|
||||
int GetNumPipelines() const { return (int)pipelines_.size(); }
|
||||
|
||||
void Clear();
|
||||
@@ -97,7 +97,6 @@ public:
|
||||
void DeviceRestore(VulkanContext *vulkan);
|
||||
|
||||
void InvalidateMSAAPipelines();
|
||||
void BlockUntilReady();
|
||||
|
||||
std::string DebugGetObjectString(const std::string &id, DebugShaderType type, DebugShaderStringType stringType, ShaderManagerVulkan *shaderManager);
|
||||
std::vector<std::string> DebugGetObjectIDs(DebugShaderType type) const;
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
#include "GPU/GPUState.h"
|
||||
#include "GPU/Common/FragmentShaderGenerator.h"
|
||||
#include "GPU/Common/VertexShaderGenerator.h"
|
||||
#include "GPU/Common/GeometryShaderGenerator.h"
|
||||
#include "GPU/Vulkan/ShaderManagerVulkan.h"
|
||||
#include "GPU/Vulkan/DrawEngineVulkan.h"
|
||||
|
||||
@@ -167,42 +166,10 @@ std::string VulkanVertexShader::GetShaderString(DebugShaderStringType type) cons
|
||||
}
|
||||
}
|
||||
|
||||
VulkanGeometryShader::VulkanGeometryShader(VulkanContext *vulkan, GShaderID id, const char *code)
|
||||
: vulkan_(vulkan), id_(id) {
|
||||
_assert_(!id.is_invalid());
|
||||
source_ = code;
|
||||
module_ = CompileShaderModuleAsync(vulkan, VK_SHADER_STAGE_GEOMETRY_BIT, source_.c_str(), new std::string(GeometryShaderDesc(id).c_str()));
|
||||
VERBOSE_LOG(Log::G3D, "Compiled geometry shader:\n%s\n", (const char *)code);
|
||||
}
|
||||
|
||||
VulkanGeometryShader::~VulkanGeometryShader() {
|
||||
if (module_) {
|
||||
VkShaderModule shaderModule = module_->BlockUntilReady();
|
||||
if (shaderModule) {
|
||||
vulkan_->Delete().QueueDeleteShaderModule(shaderModule);
|
||||
}
|
||||
vulkan_->Delete().QueueCallback([](VulkanContext *vulkan, void *m) {
|
||||
auto module = (Promise<VkShaderModule> *)m;
|
||||
delete module;
|
||||
}, module_);
|
||||
}
|
||||
}
|
||||
|
||||
std::string VulkanGeometryShader::GetShaderString(DebugShaderStringType type) const {
|
||||
switch (type) {
|
||||
case SHADER_STRING_SOURCE_CODE:
|
||||
return source_;
|
||||
case SHADER_STRING_SHORT_DESC:
|
||||
return GeometryShaderDesc(id_);
|
||||
default:
|
||||
return "N/A";
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr size_t CODE_BUFFER_SIZE = 32768;
|
||||
|
||||
ShaderManagerVulkan::ShaderManagerVulkan(Draw::DrawContext *draw)
|
||||
: ShaderManagerCommon(draw), compat_(GLSL_VULKAN), fsCache_(16), vsCache_(16), gsCache_(16) {
|
||||
: ShaderManagerCommon(draw), compat_(GLSL_VULKAN), fsCache_(16), vsCache_(16) {
|
||||
codeBuffer_ = new char[CODE_BUFFER_SIZE];
|
||||
VulkanContext *vulkan = (VulkanContext *)draw->GetNativeObject(Draw::NativeObject::CONTEXT);
|
||||
uboAlignment_ = vulkan->GetPhysicalDeviceProperties().properties.limits.minUniformBufferOffsetAlignment;
|
||||
@@ -239,43 +206,35 @@ void ShaderManagerVulkan::Clear() {
|
||||
vsCache_.Iterate([&](const VShaderID &key, VulkanVertexShader *shader) {
|
||||
delete shader;
|
||||
});
|
||||
gsCache_.Iterate([&](const GShaderID &key, VulkanGeometryShader *shader) {
|
||||
delete shader;
|
||||
});
|
||||
fsCache_.Clear();
|
||||
vsCache_.Clear();
|
||||
gsCache_.Clear();
|
||||
lastFSID_.set_invalid();
|
||||
lastVSID_.set_invalid();
|
||||
lastGSID_.set_invalid();
|
||||
lastVShader_ = nullptr;
|
||||
lastFShader_ = nullptr;
|
||||
lastGShader_ = nullptr;
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE);
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE);
|
||||
}
|
||||
|
||||
void ShaderManagerVulkan::ClearShaders() {
|
||||
Clear();
|
||||
DirtyLastShader();
|
||||
gstate_c.Dirty(DIRTY_ALL_UNIFORMS | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE);
|
||||
gstate_c.Dirty(DIRTY_ALL_UNIFORMS | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE);
|
||||
}
|
||||
|
||||
void ShaderManagerVulkan::DirtyLastShader() {
|
||||
// Forget the last shader ID
|
||||
lastFSID_.set_invalid();
|
||||
lastVSID_.set_invalid();
|
||||
lastGSID_.set_invalid();
|
||||
lastVShader_ = nullptr;
|
||||
lastFShader_ = nullptr;
|
||||
lastGShader_ = nullptr;
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE | DIRTY_GEOMETRYSHADER_STATE);
|
||||
gstate_c.Dirty(DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE);
|
||||
}
|
||||
|
||||
uint64_t ShaderManagerVulkan::UpdateUniforms(bool useBufferedRendering) {
|
||||
uint64_t dirty = gstate_c.GetDirtyUniforms();
|
||||
if (dirty != 0) {
|
||||
if (dirty & DIRTY_BASE_UNIFORMS)
|
||||
BaseUpdateUniforms(&uniforms_->ub_base, dirty, false, useBufferedRendering);
|
||||
BaseUpdateUniforms(&uniforms_->ub_base, dirty, useBufferedRendering);
|
||||
if (dirty & DIRTY_LIGHT_UNIFORMS)
|
||||
LightUpdateUniforms(&uniforms_->ub_lights, dirty);
|
||||
if (dirty & DIRTY_BONE_UNIFORMS)
|
||||
@@ -285,14 +244,17 @@ uint64_t ShaderManagerVulkan::UpdateUniforms(bool useBufferedRendering) {
|
||||
return dirty;
|
||||
}
|
||||
|
||||
void ShaderManagerVulkan::GetShaders(int prim, u32 vertexType, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, VulkanGeometryShader **gshader, 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;
|
||||
VulkanVertexShader *vs = nullptr;
|
||||
bool recomputedVS = false;
|
||||
bool recomputedFS = false;
|
||||
if (gstate_c.IsDirty(DIRTY_VERTEXSHADER_STATE)) {
|
||||
gstate_c.Clean(DIRTY_VERTEXSHADER_STATE);
|
||||
ComputeVertexShaderID(&VSID, vertexType, useHWTransform, useHWTessellation, weightsAsFloat, useSkinInDecode);
|
||||
recomputedVS = true;
|
||||
ComputeVertexShaderID(&VSID, vertexType, useHWTransform, useHWTessellation, weightsAsFloat, useSkinInDecode, clipInfoFlags);
|
||||
if (VSID == lastVSID_) {
|
||||
_dbg_assert_(lastVShader_ != nullptr);
|
||||
vs = lastVShader_;
|
||||
@@ -322,7 +284,8 @@ 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());
|
||||
ComputeFragmentShaderID(&FSID, pipelineState, draw_->GetBugs(), clipInfoFlags);
|
||||
recomputedFS = true;
|
||||
if (FSID == lastFSID_) {
|
||||
_dbg_assert_(lastFShader_ != nullptr);
|
||||
fs = lastFShader_;
|
||||
@@ -346,41 +309,12 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertexType, VulkanVertexShade
|
||||
}
|
||||
*fshader = fs;
|
||||
|
||||
GShaderID GSID;
|
||||
VulkanGeometryShader *gs = nullptr;
|
||||
if (gstate_c.IsDirty(DIRTY_GEOMETRYSHADER_STATE)) {
|
||||
gstate_c.Clean(DIRTY_GEOMETRYSHADER_STATE);
|
||||
ComputeGeometryShaderID(&GSID, draw_->GetBugs(), prim);
|
||||
if (GSID == lastGSID_) {
|
||||
// it's ok for this to be null.
|
||||
gs = lastGShader_;
|
||||
} else if (GSID.Bit(GS_BIT_ENABLED)) {
|
||||
if (!gsCache_.Get(GSID, &gs)) {
|
||||
// Geometry shader not in cache. Let's compile it.
|
||||
std::string genErrorString;
|
||||
bool success = GenerateGeometryShader(GSID, codeBuffer_, compat_, draw_->GetBugs(), &genErrorString);
|
||||
_assert_msg_(success, "GS gen error: %s", genErrorString.c_str());
|
||||
_assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "GS length error: %d", (int)strlen(codeBuffer_));
|
||||
|
||||
gs = new VulkanGeometryShader(vulkan, GSID, codeBuffer_);
|
||||
gsCache_.Insert(GSID, gs);
|
||||
}
|
||||
} else {
|
||||
gs = nullptr;
|
||||
}
|
||||
lastGShader_ = gs;
|
||||
lastGSID_ = GSID;
|
||||
} else {
|
||||
GSID = lastGSID_;
|
||||
gs = lastGShader_;
|
||||
}
|
||||
*gshader = gs;
|
||||
|
||||
// If you hit these, look at recomputedVS and recomputedFS to determine if it's a dirty-flag problem
|
||||
// or an ID generation problem (if any of them are false, it's a dirty-flag problem).
|
||||
_dbg_assert_(FSID.Bit(FS_BIT_FLATSHADE) == VSID.Bit(VS_BIT_FLATSHADE));
|
||||
_dbg_assert_(FSID.Bit(FS_BIT_LMODE) == VSID.Bit(VS_BIT_LMODE));
|
||||
if (GSID.Bit(GS_BIT_ENABLED)) {
|
||||
_dbg_assert_(GSID.Bit(GS_BIT_LMODE) == VSID.Bit(VS_BIT_LMODE));
|
||||
}
|
||||
_dbg_assert_(FSID.Bit(FS_BIT_MINMAX_DISCARD) == VSID.Bit(VS_BIT_FS_MINMAX_DISCARD));
|
||||
_dbg_assert_(FSID.Bit(FS_BIT_DEPTH_CLAMP) == VSID.Bit(VS_BIT_FS_DEPTH_CLAMP));
|
||||
|
||||
_dbg_assert_msg_((*vshader)->UseHWTransform() == useHWTransform, "Bad vshader was computed");
|
||||
}
|
||||
@@ -403,12 +337,7 @@ std::vector<std::string> ShaderManagerVulkan::DebugGetShaderIDs(DebugShaderType
|
||||
});
|
||||
break;
|
||||
case SHADER_TYPE_GEOMETRY:
|
||||
gsCache_.Iterate([&](const GShaderID &id, VulkanGeometryShader *shader) {
|
||||
std::string idstr;
|
||||
id.ToString(&idstr);
|
||||
ids.push_back(idstr);
|
||||
});
|
||||
break;
|
||||
return ids;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -439,12 +368,7 @@ std::string ShaderManagerVulkan::DebugGetShaderString(std::string id, DebugShade
|
||||
}
|
||||
case SHADER_TYPE_GEOMETRY:
|
||||
{
|
||||
VulkanGeometryShader *gs;
|
||||
if (gsCache_.Get(GShaderID(shaderId), &gs)) {
|
||||
return gs ? gs->GetShaderString(stringType) : "null (bad)";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
default:
|
||||
return "N/A";
|
||||
@@ -473,17 +397,6 @@ VulkanFragmentShader *ShaderManagerVulkan::GetFragmentShaderFromModule(VkShaderM
|
||||
return fs;
|
||||
}
|
||||
|
||||
VulkanGeometryShader *ShaderManagerVulkan::GetGeometryShaderFromModule(VkShaderModule module) {
|
||||
VulkanGeometryShader *gs = nullptr;
|
||||
gsCache_.Iterate([&](const GShaderID &id, VulkanGeometryShader *shader) {
|
||||
Promise<VkShaderModule> *p = shader->GetModule();
|
||||
VkShaderModule m = p->BlockUntilReady();
|
||||
if (m == module)
|
||||
gs = shader;
|
||||
});
|
||||
return gs;
|
||||
}
|
||||
|
||||
// Shader cache.
|
||||
//
|
||||
// We simply store the IDs of the shaders used during gameplay. On next startup of
|
||||
@@ -497,7 +410,7 @@ enum class VulkanCacheDetectFlags {
|
||||
};
|
||||
|
||||
#define CACHE_HEADER_MAGIC 0xff51f420
|
||||
#define CACHE_VERSION 53
|
||||
#define CACHE_VERSION 55
|
||||
|
||||
struct VulkanCacheHeader {
|
||||
uint32_t magic;
|
||||
@@ -524,10 +437,6 @@ bool ShaderManagerVulkan::LoadCacheFlags(FILE *f, DrawEngineVulkan *drawEngine)
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((header.detectFlags & (uint32_t)VulkanCacheDetectFlags::EQUAL_DEPTH) != 0) {
|
||||
drawEngine->SetEverUsedExactEqualDepth(true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -596,29 +505,6 @@ bool ShaderManagerVulkan::LoadCache(FILE *f) {
|
||||
}
|
||||
}
|
||||
|
||||
// If it's not enabled, don't create shaders cached from earlier runs - creation will likely fail.
|
||||
if (gstate_c.Use(GPU_USE_GS_CULLING)) {
|
||||
for (int i = 0; i < header.numGeometryShaders; i++) {
|
||||
GShaderID id;
|
||||
if (fread(&id, sizeof(id), 1, f) != 1) {
|
||||
ERROR_LOG(Log::G3D, "Vulkan shader cache truncated (in GeometryShaders)");
|
||||
return false;
|
||||
}
|
||||
std::string genErrorString;
|
||||
if (!GenerateGeometryShader(id, codeBuffer_, compat_, draw_->GetBugs(), &genErrorString)) {
|
||||
ERROR_LOG(Log::G3D, "Failed to generate geometry shader during cache load");
|
||||
// We just ignore this one and carry on.
|
||||
failCount++;
|
||||
continue;
|
||||
}
|
||||
_assert_msg_(strlen(codeBuffer_) < CODE_BUFFER_SIZE, "GS length error: %d", (int)strlen(codeBuffer_));
|
||||
if (!gsCache_.ContainsKey(id)) {
|
||||
VulkanGeometryShader *gs = new VulkanGeometryShader(vulkan, id, codeBuffer_);
|
||||
gsCache_.Insert(id, gs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NOTICE_LOG(Log::G3D, "ShaderCache: Loaded %d vertex, %d fragment shaders and %d geometry shaders (failed %d)", header.numVertexShaders, header.numFragmentShaders, header.numGeometryShaders, failCount);
|
||||
return true;
|
||||
}
|
||||
@@ -629,11 +515,9 @@ void ShaderManagerVulkan::SaveCache(FILE *f, DrawEngineVulkan *drawEngine) {
|
||||
header.version = CACHE_VERSION;
|
||||
header.useFlags = gstate_c.GetUseFlags();
|
||||
header.detectFlags = 0;
|
||||
if (drawEngine->EverUsedExactEqualDepth())
|
||||
header.detectFlags |= (uint32_t)VulkanCacheDetectFlags::EQUAL_DEPTH;
|
||||
header.numVertexShaders = (int)vsCache_.size();
|
||||
header.numFragmentShaders = (int)fsCache_.size();
|
||||
header.numGeometryShaders = (int)gsCache_.size();
|
||||
header.numGeometryShaders = 0;
|
||||
bool writeFailed = fwrite(&header, sizeof(header), 1, f) != 1;
|
||||
vsCache_.Iterate([&](const VShaderID &id, VulkanVertexShader *vs) {
|
||||
writeFailed = writeFailed || fwrite(&id, sizeof(id), 1, f) != 1;
|
||||
@@ -641,9 +525,6 @@ void ShaderManagerVulkan::SaveCache(FILE *f, DrawEngineVulkan *drawEngine) {
|
||||
fsCache_.Iterate([&](const FShaderID &id, VulkanFragmentShader *fs) {
|
||||
writeFailed = writeFailed || fwrite(&id, sizeof(id), 1, f) != 1;
|
||||
});
|
||||
gsCache_.Iterate([&](const GShaderID &id, VulkanGeometryShader *gs) {
|
||||
writeFailed = writeFailed || fwrite(&id, sizeof(id), 1, f) != 1;
|
||||
});
|
||||
if (writeFailed) {
|
||||
ERROR_LOG(Log::G3D, "Failed to write Vulkan shader cache, disk full?");
|
||||
} else {
|
||||
|
||||
@@ -85,26 +85,6 @@ protected:
|
||||
VertexShaderFlags flags_;
|
||||
};
|
||||
|
||||
class VulkanGeometryShader {
|
||||
public:
|
||||
VulkanGeometryShader(VulkanContext *vulkan, GShaderID id, const char *code);
|
||||
~VulkanGeometryShader();
|
||||
|
||||
const std::string &source() const { return source_; }
|
||||
|
||||
std::string GetShaderString(DebugShaderStringType type) const;
|
||||
|
||||
Promise<VkShaderModule> *GetModule() const { return module_; }
|
||||
const GShaderID &GetID() { return id_; }
|
||||
|
||||
protected:
|
||||
Promise<VkShaderModule> *module_ = nullptr;
|
||||
|
||||
VulkanContext *vulkan_;
|
||||
std::string source_;
|
||||
GShaderID id_;
|
||||
};
|
||||
|
||||
struct Uniforms {
|
||||
// Uniform block scratchpad. These (the relevant ones) are copied to the current pushbuffer at draw time.
|
||||
UB_VS_FS_Base ub_base{};
|
||||
@@ -112,6 +92,8 @@ struct Uniforms {
|
||||
UB_VS_Bones ub_bones{};
|
||||
};
|
||||
|
||||
enum class ClipInfoFlags;
|
||||
|
||||
class ShaderManagerVulkan : public ShaderManagerCommon {
|
||||
public:
|
||||
ShaderManagerVulkan(Draw::DrawContext *draw);
|
||||
@@ -120,22 +102,19 @@ public:
|
||||
void DeviceLost() override;
|
||||
void DeviceRestore(Draw::DrawContext *draw) override;
|
||||
|
||||
void GetShaders(int prim, u32 vertexType, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, VulkanGeometryShader **gshader, 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;
|
||||
|
||||
int GetNumVertexShaders() const { return (int)vsCache_.size(); }
|
||||
int GetNumFragmentShaders() const { return (int)fsCache_.size(); }
|
||||
int GetNumGeometryShaders() const { return (int)gsCache_.size(); }
|
||||
|
||||
// Used for saving/loading the cache. Don't need to be particularly fast.
|
||||
VulkanVertexShader *GetVertexShaderFromID(VShaderID id) { return vsCache_.GetOrNull(id); }
|
||||
VulkanFragmentShader *GetFragmentShaderFromID(FShaderID id) { return fsCache_.GetOrNull(id); }
|
||||
VulkanGeometryShader *GetGeometryShaderFromID(GShaderID id) { return gsCache_.GetOrNull(id); }
|
||||
|
||||
VulkanVertexShader *GetVertexShaderFromModule(VkShaderModule module);
|
||||
VulkanFragmentShader *GetFragmentShaderFromModule(VkShaderModule module);
|
||||
VulkanGeometryShader *GetGeometryShaderFromModule(VkShaderModule module);
|
||||
|
||||
std::vector<std::string> DebugGetShaderIDs(DebugShaderType type) override;
|
||||
std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType) override;
|
||||
@@ -174,9 +153,6 @@ private:
|
||||
typedef DenseHashMap<VShaderID, VulkanVertexShader *> VSCache;
|
||||
VSCache vsCache_;
|
||||
|
||||
typedef DenseHashMap<GShaderID, VulkanGeometryShader *> GSCache;
|
||||
GSCache gsCache_;
|
||||
|
||||
char *codeBuffer_;
|
||||
|
||||
uint64_t uboAlignment_;
|
||||
@@ -185,9 +161,7 @@ private:
|
||||
|
||||
VulkanFragmentShader *lastFShader_ = nullptr;
|
||||
VulkanVertexShader *lastVShader_ = nullptr;
|
||||
VulkanGeometryShader *lastGShader_ = nullptr;
|
||||
|
||||
FShaderID lastFSID_;
|
||||
VShaderID lastVSID_;
|
||||
GShaderID lastGSID_;
|
||||
};
|
||||
|
||||
@@ -224,9 +224,8 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag
|
||||
key.depthClampEnable = false;
|
||||
} else {
|
||||
if (gstate.getDepthRangeMin() == 0 || gstate.getDepthRangeMax() == 65535) {
|
||||
// TODO: Still has a bug where we clamp to depth range if one is not the full range.
|
||||
// But the alternate is not clamping in either direction...
|
||||
key.depthClampEnable = gstate.isDepthClampEnabled() && gstate_c.Use(GPU_USE_DEPTH_CLAMP);
|
||||
// We get some extra clamping behavior if clipping is enabled.
|
||||
key.depthClampEnable = gstate.isDepthClipEnabled() && gstate_c.Use(GPU_USE_DEPTH_CLAMP);
|
||||
} else {
|
||||
// We just want to clip in this case, the clamp would be clipped anyway.
|
||||
key.depthClampEnable = false;
|
||||
@@ -333,21 +332,13 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag
|
||||
fbManager.GetRenderWidth(), fbManager.GetRenderHeight(),
|
||||
fbManager.GetTargetBufferWidth(), fbManager.GetTargetBufferHeight(),
|
||||
vpAndScissor);
|
||||
UpdateCachedViewportState(vpAndScissor);
|
||||
|
||||
float depthMin = vpAndScissor.depthRangeMin;
|
||||
float depthMax = vpAndScissor.depthRangeMax;
|
||||
|
||||
if (depthMin < 0.0f) depthMin = 0.0f;
|
||||
if (depthMax > 1.0f) depthMax = 1.0f;
|
||||
|
||||
VkViewport &vp = dynState.viewport;
|
||||
vp.x = vpAndScissor.viewportX;
|
||||
vp.y = vpAndScissor.viewportY;
|
||||
vp.width = vpAndScissor.viewportW;
|
||||
vp.height = vpAndScissor.viewportH;
|
||||
vp.minDepth = vpAndScissor.depthRangeMin;
|
||||
vp.maxDepth = vpAndScissor.depthRangeMax;
|
||||
vp.minDepth = 0.0f;
|
||||
vp.maxDepth = 1.0f;
|
||||
|
||||
ScissorRect &scissor = dynState.scissor;
|
||||
scissor.x = vpAndScissor.scissorX;
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ enum GECommand : uint8_t {
|
||||
GE_CMD_LIGHTENABLE1 = 0x19,
|
||||
GE_CMD_LIGHTENABLE2 = 0x1A,
|
||||
GE_CMD_LIGHTENABLE3 = 0x1B,
|
||||
GE_CMD_DEPTHCLAMPENABLE = 0x1C,
|
||||
GE_CMD_DEPTHCLIPENABLE = 0x1C,
|
||||
GE_CMD_CULLFACEENABLE = 0x1D,
|
||||
GE_CMD_TEXTUREMAPENABLE = 0x1E,
|
||||
GE_CMD_FOGENABLE = 0x1F,
|
||||
|
||||
@@ -1407,7 +1407,7 @@ static const StateItem g_rasterState[] = {
|
||||
{true, GE_CMD_NOP, "Clipping/Clamping"},
|
||||
{false, GE_CMD_MINZ},
|
||||
{false, GE_CMD_MAXZ},
|
||||
{false, GE_CMD_DEPTHCLAMPENABLE},
|
||||
{false, GE_CMD_DEPTHCLIPENABLE},
|
||||
|
||||
{true, GE_CMD_NOP, "Other raster state"},
|
||||
{false, GE_CMD_MASKRGB},
|
||||
|
||||
@@ -442,6 +442,11 @@ bool ViewSearch::Key(UI::ViewGroup *viewGroup, const KeyInput &input) {
|
||||
if (input.flags & KeyInputFlags::CHAR) {
|
||||
const int unichar = input.keyCode;
|
||||
if (unichar >= 0x20 && unichar != 127) { // 127 gets produced from Ctrl+Backspace on Windows for some reason.
|
||||
// Don't allow spaces as the first character, it looks confusing (empty search field)
|
||||
if (searchFilter.empty() && unichar == ' ') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Save focus state here.
|
||||
// Insert it! (todo: do it with a string insert)
|
||||
char buf[8];
|
||||
|
||||
@@ -94,7 +94,6 @@
|
||||
<ClInclude Include="..\..\GPU\Common\DrawEngineCommon.h" />
|
||||
<ClInclude Include="..\..\GPU\Common\FragmentShaderGenerator.h" />
|
||||
<ClInclude Include="..\..\GPU\Common\FramebufferManagerCommon.h" />
|
||||
<ClInclude Include="..\..\GPU\Common\GeometryShaderGenerator.h" />
|
||||
<ClInclude Include="..\..\GPU\Common\PresentationCommon.h" />
|
||||
<ClInclude Include="..\..\GPU\Common\GPUDebugInterface.h" />
|
||||
<ClInclude Include="..\..\GPU\Common\GPUStateUtils.h" />
|
||||
@@ -163,7 +162,6 @@
|
||||
<ClCompile Include="..\..\GPU\Common\DrawEngineCommon.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Common\FragmentShaderGenerator.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Common\FramebufferManagerCommon.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Common\GeometryShaderGenerator.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Common\PresentationCommon.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Common\GPUDebugInterface.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Common\GPUStateUtils.cpp" />
|
||||
@@ -239,4 +237,4 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
<ClCompile Include="..\..\GPU\Software\RasterizerRectangle.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Software\RasterizerRegCache.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Common\FragmentShaderGenerator.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Common\GeometryShaderGenerator.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Common\VertexShaderGenerator.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Common\ReinterpretFramebuffer.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Common\Draw2D.cpp" />
|
||||
@@ -132,7 +131,6 @@
|
||||
<ClInclude Include="..\..\GPU\Software\RasterizerRectangle.h" />
|
||||
<ClInclude Include="..\..\GPU\Software\RasterizerRegCache.h" />
|
||||
<ClInclude Include="..\..\GPU\Common\FragmentShaderGenerator.h" />
|
||||
<ClInclude Include="..\..\GPU\Common\GeometryShaderGenerator.h" />
|
||||
<ClInclude Include="..\..\GPU\Common\VertexShaderGenerator.h" />
|
||||
<ClInclude Include="..\..\GPU\Common\ReinterpretFramebuffer.h" />
|
||||
<ClInclude Include="..\..\GPU\Common\Draw2D.h" />
|
||||
@@ -171,4 +169,4 @@
|
||||
<UniqueIdentifier>{49bcf7f6-518a-4ecd-af55-bda3a344efe7}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -66,7 +66,7 @@ const GECommand g_stateFlagsRows[] = {
|
||||
GE_CMD_LIGHTENABLE1,
|
||||
GE_CMD_LIGHTENABLE2,
|
||||
GE_CMD_LIGHTENABLE3,
|
||||
GE_CMD_DEPTHCLAMPENABLE,
|
||||
GE_CMD_DEPTHCLIPENABLE,
|
||||
GE_CMD_CULLFACEENABLE,
|
||||
GE_CMD_TEXTUREMAPENABLE,
|
||||
GE_CMD_FOGENABLE,
|
||||
|
||||
@@ -560,7 +560,6 @@ EXEC_AND_LIB_FILES := \
|
||||
$(SRC)/GPU/Common/PostShader.cpp \
|
||||
$(SRC)/GPU/Common/ShaderUniforms.cpp \
|
||||
$(SRC)/GPU/Common/VertexShaderGenerator.cpp \
|
||||
$(SRC)/GPU/Common/GeometryShaderGenerator.cpp \
|
||||
$(SRC)/GPU/Common/TextureReplacer.cpp \
|
||||
$(SRC)/GPU/Common/ReplacedTexture.cpp \
|
||||
$(SRC)/GPU/Debugger/Breakpoints.cpp \
|
||||
|
||||
+4
-35
@@ -135,6 +135,9 @@ NPJH50625 = true
|
||||
ULJM08069 = true
|
||||
NPJH50625 = true
|
||||
|
||||
# Kidou Keisatsu
|
||||
ULJS00026 = true
|
||||
|
||||
[DepthRangeHack]
|
||||
# Phantasy Star Portable 2 and Infinity as well as some FromSoftware titles both use viewport depth outside [0, 1].
|
||||
# This gets clamped in our current implementation, but attempts to fix it run into
|
||||
@@ -234,6 +237,7 @@ NPEH90042 = true # demo
|
||||
# This applies a hack to Dangan Ronpa, its demo, and its sequel, Super Dangan Ronpa 2.
|
||||
# The game draws solid colors to a small framebuffer, and then reads this directly in VRAM.
|
||||
# We force this framebuffer to 1x and force download it automatically.
|
||||
# It might be even better to extend the software depth rasterizer to do basic color.
|
||||
|
||||
# Danganronpa
|
||||
ULJS00337 = true
|
||||
@@ -555,41 +559,6 @@ ULES00251 = true
|
||||
ULUS10068 = true
|
||||
ULJM05043 = true
|
||||
|
||||
[DisableAccurateDepth]
|
||||
# Midnight Club: LA Remix
|
||||
ULUS10383 = true
|
||||
ULES01144 = true
|
||||
ULJS00180 = true
|
||||
ULJS00267 = true
|
||||
ULJM05904 = true
|
||||
NPJH50440 = true
|
||||
# Midnight Club 3: DUB Edition
|
||||
ULUS10021 = true
|
||||
ULES00108 = true
|
||||
|
||||
# Shadow of Destiny (#9545)
|
||||
ULUS10459 = true
|
||||
NPJH50036 = true
|
||||
|
||||
# Burnout games have problems with this on Mali, and have no use for it
|
||||
# Legends
|
||||
#ULES00125 = true
|
||||
#ULUS10025 = true
|
||||
#ULJM05228 = true
|
||||
#NPJH50305 = true
|
||||
#ULJM05049 = true
|
||||
#ULKS46027 = true
|
||||
#ULAS42019 = true
|
||||
|
||||
# Dominator
|
||||
ULUS10236 = true
|
||||
ULES00750 = true
|
||||
ULJM05242 = true
|
||||
ULJM05371 = true
|
||||
NPJH50304 = true
|
||||
ULES00703 = true
|
||||
ULAS42095 = true
|
||||
|
||||
[RequireDefaultCPUClock]
|
||||
# GOW : Ghost of Sparta
|
||||
UCUS98737 = true
|
||||
|
||||
@@ -591,7 +591,6 @@ SOURCES_CXX += \
|
||||
$(GPUDIR)/Debugger/Stepping.cpp \
|
||||
$(GPUDIR)/Common/FragmentShaderGenerator.cpp \
|
||||
$(GPUDIR)/Common/VertexShaderGenerator.cpp \
|
||||
$(GPUDIR)/Common/GeometryShaderGenerator.cpp \
|
||||
$(GPUDIR)/Common/TextureCacheCommon.cpp \
|
||||
$(GPUDIR)/Common/TextureScalerCommon.cpp \
|
||||
$(GPUDIR)/Common/SoftwareTransformCommon.cpp \
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
#include "GPU/Common/FragmentShaderGenerator.h"
|
||||
#include "GPU/Common/VertexShaderGenerator.h"
|
||||
#include "GPU/Common/GeometryShaderGenerator.h"
|
||||
#include "GPU/Common/ReinterpretFramebuffer.h"
|
||||
#include "GPU/Common/StencilCommon.h"
|
||||
#include "GPU/Common/DepalettizeShaderCommon.h"
|
||||
@@ -90,34 +89,6 @@ bool GenerateVShader(VShaderID id, char *buffer, ShaderLanguage lang, Draw::Bugs
|
||||
}
|
||||
}
|
||||
|
||||
bool GenerateGShader(GShaderID id, char *buffer, ShaderLanguage lang, Draw::Bugs bugs, std::string *errorString) {
|
||||
buffer[0] = '\0';
|
||||
|
||||
errorString->clear();
|
||||
|
||||
switch (lang) {
|
||||
case ShaderLanguage::GLSL_VULKAN:
|
||||
{
|
||||
ShaderLanguageDesc compat(ShaderLanguage::GLSL_VULKAN);
|
||||
return GenerateGeometryShader(id, buffer, compat, bugs, errorString);
|
||||
}
|
||||
/*
|
||||
case ShaderLanguage::GLSL_3xx:
|
||||
{
|
||||
ShaderLanguageDesc compat(ShaderLanguage::GLSL_3xx);
|
||||
return GenerateGeometryShader(id, buffer, compat, bugs, errorString);
|
||||
}
|
||||
case ShaderLanguage::HLSL_D3D11:
|
||||
{
|
||||
ShaderLanguageDesc compat(ShaderLanguage::HLSL_D3D11);
|
||||
return GenerateGeometryShader(id, buffer, compat, bugs, errorString);
|
||||
}
|
||||
*/
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static VkShaderStageFlagBits StageToVulkan(ShaderStage stage) {
|
||||
switch (stage) {
|
||||
case ShaderStage::Vertex: return VK_SHADER_STAGE_VERTEX_BIT;
|
||||
@@ -521,77 +492,6 @@ bool TestFragmentShaders() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool TestGeometryShaders() {
|
||||
char *buffer[numLanguages];
|
||||
|
||||
for (int i = 0; i < numLanguages; i++) {
|
||||
buffer[i] = new char[65536];
|
||||
}
|
||||
GMRng rng;
|
||||
int successes = 0;
|
||||
int count = 30;
|
||||
|
||||
Draw::Bugs bugs;
|
||||
|
||||
// Generate a bunch of random fragment shader IDs, try to generate shader source.
|
||||
// Then compile it and check that it's ok.
|
||||
for (int i = 0; i < count; i++) {
|
||||
uint32_t bottom = i << 1;
|
||||
GShaderID id;
|
||||
id.d[0] = bottom;
|
||||
id.d[1] = 0;
|
||||
|
||||
id.SetBit(GS_BIT_ENABLED, true);
|
||||
|
||||
bool generateSuccess[numLanguages]{};
|
||||
std::string genErrorString[numLanguages];
|
||||
|
||||
for (int j = 0; j < numLanguages; j++) {
|
||||
buffer[j][0] = 0;
|
||||
generateSuccess[j] = GenerateGShader(id, buffer[j], languages[j], bugs, &genErrorString[j]);
|
||||
if (!genErrorString[j].empty()) {
|
||||
printf("%s\n", genErrorString[j].c_str());
|
||||
}
|
||||
// We ignore the contents of the error string here, not even gonna try to compile if it errors.
|
||||
}
|
||||
|
||||
for (int j = 0; j < numLanguages; j++) {
|
||||
if (strlen(buffer[j]) >= CODE_BUFFER_SIZE) {
|
||||
printf("Geometry shader exceeded buffer:\n\n%s\n", LineNumberString(buffer[j]).c_str());
|
||||
for (int i = 0; i < numLanguages; i++) {
|
||||
delete[] buffer[i];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we have the strings ready for easy comparison (buffer,4 in the watch window),
|
||||
// let's try to compile them.
|
||||
for (int j = 0; j < numLanguages; j++) {
|
||||
if (generateSuccess[j]) {
|
||||
std::string errorMessage;
|
||||
if (!TestCompileShader(buffer[j], languages[j], ShaderStage::Geometry, &errorMessage)) {
|
||||
printf("Error compiling geometry shader:\n\n%s\n\n%s\n", LineNumberString(buffer[j]).c_str(), errorMessage.c_str());
|
||||
for (int i = 0; i < numLanguages; i++) {
|
||||
delete[] buffer[i];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
successes++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("%d/%d geometry shaders generated (it's normal that it's not all, there are invalid bit combos)\n", successes, count * numLanguages);
|
||||
|
||||
for (int i = 0; i < numLanguages; i++) {
|
||||
delete[] buffer[i];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool TestShaderGenerators() {
|
||||
#if PPSSPP_PLATFORM(WINDOWS)
|
||||
LoadD3D11();
|
||||
@@ -604,10 +504,6 @@ bool TestShaderGenerators() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TestGeometryShaders()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TestReinterpretShaders()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -902,13 +902,8 @@ static bool TestDepthMath() {
|
||||
// over in GPUStateUtils.cpp):
|
||||
static const u32 useFlagsArray[] = {
|
||||
0,
|
||||
GPU_USE_ACCURATE_DEPTH,
|
||||
GPU_USE_ACCURATE_DEPTH | GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT,
|
||||
GPU_USE_DEPTH_CLAMP | GPU_USE_ACCURATE_DEPTH,
|
||||
GPU_USE_DEPTH_CLAMP | GPU_USE_ACCURATE_DEPTH | GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT, // Here, GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT should take precedence over USE_DEPTH_CLAMP.
|
||||
GPU_USE_DEPTH_CLAMP,
|
||||
};
|
||||
static const float expectedScale[] = { 65535.0f, 262140.0f, 16777215.0f, 65535.0f, 16777215.0f, };
|
||||
static const float expectedOffset[] = { 0.0f, 0.375f, 0.498047f, 0.0f, 0.498047f, };
|
||||
|
||||
EXPECT_REL_EQ_FLOAT(100000.0f, 100001.0f, 0.00001f);
|
||||
|
||||
@@ -917,8 +912,6 @@ static bool TestDepthMath() {
|
||||
printf("j: %d useflags: %d\n", j, useFlags);
|
||||
DepthScaleFactors factors = GetDepthScaleFactors(useFlags);
|
||||
|
||||
EXPECT_EQ_FLOAT(factors.ScaleU16(), expectedScale[j]);
|
||||
EXPECT_REL_EQ_FLOAT(factors.Offset(), expectedOffset[j], 0.00001f);
|
||||
EXPECT_REL_EQ_FLOAT(factors.Scale(), DepthSliceFactor(useFlags), 0.0001f);
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(testValues); i++) {
|
||||
|
||||
Reference in New Issue
Block a user