1 #include "device.hpp"
2 #include "renderer/renderer.hpp"
3 #include "stb_image_write.h"
4
5 #ifdef VULKAN_WSI
6 #include "wsi.hpp"
7 #endif
8 #include <cmath>
9 #include <random>
10 #include <renderer.hpp>
11 #include <stdio.h>
12 #include <string.h>
13 #include <vector>
14
15 using namespace PSX;
16 using namespace std;
17 using namespace Vulkan;
18
19 struct CLIParser;
20 struct CLICallbacks
21 {
addCLICallbacks22 void add(const char *cli, const function<void(CLIParser &)> &func)
23 {
24 callbacks[cli] = func;
25 }
26 unordered_map<string, function<void(CLIParser &)>> callbacks;
27 function<void()> error_handler;
28 function<void(const char *)> default_handler;
29 };
30
31 struct CLIParser
32 {
CLIParserCLIParser33 CLIParser(CLICallbacks cbs, int argc, char *argv[])
34 : cbs(move(cbs))
35 , argc(argc)
36 , argv(argv)
37 {
38 }
39
parseCLIParser40 bool parse()
41 {
42 try
43 {
44 while (argc && !ended_state)
45 {
46 const char *next = *argv++;
47 argc--;
48
49 if (*next != '-' && cbs.default_handler)
50 {
51 cbs.default_handler(next);
52 }
53 else
54 {
55 auto itr = cbs.callbacks.find(next);
56 if (itr == ::end(cbs.callbacks))
57 {
58 throw logic_error("Invalid argument.\n");
59 }
60
61 itr->second(*this);
62 }
63 }
64
65 return true;
66 }
67 catch (...)
68 {
69 if (cbs.error_handler)
70 {
71 cbs.error_handler();
72 }
73 return false;
74 }
75 }
76
endCLIParser77 void end()
78 {
79 ended_state = true;
80 }
81
next_uintCLIParser82 uint32_t next_uint()
83 {
84 if (!argc)
85 {
86 throw logic_error("Tried to parse uint, but nothing left in arguments.\n");
87 }
88
89 uint32_t val = stoul(*argv);
90 if (val > numeric_limits<uint32_t>::max())
91 {
92 throw out_of_range("next_uint() out of range.\n");
93 }
94
95 argc--;
96 argv++;
97
98 return val;
99 }
100
next_doubleCLIParser101 double next_double()
102 {
103 if (!argc)
104 {
105 throw logic_error("Tried to parse double, but nothing left in arguments.\n");
106 }
107
108 double val = stod(*argv);
109
110 argc--;
111 argv++;
112
113 return val;
114 }
115
next_stringCLIParser116 const char *next_string()
117 {
118 if (!argc)
119 {
120 throw logic_error("Tried to parse string, but nothing left in arguments.\n");
121 }
122
123 const char *ret = *argv;
124 argc--;
125 argv++;
126 return ret;
127 }
128
129 CLICallbacks cbs;
130 int argc;
131 char **argv;
132 bool ended_state = false;
133 };
134
135 struct CLIArguments
136 {
137 const char *dump = nullptr;
138 const char *frame_output = nullptr;
139 const char *trace_output = nullptr;
140 unsigned trace_frame = 0;
141 unsigned scale = 4;
142 bool trace = false;
143 bool verbose = false;
144 };
145
146 //#define DUMP_VRAM
147 #define SCALING 4
148 //#define DETAIL_DUMP_FRAME 153
149 //#define BREAK_FRAME 40
150 //#define BREAK_DRAW 216
151
152 #define BREAKPOINT __builtin_trap
153
154 // This enum should always be kept equivalent to the enum in rsx_dump.cpp
155 enum
156 {
157 RSX_END = 0,
158 RSX_PREPARE_FRAME,
159 RSX_FINALIZE_FRAME,
160 RSX_TEX_WINDOW,
161 RSX_DRAW_OFFSET,
162 RSX_DRAW_AREA,
163 RSX_VRAM_COORDS,
164 RSX_HORIZONTAL_RANGE,
165 RSX_VERTICAL_RANGE,
166 RSX_DISPLAY_MODE,
167 RSX_TRIANGLE,
168 RSX_QUAD,
169 RSX_LINE,
170 RSX_LOAD_IMAGE,
171 RSX_FILL_RECT,
172 RSX_COPY_RECT,
173 RSX_TOGGLE_DISPLAY
174 };
175
read_tag(FILE * file)176 static void read_tag(FILE *file)
177 {
178 char buffer[8];
179 if (fread(buffer, sizeof(buffer), 1, file) != 1)
180 throw runtime_error("Failed to read tag.");
181 if (memcmp(buffer, "RSXDUMP3", sizeof(buffer)))
182 throw runtime_error("Failed to read tag.");
183 }
184
read_u32(FILE * file)185 static uint32_t read_u32(FILE *file)
186 {
187 uint32_t val;
188 if (fread(&val, sizeof(val), 1, file) != 1)
189 throw runtime_error("Failed to read u32");
190 return val;
191 }
192
read_i32(FILE * file)193 static int32_t read_i32(FILE *file)
194 {
195 int32_t val;
196 if (fread(&val, sizeof(val), 1, file) != 1)
197 throw runtime_error("Failed to read i32");
198 return val;
199 }
200
read_f32(FILE * file)201 static int32_t read_f32(FILE *file)
202 {
203 float val;
204 if (fread(&val, sizeof(val), 1, file) != 1)
205 throw runtime_error("Failed to read f32");
206 return val;
207 }
208
209 struct CommandVertex
210 {
211 float x, y, w;
212 uint32_t color;
213 uint16_t tx, ty;
214 };
215
216 struct RenderState
217 {
218 uint16_t texpage_x, texpage_y;
219 uint16_t clut_x, clut_y;
220 uint8_t texture_blend_mode;
221 uint8_t depth_shift;
222 bool dither;
223 uint32_t blend_mode;
224 bool mask_test;
225 bool set_mask;
226 };
227
read_vertex(FILE * file)228 CommandVertex read_vertex(FILE *file)
229 {
230 CommandVertex buf = {};
231 buf.x = read_f32(file);
232 buf.y = read_f32(file);
233 buf.w = read_f32(file);
234 buf.color = read_u32(file);
235 buf.tx = uint16_t(read_u32(file));
236 buf.ty = uint16_t(read_u32(file));
237 return buf;
238 }
239
read_state(FILE * file)240 RenderState read_state(FILE *file)
241 {
242 RenderState state = {};
243 state.texpage_x = read_u32(file);
244 state.texpage_y = read_u32(file);
245 state.clut_x = read_u32(file);
246 state.clut_y = read_u32(file);
247 state.texture_blend_mode = read_u32(file);
248 state.depth_shift = read_u32(file);
249 state.dither = read_u32(file) != 0;
250 state.blend_mode = read_u32(file);
251 state.mask_test = read_u32(file) != 0;
252 state.set_mask = read_u32(file) != 0;
253 return state;
254 }
255
256 struct CommandLine
257 {
258 int16_t x0, y0, x1, y1;
259 uint32_t c0, c1;
260 bool dither;
261 uint32_t blend_mode;
262 bool mask_test;
263 bool set_mask;
264 };
265
read_line(FILE * file)266 CommandLine read_line(FILE *file)
267 {
268 CommandLine line = {};
269 line.x0 = read_i32(file);
270 line.y0 = read_i32(file);
271 line.x1 = read_i32(file);
272 line.y1 = read_i32(file);
273 line.c0 = read_u32(file);
274 line.c1 = read_u32(file);
275 line.dither = read_u32(file) != 0;
276 line.blend_mode = read_u32(file);
277 line.mask_test = read_u32(file) != 0;
278 line.set_mask = read_u32(file) != 0;
279 return line;
280 }
281
282 #if 0
283 static void log_vertex(const CommandVertex &v)
284 {
285 fprintf(stderr, " x = %.1f, y = %.1f, w = %.1f, c = 0x%x, u = %u, v = %u\n", v.x, v.y, v.w, v.color, v.tx, v.ty);
286 }
287
288 static void log_state(const RenderState &s)
289 {
290 fprintf(
291 stderr,
292 " Page = (%u, %u), CLUT = (%u, %u), texture_blend_mode = %u, depth_shift = %u, dither = %s, blend_mode = %u\n",
293 s.texpage_x, s.texpage_y, s.clut_x, s.clut_y, s.texture_blend_mode, s.depth_shift, s.dither ? "on" : "off",
294 s.blend_mode);
295 }
296 #endif
297
set_renderer_state(Renderer & renderer,const RenderState & state)298 static void set_renderer_state(Renderer &renderer, const RenderState &state)
299 {
300 renderer.set_texture_color_modulate(state.texture_blend_mode == 2);
301 renderer.set_palette_offset(state.clut_x, state.clut_y);
302 renderer.set_texture_offset(state.texpage_x, state.texpage_y);
303 renderer.set_dither(state.dither);
304 renderer.set_mask_test(state.mask_test);
305 renderer.set_force_mask_bit(state.set_mask);
306 if (state.texture_blend_mode != 0)
307 {
308 switch (state.depth_shift)
309 {
310 default:
311 case 0:
312 renderer.set_texture_mode(TextureMode::ABGR1555);
313 break;
314 case 1:
315 renderer.set_texture_mode(TextureMode::Palette8bpp);
316 break;
317 case 2:
318 renderer.set_texture_mode(TextureMode::Palette4bpp);
319 break;
320 }
321 }
322 else
323 renderer.set_texture_mode(TextureMode::None);
324
325 switch (state.blend_mode)
326 {
327 default:
328 renderer.set_semi_transparent(SemiTransparentMode::None);
329 break;
330
331 case 0:
332 renderer.set_semi_transparent(SemiTransparentMode::Average);
333 break;
334 case 1:
335 renderer.set_semi_transparent(SemiTransparentMode::Add);
336 break;
337 case 2:
338 renderer.set_semi_transparent(SemiTransparentMode::Sub);
339 break;
340 case 3:
341 renderer.set_semi_transparent(SemiTransparentMode::AddQuarter);
342 break;
343 }
344 }
345
dump_to_file(const CLIArguments & args,Device & device,Renderer & renderer,unsigned index,unsigned subindex)346 static void dump_to_file(const CLIArguments &args, Device &device, Renderer &renderer, unsigned index,
347 unsigned subindex)
348 {
349 unsigned width, height;
350 auto buffer = renderer.scanout_vram_to_buffer(width, height);
351 if (!buffer)
352 return;
353
354 char path[1024];
355 snprintf(path, sizeof(path), "%s-%06u-%06u.bmp", args.trace_output, index, subindex);
356
357 uint32_t *data = static_cast<uint32_t *>(device.map_host_buffer(*buffer, MEMORY_ACCESS_READ));
358 for (unsigned i = 0; i < width * height; i++)
359 data[i] |= 0xff000000u;
360
361 if (!stbi_write_bmp(path, width, height, 4, data))
362 LOG("Failed to write image.");
363 device.unmap_host_buffer(*buffer);
364 }
365
dump_vram_to_file(const CLIArguments & args,Device & device,Renderer & renderer,unsigned index)366 static void dump_vram_to_file(const CLIArguments &args, Device &device, Renderer &renderer, unsigned index)
367 {
368 unsigned width, height;
369 auto buffer = renderer.scanout_vram_to_buffer(width, height);
370 if (!buffer)
371 return;
372
373 char path[1024];
374 snprintf(path, sizeof(path), "%s-vram-%06u.bmp", args.frame_output, index);
375
376 uint32_t *data = static_cast<uint32_t *>(device.map_host_buffer(*buffer, MEMORY_ACCESS_READ));
377 for (unsigned i = 0; i < width * height; i++)
378 data[i] |= 0xff000000u;
379
380 if (!stbi_write_bmp(path, width, height, 4, data))
381 LOG("Failed to write image.");
382 device.unmap_host_buffer(*buffer);
383 }
384
read_command(const CLIArguments & args,FILE * file,Device & device,Renderer & renderer,bool & eof,unsigned & frame,unsigned & draw_call)385 static bool read_command(const CLIArguments &args, FILE *file, Device &device, Renderer &renderer, bool &eof,
386 unsigned &frame, unsigned &draw_call)
387 {
388 auto op = read_u32(file);
389 eof = false;
390 switch (op)
391 {
392 case RSX_PREPARE_FRAME:
393 break;
394 case RSX_FINALIZE_FRAME:
395 return false;
396 case RSX_END:
397 eof = true;
398 return false;
399
400 case RSX_TEX_WINDOW:
401 {
402 auto tww = read_u32(file);
403 auto twh = read_u32(file);
404 auto twx = read_u32(file);
405 auto twy = read_u32(file);
406
407 auto tex_x_mask = ~(tww << 3);
408 auto tex_y_mask = ~(twh << 3);
409 auto tex_x_or = (twx & tww) << 3;
410 auto tex_y_or = (twy & twh) << 3;
411
412 renderer.set_texture_window({ uint8_t(tex_x_mask), uint8_t(tex_y_mask), uint8_t(tex_x_or), uint8_t(tex_y_or) });
413 break;
414 }
415
416 case RSX_DRAW_OFFSET:
417 {
418 auto x = read_i32(file);
419 auto y = read_i32(file);
420
421 renderer.set_draw_offset(x, y);
422 break;
423 }
424
425 case RSX_DRAW_AREA:
426 {
427 auto x0 = read_u32(file);
428 auto y0 = read_u32(file);
429 auto x1 = read_u32(file);
430 auto y1 = read_u32(file);
431
432 int width = x1 - x0 + 1;
433 int height = y1 - y0 + 1;
434 width = max(width, 0);
435 height = max(height, 0);
436
437 width = min(width, int(FB_WIDTH - x0));
438 height = min(height, int(FB_HEIGHT - y0));
439 renderer.set_draw_rect({ x0, y0, unsigned(width), unsigned(height) });
440 break;
441 }
442
443 case RSX_VRAM_COORDS:
444 {
445 auto xstart = read_u32(file);
446 auto ystart = read_u32(file);
447
448 renderer.set_vram_framebuffer_coords(xstart, ystart);
449 break;
450 }
451
452 case RSX_HORIZONTAL_RANGE:
453 {
454 auto x1 = read_u32(file);
455 auto x2 = read_u32(file);
456
457 renderer.set_horizontal_display_range(x1, x2);
458 break;
459 }
460
461 case RSX_VERTICAL_RANGE:
462 {
463 auto y1 = read_u32(file);
464 auto y2 = read_u32(file);
465
466 renderer.set_vertical_display_range(y1, y2);
467 break;
468 }
469
470 case RSX_DISPLAY_MODE:
471 {
472 auto depth_24bpp = read_u32(file);
473 auto is_pal = readu32(file);
474 auto is_480i = readu32(file);
475 auto width_mode = readu32(file);
476
477 renderer.set_display_mode(depth_24bpp != 0, is_pal != 0, is_480i != 0,
478 static_cast<Renderer::WidthMode>(width_mode));
479 break;
480 }
481
482 case RSX_TRIANGLE:
483 {
484 auto v0 = read_vertex(file);
485 auto v1 = read_vertex(file);
486 auto v2 = read_vertex(file);
487 auto state = read_state(file);
488
489 Vertex vertices[3] = {
490 { v0.x, v0.y, v0.w, v0.color, v0.tx, v0.ty },
491 { v1.x, v1.y, v1.w, v1.color, v1.tx, v1.ty },
492 { v2.x, v2.y, v2.w, v2.color, v2.tx, v2.ty },
493 };
494
495 set_renderer_state(renderer, state);
496 renderer.draw_triangle(vertices);
497
498 if (args.trace && frame == args.trace_frame)
499 dump_to_file(args, device, renderer, frame, draw_call);
500
501 draw_call++;
502 break;
503 }
504
505 case RSX_QUAD:
506 {
507 auto v0 = read_vertex(file);
508 auto v1 = read_vertex(file);
509 auto v2 = read_vertex(file);
510 auto v3 = read_vertex(file);
511 auto state = read_state(file);
512
513 Vertex vertices[4] = {
514 { v0.x, v0.y, v0.w, v0.color, v0.tx, v0.ty },
515 { v1.x, v1.y, v1.w, v1.color, v1.tx, v1.ty },
516 { v2.x, v2.y, v2.w, v2.color, v2.tx, v2.ty },
517 { v3.x, v3.y, v3.w, v3.color, v3.tx, v3.ty },
518 };
519
520 set_renderer_state(renderer, state);
521 renderer.draw_quad(vertices);
522
523 if (args.trace && frame == args.trace_frame)
524 dump_to_file(args, device, renderer, frame, draw_call);
525
526 draw_call++;
527 break;
528 }
529
530 case RSX_LINE:
531 {
532 auto line = read_line(file);
533
534 Vertex vertices[2] = {
535 { float(line.x0), float(line.y0), 1.0f, line.c0, 0, 0 },
536 { float(line.x1), float(line.y1), 1.0f, line.c1, 0, 0 },
537 };
538
539 renderer.set_texture_color_modulate(false);
540 renderer.set_texture_mode(TextureMode::None);
541 renderer.set_dither(line.dither);
542 renderer.set_mask_test(line.mask_test);
543 renderer.set_force_mask_bit(line.set_mask);
544 switch (line.blend_mode)
545 {
546 default:
547 renderer.set_semi_transparent(SemiTransparentMode::None);
548 break;
549
550 case 0:
551 renderer.set_semi_transparent(SemiTransparentMode::Average);
552 break;
553 case 1:
554 renderer.set_semi_transparent(SemiTransparentMode::Add);
555 break;
556 case 2:
557 renderer.set_semi_transparent(SemiTransparentMode::Sub);
558 break;
559 case 3:
560 renderer.set_semi_transparent(SemiTransparentMode::AddQuarter);
561 break;
562 }
563
564 renderer.draw_line(vertices);
565
566 if (args.trace && frame == args.trace_frame)
567 dump_to_file(args, device, renderer, frame, draw_call);
568
569 draw_call++;
570 break;
571 }
572
573 case RSX_LOAD_IMAGE:
574 {
575 auto x = read_u32(file);
576 auto y = read_u32(file);
577 auto width = read_u32(file);
578 auto height = read_u32(file);
579 bool mask_test = read_u32(file) != 0;
580 bool set_mask = read_u32(file) != 0;
581
582 renderer.set_mask_test(mask_test);
583 renderer.set_force_mask_bit(set_mask);
584 auto handle = renderer.copy_cpu_to_vram({ x, y, width, height });
585 uint16_t *ptr = renderer.begin_copy(handle);
586 fread(ptr, sizeof(uint16_t), width * height, file);
587 renderer.end_copy(handle);
588
589 if (args.trace && frame == args.trace_frame)
590 dump_to_file(args, device, renderer, frame, draw_call);
591 draw_call++;
592 break;
593 }
594
595 case RSX_FILL_RECT:
596 {
597 auto color = read_u32(file);
598 auto x = read_u32(file);
599 auto y = read_u32(file);
600 auto w = read_u32(file);
601 auto h = read_u32(file);
602
603 renderer.clear_rect({ x, y, w, h }, color);
604
605 if (args.trace && frame == args.trace_frame)
606 dump_to_file(args, device, renderer, frame, draw_call);
607 draw_call++;
608 break;
609 }
610
611 case RSX_COPY_RECT:
612 {
613 auto src_x = read_u32(file);
614 auto src_y = read_u32(file);
615 auto dst_x = read_u32(file);
616 auto dst_y = read_u32(file);
617 auto w = read_u32(file);
618 auto h = read_u32(file);
619 bool mask_test = read_u32(file) != 0;
620 bool set_mask = read_u32(file) != 0;
621 renderer.set_mask_test(mask_test);
622 renderer.set_force_mask_bit(set_mask);
623 if (src_x != dst_x || src_y != dst_y)
624 renderer.blit_vram({ dst_x, dst_y, w, h }, { src_x, src_y, w, h });
625
626 if (args.trace && frame == args.trace_frame)
627 dump_to_file(args, device, renderer, frame, draw_call);
628 draw_call++;
629 break;
630 }
631
632 case RSX_TOGGLE_DISPLAY:
633 {
634 auto toggle = read_u32(file);
635 renderer.toggle_display(toggle == 0);
636 break;
637 }
638
639 default:
640 throw runtime_error("Invalid opcode.");
641 }
642 return true;
643 }
644
gettime()645 static double gettime()
646 {
647 timespec ts;
648 clock_gettime(CLOCK_MONOTONIC, &ts);
649 return ts.tv_sec + 1e-9 * ts.tv_nsec;
650 }
651
print_help()652 static void print_help()
653 {
654 fprintf(stderr, "rsx-player [dump] [--scale <scale>] [--dump-vram <path>] [--trace-frame <frame> <path>] "
655 "[--verbose] [--help]\n");
656 }
657
main(int argc,char * argv[])658 int main(int argc, char *argv[])
659 {
660 CLIArguments args;
661 CLICallbacks cbs;
662
663 cbs.add("--help", [](CLIParser &parser) {
664 print_help();
665 parser.end();
666 });
667 cbs.add("--dump-vram", [&args](CLIParser &parser) { args.frame_output = parser.next_string(); });
668 cbs.add("--trace-frame", [&args](CLIParser &parser) {
669 args.trace_frame = parser.next_uint();
670 args.trace_output = parser.next_string();
671 args.trace = true;
672 });
673 cbs.add("--scale", [&args](CLIParser &parser) { args.scale = parser.next_uint(); });
674 cbs.add("--verbose", [&args](CLIParser &) { args.verbose = true; });
675 cbs.error_handler = [] { print_help(); };
676 cbs.default_handler = [&args](const char *value) { args.dump = value; };
677 CLIParser parser{ move(cbs), argc - 1, argv + 1 };
678 if (!parser.parse())
679 return 1;
680 else if (parser.ended_state)
681 return 0;
682
683 if (!args.dump)
684 {
685 fprintf(stderr, "Didn't specify input file.\n");
686 print_help();
687 return 1;
688 }
689
690 WSI wsi;
691 wsi.init(1280, 960);
692 auto &device = wsi.get_device();
693 Renderer renderer(device, args.scale, nullptr);
694
695 FILE *file = fopen(args.dump, "rb");
696 if (!file)
697 return 1;
698
699 read_tag(file);
700
701 bool eof = false;
702 unsigned frames = 0;
703 unsigned draw_call = 0;
704 double total_time = 0.0;
705 while (!eof && wsi.alive())
706 {
707 draw_call = 0;
708
709 double start = gettime();
710 wsi.begin_frame();
711 renderer.reset_counters();
712 while (read_command(args, file, device, renderer, eof, frames, draw_call))
713 ;
714 renderer.scanout();
715
716 if (args.frame_output)
717 dump_vram_to_file(args, device, renderer, frames);
718
719 renderer.flush();
720 wsi.end_frame();
721 double end = gettime();
722 total_time += end - start;
723 frames++;
724
725 if (args.verbose)
726 {
727 if (renderer.counters.render_passes)
728 {
729 LOG("========================\n");
730 LOG("Completed frame %u.\n", frames);
731 LOG("Render passes: %u\n", renderer.counters.render_passes);
732 LOG("Readback pixels: %u\n", renderer.counters.fragment_readback_pixels);
733 LOG("Writeout pixels: %u\n", renderer.counters.fragment_writeout_pixels);
734 LOG("Draw calls: %u\n", renderer.counters.draw_calls);
735 LOG("Vertices: %u\n", renderer.counters.vertices);
736 LOG("========================\n");
737 }
738 else
739 {
740 LOG("========================\n");
741 LOG("Completed frame %u.\n", frames);
742 LOG("========================\n");
743 }
744 }
745 }
746
747 LOG("Ran %u frames in %f s! (%.3f ms / frame).\n", frames, total_time, 1000.0 * total_time / frames);
748 }
749