mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-20 17:59:08 +02:00
Merge pull request #22593 from hrydgard/null-gpu-minimal
GPU: Add a null GPU (headless --graphics=null), and skip discarded AA lines like culled draws
This commit is contained in:
20 files changed
+651
-287
No files matched your search
@@ -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;
|
||||
|
||||
@@ -33,6 +33,8 @@ struct CommandLineOptions {
|
||||
std::optional<bool> fullscreen;
|
||||
std::optional<GPUBackend> gpuBackend;
|
||||
std::optional<bool> softwareRendering;
|
||||
// --graphics=null: headless only, see GPU_Null.h.
|
||||
std::optional<bool> nullGraphics;
|
||||
std::optional<bool> enableLogging;
|
||||
std::optional<LogLevel> logLevel; // Override log level with this.
|
||||
std::optional<std::string> log;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
+4
-1
@@ -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);
|
||||
|
||||
@@ -347,6 +347,7 @@
|
||||
<ClInclude Include="Software\RasterizerTypes.h" />
|
||||
<ClInclude Include="Software\Sampler.h" />
|
||||
<ClInclude Include="Software\SoftGpu.h" />
|
||||
<ClInclude Include="Null\GPU_Null.h" />
|
||||
<ClInclude Include="Software\TransformUnit.h" />
|
||||
<ClInclude Include="Software\GEMath.h" />
|
||||
<ClInclude Include="Common\TextureDecoder.h" />
|
||||
@@ -471,6 +472,7 @@
|
||||
<ClCompile Include="Software\RasterizerRectangle.cpp" />
|
||||
<ClCompile Include="Software\Sampler.cpp" />
|
||||
<ClCompile Include="Software\SoftGpu.cpp" />
|
||||
<ClCompile Include="Null\GPU_Null.cpp" />
|
||||
<ClCompile Include="Software\TransformUnit.cpp" />
|
||||
<ClCompile Include="Software\GEMath.cpp" />
|
||||
<ClCompile Include="Common\TextureDecoder.cpp" />
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
<Filter Include="GLES">
|
||||
<UniqueIdentifier>{f7563dba-8146-4c21-a092-e864ff145d79}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Null">
|
||||
<UniqueIdentifier>{9879c213-d775-44fc-b1e4-a9d19ac2dff5}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Software">
|
||||
<UniqueIdentifier>{4f6d1284-2c23-4ebc-842c-666a1305bfed}</UniqueIdentifier>
|
||||
</Filter>
|
||||
@@ -48,6 +51,9 @@
|
||||
<ClInclude Include="Software\SoftGpu.h">
|
||||
<Filter>Software</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Null\GPU_Null.h">
|
||||
<Filter>Null</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Software\TransformUnit.h">
|
||||
<Filter>Software</Filter>
|
||||
</ClInclude>
|
||||
@@ -299,6 +305,9 @@
|
||||
<ClCompile Include="Software\SoftGpu.cpp">
|
||||
<Filter>Software</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Null\GPU_Null.cpp">
|
||||
<Filter>Null</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Software\TransformUnit.cpp">
|
||||
<Filter>Software</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
+7
-272
@@ -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<GEPrimitiveType>((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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 <cstring>
|
||||
|
||||
#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<const VirtualFramebuffer *> 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<GEPrimitiveType>((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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -148,6 +148,7 @@
|
||||
<ClInclude Include="..\..\GPU\Software\RasterizerTypes.h" />
|
||||
<ClInclude Include="..\..\GPU\Software\Sampler.h" />
|
||||
<ClInclude Include="..\..\GPU\Software\SoftGpu.h" />
|
||||
<ClInclude Include="..\..\GPU\Null\GPU_Null.h" />
|
||||
<ClInclude Include="..\..\GPU\Software\TransformUnit.h" />
|
||||
<ClInclude Include="..\..\GPU\Software\GEMath.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
@@ -216,6 +217,7 @@
|
||||
<ClCompile Include="..\..\GPU\Software\RasterizerRectangle.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Software\Sampler.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Software\SoftGpu.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Null\GPU_Null.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Software\TransformUnit.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Software\GEMath.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
<ClCompile Include="..\..\GPU\Software\Rasterizer.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Software\Sampler.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Software\SoftGpu.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Null\GPU_Null.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Software\TransformUnit.cpp" />
|
||||
<ClCompile Include="..\..\GPU\Software\GEMath.cpp" />
|
||||
<ClCompile Include="pch.cpp" />
|
||||
@@ -125,6 +126,7 @@
|
||||
<ClInclude Include="..\..\GPU\Software\Rasterizer.h" />
|
||||
<ClInclude Include="..\..\GPU\Software\Sampler.h" />
|
||||
<ClInclude Include="..\..\GPU\Software\SoftGpu.h" />
|
||||
<ClInclude Include="..\..\GPU\Null\GPU_Null.h" />
|
||||
<ClInclude Include="..\..\GPU\Software\TransformUnit.h" />
|
||||
<ClInclude Include="..\..\GPU\Software\GEMath.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 \
|
||||
|
||||
Reference in new issue
Block a user