CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/GPU/Debugger/Record.cpp
Views: 1401
1
// Copyright (c) 2017- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include <algorithm>
19
#include <atomic>
20
#include <cstring>
21
#include <functional>
22
#include <set>
23
#include <vector>
24
#include <mutex>
25
#include <zstd.h>
26
27
#include "Common/CommonTypes.h"
28
#include "Common/File/FileUtil.h"
29
#include "Common/Thread/ParallelLoop.h"
30
#include "Common/Log.h"
31
#include "Common/StringUtils.h"
32
#include "Common/System/System.h"
33
34
#include "Core/Core.h"
35
#include "Core/ELF/ParamSFO.h"
36
#include "Core/HLE/sceDisplay.h"
37
#include "Core/MemMap.h"
38
#include "Core/System.h"
39
#include "Core/ThreadPools.h"
40
#include "GPU/Common/GPUDebugInterface.h"
41
#include "GPU/GPUInterface.h"
42
#include "GPU/GPUState.h"
43
#include "GPU/ge_constants.h"
44
#include "GPU/Common/TextureDecoder.h"
45
#include "GPU/Common/VertexDecoderCommon.h"
46
#include "GPU/Debugger/Record.h"
47
#include "GPU/Debugger/RecordFormat.h"
48
49
namespace GPURecord {
50
51
static bool active = false;
52
static std::atomic<bool> nextFrame = false;
53
static int flipLastAction = -1;
54
static int flipFinishAt = -1;
55
static uint32_t lastEdramTrans = 0x400;
56
static std::function<void(const Path &)> writeCallback;
57
58
static std::vector<u8> pushbuf;
59
static std::vector<Command> commands;
60
static std::vector<u32> lastRegisters;
61
static std::vector<u32> lastTextures;
62
static std::set<u32> lastRenderTargets;
63
static std::vector<u8> lastVRAM;
64
65
enum class DirtyVRAMFlag : uint8_t {
66
CLEAN = 0,
67
UNKNOWN = 1,
68
DIRTY = 2,
69
DRAWN = 3,
70
};
71
static constexpr uint32_t DIRTY_VRAM_SHIFT = 8;
72
static constexpr uint32_t DIRTY_VRAM_ROUND = (1 << DIRTY_VRAM_SHIFT) - 1;
73
static constexpr uint32_t DIRTY_VRAM_SIZE = (2 * 1024 * 1024) >> DIRTY_VRAM_SHIFT;
74
static constexpr uint32_t DIRTY_VRAM_MASK = (2 * 1024 * 1024 - 1) >> DIRTY_VRAM_SHIFT;
75
static DirtyVRAMFlag dirtyVRAM[DIRTY_VRAM_SIZE];
76
77
static void FlushRegisters() {
78
if (!lastRegisters.empty()) {
79
Command last{CommandType::REGISTERS};
80
last.ptr = (u32)pushbuf.size();
81
last.sz = (u32)(lastRegisters.size() * sizeof(u32));
82
pushbuf.resize(pushbuf.size() + last.sz);
83
memcpy(pushbuf.data() + last.ptr, lastRegisters.data(), last.sz);
84
lastRegisters.clear();
85
86
commands.push_back(last);
87
}
88
}
89
90
static Path GenRecordingFilename() {
91
const Path dumpDir = GetSysDirectory(DIRECTORY_DUMP);
92
93
File::CreateFullPath(dumpDir);
94
95
const std::string prefix = g_paramSFO.GetDiscID();
96
97
for (int n = 1; n < 10000; ++n) {
98
std::string filename = StringFromFormat("%s_%04d.ppdmp", prefix.c_str(), n);
99
100
const Path path = dumpDir / filename;
101
102
if (!File::Exists(path)) {
103
return path;
104
}
105
}
106
107
return dumpDir / StringFromFormat("%s_%04d.ppdmp", prefix.c_str(), 9999);
108
}
109
110
static void DirtyAllVRAM(DirtyVRAMFlag flag) {
111
if (flag == DirtyVRAMFlag::UNKNOWN) {
112
for (uint32_t i = 0; i < DIRTY_VRAM_SIZE; ++i) {
113
if (dirtyVRAM[i] == DirtyVRAMFlag::CLEAN)
114
dirtyVRAM[i] = DirtyVRAMFlag::UNKNOWN;
115
}
116
} else {
117
for (uint32_t i = 0; i < DIRTY_VRAM_SIZE; ++i)
118
dirtyVRAM[i] = flag;
119
}
120
}
121
122
static void DirtyVRAM(u32 start, u32 sz, DirtyVRAMFlag flag) {
123
u32 count = (sz + DIRTY_VRAM_ROUND) >> DIRTY_VRAM_SHIFT;
124
u32 first = (start >> DIRTY_VRAM_SHIFT) & DIRTY_VRAM_MASK;
125
if (first + count > DIRTY_VRAM_SIZE) {
126
DirtyAllVRAM(flag);
127
return;
128
}
129
130
for (u32 i = 0; i < count; ++i)
131
dirtyVRAM[first + i] = flag;
132
}
133
134
static void DirtyDrawnVRAM() {
135
int w = std::min(gstate.getScissorX2(), gstate.getRegionX2()) + 1;
136
int h = std::min(gstate.getScissorY2(), gstate.getRegionY2()) + 1;
137
138
bool drawZ = !gstate.isModeClear() && gstate.isDepthWriteEnabled() && gstate.isDepthTestEnabled();
139
bool clearZ = gstate.isModeClear() && gstate.isClearModeDepthMask();
140
if (drawZ || clearZ) {
141
int bytes = 2 * gstate.DepthBufStride() * h;
142
if (w > gstate.DepthBufStride())
143
bytes += 2 * (w - gstate.DepthBufStride());
144
DirtyVRAM(gstate.getDepthBufAddress(), bytes, DirtyVRAMFlag::DRAWN);
145
}
146
147
int bpp = gstate.FrameBufFormat() == GE_FORMAT_8888 ? 4 : 2;
148
int bytes = bpp * gstate.FrameBufStride() * h;
149
if (w > gstate.FrameBufStride())
150
bytes += bpp * (w - gstate.FrameBufStride());
151
DirtyVRAM(gstate.getFrameBufAddress(), bytes, DirtyVRAMFlag::DRAWN);
152
}
153
154
static void BeginRecording() {
155
active = true;
156
nextFrame = false;
157
lastTextures.clear();
158
lastRenderTargets.clear();
159
flipLastAction = gpuStats.numFlips;
160
flipFinishAt = -1;
161
162
u32 ptr = (u32)pushbuf.size();
163
u32 sz = 512 * 4;
164
pushbuf.resize(pushbuf.size() + sz);
165
gstate.Save((u32_le *)(pushbuf.data() + ptr));
166
commands.push_back({CommandType::INIT, sz, ptr});
167
lastVRAM.resize(2 * 1024 * 1024);
168
169
// Also save the initial CLUT.
170
GPUDebugBuffer clut;
171
if (gpuDebug->GetCurrentClut(clut)) {
172
sz = clut.GetStride() * clut.PixelSize();
173
_assert_msg_(sz == 1024, "CLUT should be 1024 bytes");
174
ptr = (u32)pushbuf.size();
175
pushbuf.resize(pushbuf.size() + sz);
176
memcpy(pushbuf.data() + ptr, clut.GetData(), sz);
177
commands.push_back({ CommandType::CLUT, sz, ptr });
178
}
179
180
DirtyAllVRAM(DirtyVRAMFlag::DIRTY);
181
}
182
183
static void WriteCompressed(FILE *fp, const void *p, size_t sz) {
184
size_t compressed_size = ZSTD_compressBound(sz);
185
u8 *compressed = new u8[compressed_size];
186
compressed_size = ZSTD_compress(compressed, compressed_size, p, sz, 6);
187
188
u32 write_size = (u32)compressed_size;
189
fwrite(&write_size, sizeof(write_size), 1, fp);
190
fwrite(compressed, compressed_size, 1, fp);
191
192
delete [] compressed;
193
}
194
195
static Path WriteRecording() {
196
FlushRegisters();
197
198
const Path filename = GenRecordingFilename();
199
200
NOTICE_LOG(Log::G3D, "Recording filename: %s", filename.c_str());
201
202
FILE *fp = File::OpenCFile(filename, "wb");
203
Header header{};
204
strncpy(header.magic, HEADER_MAGIC, sizeof(header.magic));
205
header.version = VERSION;
206
strncpy(header.gameID, g_paramSFO.GetDiscID().c_str(), sizeof(header.gameID));
207
fwrite(&header, sizeof(header), 1, fp);
208
209
u32 sz = (u32)commands.size();
210
fwrite(&sz, sizeof(sz), 1, fp);
211
u32 bufsz = (u32)pushbuf.size();
212
fwrite(&bufsz, sizeof(bufsz), 1, fp);
213
214
WriteCompressed(fp, commands.data(), commands.size() * sizeof(Command));
215
WriteCompressed(fp, pushbuf.data(), bufsz);
216
217
fclose(fp);
218
219
return filename;
220
}
221
222
static void GetVertDataSizes(int vcount, const void *indices, u32 &vbytes, u32 &ibytes) {
223
VertexDecoder vdec;
224
VertexDecoderOptions opts{};
225
vdec.SetVertexType(gstate.vertType, opts);
226
227
if (indices) {
228
u16 lower = 0;
229
u16 upper = 0;
230
GetIndexBounds(indices, vcount, gstate.vertType, &lower, &upper);
231
232
vbytes = (upper + 1) * vdec.VertexSize();
233
u32 idx = gstate.vertType & GE_VTYPE_IDX_MASK;
234
if (idx == GE_VTYPE_IDX_8BIT) {
235
ibytes = vcount * sizeof(u8);
236
} else if (idx == GE_VTYPE_IDX_16BIT) {
237
ibytes = vcount * sizeof(u16);
238
} else if (idx == GE_VTYPE_IDX_32BIT) {
239
ibytes = vcount * sizeof(u32);
240
}
241
} else {
242
vbytes = vcount * vdec.VertexSize();
243
}
244
}
245
246
static const u8 *mymemmem(const u8 *haystack, size_t off, size_t hlen, const u8 *needle, size_t nlen, uintptr_t align) {
247
if (!nlen) {
248
return nullptr;
249
}
250
251
const u8 *last_possible = haystack + hlen - nlen;
252
const u8 *first_possible = haystack + off;
253
int first = *needle;
254
255
const u8 *result = nullptr;
256
std::mutex resultLock;
257
258
int range = (int)(last_possible - first_possible);
259
ParallelRangeLoop(&g_threadManager, [&](int l, int h) {
260
const u8 *p = haystack + off + l;
261
const u8 *pend = haystack + off + h;
262
263
const uintptr_t align_mask = align - 1;
264
auto poffset = [&]() {
265
return ((uintptr_t)(p - haystack) & align_mask);
266
};
267
auto alignp = [&]() {
268
uintptr_t offset = poffset();
269
if (offset != 0)
270
p += align - offset;
271
};
272
273
alignp();
274
while (p <= pend) {
275
p = (const u8 *)memchr(p, first, pend - p + 1);
276
if (!p) {
277
return;
278
}
279
if (poffset() == 0 && !memcmp(p, needle, nlen)) {
280
std::lock_guard<std::mutex> guard(resultLock);
281
// Take the lowest result so we get the same file for any # of threads.
282
if (!result || p < result)
283
result = p;
284
return;
285
}
286
287
p++;
288
alignp();
289
}
290
}, 0, range, 128 * 1024, TaskPriority::LOW);
291
292
return result;
293
}
294
295
static Command EmitCommandWithRAM(CommandType t, const void *p, u32 sz, u32 align) {
296
FlushRegisters();
297
298
Command cmd{t, sz, 0};
299
300
if (sz) {
301
// If at all possible, try to find it already in the buffer.
302
const u8 *prev = nullptr;
303
const size_t NEAR_WINDOW = std::max((int)sz * 2, 1024 * 10);
304
// Let's try nearby first... it will often be nearby.
305
if (pushbuf.size() > NEAR_WINDOW) {
306
prev = mymemmem(pushbuf.data(), pushbuf.size() - NEAR_WINDOW, pushbuf.size(), (const u8 *)p, sz, align);
307
}
308
if (!prev) {
309
prev = mymemmem(pushbuf.data(), 0, pushbuf.size(), (const u8 *)p, sz, align);
310
}
311
312
if (prev) {
313
cmd.ptr = (u32)(prev - pushbuf.data());
314
} else {
315
cmd.ptr = (u32)pushbuf.size();
316
int pad = 0;
317
if (cmd.ptr & (align - 1)) {
318
pad = align - (cmd.ptr & (align - 1));
319
cmd.ptr += pad;
320
}
321
pushbuf.resize(pushbuf.size() + sz + pad);
322
if (pad) {
323
memset(pushbuf.data() + cmd.ptr - pad, 0, pad);
324
}
325
memcpy(pushbuf.data() + cmd.ptr, p, sz);
326
}
327
}
328
329
commands.push_back(cmd);
330
331
return cmd;
332
}
333
334
static void UpdateLastVRAM(u32 addr, u32 bytes) {
335
u32 base = addr & 0x001FFFFF;
336
if (base + bytes > 0x00200000) {
337
memcpy(&lastVRAM[base], Memory::GetPointerUnchecked(0x04000000 | base), 0x00200000 - base);
338
bytes = base + bytes - 0x00200000;
339
base = 0;
340
}
341
memcpy(&lastVRAM[base], Memory::GetPointerUnchecked(0x04000000 | base), bytes);
342
}
343
344
static void ClearLastVRAM(u32 addr, u8 c, u32 bytes) {
345
u32 base = addr & 0x001FFFFF;
346
if (base + bytes > 0x00200000) {
347
memset(&lastVRAM[base], c, 0x00200000 - base);
348
bytes = base + bytes - 0x00200000;
349
base = 0;
350
}
351
memset(&lastVRAM[base], c, bytes);
352
}
353
354
static int CompareLastVRAM(u32 addr, u32 bytes) {
355
u32 base = addr & 0x001FFFFF;
356
if (base + bytes > 0x00200000) {
357
int result = memcmp(&lastVRAM[base], Memory::GetPointerUnchecked(0x04000000 | base), 0x00200000 - base);
358
if (result != 0)
359
return result;
360
361
bytes = base + bytes - 0x00200000;
362
base = 0;
363
}
364
return memcmp(&lastVRAM[base], Memory::GetPointerUnchecked(0x04000000 | base), bytes);
365
}
366
367
static u32 GetTargetFlags(u32 addr, u32 sizeInRAM) {
368
addr &= 0x041FFFFF;
369
const bool isTarget = lastRenderTargets.find(addr) != lastRenderTargets.end();
370
371
bool isUnknownVRAM = false;
372
bool isDirtyVRAM = false;
373
bool isDrawnVRAM = false;
374
uint32_t start = (addr >> DIRTY_VRAM_SHIFT) & DIRTY_VRAM_MASK;
375
uint32_t blocks = (sizeInRAM + DIRTY_VRAM_ROUND) >> DIRTY_VRAM_SHIFT;
376
if (start + blocks >= DIRTY_VRAM_SIZE)
377
return 0;
378
bool startEven = (addr & DIRTY_VRAM_ROUND) == 0;
379
bool endEven = ((addr + sizeInRAM) & DIRTY_VRAM_ROUND) == 0;
380
for (uint32_t i = 0; i < blocks; ++i) {
381
DirtyVRAMFlag flag = dirtyVRAM[start + i];
382
isUnknownVRAM = (isUnknownVRAM || flag == DirtyVRAMFlag::UNKNOWN) && flag != DirtyVRAMFlag::DIRTY && flag != DirtyVRAMFlag::DRAWN;
383
isDirtyVRAM = isDirtyVRAM || flag != DirtyVRAMFlag::CLEAN;
384
isDrawnVRAM = isDrawnVRAM || flag == DirtyVRAMFlag::DRAWN;
385
386
// Mark the VRAM clean now that it's been copied to VRAM.
387
if (flag == DirtyVRAMFlag::UNKNOWN || flag == DirtyVRAMFlag::DIRTY) {
388
if ((i > 0 || startEven) && (i < blocks || endEven))
389
dirtyVRAM[start + i] = DirtyVRAMFlag::CLEAN;
390
}
391
}
392
393
if (isUnknownVRAM && isDirtyVRAM) {
394
// This means it's only UNKNOWN/CLEAN and not known to be actually dirty.
395
// Let's check our shadow copy of what we last sent for this VRAM.
396
int diff = CompareLastVRAM(addr, sizeInRAM);
397
if (diff == 0)
398
isDirtyVRAM = false;
399
}
400
401
// The isTarget flag is mostly used for replay of dumps on a PSP.
402
u32 flags = isTarget ? 1 : 0;
403
// The unchangedVRAM flag tells us we can skip recopying.
404
if (!isDirtyVRAM)
405
flags |= 2;
406
// And the drawn flag tells us this data was potentially drawn to.
407
if (isDrawnVRAM)
408
flags |= 4;
409
410
return flags;
411
}
412
413
static void EmitTextureData(int level, u32 texaddr) {
414
GETextureFormat format = gstate.getTextureFormat();
415
int w = gstate.getTextureWidth(level);
416
int h = gstate.getTextureHeight(level);
417
int bufw = GetTextureBufw(level, texaddr, format);
418
int extraw = w > bufw ? w - bufw : 0;
419
u32 sizeInRAM = (textureBitsPerPixel[format] * (bufw * h + extraw)) / 8;
420
421
CommandType type = CommandType((int)CommandType::TEXTURE0 + level);
422
const u8 *p = Memory::GetPointerUnchecked(texaddr);
423
u32 bytes = Memory::ValidSize(texaddr, sizeInRAM);
424
std::vector<u8> framebufData;
425
426
if (Memory::IsVRAMAddress(texaddr)) {
427
struct FramebufData {
428
u32 addr;
429
int bufw;
430
u32 flags;
431
u32 pad;
432
};
433
434
u32 flags = GetTargetFlags(texaddr, bytes);
435
FramebufData framebuf{ texaddr, bufw, flags };
436
framebufData.resize(sizeof(framebuf) + bytes);
437
memcpy(&framebufData[0], &framebuf, sizeof(framebuf));
438
memcpy(&framebufData[sizeof(framebuf)], p, bytes);
439
p = &framebufData[0];
440
441
if ((flags & 2) == 0)
442
UpdateLastVRAM(texaddr, bytes);
443
444
// Okay, now we'll just emit this instead.
445
type = CommandType((int)CommandType::FRAMEBUF0 + level);
446
bytes += (u32)sizeof(framebuf);
447
}
448
449
if (bytes > 0) {
450
FlushRegisters();
451
452
// Dumps are huge - let's try to find this already emitted.
453
for (u32 prevptr : lastTextures) {
454
if (pushbuf.size() < prevptr + bytes) {
455
continue;
456
}
457
458
if (memcmp(pushbuf.data() + prevptr, p, bytes) == 0) {
459
commands.push_back({type, bytes, prevptr});
460
// Okay, that was easy. Bail out.
461
return;
462
}
463
}
464
465
// Not there, gotta emit anew.
466
Command cmd = EmitCommandWithRAM(type, p, bytes, 16);
467
lastTextures.push_back(cmd.ptr);
468
}
469
}
470
471
static void FlushPrimState(int vcount) {
472
// TODO: Eventually, how do we handle texturing from framebuf/zbuf?
473
// TODO: Do we need to preload color/depth/stencil (in case from last frame)?
474
475
lastRenderTargets.insert(PSP_GetVidMemBase() | gstate.getFrameBufRawAddress());
476
lastRenderTargets.insert(PSP_GetVidMemBase() | gstate.getDepthBufRawAddress());
477
478
// We re-flush textures always in case the game changed them... kinda expensive.
479
bool textureEnabled = gstate.isTextureMapEnabled() || gstate.isAntiAliasEnabled();
480
// Play it safe and allow texture coords to emit data too.
481
bool textureCoords = (gstate.vertType & GE_VTYPE_TC_MASK) != 0;
482
for (int level = 0; level < 8; ++level) {
483
u32 texaddr = gstate.getTextureAddress(level);
484
if (texaddr && (textureEnabled || textureCoords)) {
485
EmitTextureData(level, texaddr);
486
}
487
}
488
489
const void *verts = Memory::GetPointer(gstate_c.vertexAddr);
490
const void *indices = nullptr;
491
if ((gstate.vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) {
492
indices = Memory::GetPointer(gstate_c.indexAddr);
493
}
494
495
u32 ibytes = 0;
496
u32 vbytes = 0;
497
GetVertDataSizes(vcount, indices, vbytes, ibytes);
498
499
if (indices && ibytes > 0) {
500
EmitCommandWithRAM(CommandType::INDICES, indices, ibytes, 4);
501
}
502
if (verts && vbytes > 0) {
503
EmitCommandWithRAM(CommandType::VERTICES, verts, vbytes, 4);
504
}
505
}
506
507
static void EmitTransfer(u32 op) {
508
FlushRegisters();
509
510
// This may not make a lot of sense right now, unless it's to a framebuf...
511
u32 dstBasePtr = gstate.getTransferDstAddress();
512
if (!Memory::IsVRAMAddress(dstBasePtr)) {
513
// Skip, not VRAM, so can't affect drawing (we flush textures each prim.)
514
return;
515
}
516
517
u32 srcBasePtr = gstate.getTransferSrcAddress();
518
u32 srcStride = gstate.getTransferSrcStride();
519
int srcX = gstate.getTransferSrcX();
520
int srcY = gstate.getTransferSrcY();
521
u32 dstStride = gstate.getTransferDstStride();
522
int dstX = gstate.getTransferDstX();
523
int dstY = gstate.getTransferDstY();
524
int width = gstate.getTransferWidth();
525
int height = gstate.getTransferHeight();
526
int bpp = gstate.getTransferBpp();
527
528
u32 srcBytes = ((srcY + height - 1) * srcStride + (srcX + width)) * bpp;
529
srcBytes = Memory::ValidSize(srcBasePtr, srcBytes);
530
531
u32 dstBytes = ((dstY + height - 1) * dstStride + (dstX + width)) * bpp;
532
dstBytes = Memory::ValidSize(dstBasePtr, dstBytes);
533
534
if (srcBytes != 0) {
535
EmitCommandWithRAM(CommandType::TRANSFERSRC, Memory::GetPointerUnchecked(srcBasePtr), srcBytes, 16);
536
DirtyVRAM(dstBasePtr, dstBytes, DirtyVRAMFlag::DIRTY);
537
}
538
539
lastRegisters.push_back(op);
540
}
541
542
static void EmitClut(u32 op) {
543
u32 addr = gstate.getClutAddress();
544
545
// Hardware rendering may be using a framebuffer as CLUT.
546
// To get at this, we first run the command (normally we're called right before it has run.)
547
if (Memory::IsVRAMAddress(addr))
548
gpuDebug->SetCmdValue(op);
549
550
// Actually should only be 0x3F, but we allow enhanced CLUTs. See #15727.
551
u32 blocks = (op & 0x7F) == 0x40 ? 0x40 : (op & 0x3F);
552
u32 bytes = blocks * 32;
553
bytes = Memory::ValidSize(addr, bytes);
554
555
if (bytes != 0) {
556
// Send the original address so VRAM can be reasoned about.
557
if (Memory::IsVRAMAddress(addr)) {
558
struct ClutAddrData {
559
u32 addr;
560
u32 flags;
561
};
562
u32 flags = GetTargetFlags(addr, bytes);
563
ClutAddrData data{ addr, flags };
564
565
FlushRegisters();
566
Command cmd{CommandType::CLUTADDR, sizeof(data), (u32)pushbuf.size()};
567
pushbuf.resize(pushbuf.size() + sizeof(data));
568
memcpy(pushbuf.data() + cmd.ptr, &data, sizeof(data));
569
commands.push_back(cmd);
570
571
if ((flags & 2) == 0)
572
UpdateLastVRAM(addr, bytes);
573
}
574
EmitCommandWithRAM(CommandType::CLUT, Memory::GetPointerUnchecked(addr), bytes, 16);
575
}
576
577
lastRegisters.push_back(op);
578
}
579
580
static void EmitPrim(u32 op) {
581
FlushPrimState(op & 0x0000FFFF);
582
583
lastRegisters.push_back(op);
584
DirtyDrawnVRAM();
585
}
586
587
static void EmitBezierSpline(u32 op) {
588
int ucount = op & 0xFF;
589
int vcount = (op >> 8) & 0xFF;
590
FlushPrimState(ucount * vcount);
591
592
lastRegisters.push_back(op);
593
DirtyDrawnVRAM();
594
}
595
596
bool IsActive() {
597
return active;
598
}
599
600
bool IsActivePending() {
601
return nextFrame || active;
602
}
603
604
bool RecordNextFrame(const std::function<void(const Path &)> callback) {
605
if (!nextFrame) {
606
flipLastAction = gpuStats.numFlips;
607
flipFinishAt = -1;
608
writeCallback = callback;
609
nextFrame = true;
610
return true;
611
}
612
return false;
613
}
614
615
void ClearCallback() {
616
// Not super thread safe..
617
writeCallback = nullptr;
618
}
619
620
static void FinishRecording() {
621
// We're done - this was just to write the result out.
622
Path filename = WriteRecording();
623
commands.clear();
624
pushbuf.clear();
625
lastVRAM.clear();
626
627
NOTICE_LOG(Log::System, "Recording finished");
628
active = false;
629
flipLastAction = gpuStats.numFlips;
630
flipFinishAt = -1;
631
lastEdramTrans = 0x400;
632
633
if (writeCallback) {
634
writeCallback(filename);
635
}
636
writeCallback = nullptr;
637
}
638
639
static void CheckEdramTrans() {
640
if (!gpuDebug)
641
return;
642
643
uint32_t value = gpuDebug->GetAddrTranslation();
644
if (value == lastEdramTrans)
645
return;
646
lastEdramTrans = value;
647
648
FlushRegisters();
649
Command cmd{CommandType::EDRAMTRANS, sizeof(value), (u32)pushbuf.size()};
650
pushbuf.resize(pushbuf.size() + sizeof(value));
651
memcpy(pushbuf.data() + cmd.ptr, &value, sizeof(value));
652
commands.push_back(cmd);
653
}
654
655
void NotifyCommand(u32 pc) {
656
if (!active) {
657
return;
658
}
659
660
CheckEdramTrans();
661
const u32 op = Memory::Read_U32(pc);
662
const GECommand cmd = GECommand(op >> 24);
663
664
switch (cmd) {
665
case GE_CMD_VADDR:
666
case GE_CMD_IADDR:
667
case GE_CMD_JUMP:
668
case GE_CMD_CALL:
669
case GE_CMD_RET:
670
case GE_CMD_END:
671
case GE_CMD_SIGNAL:
672
case GE_CMD_FINISH:
673
case GE_CMD_BASE:
674
case GE_CMD_OFFSETADDR:
675
case GE_CMD_ORIGIN:
676
// These just prepare future commands, and are flushed with those commands.
677
// TODO: Maybe add a command just to log that these were hit?
678
break;
679
680
case GE_CMD_BOUNDINGBOX:
681
case GE_CMD_BJUMP:
682
// Since we record each command, this is theoretically not relevant.
683
// TODO: Output a CommandType to validate this.
684
break;
685
686
case GE_CMD_PRIM:
687
EmitPrim(op);
688
break;
689
690
case GE_CMD_BEZIER:
691
case GE_CMD_SPLINE:
692
EmitBezierSpline(op);
693
break;
694
695
case GE_CMD_LOADCLUT:
696
EmitClut(op);
697
break;
698
699
case GE_CMD_TRANSFERSTART:
700
EmitTransfer(op);
701
break;
702
703
default:
704
lastRegisters.push_back(op);
705
break;
706
}
707
}
708
709
void NotifyMemcpy(u32 dest, u32 src, u32 sz) {
710
if (!active) {
711
return;
712
}
713
714
CheckEdramTrans();
715
if (Memory::IsVRAMAddress(dest)) {
716
FlushRegisters();
717
Command cmd{CommandType::MEMCPYDEST, sizeof(dest), (u32)pushbuf.size()};
718
pushbuf.resize(pushbuf.size() + sizeof(dest));
719
memcpy(pushbuf.data() + cmd.ptr, &dest, sizeof(dest));
720
commands.push_back(cmd);
721
722
sz = Memory::ValidSize(dest, sz);
723
if (sz != 0) {
724
EmitCommandWithRAM(CommandType::MEMCPYDATA, Memory::GetPointerUnchecked(dest), sz, 1);
725
UpdateLastVRAM(dest, sz);
726
DirtyVRAM(dest, sz, DirtyVRAMFlag::CLEAN);
727
}
728
}
729
}
730
731
void NotifyMemset(u32 dest, int v, u32 sz) {
732
if (!active) {
733
return;
734
}
735
736
CheckEdramTrans();
737
struct MemsetCommand {
738
u32 dest;
739
int value;
740
u32 sz;
741
};
742
743
if (Memory::IsVRAMAddress(dest)) {
744
sz = Memory::ValidSize(dest, sz);
745
MemsetCommand data{dest, v, sz};
746
747
FlushRegisters();
748
Command cmd{CommandType::MEMSET, sizeof(data), (u32)pushbuf.size()};
749
pushbuf.resize(pushbuf.size() + sizeof(data));
750
memcpy(pushbuf.data() + cmd.ptr, &data, sizeof(data));
751
commands.push_back(cmd);
752
ClearLastVRAM(dest, v, sz);
753
DirtyVRAM(dest, sz, DirtyVRAMFlag::CLEAN);
754
}
755
}
756
757
void NotifyUpload(u32 dest, u32 sz) {
758
// This also checks the edram translation value and dirties VRAM.
759
NotifyMemcpy(dest, dest, sz);
760
}
761
762
static bool HasDrawCommands() {
763
if (commands.empty())
764
return false;
765
766
for (const Command &cmd : commands) {
767
switch (cmd.type) {
768
case CommandType::INIT:
769
case CommandType::DISPLAY:
770
continue;
771
772
default:
773
return true;
774
}
775
}
776
777
// Only init and display commands, keep going.
778
return false;
779
}
780
781
void NotifyDisplay(u32 framebuf, int stride, int fmt) {
782
bool writePending = false;
783
if (active && HasDrawCommands()) {
784
writePending = true;
785
}
786
if (!active && nextFrame && (gstate_c.skipDrawReason & SKIPDRAW_SKIPFRAME) == 0) {
787
NOTICE_LOG(Log::System, "Recording starting on display...");
788
BeginRecording();
789
}
790
if (!active) {
791
return;
792
}
793
794
CheckEdramTrans();
795
struct DisplayBufData {
796
PSPPointer<u8> topaddr;
797
int linesize, pixelFormat;
798
};
799
800
DisplayBufData disp{ { framebuf }, stride, fmt };
801
802
FlushRegisters();
803
u32 ptr = (u32)pushbuf.size();
804
u32 sz = (u32)sizeof(disp);
805
pushbuf.resize(pushbuf.size() + sz);
806
memcpy(pushbuf.data() + ptr, &disp, sz);
807
808
commands.push_back({ CommandType::DISPLAY, sz, ptr });
809
810
if (writePending) {
811
NOTICE_LOG(Log::System, "Recording complete on display");
812
FinishRecording();
813
}
814
}
815
816
void NotifyBeginFrame() {
817
const bool noDisplayAction = flipLastAction + 4 < gpuStats.numFlips;
818
// We do this only to catch things that don't call NotifyDisplay.
819
if (active && HasDrawCommands() && (noDisplayAction || gpuStats.numFlips == flipFinishAt)) {
820
NOTICE_LOG(Log::System, "Recording complete on frame");
821
822
CheckEdramTrans();
823
struct DisplayBufData {
824
PSPPointer<u8> topaddr;
825
u32 linesize, pixelFormat;
826
};
827
828
DisplayBufData disp;
829
__DisplayGetFramebuf(&disp.topaddr, &disp.linesize, &disp.pixelFormat, 0);
830
831
FlushRegisters();
832
u32 ptr = (u32)pushbuf.size();
833
u32 sz = (u32)sizeof(disp);
834
pushbuf.resize(pushbuf.size() + sz);
835
memcpy(pushbuf.data() + ptr, &disp, sz);
836
837
commands.push_back({ CommandType::DISPLAY, sz, ptr });
838
839
FinishRecording();
840
}
841
if (!active && nextFrame && (gstate_c.skipDrawReason & SKIPDRAW_SKIPFRAME) == 0 && noDisplayAction) {
842
NOTICE_LOG(Log::System, "Recording starting on frame...");
843
BeginRecording();
844
// If we began on a BeginFrame, end on a BeginFrame.
845
flipFinishAt = gpuStats.numFlips + 1;
846
}
847
}
848
849
void NotifyCPU() {
850
if (!active) {
851
return;
852
}
853
854
DirtyAllVRAM(DirtyVRAMFlag::UNKNOWN);
855
}
856
857
};
858
859