mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 23:10:33 +02:00
Dumps recorded before savedContextVersion 1 hold PPSSPP's old context layout, with the matrices as raw floats, but GEState::Restore picks the layout from savedContextVersion, which only a savestate sets. Playback restored them as the new layout, so the matrices became garbage and the scene vanished (4140 drew only its HUD). Tell the layouts apart by the END the new one ends with, as the PSP replayer does. Other callers are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
439 lines
12 KiB
C++
439 lines
12 KiB
C++
// 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 "ppsspp_config.h"
|
|
#include "Common/Common.h"
|
|
#include "Common/Math/SIMDHeaders.h"
|
|
#include "Common/Serialize/Serializer.h"
|
|
#include "Common/Serialize/SerializeFuncs.h"
|
|
#include "Common/Math/CrossSIMD.h"
|
|
#include "Core/MemMap.h"
|
|
#include "GPU/ge_constants.h"
|
|
#include "GPU/GPUCommon.h"
|
|
#include "GPU/GPUState.h"
|
|
|
|
// This must be aligned so that the matrices within are aligned.
|
|
alignas(16) GEState gstate;
|
|
// Let's align this one too for good measure.
|
|
alignas(16) GPUStateCache gstate_c;
|
|
|
|
// For save state compatibility.
|
|
static int savedContextVersion = 1;
|
|
|
|
struct CmdRange {
|
|
u8 start;
|
|
u8 end;
|
|
};
|
|
|
|
static const CmdRange contextCmdRanges[] = {
|
|
{0x00, 0x02},
|
|
// Skip: {0x03, 0x0F},
|
|
{0x10, 0x10},
|
|
// Skip: {0x11, 0x11},
|
|
{0x12, 0x28},
|
|
// Skip: {0x29, 0x2B},
|
|
{0x2c, 0x33},
|
|
// Skip: {0x34, 0x35},
|
|
{0x36, 0x38},
|
|
// Skip: {0x39, 0x41},
|
|
{0x42, 0x4D},
|
|
// Skip: {0x4E, 0x4F},
|
|
{0x50, 0x51},
|
|
// Skip: {0x52, 0x52},
|
|
{0x53, 0x58},
|
|
// Skip: {0x59, 0x5A},
|
|
{0x5B, 0xB5},
|
|
// Skip: {0xB6, 0xB7},
|
|
{0xB8, 0xC3},
|
|
// Skip: {0xC4, 0xC4},
|
|
{0xC5, 0xD0},
|
|
// Skip: {0xD1, 0xD1}
|
|
{0xD2, 0xE9},
|
|
// Skip: {0xEA, 0xEA},
|
|
{0xEB, 0xEC},
|
|
// Skip: {0xED, 0xED},
|
|
{0xEE, 0xEE},
|
|
// Skip: {0xEF, 0xEF},
|
|
{0xF0, 0xF6},
|
|
// Skip: {0xF7, 0xF7},
|
|
{0xF8, 0xF9},
|
|
// Skip: {0xFA, 0xFF},
|
|
};
|
|
|
|
static u32_le *SaveMatrix(u32_le *cmds, GEMatrixType type, int sz, int numcmd, int datacmd) {
|
|
if (!gpu)
|
|
return cmds;
|
|
|
|
*cmds++ = numcmd << 24;
|
|
// This saves the CPU-visible values, not the actual used ones, which may differ.
|
|
// Note that Restore overwrites both values.
|
|
if (type == GE_MTX_BONE0) {
|
|
for (int i = 0; i < 8; ++i)
|
|
gpu->GetMatrix24(GEMatrixType(GE_MTX_BONE0 + i), cmds + i * 12, datacmd << 24);
|
|
} else {
|
|
gpu->GetMatrix24(type, cmds, datacmd << 24);
|
|
}
|
|
cmds += sz;
|
|
|
|
return cmds;
|
|
}
|
|
|
|
static const u32_le *LoadMatrix(const u32_le *cmds, float *mtx, int sz) {
|
|
// Skip the reset.
|
|
cmds++;
|
|
for (int i = 0; i < sz; ++i) {
|
|
mtx[i] = getFloat24(*cmds++);
|
|
}
|
|
|
|
return cmds;
|
|
}
|
|
|
|
void GEState::Reset() {
|
|
memset(gstate.cmdmem, 0, sizeof(gstate.cmdmem));
|
|
for (int i = 0; i < 256; i++) {
|
|
gstate.cmdmem[i] = i << 24;
|
|
}
|
|
|
|
// Lighting is not enabled by default, matrices are zero initialized.
|
|
memset(gstate.worldMatrix, 0, sizeof(gstate.worldMatrix));
|
|
memset(gstate.viewMatrix, 0, sizeof(gstate.viewMatrix));
|
|
memset(gstate.projMatrix, 0, sizeof(gstate.projMatrix));
|
|
memset(gstate.tgenMatrix, 0, sizeof(gstate.tgenMatrix));
|
|
memset(gstate.boneMatrix, 0, sizeof(gstate.boneMatrix));
|
|
|
|
savedContextVersion = 1;
|
|
|
|
gstate_c.Dirty(DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX);
|
|
}
|
|
|
|
void GEState::Save(u32_le *ptr) {
|
|
// Not sure what the first 10 values are, exactly, but these seem right.
|
|
ptr[5] = gstate_c.vertexAddr;
|
|
ptr[6] = gstate_c.indexAddr;
|
|
ptr[7] = gstate_c.offsetAddr;
|
|
|
|
// Command values start 17 ints in.
|
|
u32_le *cmds = ptr + 17;
|
|
for (size_t i = 0; i < ARRAY_SIZE(contextCmdRanges); ++i) {
|
|
for (int n = contextCmdRanges[i].start; n <= contextCmdRanges[i].end; ++n) {
|
|
// We'll run ReapplyGfxState after this to process dirtying.
|
|
*cmds++ = cmdmem[n];
|
|
}
|
|
}
|
|
|
|
if (savedContextVersion == 0) {
|
|
if (Memory::IsValidAddress(getClutAddress()))
|
|
*cmds++ = loadclut;
|
|
|
|
// Seems like it actually writes commands to load the matrices and then reset the counts.
|
|
*cmds++ = boneMatrixNumber;
|
|
*cmds++ = worldmtxnum;
|
|
*cmds++ = viewmtxnum;
|
|
*cmds++ = projmtxnum;
|
|
*cmds++ = texmtxnum;
|
|
|
|
u8 *matrices = (u8 *)cmds;
|
|
memcpy(matrices, boneMatrix, sizeof(boneMatrix)); matrices += sizeof(boneMatrix);
|
|
memcpy(matrices, worldMatrix, sizeof(worldMatrix)); matrices += sizeof(worldMatrix);
|
|
memcpy(matrices, viewMatrix, sizeof(viewMatrix)); matrices += sizeof(viewMatrix);
|
|
memcpy(matrices, projMatrix, sizeof(projMatrix)); matrices += sizeof(projMatrix);
|
|
memcpy(matrices, tgenMatrix, sizeof(tgenMatrix)); matrices += sizeof(tgenMatrix);
|
|
} else {
|
|
cmds = SaveMatrix(cmds, GE_MTX_BONE0, ARRAY_SIZE(boneMatrix), GE_CMD_BONEMATRIXNUMBER, GE_CMD_BONEMATRIXDATA);
|
|
cmds = SaveMatrix(cmds, GE_MTX_WORLD, ARRAY_SIZE(worldMatrix), GE_CMD_WORLDMATRIXNUMBER, GE_CMD_WORLDMATRIXDATA);
|
|
cmds = SaveMatrix(cmds, GE_MTX_VIEW, ARRAY_SIZE(viewMatrix), GE_CMD_VIEWMATRIXNUMBER, GE_CMD_VIEWMATRIXDATA);
|
|
cmds = SaveMatrix(cmds, GE_MTX_PROJECTION, ARRAY_SIZE(projMatrix), GE_CMD_PROJMATRIXNUMBER, GE_CMD_PROJMATRIXDATA);
|
|
cmds = SaveMatrix(cmds, GE_MTX_TEXGEN, ARRAY_SIZE(tgenMatrix), GE_CMD_TGENMATRIXNUMBER, GE_CMD_TGENMATRIXDATA);
|
|
|
|
*cmds++ = boneMatrixNumber & 0xFF00007F;
|
|
*cmds++ = worldmtxnum & 0xFF00000F;
|
|
*cmds++ = viewmtxnum & 0xFF00000F;
|
|
*cmds++ = projmtxnum & 0xFF00000F;
|
|
*cmds++ = texmtxnum & 0xFF00000F;
|
|
*cmds++ = GE_CMD_END << 24;
|
|
}
|
|
}
|
|
|
|
void GEState::FastLoadBoneMatrix(u32 addr) {
|
|
const u32 *src = (const u32 *)Memory::GetPointerUnchecked(addr);
|
|
u32 num = boneMatrixNumber;
|
|
u32 *dst = (u32 *)(boneMatrix + (num & 0x7F));
|
|
|
|
#ifdef _M_SSE
|
|
__m128i row1 = _mm_slli_epi32(_mm_loadu_si128((const __m128i *)src), 8);
|
|
__m128i row2 = _mm_slli_epi32(_mm_loadu_si128((const __m128i *)(src + 4)), 8);
|
|
__m128i row3 = _mm_slli_epi32(_mm_loadu_si128((const __m128i *)(src + 8)), 8);
|
|
_mm_storeu_si128((__m128i *)dst, row1);
|
|
_mm_storeu_si128((__m128i *)(dst + 4), row2);
|
|
_mm_storeu_si128((__m128i *)(dst + 8), row3);
|
|
#elif PPSSPP_ARCH(ARM_NEON)
|
|
const uint32x4_t row1 = vshlq_n_u32(vld1q_u32(src), 8);
|
|
const uint32x4_t row2 = vshlq_n_u32(vld1q_u32(src + 4), 8);
|
|
const uint32x4_t row3 = vshlq_n_u32(vld1q_u32(src + 8), 8);
|
|
vst1q_u32(dst, row1);
|
|
vst1q_u32(dst + 4, row2);
|
|
vst1q_u32(dst + 8, row3);
|
|
#elif !CROSSSIMD_SLOW
|
|
Vec4F32 row1 = Vec4F32::LoadF24x4(src);
|
|
Vec4F32 row2 = Vec4F32::LoadF24x4(src + 4);
|
|
Vec4F32 row3 = Vec4F32::LoadF24x4(src + 8);
|
|
row1.Store((float *)dst);
|
|
row2.Store((float *)(dst + 4));
|
|
row3.Store((float *)(dst + 8));
|
|
#else
|
|
for (int i = 0; i < 12; i++) {
|
|
dst[i] = src[i] << 8;
|
|
}
|
|
#endif
|
|
|
|
num += 12;
|
|
gstate.boneMatrixNumber = (GE_CMD_BONEMATRIXNUMBER << 24) | (num & 0x00FFFFFF);
|
|
}
|
|
|
|
void GEState::Restore(const u32_le *ptr) {
|
|
Restore(ptr, savedContextVersion == 0);
|
|
}
|
|
|
|
void GEState::Restore(const u32_le *ptr, bool oldLayout) {
|
|
// Not sure what the first 10 values are, exactly, but these seem right.
|
|
gstate_c.vertexAddr = ptr[5];
|
|
gstate_c.indexAddr = ptr[6];
|
|
gstate_c.offsetAddr = ptr[7];
|
|
|
|
// Command values start 17 ints in.
|
|
const u32_le *cmds = ptr + 17;
|
|
for (size_t i = 0; i < ARRAY_SIZE(contextCmdRanges); ++i) {
|
|
for (int n = contextCmdRanges[i].start; n <= contextCmdRanges[i].end; ++n) {
|
|
cmdmem[n] = *cmds++;
|
|
}
|
|
}
|
|
|
|
if (oldLayout) {
|
|
if (Memory::IsValidAddress(getClutAddress()))
|
|
loadclut = *cmds++;
|
|
boneMatrixNumber = *cmds++;
|
|
worldmtxnum = *cmds++;
|
|
viewmtxnum = *cmds++;
|
|
projmtxnum = *cmds++;
|
|
texmtxnum = *cmds++;
|
|
|
|
u8 *matrices = (u8 *)cmds;
|
|
memcpy(boneMatrix, matrices, sizeof(boneMatrix)); matrices += sizeof(boneMatrix);
|
|
memcpy(worldMatrix, matrices, sizeof(worldMatrix)); matrices += sizeof(worldMatrix);
|
|
memcpy(viewMatrix, matrices, sizeof(viewMatrix)); matrices += sizeof(viewMatrix);
|
|
memcpy(projMatrix, matrices, sizeof(projMatrix)); matrices += sizeof(projMatrix);
|
|
memcpy(tgenMatrix, matrices, sizeof(tgenMatrix)); matrices += sizeof(tgenMatrix);
|
|
} else {
|
|
cmds = LoadMatrix(cmds, boneMatrix, ARRAY_SIZE(boneMatrix));
|
|
cmds = LoadMatrix(cmds, worldMatrix, ARRAY_SIZE(worldMatrix));
|
|
cmds = LoadMatrix(cmds, viewMatrix, ARRAY_SIZE(viewMatrix));
|
|
cmds = LoadMatrix(cmds, projMatrix, ARRAY_SIZE(projMatrix));
|
|
cmds = LoadMatrix(cmds, tgenMatrix, ARRAY_SIZE(tgenMatrix));
|
|
|
|
boneMatrixNumber = (*cmds++) & 0xFF00007F;
|
|
worldmtxnum = (*cmds++) & 0xFF00000F;
|
|
viewmtxnum = (*cmds++) & 0xFF00000F;
|
|
projmtxnum = (*cmds++) & 0xFF00000F;
|
|
texmtxnum = (*cmds++) & 0xFF00000F;
|
|
}
|
|
|
|
if (gpu)
|
|
gpu->ResetMatrices();
|
|
|
|
gstate_c.Dirty(DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX);
|
|
}
|
|
|
|
bool vertTypeIsSkinningEnabled(u32 vertType) {
|
|
return ((vertType & GE_VTYPE_WEIGHT_MASK) != GE_VTYPE_WEIGHT_NONE);
|
|
}
|
|
|
|
struct GPUStateCache_v0 {
|
|
u32 vertexAddr;
|
|
u32 indexAddr;
|
|
|
|
u32 offsetAddr;
|
|
|
|
bool textureChanged;
|
|
bool textureSolidAlpha;
|
|
bool vertexFullAlpha;
|
|
bool framebufChanged;
|
|
|
|
int skipDrawReason;
|
|
|
|
UVScale uv;
|
|
bool flipTexture;
|
|
};
|
|
|
|
void GPUStateCache::Reset() {
|
|
memset(&gstate_c, 0, sizeof(gstate_c));
|
|
}
|
|
|
|
void GPUStateCache::DoState(PointerWrap &p) {
|
|
auto s = p.Section("GPUStateCache", 0, 5);
|
|
if (!s) {
|
|
// Old state, this was not versioned.
|
|
GPUStateCache_v0 old;
|
|
Do(p, old);
|
|
|
|
vertexAddr = old.vertexAddr;
|
|
indexAddr = old.indexAddr;
|
|
offsetAddr = old.offsetAddr;
|
|
gstate_c.Dirty(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS);
|
|
textureSolidAlpha = old.textureSolidAlpha;
|
|
vertexFullAlpha = old.vertexFullAlpha;
|
|
skipDrawReason = old.skipDrawReason;
|
|
|
|
savedContextVersion = 0;
|
|
} else {
|
|
Do(p, vertexAddr);
|
|
Do(p, indexAddr);
|
|
Do(p, offsetAddr);
|
|
|
|
uint8_t textureChanged = 0;
|
|
Do(p, textureChanged); // legacy
|
|
if (p.mode == p.MODE_READ) {
|
|
gstate_c.Dirty(DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS);
|
|
}
|
|
Do(p, textureSolidAlpha);
|
|
Do(p, vertexFullAlpha);
|
|
bool framebufChanged = false; // legacy
|
|
Do(p, framebufChanged);
|
|
|
|
Do(p, skipDrawReason);
|
|
|
|
// Legacy, remove in the next bump.
|
|
UVScale uv{};
|
|
Do(p, uv);
|
|
|
|
bool oldFlipTexture = false;
|
|
Do(p, oldFlipTexture); // legacy
|
|
}
|
|
|
|
// needShaderTexClamp and bgraTexture don't need to be saved.
|
|
|
|
if (s >= 3) {
|
|
bool oldTextureSimpleAlpha = false;
|
|
Do(p, oldTextureSimpleAlpha);
|
|
}
|
|
|
|
if (s < 2) {
|
|
float l12[12];
|
|
float l4[4];
|
|
Do(p, l12); // lightpos
|
|
Do(p, l12); // lightdir
|
|
Do(p, l12); // lightattr
|
|
Do(p, l12); // lightcol0
|
|
Do(p, l12); // lightcol1
|
|
Do(p, l12); // lightcol2
|
|
Do(p, l4); // lightangle
|
|
Do(p, l4); // lightspot
|
|
}
|
|
|
|
Do(p, morphWeights);
|
|
|
|
Do(p, curTextureWidth);
|
|
Do(p, curTextureHeight);
|
|
Do(p, actualTextureHeight);
|
|
// curTextureXOffset and curTextureYOffset don't need to be saved. Well, the above don't either...
|
|
|
|
// previously vpWidth, vpHeight. Remove in future versions.
|
|
float dummy = 0.0f;
|
|
Do(p, dummy);
|
|
Do(p, dummy);
|
|
|
|
if (s == 4) {
|
|
float oldDepth = 1.0f;
|
|
Do(p, oldDepth);
|
|
}
|
|
|
|
Do(p, curRTWidth);
|
|
Do(p, curRTHeight);
|
|
|
|
// curRTBufferWidth, curRTBufferHeight, and cutRTOffsetX don't need to be saved.
|
|
if (s < 5) {
|
|
savedContextVersion = 0;
|
|
} else {
|
|
Do(p, savedContextVersion);
|
|
}
|
|
|
|
if (p.GetMode() == PointerWrap::MODE_READ) {
|
|
gstate_c.Dirty(DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX);
|
|
}
|
|
}
|
|
|
|
static constexpr const char * g_gpuUseFlagNames[32] = {
|
|
"GPU_USE_DUALSOURCE_BLEND",
|
|
"GPU_USE_LIGHT_UBERSHADER",
|
|
"GPU_USE_FRAGMENT_TEST_CACHE",
|
|
"GPU_USE_VS_RANGE_CULLING",
|
|
"GPU_USE_BLEND_MINMAX",
|
|
"GPU_USE_LOGIC_OP",
|
|
"N/A",
|
|
"N/A",
|
|
"GPU_USE_ANISOTROPY",
|
|
"GPU_USE_CLEAR_RAM_HACK",
|
|
"N/A",
|
|
"N/A",
|
|
"N/A",
|
|
"GPU_USE_16BIT_FORMATS",
|
|
"GPU_USE_DEPTH_CLAMP",
|
|
"GPU_USE_TEXTURE_LOD_CONTROL",
|
|
"GPU_USE_DEPTH_TEXTURE",
|
|
"N/A",
|
|
"N/A",
|
|
"GPU_USE_FRAMEBUFFER_ARRAYS",
|
|
"GPU_USE_FRAMEBUFFER_FETCH",
|
|
"N/A",
|
|
"GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT",
|
|
"GPU_ROUND_DEPTH_TO_16BIT",
|
|
"GPU_USE_CLIP_DISTANCE",
|
|
"GPU_USE_CULL_DISTANCE",
|
|
"GPU_USE_SHADER_BLENDING", // bit 26
|
|
"GPU_USE_NONBUFFERED_FLIP", // bit 27
|
|
"GPU_USE_PRE_ROTATION", // bit 28
|
|
"GPU_USE_VIRTUAL_REALITY",
|
|
"GPU_USE_SINGLE_PASS_STEREO",
|
|
"GPU_USE_SIMPLE_STEREO_PERSPECTIVE",
|
|
};
|
|
|
|
const char *GpuUseFlagToString(int useFlag) {
|
|
if ((u32)useFlag < 32) {
|
|
return g_gpuUseFlagNames[useFlag];
|
|
} else {
|
|
return "N/A";
|
|
}
|
|
}
|
|
|
|
bool GPUStateCache::SetUseFlags(const u32 newFlags) {
|
|
if (newFlags != useFlags_) {
|
|
if (useFlags_ != 0 && newFlags != 0) {
|
|
INFO_LOG(Log::G3D, "Shader useflags changed from %08x to %08x:", useFlags_, newFlags);
|
|
for (int i = 0; i < 32; i++) {
|
|
const int mask = 1 << i;
|
|
bool oldVal = (useFlags_ & mask) != 0;
|
|
bool newVal = (newFlags & mask) != 0;
|
|
if (oldVal != newVal) {
|
|
INFO_LOG(Log::G3D, "%s changed from %d to %d", g_gpuUseFlagNames[i], (int)oldVal, (int)newVal);
|
|
}
|
|
}
|
|
useFlagsChanged = true;
|
|
}
|
|
useFlags_ = newFlags;
|
|
}
|
|
return useFlagsChanged;
|
|
}
|