From 70bb4e1c85b90aa907d3371276eeac5efcd915b9 Mon Sep 17 00:00:00 2001 From: Henrik Rydgard Date: Sat, 20 Apr 2013 20:20:03 +0200 Subject: [PATCH] Fudge GPU cycle estimates to let GoW runs fast and GTA smooth. See comments for the horrible details. I hope this doesn't affect other games adversely. --- GPU/GLES/DisplayListInterpreter.cpp | 18 ++++++-- GPU/GLES/TransformPipeline.cpp | 71 ++++++++++++++++++++++------- GPU/GLES/TransformPipeline.h | 5 ++ GPU/GLES/VertexShaderGenerator.cpp | 24 +++++----- 4 files changed, 86 insertions(+), 32 deletions(-) diff --git a/GPU/GLES/DisplayListInterpreter.cpp b/GPU/GLES/DisplayListInterpreter.cpp index b92462aac7..eace61701c 100644 --- a/GPU/GLES/DisplayListInterpreter.cpp +++ b/GPU/GLES/DisplayListInterpreter.cpp @@ -321,21 +321,25 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) { // This drives all drawing. All other state we just buffer up, then we apply it only // when it's time to draw. As most PSP games set state redundantly ALL THE TIME, this is a huge optimization. + u32 count = data & 0xFFFF; + u32 type = data >> 16; + // This also make skipping drawing very effective. framebufferManager_.SetRenderFrameBuffer(); if (gstate_c.skipDrawReason & (SKIPDRAW_SKIPFRAME | SKIPDRAW_NON_DISPLAYED_FB)) + { + transformDraw_.SetupVertexDecoder(gstate.vertType); + // Rough estimate, not sure what's correct. + int vertexCost = transformDraw_.EstimatePerVertexCost(); + cyclesExecuted += vertexCost * count; return; - - u32 count = data & 0xFFFF; - u32 type = data >> 16; + } if (!Memory::IsValidAddress(gstate_c.vertexAddr)) { ERROR_LOG(G3D, "Bad vertex address %08x!", gstate_c.vertexAddr); break; } - // Rough estimate, not sure what's correct. - cyclesExecuted += 80 * count; // TODO: Split this so that we can collect sequences of primitives, can greatly speed things up // on platforms where draw calls are expensive like mobile and D3D @@ -351,6 +355,10 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) { int bytesRead; transformDraw_.SubmitPrim(verts, inds, type, count, gstate.vertType, -1, &bytesRead); + + int vertexCost = transformDraw_.EstimatePerVertexCost(); + cyclesExecuted += vertexCost * count; + // After drawing, we advance the vertexAddr (when non indexed) or indexAddr (when indexed). // Some games rely on this, they don't bother reloading VADDR and IADDR. // Q: Are these changed reflected in the real registers? Needs testing. diff --git a/GPU/GLES/TransformPipeline.cpp b/GPU/GLES/TransformPipeline.cpp index 6cd715955f..f9ef854ea9 100644 --- a/GPU/GLES/TransformPipeline.cpp +++ b/GPU/GLES/TransformPipeline.cpp @@ -18,17 +18,19 @@ #include "base/timeutil.h" #include "Common/MemoryUtil.h" -#include "../../Core/MemMap.h" -#include "../../Core/Host.h" -#include "../../Core/System.h" -#include "../../Core/Reporting.h" -#include "../../native/gfx_es2/gl_state.h" -#include "../../native/ext/cityhash/city.h" +#include "Core/MemMap.h" +#include "Core/Host.h" +#include "Core/System.h" +#include "Core/Reporting.h" +#include "Core/Config.h" +#include "Core/CoreTiming.h" -#include "../Math3D.h" -#include "../GPUState.h" -#include "../ge_constants.h" -#include "../../Core/Config.h" +#include "native/gfx_es2/gl_state.h" +#include "native/ext/cityhash/city.h" + +#include "GPU/Math3D.h" +#include "GPU/GPUState.h" +#include "GPU/ge_constants.h" #include "StateMapping.h" #include "TextureCache.h" @@ -807,6 +809,48 @@ VertexDecoder *TransformDrawEngine::GetVertexDecoder(u32 vtype) { return dec; } +void TransformDrawEngine::SetupVertexDecoder(u32 vertType) { + // If vtype has changed, setup the vertex decoder. + // TODO: Simply cache the setup decoders instead. + if (vertType != lastVType_) { + dec_ = GetVertexDecoder(vertType); + lastVType_ = vertType; + } +} + +int TransformDrawEngine::EstimatePerVertexCost() { + // TODO: This is transform cost, also account for rasterization cost somehow... although it probably + // runs in parallel with transform. + + // Also, this is all pure guesswork. If we can find a way to do measurements, that would be great. + + // GTA wants a low value to run smooth, GoW wants a high value (otherwise it thinks things + // went too fast and starts doing all the work over again). + + int cost = 20; + if (gstate.isLightingEnabled()) { + cost += 10; + } + + for (int i = 0; i < 4; i++) { + if (gstate.lightEnable[i] & 1) + cost += 20; + } + if (gstate.getUVGenMode() != 0) { + cost += 20; + } + if (dec_ && dec_->morphcount > 1) { + cost += 5 * dec_->morphcount; + } + + if (CoreTiming::GetClockFrequencyMHz() == 333) { + // Just brutally double to make God of War happier. + // FUDGE FACTORS! Delicious fudge factors! + cost *= 2; + } + return cost; +} + void TransformDrawEngine::SubmitPrim(void *verts, void *inds, int prim, int vertexCount, u32 vertType, int forceIndexType, int *bytesRead) { if (vertexCount == 0) return; // we ignore zero-sized draw calls. @@ -814,12 +858,7 @@ void TransformDrawEngine::SubmitPrim(void *verts, void *inds, int prim, int vert if (!indexGen.PrimCompatible(prevPrim_, prim) || numDrawCalls >= MAX_DEFERRED_DRAW_CALLS) Flush(); prevPrim_ = prim; - // If vtype has changed, setup the vertex decoder. - // TODO: Simply cache the setup decoders instead. - if (vertType != lastVType_) { - dec_ = GetVertexDecoder(vertType); - lastVType_ = vertType; - } + SetupVertexDecoder(vertType); dec_->IncrementStat(STAT_VERTSSUBMITTED, vertexCount); diff --git a/GPU/GLES/TransformPipeline.h b/GPU/GLES/TransformPipeline.h index c5e201cad1..15abf50fb1 100644 --- a/GPU/GLES/TransformPipeline.h +++ b/GPU/GLES/TransformPipeline.h @@ -119,6 +119,11 @@ public: void DecimateTrackedVertexArrays(); void ClearTrackedVertexArrays(); + void SetupVertexDecoder(u32 vertType); + + // This requires a SetupVertexDecoder call first. + int EstimatePerVertexCost(); + private: void SoftwareTransformAndDraw(int prim, u8 *decoded, LinkedShader *program, int vertexCount, u32 vertexType, void *inds, int indexType, const DecVtxFormat &decVtxFormat, int maxIndex); void ApplyDrawState(int prim); diff --git a/GPU/GLES/VertexShaderGenerator.cpp b/GPU/GLES/VertexShaderGenerator.cpp index 4b63443620..5d584e16fb 100644 --- a/GPU/GLES/VertexShaderGenerator.cpp +++ b/GPU/GLES/VertexShaderGenerator.cpp @@ -44,12 +44,13 @@ bool CanUseHardwareTransform(int prim) { // prim so we can special case for RECTANGLES :( void ComputeVertexShaderID(VertexShaderID *id, int prim) { + const u32 vertType = gstate.vertType; int doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear(); bool doTextureProjection = gstate.getUVGenMode() == 1; - bool hasColor = (gstate.vertType & GE_VTYPE_COL_MASK) != 0; - bool hasNormal = (gstate.vertType & GE_VTYPE_NRM_MASK) != 0; - bool hasBones = (gstate.vertType & GE_VTYPE_WEIGHT_MASK) != 0; + bool hasColor = (vertType & GE_VTYPE_COL_MASK) != 0; + bool hasNormal = (vertType & GE_VTYPE_NRM_MASK) != 0; + bool hasBones = (vertType & GE_VTYPE_WEIGHT_MASK) != 0; bool enableFog = gstate.isFogEnabled() && !gstate.isModeThrough() && !gstate.isModeClear(); bool lmode = (gstate.lmode & 1) && gstate.isLightingEnabled(); @@ -143,15 +144,16 @@ void GenerateVertexShader(int prim, char *buffer) { WRITE(p, "#define lowp\n"); WRITE(p, "#define mediump\n"); #endif + const u32 vertType = gstate.vertType; int lmode = (gstate.lmode & 1) && gstate.isLightingEnabled(); int doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear(); bool hwXForm = CanUseHardwareTransform(prim); - bool hasColor = (gstate.vertType & GE_VTYPE_COL_MASK) != 0 || !hwXForm; - bool hasNormal = (gstate.vertType & GE_VTYPE_NRM_MASK) != 0 && hwXForm; + bool hasColor = (vertType & GE_VTYPE_COL_MASK) != 0 || !hwXForm; + bool hasNormal = (vertType & GE_VTYPE_NRM_MASK) != 0 && hwXForm; bool enableFog = gstate.isFogEnabled() && !gstate.isModeThrough() && !gstate.isModeClear(); - bool throughmode = (gstate.vertType & GE_VTYPE_THROUGH_MASK) != 0; + bool throughmode = (vertType & GE_VTYPE_THROUGH_MASK) != 0; bool flipV = gstate_c.flipTexture; bool doTextureProjection = gstate.getUVGenMode() == 1; @@ -169,7 +171,7 @@ void GenerateVertexShader(int prim, char *buffer) { } } - if ((gstate.vertType & GE_VTYPE_WEIGHT_MASK) != GE_VTYPE_WEIGHT_NONE) { + if ((vertType & GE_VTYPE_WEIGHT_MASK) != GE_VTYPE_WEIGHT_NONE) { WRITE(p, "%s", boneWeightAttrDecl[gstate.getNumBoneWeights() - 1]); } @@ -215,8 +217,8 @@ void GenerateVertexShader(int prim, char *buffer) { WRITE(p, "uniform vec4 u_uvscaleoffset;\n"); else if (gstate.getUVGenMode() == 1) WRITE(p, "uniform mat4 u_texmtx;\n"); - if ((gstate.vertType & GE_VTYPE_WEIGHT_MASK) != GE_VTYPE_WEIGHT_NONE) { - int numBones = 1 + ((gstate.vertType & GE_VTYPE_WEIGHTCOUNT_MASK) >> GE_VTYPE_WEIGHTCOUNT_SHIFT); + if ((vertType & GE_VTYPE_WEIGHT_MASK) != GE_VTYPE_WEIGHT_NONE) { + int numBones = 1 + ((vertType & GE_VTYPE_WEIGHTCOUNT_MASK) >> GE_VTYPE_WEIGHTCOUNT_SHIFT); for (int i = 0; i < numBones; i++) { WRITE(p, "uniform mat4 u_bone%i;\n", i); } @@ -283,7 +285,7 @@ void GenerateVertexShader(int prim, char *buffer) { } } else { // Step 1: World Transform / Skinning - if ((gstate.vertType & GE_VTYPE_WEIGHT_MASK) == GE_VTYPE_WEIGHT_NONE) { + if ((vertType & GE_VTYPE_WEIGHT_MASK) == GE_VTYPE_WEIGHT_NONE) { // No skinning, just standard T&L. WRITE(p, " vec3 worldpos = (u_world * vec4(a_position.xyz, 1.0)).xyz;\n"); if (hasNormal) @@ -292,7 +294,7 @@ void GenerateVertexShader(int prim, char *buffer) { WRITE(p, " vec3 worldpos = vec3(0.0);\n"); if (hasNormal) WRITE(p, " vec3 worldnormal = vec3(0.0);\n"); - int numWeights = 1 + ((gstate.vertType & GE_VTYPE_WEIGHTCOUNT_MASK) >> GE_VTYPE_WEIGHTCOUNT_SHIFT); + int numWeights = 1 + ((vertType & GE_VTYPE_WEIGHTCOUNT_MASK) >> GE_VTYPE_WEIGHTCOUNT_SHIFT); for (int i = 0; i < numWeights; i++) { const char *weightAttr = boneWeightAttr[i]; // workaround for "cant do .x of scalar" issue