diff --git a/Core/CmdLine.cpp b/Core/CmdLine.cpp index b3119cd4c2..79d0a04069 100644 --- a/Core/CmdLine.cpp +++ b/Core/CmdLine.cpp @@ -344,6 +344,9 @@ int CommandLineOptions::PrintUsage(const char *progname, const char *situationTe PRINT_STDOUT(" --graphics=BACKEND use a different gpu backend\n"); PRINT_STDOUT(" options: gles, software, etc. (also opengl3.1, etc.)\n"); + if (mode == CmdLineMode::Headless) { + PRINT_STDOUT(" and null, which draws nothing\n"); + } return 0; } @@ -466,6 +469,10 @@ CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[], C } else if (restOfOption == "software") { gpuBackend = GPUBackend::OPENGL; softwareRendering = true; + } else if (restOfOption == "null" && mode == CmdLineMode::Headless) { + // Runs display lists but draws nothing, for measuring the CPU emulation. + nullGraphics = true; + softwareRendering = false; } else if (sscanf(restOfOption.c_str(), "gles%lg", &glVersionTemp) == 1 || sscanf(restOfOption.c_str(), "opengl%lg", &glVersionTemp) == 1) { gpuBackend = GPUBackend::OPENGL; softwareRendering = false; diff --git a/Core/CmdLine.h b/Core/CmdLine.h index da797d26c0..720ce698db 100644 --- a/Core/CmdLine.h +++ b/Core/CmdLine.h @@ -33,6 +33,8 @@ struct CommandLineOptions { std::optional fullscreen; std::optional gpuBackend; std::optional softwareRendering; + // --graphics=null: headless only, see GPU_Null.h. + std::optional nullGraphics; std::optional enableLogging; std::optional logLevel; // Override log level with this. std::optional log; diff --git a/Core/CoreParameter.h b/Core/CoreParameter.h index 9db8701b8c..195a49d25a 100644 --- a/Core/CoreParameter.h +++ b/Core/CoreParameter.h @@ -28,6 +28,7 @@ enum GPUCore : int { GPUCORE_SOFTWARE = 1, GPUCORE_DIRECTX11 = 3, GPUCORE_VULKAN = 4, + GPUCORE_NULL = 5, // Draws nothing, see GPU_Null.h. }; enum class FPSLimit { diff --git a/Core/System.cpp b/Core/System.cpp index 7c9567ade7..d5254c5c99 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -636,7 +636,7 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin // Update things that depend on game-specific config here. // Compat settings can override the software renderer, take care of that here. - if (g_Config.bSoftwareRendering || g_CoreParameter.compat.flags().ForceSoftwareRenderer) { + if ((g_Config.bSoftwareRendering || g_CoreParameter.compat.flags().ForceSoftwareRenderer) && g_CoreParameter.gpuCore != GPUCORE_NULL) { g_CoreParameter.gpuCore = GPUCORE_SOFTWARE; } diff --git a/GPU/CMakeLists.txt b/GPU/CMakeLists.txt index 2bb8284fcc..a17a4a48a5 100644 --- a/GPU/CMakeLists.txt +++ b/GPU/CMakeLists.txt @@ -189,6 +189,8 @@ set(GPU_SOURCES Software/Sampler.h Software/SoftGpu.cpp Software/SoftGpu.h + Null/GPU_Null.cpp + Null/GPU_Null.h Software/TransformUnit.cpp Software/TransformUnit.h ) diff --git a/GPU/GPU.cpp b/GPU/GPU.cpp index d9502fc6e8..b6161d6ab3 100644 --- a/GPU/GPU.cpp +++ b/GPU/GPU.cpp @@ -31,6 +31,7 @@ #endif #include "GPU/Vulkan/GPU_Vulkan.h" #include "GPU/Software/SoftGpu.h" +#include "GPU/Null/GPU_Null.h" #if PPSSPP_API(D3D11) #include "GPU/D3D11/GPU_D3D11.h" @@ -69,13 +70,15 @@ static GPUCommon *CreateGPUCore(GPUCore gpuCore, GraphicsContext *ctx, Draw::Dra } return new GPU_Vulkan(ctx, draw); #endif + case GPUCORE_NULL: + return CreateNullGPU(ctx); default: return nullptr; } } bool GPU_Init(GPUCore gpuCore, GraphicsContext *ctx, Draw::DrawContext *draw) { - _dbg_assert_(draw || gpuCore == GPUCORE_SOFTWARE); + _dbg_assert_(draw || gpuCore == GPUCORE_SOFTWARE || gpuCore == GPUCORE_NULL); _dbg_assert_(!gpu); GPUCommon *createdGPU = CreateGPUCore(gpuCore, ctx, draw); diff --git a/GPU/GPU.vcxproj b/GPU/GPU.vcxproj index 37b12e9950..760262b184 100644 --- a/GPU/GPU.vcxproj +++ b/GPU/GPU.vcxproj @@ -347,6 +347,7 @@ + @@ -471,6 +472,7 @@ + diff --git a/GPU/GPU.vcxproj.filters b/GPU/GPU.vcxproj.filters index 300e791cdc..50c96af50e 100644 --- a/GPU/GPU.vcxproj.filters +++ b/GPU/GPU.vcxproj.filters @@ -4,6 +4,9 @@ {f7563dba-8146-4c21-a092-e864ff145d79} + + {9879c213-d775-44fc-b1e4-a9d19ac2dff5} + {4f6d1284-2c23-4ebc-842c-666a1305bfed} @@ -48,6 +51,9 @@ Software + + Null + Software @@ -299,6 +305,9 @@ Software + + Null + Software diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 6c17295cc4..113ebad623 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -1629,6 +1629,269 @@ void GPUCommon::FastLoadBoneMatrix(u32 target) { } } +void GPUCommon::Execute_WorldMtxNum(u32 op, u32 diff) { + if (!currentList) { + gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (op & 0xF); + return; + } + + // This is almost always followed by GE_CMD_WORLDMATRIXDATA. + const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); + u32 *dst = (u32 *)(gstate.worldMatrix + (op & 0xF)); + const int end = 12 - (op & 0xF); + int i = 0; + + // We must record the individual data commands while debugRecording_. + bool fastLoad = !debugRecording_ && end > 0; + // Stalling in the middle of a matrix would be stupid, I doubt this check is necessary. + if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { + fastLoad = false; + } + + if (fastLoad) { + while ((src[i] >> 24) == GE_CMD_WORLDMATRIXDATA) { + const u32 newVal = src[i] << 8; + if (dst[i] != newVal) { + Flush(); + dst[i] = newVal; + gstate_c.Dirty(DIRTY_WORLDMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX); + } + if (++i >= end) { + break; + } + } + } + + const int count = i; + gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | ((op & 0xF) + count); + + // Skip over the loaded data, it's done now. + currentList->pc += count * 4; + UpdatePC(currentList->pc, currentList->pc); +} + +void GPUCommon::Execute_WorldMtxData(u32 op, u32 diff) { + // Note: it's uncommon to get here now, see above. + int num = gstate.worldmtxnum & 0x00FFFFFF; + u32 newVal = op << 8; + 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 | DIRTY_CULL_MATRIX); + } + num++; + gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (num & 0x00FFFFFF); + gstate.worldmtxdata = GE_CMD_WORLDMATRIXDATA << 24; +} + +void GPUCommon::Execute_ViewMtxNum(u32 op, u32 diff) { + if (!currentList) { + gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (op & 0xF); + return; + } + + // This is almost always followed by GE_CMD_VIEWMATRIXDATA. + const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); + u32 *dst = (u32 *)(gstate.viewMatrix + (op & 0xF)); + const int end = 12 - (op & 0xF); + int i = 0; + + bool fastLoad = !debugRecording_ && end > 0; + if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { + fastLoad = false; + } + + if (fastLoad) { + while ((src[i] >> 24) == GE_CMD_VIEWMATRIXDATA) { + const u32 newVal = src[i] << 8; + if (dst[i] != newVal) { + Flush(); + dst[i] = newVal; + gstate_c.Dirty(DIRTY_VIEWMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX) ; + } + if (++i >= end) { + break; + } + } + } + + const int count = i; + gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | ((op & 0xF) + count); + + // Skip over the loaded data, it's done now. + currentList->pc += count * 4; + UpdatePC(currentList->pc, currentList->pc); +} + +void GPUCommon::Execute_ViewMtxData(u32 op, u32 diff) { + // Note: it's uncommon to get here now, see above. + int num = gstate.viewmtxnum & 0x00FFFFFF; + u32 newVal = op << 8; + 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 | DIRTY_CULL_MATRIX); + } + num++; + gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (num & 0x00FFFFFF); + gstate.viewmtxdata = GE_CMD_VIEWMATRIXDATA << 24; +} + +void GPUCommon::Execute_ProjMtxNum(u32 op, u32 diff) { + if (!currentList) { + gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | (op & 0xF); + return; + } + + // This is almost always followed by GE_CMD_PROJMATRIXDATA. + const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); + u32 *dst = (u32 *)(gstate.projMatrix + (op & 0xF)); + const int end = 16 - (op & 0xF); + int i = 0; + + bool fastLoad = !debugRecording_; + if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { + fastLoad = false; + } + + if (fastLoad) { + while ((src[i] >> 24) == GE_CMD_PROJMATRIXDATA) { + const u32 newVal = src[i] << 8; + if (dst[i] != newVal) { + Flush(); + dst[i] = newVal; + gstate_c.Dirty(DIRTY_PROJMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX); + } + if (++i >= end) { + break; + } + } + } + + const int count = i; + gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | ((op & 0xF) + count); + + // Skip over the loaded data, it's done now. + currentList->pc += count * 4; + UpdatePC(currentList->pc, currentList->pc); +} + +void GPUCommon::Execute_ProjMtxData(u32 op, u32 diff) { + // Note: it's uncommon to get here now, see above. + int num = gstate.projmtxnum & 0x00FFFFFF; + u32 newVal = op << 8; + 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 | DIRTY_CULL_MATRIX); + } + num++; + if (num <= 16) + gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | (num & 0x00FFFFFF); + gstate.projmtxdata = GE_CMD_PROJMATRIXDATA << 24; +} + +void GPUCommon::Execute_TgenMtxNum(u32 op, u32 diff) { + if (!currentList) { + gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | (op & 0xF); + return; + } + + // This is almost always followed by GE_CMD_TGENMATRIXDATA. + const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); + u32 *dst = (u32 *)(gstate.tgenMatrix + (op & 0xF)); + const int end = 12 - (op & 0xF); + int i = 0; + + bool fastLoad = !debugRecording_ && end > 0; + if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { + fastLoad = false; + } + + if (fastLoad) { + while ((src[i] >> 24) == GE_CMD_TGENMATRIXDATA) { + const u32 newVal = src[i] << 8; + if (dst[i] != newVal) { + Flush(); + dst[i] = newVal; + // We check the matrix to see if we need projection. + gstate_c.Dirty(DIRTY_TEXMATRIX | DIRTY_FRAGMENTSHADER_STATE); + } + if (++i >= end) { + break; + } + } + } + + const int count = i; + gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | ((op & 0xF) + count); + + // Skip over the loaded data, it's done now. + currentList->pc += count * 4; + UpdatePC(currentList->pc, currentList->pc); +} + +void GPUCommon::Execute_TgenMtxData(u32 op, u32 diff) { + // Note: it's uncommon to get here now, see above. + int num = gstate.texmtxnum & 0x00FFFFFF; + u32 newVal = op << 8; + if (num < 12 && newVal != ((const u32 *)gstate.tgenMatrix)[num]) { + Flush(); + ((u32 *)gstate.tgenMatrix)[num] = newVal; + gstate_c.Dirty(DIRTY_TEXMATRIX | DIRTY_FRAGMENTSHADER_STATE); // We check the matrix to see if we need projection + } + num++; + gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | (num & 0x00FFFFFF); + gstate.texmtxdata = GE_CMD_TGENMATRIXDATA << 24; +} + +void GPUCommon::Execute_BoneMtxNum(u32 op, u32 diff) { + if (!currentList) { + gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (op & 0x7F); + return; + } + + // This is almost always followed by GE_CMD_BONEMATRIXDATA. + const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); + u32 *dst = (u32 *)(gstate.boneMatrix + (op & 0x7F)); + const int end = 12 * 8 - (op & 0x7F); + int i = 0; + + bool fastLoad = !debugRecording_ && end > 0; + if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { + fastLoad = false; + } + + if (fastLoad) { + while ((src[i] >> 24) == GE_CMD_BONEMATRIXDATA) { + dst[i] = src[i] << 8; + if (++i >= end) { + break; + } + } + } + + const int count = i; + gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | ((op & 0x7F) + count); + + // Skip over the loaded data, it's done now. + currentList->pc += count * 4; + UpdatePC(currentList->pc, currentList->pc); +} + +void GPUCommon::Execute_BoneMtxData(u32 op, u32 diff) { + // Note: it's uncommon to get here now, see above. + int num = gstate.boneMatrixNumber & 0x00FFFFFF; + u32 newVal = op << 8; + if (num < 96 && newVal != ((const u32 *)gstate.boneMatrix)[num]) { + // Bone matrices should NOT flush, as we're always doing skinning in decode nowadays! + ((u32 *)gstate.boneMatrix)[num] = newVal; + } + num++; + gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (num & 0x00FFFFFF); + gstate.boneMatrixData = GE_CMD_BONEMATRIXDATA << 24; +} + struct DisplayList_v1 { int id; u32 startpc; diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 04a57ba4e2..12ab3eb933 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -194,6 +194,18 @@ public: void Execute_ImmVertexAlphaPrim(u32 op, u32 diff); + // Matrix loads, with a fast path for the DATA commands following a NUM. SoftGPU has its own. + void Execute_WorldMtxNum(u32 op, u32 diff); + void Execute_WorldMtxData(u32 op, u32 diff); + void Execute_ViewMtxNum(u32 op, u32 diff); + void Execute_ViewMtxData(u32 op, u32 diff); + void Execute_ProjMtxNum(u32 op, u32 diff); + void Execute_ProjMtxData(u32 op, u32 diff); + void Execute_TgenMtxNum(u32 op, u32 diff); + void Execute_TgenMtxData(u32 op, u32 diff); + void Execute_BoneMtxNum(u32 op, u32 diff); + void Execute_BoneMtxData(u32 op, u32 diff); + void Execute_Unknown(u32 op, u32 diff); static int EstimatePerVertexCost(); diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 269435a77a..26e00da8ec 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -878,12 +878,10 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) { SetDrawType(DRAW_PRIM, prim); // Discard AA lines as we can't do anything that makes sense with these anyway. The SW plugin might, though. - if (gstate.isAntiAliasEnabled()) { - // Heuristic derived from discussions in #6483 and #12588. - // Discard AA lines in Persona 3 Portable, DOA Paradise and Summon Night 5, while still keeping AA lines in Echochrome. - if ((prim == GE_PRIM_LINE_STRIP || prim == GE_PRIM_LINES) && gstate.getTextureFunction() == GE_TEXFUNC_REPLACE) - return; - } + // Heuristic derived from discussions in #6483 and #12588. + // Discard AA lines in Persona 3 Portable, DOA Paradise and Summon Night 5, while still keeping AA lines in Echochrome. + // They're skipped like culled draws, so the pointers still move past them and the time is still taken. + const bool discardLines = gstate.isAntiAliasEnabled() && (prim == GE_PRIM_LINE_STRIP || prim == GE_PRIM_LINES) && gstate.getTextureFunction() == GE_TEXFUNC_REPLACE; // Update cached framebuffer format. // We store it in the cache so it can be modified for blue-to-alpha, next. @@ -1012,11 +1010,11 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) { #endif // If certain conditions are true, do frustum culling. - bool passCulling = PASSES_CULLING; + bool passCulling = PASSES_CULLING && !discardLines; if (passCulling && !(flags & ClipInfoFlags::Valid)) { flags = DrawEngineCommon::UncheckedClipInfoFlags(); } - if (!passCulling) { + if (!passCulling && !discardLines) { // Do software culling. if (drawEngineCommon_->TestBoundingBoxFast(gstate_c.cullMatrix, verts, inds, count, decoder, vertexType, &flags)) { passCulling = true; @@ -1068,7 +1066,7 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) { case GE_CMD_PRIM: { GEPrimitiveType newPrim = static_cast((data >> 16) & 7); - if (IsTrianglePrim(newPrim) != isTriangle) + if (IsTrianglePrim(newPrim) != isTriangle || discardLines) goto bail; // Can't join over this boundary. Might as well exit and get this on the next time around. // TODO: more efficient updating of verts/inds @@ -1471,269 +1469,6 @@ void GPUCommonHW::Execute_LoadClut(u32 op, u32 diff) { textureCache_->LoadClut(gstate.getClutAddress(), gstate.getClutLoadBytes(), &recorder_); } -void GPUCommonHW::Execute_WorldMtxNum(u32 op, u32 diff) { - if (!currentList) { - gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (op & 0xF); - return; - } - - // This is almost always followed by GE_CMD_WORLDMATRIXDATA. - const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); - u32 *dst = (u32 *)(gstate.worldMatrix + (op & 0xF)); - const int end = 12 - (op & 0xF); - int i = 0; - - // We must record the individual data commands while debugRecording_. - bool fastLoad = !debugRecording_ && end > 0; - // Stalling in the middle of a matrix would be stupid, I doubt this check is necessary. - if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { - fastLoad = false; - } - - if (fastLoad) { - while ((src[i] >> 24) == GE_CMD_WORLDMATRIXDATA) { - const u32 newVal = src[i] << 8; - if (dst[i] != newVal) { - Flush(); - dst[i] = newVal; - gstate_c.Dirty(DIRTY_WORLDMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX); - } - if (++i >= end) { - break; - } - } - } - - const int count = i; - gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | ((op & 0xF) + count); - - // Skip over the loaded data, it's done now. - currentList->pc += count * 4; - UpdatePC(currentList->pc, currentList->pc); -} - -void GPUCommonHW::Execute_WorldMtxData(u32 op, u32 diff) { - // Note: it's uncommon to get here now, see above. - int num = gstate.worldmtxnum & 0x00FFFFFF; - u32 newVal = op << 8; - 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 | DIRTY_CULL_MATRIX); - } - num++; - gstate.worldmtxnum = (GE_CMD_WORLDMATRIXNUMBER << 24) | (num & 0x00FFFFFF); - gstate.worldmtxdata = GE_CMD_WORLDMATRIXDATA << 24; -} - -void GPUCommonHW::Execute_ViewMtxNum(u32 op, u32 diff) { - if (!currentList) { - gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (op & 0xF); - return; - } - - // This is almost always followed by GE_CMD_VIEWMATRIXDATA. - const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); - u32 *dst = (u32 *)(gstate.viewMatrix + (op & 0xF)); - const int end = 12 - (op & 0xF); - int i = 0; - - bool fastLoad = !debugRecording_ && end > 0; - if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { - fastLoad = false; - } - - if (fastLoad) { - while ((src[i] >> 24) == GE_CMD_VIEWMATRIXDATA) { - const u32 newVal = src[i] << 8; - if (dst[i] != newVal) { - Flush(); - dst[i] = newVal; - gstate_c.Dirty(DIRTY_VIEWMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX) ; - } - if (++i >= end) { - break; - } - } - } - - const int count = i; - gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | ((op & 0xF) + count); - - // Skip over the loaded data, it's done now. - currentList->pc += count * 4; - UpdatePC(currentList->pc, currentList->pc); -} - -void GPUCommonHW::Execute_ViewMtxData(u32 op, u32 diff) { - // Note: it's uncommon to get here now, see above. - int num = gstate.viewmtxnum & 0x00FFFFFF; - u32 newVal = op << 8; - 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 | DIRTY_CULL_MATRIX); - } - num++; - gstate.viewmtxnum = (GE_CMD_VIEWMATRIXNUMBER << 24) | (num & 0x00FFFFFF); - gstate.viewmtxdata = GE_CMD_VIEWMATRIXDATA << 24; -} - -void GPUCommonHW::Execute_ProjMtxNum(u32 op, u32 diff) { - if (!currentList) { - gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | (op & 0xF); - return; - } - - // This is almost always followed by GE_CMD_PROJMATRIXDATA. - const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); - u32 *dst = (u32 *)(gstate.projMatrix + (op & 0xF)); - const int end = 16 - (op & 0xF); - int i = 0; - - bool fastLoad = !debugRecording_; - if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { - fastLoad = false; - } - - if (fastLoad) { - while ((src[i] >> 24) == GE_CMD_PROJMATRIXDATA) { - const u32 newVal = src[i] << 8; - if (dst[i] != newVal) { - Flush(); - dst[i] = newVal; - gstate_c.Dirty(DIRTY_PROJMATRIX | DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX); - } - if (++i >= end) { - break; - } - } - } - - const int count = i; - gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | ((op & 0xF) + count); - - // Skip over the loaded data, it's done now. - currentList->pc += count * 4; - UpdatePC(currentList->pc, currentList->pc); -} - -void GPUCommonHW::Execute_ProjMtxData(u32 op, u32 diff) { - // Note: it's uncommon to get here now, see above. - int num = gstate.projmtxnum & 0x00FFFFFF; - u32 newVal = op << 8; - 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 | DIRTY_CULL_MATRIX); - } - num++; - if (num <= 16) - gstate.projmtxnum = (GE_CMD_PROJMATRIXNUMBER << 24) | (num & 0x00FFFFFF); - gstate.projmtxdata = GE_CMD_PROJMATRIXDATA << 24; -} - -void GPUCommonHW::Execute_TgenMtxNum(u32 op, u32 diff) { - if (!currentList) { - gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | (op & 0xF); - return; - } - - // This is almost always followed by GE_CMD_TGENMATRIXDATA. - const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); - u32 *dst = (u32 *)(gstate.tgenMatrix + (op & 0xF)); - const int end = 12 - (op & 0xF); - int i = 0; - - bool fastLoad = !debugRecording_ && end > 0; - if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { - fastLoad = false; - } - - if (fastLoad) { - while ((src[i] >> 24) == GE_CMD_TGENMATRIXDATA) { - const u32 newVal = src[i] << 8; - if (dst[i] != newVal) { - Flush(); - dst[i] = newVal; - // We check the matrix to see if we need projection. - gstate_c.Dirty(DIRTY_TEXMATRIX | DIRTY_FRAGMENTSHADER_STATE); - } - if (++i >= end) { - break; - } - } - } - - const int count = i; - gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | ((op & 0xF) + count); - - // Skip over the loaded data, it's done now. - currentList->pc += count * 4; - UpdatePC(currentList->pc, currentList->pc); -} - -void GPUCommonHW::Execute_TgenMtxData(u32 op, u32 diff) { - // Note: it's uncommon to get here now, see above. - int num = gstate.texmtxnum & 0x00FFFFFF; - u32 newVal = op << 8; - if (num < 12 && newVal != ((const u32 *)gstate.tgenMatrix)[num]) { - Flush(); - ((u32 *)gstate.tgenMatrix)[num] = newVal; - gstate_c.Dirty(DIRTY_TEXMATRIX | DIRTY_FRAGMENTSHADER_STATE); // We check the matrix to see if we need projection - } - num++; - gstate.texmtxnum = (GE_CMD_TGENMATRIXNUMBER << 24) | (num & 0x00FFFFFF); - gstate.texmtxdata = GE_CMD_TGENMATRIXDATA << 24; -} - -void GPUCommonHW::Execute_BoneMtxNum(u32 op, u32 diff) { - if (!currentList) { - gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (op & 0x7F); - return; - } - - // This is almost always followed by GE_CMD_BONEMATRIXDATA. - const u32_le *src = (const u32_le *)Memory::GetPointerUnchecked(currentList->pc + 4); - u32 *dst = (u32 *)(gstate.boneMatrix + (op & 0x7F)); - const int end = 12 * 8 - (op & 0x7F); - int i = 0; - - bool fastLoad = !debugRecording_ && end > 0; - if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { - fastLoad = false; - } - - if (fastLoad) { - while ((src[i] >> 24) == GE_CMD_BONEMATRIXDATA) { - dst[i] = src[i] << 8; - if (++i >= end) { - break; - } - } - } - - const int count = i; - gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | ((op & 0x7F) + count); - - // Skip over the loaded data, it's done now. - currentList->pc += count * 4; - UpdatePC(currentList->pc, currentList->pc); -} - -void GPUCommonHW::Execute_BoneMtxData(u32 op, u32 diff) { - // Note: it's uncommon to get here now, see above. - int num = gstate.boneMatrixNumber & 0x00FFFFFF; - u32 newVal = op << 8; - if (num < 96 && newVal != ((const u32 *)gstate.boneMatrix)[num]) { - // Bone matrices should NOT flush, as we're always doing skinning in decode nowadays! - ((u32 *)gstate.boneMatrix)[num] = newVal; - } - num++; - gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (num & 0x00FFFFFF); - gstate.boneMatrixData = GE_CMD_BONEMATRIXDATA << 24; -} - void GPUCommonHW::Execute_TexFlush(u32 op, u32 diff) { // Games call this when they need the effect of drawing to be visible to texturing. // And for a bunch of other reasons, but either way, this is what we need to do. diff --git a/GPU/GPUCommonHW.h b/GPU/GPUCommonHW.h index 69640402f5..a006173365 100644 --- a/GPU/GPUCommonHW.h +++ b/GPU/GPUCommonHW.h @@ -61,17 +61,6 @@ public: void Execute_TexLevel(u32 op, u32 diff); void Execute_LoadClut(u32 op, u32 diff); - void Execute_WorldMtxNum(u32 op, u32 diff); - void Execute_WorldMtxData(u32 op, u32 diff); - void Execute_ViewMtxNum(u32 op, u32 diff); - void Execute_ViewMtxData(u32 op, u32 diff); - void Execute_ProjMtxNum(u32 op, u32 diff); - void Execute_ProjMtxData(u32 op, u32 diff); - void Execute_TgenMtxNum(u32 op, u32 diff); - void Execute_TgenMtxData(u32 op, u32 diff); - void Execute_BoneMtxNum(u32 op, u32 diff); - void Execute_BoneMtxData(u32 op, u32 diff); - void Execute_TexFlush(u32 op, u32 diff); // TODO: Have these return an error code if they jump to a bad address. If bad, stop the FastRunLoop. diff --git a/GPU/Null/GPU_Null.cpp b/GPU/Null/GPU_Null.cpp new file mode 100644 index 0000000000..2fa25caf7a --- /dev/null +++ b/GPU/Null/GPU_Null.cpp @@ -0,0 +1,298 @@ +// 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 + +#include "Common/Profiler/Profiler.h" +#include "Core/System.h" +#include "Core/Util/PPGeDraw.h" +#include "GPU/GPUCommon.h" +#include "GPU/GPUState.h" +#include "GPU/Common/DrawEngineCommon.h" +#include "GPU/Common/VertexDecoderCommon.h" +#include "GPU/Null/GPU_Null.h" + +namespace { + +// Only here for the vertex decoders (vertex sizes, bounding box tests). Queues and draws nothing. +class NullDrawEngine : public DrawEngineCommon { +public: + void DeviceLost() override {} + void DeviceRestore(Draw::DrawContext *draw) override {} + void Flush() override {} + void DispatchSubmitImm(GEPrimitiveType prim, TransformedVertex *buffer, int vertexCount, int cullMode, bool continuation) override {} +}; + +class GPU_Null : public GPUCommon { +public: + explicit GPU_Null(GraphicsContext *gfxCtx); + + u32 CheckGPUFeatures() const override { return 0; } + void UpdateCmdInfo() override {} + void ExecuteOp(u32 op, u32 diff) override; + + void GetStats(StringWriter &w) override {} + std::vector GetFramebufferList() const override { return {}; } + + void DeviceLost() override { draw_ = nullptr; } + void DeviceRestore(Draw::DrawContext *draw) override { + draw_ = draw; + PPGeSetDrawContext(draw_); + } + + void CheckDisplayResized() override { displayResized_ = false; } + void CheckConfigChanged(const DisplayLayoutConfig &config) override; + + bool FramebufferDirty() override { return true; } + bool FramebufferReallyDirty() override { return true; } + + void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override { + NotifyDisplay(framebuf, stride, format); + } + void SetCurFramebufferDirty(bool dirty) override {} + void PrepareCopyDisplayToOutput(const DisplayLayoutConfig &config) override {} + void CopyDisplayToOutput(const DisplayLayoutConfig &config) override {} + bool PresentedThisFrame() const override { return false; } + + // Nothing is cached, and all of PSP memory is up to date, so the copies and sets are left to the caller. + void InvalidateCache(u32 addr, int size, GPUInvalidationType type) override {} + bool PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags = GPUCopyFlag::NONE) override; + bool PerformMemorySet(u32 dest, u8 v, int size) override; + void PerformWriteFormattedFromMemory(u32 addr, int size, int width, GEBufferFormat format, int rectWidth) override { + // Remembered for EstimateFillCycles. + NoteVideoRange(addr, (u32)size); + } + bool PerformWriteStencilFromMemory(u32 dest, int size, WriteStencil flags) override { return false; } + bool PerformReadbackStencilToMemory(u32 dest, int size) override { return false; } + + void Execute_Prim(u32 op, u32 diff); + void Execute_Bezier(u32 op, u32 diff); + void Execute_Spline(u32 op, u32 diff); + void Execute_BlockTransferStart(u32 op, u32 diff); + + typedef void (GPU_Null::*CmdFunc)(u32 op, u32 diff); + +protected: + void FastRunLoop(DisplayList &list) override; + void BuildReportingInfo() override { + reportingPrimaryInfo_ = "Null"; + reportingFullInfo_ = "Null"; + } + +private: + void SkipCurve(int numPointsU, int numPointsV); + + NullDrawEngine drawEngine_; +}; + +struct NullCommandInfo { + u8 flags; + GPU_Null::CmdFunc func; +}; +static NullCommandInfo nullCmdInfo[256]; + +struct NullCommandTableEntry { + u8 cmd; + u8 flags; + GPU_Null::CmdFunc func; +}; + +// Only the commands that do something beyond setting their register. The rest only matter for drawing. +const NullCommandTableEntry nullCommandTable[] = { + { GE_CMD_OFFSETADDR, FLAG_EXECUTE, &GPUCommon::Execute_OffsetAddr }, + { GE_CMD_ORIGIN, FLAG_EXECUTE | FLAG_READS_PC, &GPUCommon::Execute_Origin }, + { GE_CMD_JUMP, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, &GPUCommon::Execute_Jump }, + { GE_CMD_CALL, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, &GPUCommon::Execute_Call }, + { GE_CMD_RET, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, &GPUCommon::Execute_Ret }, + { GE_CMD_END, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, &GPUCommon::Execute_End }, + { GE_CMD_VADDR, FLAG_EXECUTE, &GPUCommon::Execute_Vaddr }, + { GE_CMD_IADDR, FLAG_EXECUTE, &GPUCommon::Execute_Iaddr }, + { GE_CMD_BJUMP, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, &GPUCommon::Execute_BJump }, + { GE_CMD_BOUNDINGBOX, FLAG_EXECUTE, &GPUCommon::Execute_BoundingBox }, + + { GE_CMD_PRIM, FLAG_EXECUTE, &GPU_Null::Execute_Prim }, + { GE_CMD_BEZIER, FLAG_EXECUTE, &GPU_Null::Execute_Bezier }, + { GE_CMD_SPLINE, FLAG_EXECUTE, &GPU_Null::Execute_Spline }, + + { GE_CMD_MORPHWEIGHT0, FLAG_EXECUTEONCHANGE, &GPUCommon::Execute_MorphWeight }, + { GE_CMD_MORPHWEIGHT1, FLAG_EXECUTEONCHANGE, &GPUCommon::Execute_MorphWeight }, + { GE_CMD_MORPHWEIGHT2, FLAG_EXECUTEONCHANGE, &GPUCommon::Execute_MorphWeight }, + { GE_CMD_MORPHWEIGHT3, FLAG_EXECUTEONCHANGE, &GPUCommon::Execute_MorphWeight }, + { GE_CMD_MORPHWEIGHT4, FLAG_EXECUTEONCHANGE, &GPUCommon::Execute_MorphWeight }, + { GE_CMD_MORPHWEIGHT5, FLAG_EXECUTEONCHANGE, &GPUCommon::Execute_MorphWeight }, + { GE_CMD_MORPHWEIGHT6, FLAG_EXECUTEONCHANGE, &GPUCommon::Execute_MorphWeight }, + { GE_CMD_MORPHWEIGHT7, FLAG_EXECUTEONCHANGE, &GPUCommon::Execute_MorphWeight }, + + { GE_CMD_TRANSFERSTART, FLAG_EXECUTE | FLAG_READS_PC, &GPU_Null::Execute_BlockTransferStart }, + + { GE_CMD_WORLDMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, &GPUCommon::Execute_WorldMtxNum }, + { GE_CMD_WORLDMATRIXDATA, FLAG_EXECUTE, &GPUCommon::Execute_WorldMtxData }, + { GE_CMD_VIEWMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, &GPUCommon::Execute_ViewMtxNum }, + { GE_CMD_VIEWMATRIXDATA, FLAG_EXECUTE, &GPUCommon::Execute_ViewMtxData }, + { GE_CMD_PROJMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, &GPUCommon::Execute_ProjMtxNum }, + { GE_CMD_PROJMATRIXDATA, FLAG_EXECUTE, &GPUCommon::Execute_ProjMtxData }, + { GE_CMD_TGENMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, &GPUCommon::Execute_TgenMtxNum }, + { GE_CMD_TGENMATRIXDATA, FLAG_EXECUTE, &GPUCommon::Execute_TgenMtxData }, + { GE_CMD_BONEMATRIXNUMBER, FLAG_EXECUTE | FLAG_READS_PC | FLAG_WRITES_PC, &GPUCommon::Execute_BoneMtxNum }, + { GE_CMD_BONEMATRIXDATA, FLAG_EXECUTE, &GPUCommon::Execute_BoneMtxData }, + + { GE_CMD_VAP, FLAG_EXECUTE, &GPUCommon::Execute_ImmVertexAlphaPrim }, +}; + +GPU_Null::GPU_Null(GraphicsContext *gfxCtx) : GPUCommon(gfxCtx, nullptr) { + memset(nullCmdInfo, 0, sizeof(nullCmdInfo)); + for (const NullCommandTableEntry &entry : nullCommandTable) { + _dbg_assert_(nullCmdInfo[entry.cmd].func == nullptr); + nullCmdInfo[entry.cmd].flags = entry.flags; + nullCmdInfo[entry.cmd].func = entry.func; + } + + drawEngine_.SetGPUCommon(this); + drawEngine_.Init(); + drawEngineCommon_ = &drawEngine_; + + // Nothing is queued, so there's nothing to flush. + flushOnParams_ = false; + + BuildReportingInfo(); +} + +void GPU_Null::CheckConfigChanged(const DisplayLayoutConfig &config) { + if (configChanged_) { + drawEngine_.NotifyConfigChanged(); + configChanged_ = false; + } +} + +void GPU_Null::FastRunLoop(DisplayList &list) { + PROFILE_THIS_SCOPE("null_runloop"); + if (!Memory::IsValidAddress(list.pc)) { + downcount = 0; + return; + } + const NullCommandInfo *cmdInfo = nullCmdInfo; + int dc = downcount; + for (; dc > 0; --dc) { + const u32 op = Memory::ReadUnchecked_U32(list.pc); + const u32 cmd = op >> 24; + const NullCommandInfo &info = cmdInfo[cmd]; + const u32 diff = op ^ gstate.cmdmem[cmd]; + if (diff) { + gstate.cmdmem[cmd] = op; + } + if ((info.flags & FLAG_EXECUTE) || (diff && (info.flags & FLAG_EXECUTEONCHANGE))) { + downcount = dc; + (this->*info.func)(op, diff); + dc = downcount; + } + list.pc += 4; + } + downcount = 0; +} + +void GPU_Null::ExecuteOp(u32 op, u32 diff) { + const NullCommandInfo &info = nullCmdInfo[op >> 24]; + if ((info.flags & FLAG_EXECUTE) || (diff && (info.flags & FLAG_EXECUTEONCHANGE))) { + (this->*info.func)(op, diff); + } +} + +void GPU_Null::Execute_Prim(u32 op, u32 diff) { + const u32 count = op & 0xFFFF; + if (count == 0) + return; + FlushImm(); + + if (!Memory::IsValidAddress(gstate_c.vertexAddr)) { + ERROR_LOG(Log::G3D, "Bad vertex address %08x!", gstate_c.vertexAddr); + return; + } + const void *verts = Memory::GetPointerUnchecked(gstate_c.vertexAddr); + const void *inds = nullptr; + const u32 vertType = gstate.vertType; + if ((vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) { + const int indexShift = ((vertType & GE_VTYPE_IDX_MASK) >> GE_VTYPE_IDX_SHIFT) - 1; + if (!Memory::IsValidRange(gstate_c.indexAddr, count << indexShift)) { + ERROR_LOG(Log::G3D, "Bad index address %08x (%d)!", gstate_c.indexAddr, count); + return; + } + inds = Memory::GetPointerUnchecked(gstate_c.indexAddr); + } + + const VertexDecoder *dec = drawEngine_.GetVertexDecoder(GetVertTypeID(vertType, gstate.getUVGenMode())); + const GEPrimitiveType prim = static_cast((op >> 16) & 7); + if (gstate.isModeThrough()) { + cyclesExecuted += EstimateFillCycles(prim, verts, inds, count, dec, vertType); + } + const int cycles = EstimatePerVertexCost() * count; + gpuStats.perFrame.vertexGPUCycles += cycles; + if (!PSP_CoreParameter().compat.flags().FastEmulatedGPU) { + cyclesExecuted += cycles; + } + + gstate_c.AdvanceVerts(vertType, count, count * dec->VertexSize()); +} + +// Moves the pointers past a curve like DrawEngineCommon::SubmitCurve would. +void GPU_Null::SkipCurve(int numPointsU, int numPointsV) { + if (!Memory::IsValidAddress(gstate_c.vertexAddr)) { + ERROR_LOG(Log::G3D, "Bad vertex address %08x!", gstate_c.vertexAddr); + return; + } + const u32 vertType = gstate.vertType; + if ((vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE && !Memory::IsValidAddress(gstate_c.indexAddr)) { + ERROR_LOG(Log::G3D, "Bad index address %08x!", gstate_c.indexAddr); + return; + } + + const int count = numPointsU * numPointsV; + int bytesRead = 0; + // SubmitCurve draws nothing, and reads nothing, with fewer than four points either way. + if (numPointsU >= 4 && numPointsV >= 4) { + bytesRead = count * drawEngine_.GetVertexDecoder(GetVertTypeID(vertType, gstate.getUVGenMode()))->VertexSize(); + } + gstate_c.AdvanceVerts(vertType, count, bytesRead); +} + +void GPU_Null::Execute_Bezier(u32 op, u32 diff) { + SkipCurve(op & 0xFF, (op >> 8) & 0xFF); +} + +void GPU_Null::Execute_Spline(u32 op, u32 diff) { + SkipCurve(op & 0xFF, (op >> 8) & 0xFF); +} + +void GPU_Null::Execute_BlockTransferStart(u32 op, u32 diff) { + DoBlockTransfer(gstate_c.skipDrawReason); +} + +bool GPU_Null::PerformMemoryCopy(u32 dest, u32 src, int size, GPUCopyFlag flags) { + if (!(flags & GPUCopyFlag::DEBUG_NOTIFIED)) + recorder_.NotifyMemcpy(dest, src, size); + return false; +} + +bool GPU_Null::PerformMemorySet(u32 dest, u8 v, int size) { + recorder_.NotifyMemset(dest, v, size); + return false; +} + +} // namespace + +GPUCommon *CreateNullGPU(GraphicsContext *gfxCtx) { + return new GPU_Null(gfxCtx); +} diff --git a/GPU/Null/GPU_Null.h b/GPU/Null/GPU_Null.h new file mode 100644 index 0000000000..a812413317 --- /dev/null +++ b/GPU/Null/GPU_Null.h @@ -0,0 +1,26 @@ +// 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/. + +#pragma once + +class GPUCommon; +class GraphicsContext; + +// A GPU that runs display lists (flow control, bounding boxes, matrices, block transfers, the vertex and +// index pointers moving past each draw, and the GE timing) but draws nothing, and needs no thin3d context. +// For measuring the CPU emulation without graphics work in the way (headless --graphics=null). +GPUCommon *CreateNullGPU(GraphicsContext *gfxCtx); diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj b/UWP/GPU_UWP/GPU_UWP.vcxproj index 2f24fbac61..229411b40b 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj @@ -148,6 +148,7 @@ + @@ -216,6 +217,7 @@ + diff --git a/UWP/GPU_UWP/GPU_UWP.vcxproj.filters b/UWP/GPU_UWP/GPU_UWP.vcxproj.filters index 87fb58b2f8..44d72b8ee2 100644 --- a/UWP/GPU_UWP/GPU_UWP.vcxproj.filters +++ b/UWP/GPU_UWP/GPU_UWP.vcxproj.filters @@ -45,6 +45,7 @@ + @@ -125,6 +126,7 @@ + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 524a2b5979..39b2f05ee3 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -640,6 +640,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/GPU/Software/RasterizerRectangle.cpp.arm \ $(SRC)/GPU/Software/Sampler.cpp \ $(SRC)/GPU/Software/SoftGpu.cpp \ + $(SRC)/GPU/Null/GPU_Null.cpp \ $(SRC)/GPU/Software/TransformUnit.cpp \ $(SRC)/Core/ELF/ElfReader.cpp \ $(SRC)/Core/ELF/PBPReader.cpp \ diff --git a/docs/debugging.md b/docs/debugging.md index ef3a967d45..67af0b05fe 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -251,6 +251,12 @@ anything else that uses the CPU, including a forgotten `usbhostfs_pc`, which can that exits with an error, misses `TIMEOUT (emulated)` or doesn't load its savestate stops the tool rather than passing as a fast run. +To measure the CPU emulation alone, use `--graphics=null` (headless only, `GPU/Null/GPU_Null.cpp`). It runs +display lists, bounding boxes, matrix loads and block transfers, moves the vertex and index pointers past +each draw, and charges the same GE time per draw, but draws nothing and creates no draw context. Games flip +the same frames as with Vulkan, in roughly half the host instructions. Nothing is rendered, so screenshots +and anything that reads a framebuffer back through the GPU (rather than from PSP memory) get nothing. + Headless decodes vertices with the JIT like the app, where the host can (`--vertex-jit=false` for the C++ decoder, which the tests don't need either). diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 089545a6a3..292d6906c5 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -1061,15 +1061,18 @@ int main(int argc, const char* argv[]) { break; } } + if (cmdLineOptions.nullGraphics.value_or(false)) { + gpuCore = GPUCORE_NULL; + } // Time to set up graphics GraphicsContext *graphicsContext = nullptr; std::string *deviceSetting = nullptr; void *window = nullptr; WindowDesc windowDesc; - if (g_Config.bSoftwareRendering) { + if (g_Config.bSoftwareRendering || gpuCore == GPUCORE_NULL) { // For software rendering, we just create a dummy graphics context (to share as much code as possible). - // We don't bother with a window. + // We don't bother with a window. The null GPU needs no draw context either. graphicsContext = new NullGraphicsContext(); } else { #if defined(HEADLESS_NO_SDL) diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 21bbb7b3c0..7dab17fab4 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -587,6 +587,7 @@ SOURCES_CXX += \ $(GPUDIR)/Software/TransformUnit.cpp \ $(GPUDIR)/Software/GEMath.cpp \ $(GPUDIR)/Software/SoftGpu.cpp \ + $(GPUDIR)/Null/GPU_Null.cpp \ $(GPUDIR)/Software/Sampler.cpp \ $(GPUDIR)/GeConstants.cpp \ $(GPUDIR)/GeDisasm.cpp \