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