summaryrefslogtreecommitdiff
path: root/src/video_core/debug_utils
diff options
context:
space:
mode:
authorGravatar darkf2014-12-29 19:47:41 -0800
committerGravatar darkf2014-12-29 19:47:41 -0800
commit8ba9ac0f74abb0408a26207a76a0c1808bad8de0 (patch)
treef1c7c3393fa726435b5b90bf335567c93e528ef1 /src/video_core/debug_utils
parentAdd comment regarding __WIN32__ in SkyEye code (diff)
parentMerge pull request #367 from bunnei/usat_ssat (diff)
downloadyuzu-8ba9ac0f74abb0408a26207a76a0c1808bad8de0.tar.gz
yuzu-8ba9ac0f74abb0408a26207a76a0c1808bad8de0.tar.xz
yuzu-8ba9ac0f74abb0408a26207a76a0c1808bad8de0.zip
Fix merge conflicts
Diffstat (limited to 'src/video_core/debug_utils')
-rw-r--r--src/video_core/debug_utils/debug_utils.cpp370
-rw-r--r--src/video_core/debug_utils/debug_utils.h157
2 files changed, 418 insertions, 109 deletions
diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp
index 275b06b7c..5921185a6 100644
--- a/src/video_core/debug_utils/debug_utils.cpp
+++ b/src/video_core/debug_utils/debug_utils.cpp
@@ -3,6 +3,8 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm> 5#include <algorithm>
6#include <condition_variable>
7#include <list>
6#include <map> 8#include <map>
7#include <fstream> 9#include <fstream>
8#include <mutex> 10#include <mutex>
@@ -12,14 +14,62 @@
12#include <png.h> 14#include <png.h>
13#endif 15#endif
14 16
17#include <nihstro/shader_binary.h>
18
19#include "common/log.h"
15#include "common/file_util.h" 20#include "common/file_util.h"
16 21
22#include "video_core/math.h"
17#include "video_core/pica.h" 23#include "video_core/pica.h"
18 24
19#include "debug_utils.h" 25#include "debug_utils.h"
20 26
27using nihstro::DVLBHeader;
28using nihstro::DVLEHeader;
29using nihstro::DVLPHeader;
30
21namespace Pica { 31namespace Pica {
22 32
33void DebugContext::OnEvent(Event event, void* data) {
34 if (!breakpoints[event].enabled)
35 return;
36
37 {
38 std::unique_lock<std::mutex> lock(breakpoint_mutex);
39
40 // TODO: Should stop the CPU thread here once we multithread emulation.
41
42 active_breakpoint = event;
43 at_breakpoint = true;
44
45 // Tell all observers that we hit a breakpoint
46 for (auto& breakpoint_observer : breakpoint_observers) {
47 breakpoint_observer->OnPicaBreakPointHit(event, data);
48 }
49
50 // Wait until another thread tells us to Resume()
51 resume_from_breakpoint.wait(lock, [&]{ return !at_breakpoint; });
52 }
53}
54
55void DebugContext::Resume() {
56 {
57 std::unique_lock<std::mutex> lock(breakpoint_mutex);
58
59 // Tell all observers that we are about to resume
60 for (auto& breakpoint_observer : breakpoint_observers) {
61 breakpoint_observer->OnPicaResume();
62 }
63
64 // Resume the waiting thread (i.e. OnEvent())
65 at_breakpoint = false;
66 }
67
68 resume_from_breakpoint.notify_one();
69}
70
71std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global
72
23namespace DebugUtils { 73namespace DebugUtils {
24 74
25void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) { 75void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) {
@@ -54,65 +104,6 @@ void GeometryDumper::Dump() {
54 } 104 }
55} 105}
56 106
57#pragma pack(1)
58struct DVLBHeader {
59 enum : u32 {
60 MAGIC_WORD = 0x424C5644, // "DVLB"
61 };
62
63 u32 magic_word;
64 u32 num_programs;
65// u32 dvle_offset_table[];
66};
67static_assert(sizeof(DVLBHeader) == 0x8, "Incorrect structure size");
68
69struct DVLPHeader {
70 enum : u32 {
71 MAGIC_WORD = 0x504C5644, // "DVLP"
72 };
73
74 u32 magic_word;
75 u32 version;
76 u32 binary_offset; // relative to DVLP start
77 u32 binary_size_words;
78 u32 swizzle_patterns_offset;
79 u32 swizzle_patterns_num_entries;
80 u32 unk2;
81};
82static_assert(sizeof(DVLPHeader) == 0x1C, "Incorrect structure size");
83
84struct DVLEHeader {
85 enum : u32 {
86 MAGIC_WORD = 0x454c5644, // "DVLE"
87 };
88
89 enum class ShaderType : u8 {
90 VERTEX = 0,
91 GEOMETRY = 1,
92 };
93
94 u32 magic_word;
95 u16 pad1;
96 ShaderType type;
97 u8 pad2;
98 u32 main_offset_words; // offset within binary blob
99 u32 endmain_offset_words;
100 u32 pad3;
101 u32 pad4;
102 u32 constant_table_offset;
103 u32 constant_table_size; // number of entries
104 u32 label_table_offset;
105 u32 label_table_size;
106 u32 output_register_table_offset;
107 u32 output_register_table_size;
108 u32 uniform_table_offset;
109 u32 uniform_table_size;
110 u32 symbol_table_offset;
111 u32 symbol_table_size;
112
113};
114static_assert(sizeof(DVLEHeader) == 0x40, "Incorrect structure size");
115#pragma pack()
116 107
117void DumpShader(const u32* binary_data, u32 binary_size, const u32* swizzle_data, u32 swizzle_size, 108void DumpShader(const u32* binary_data, u32 binary_size, const u32* swizzle_data, u32 swizzle_size,
118 u32 main_offset, const Regs::VSOutputAttributes* output_attributes) 109 u32 main_offset, const Regs::VSOutputAttributes* output_attributes)
@@ -155,7 +146,7 @@ void DumpShader(const u32* binary_data, u32 binary_size, const u32* swizzle_data
155 146
156 // This is put into a try-catch block to make sure we notice unknown configurations. 147 // This is put into a try-catch block to make sure we notice unknown configurations.
157 std::vector<OutputRegisterInfo> output_info_table; 148 std::vector<OutputRegisterInfo> output_info_table;
158 for (int i = 0; i < 7; ++i) { 149 for (unsigned i = 0; i < 7; ++i) {
159 using OutputAttributes = Pica::Regs::VSOutputAttributes; 150 using OutputAttributes = Pica::Regs::VSOutputAttributes;
160 151
161 // TODO: It's still unclear how the attribute components map to the register! 152 // TODO: It's still unclear how the attribute components map to the register!
@@ -204,8 +195,8 @@ void DumpShader(const u32* binary_data, u32 binary_size, const u32* swizzle_data
204 it->component_mask = it->component_mask | component_mask; 195 it->component_mask = it->component_mask | component_mask;
205 } 196 }
206 } catch (const std::out_of_range& ) { 197 } catch (const std::out_of_range& ) {
207 _dbg_assert_msg_(GPU, 0, "Unknown output attribute mapping"); 198 _dbg_assert_msg_(HW_GPU, 0, "Unknown output attribute mapping");
208 ERROR_LOG(GPU, "Unknown output attribute mapping: %03x, %03x, %03x, %03x", 199 LOG_ERROR(HW_GPU, "Unknown output attribute mapping: %03x, %03x, %03x, %03x",
209 (int)output_attributes[i].map_x.Value(), 200 (int)output_attributes[i].map_x.Value(),
210 (int)output_attributes[i].map_y.Value(), 201 (int)output_attributes[i].map_y.Value(),
211 (int)output_attributes[i].map_z.Value(), 202 (int)output_attributes[i].map_z.Value(),
@@ -232,8 +223,8 @@ void DumpShader(const u32* binary_data, u32 binary_size, const u32* swizzle_data
232 dvlp.binary_size_words = binary_size; 223 dvlp.binary_size_words = binary_size;
233 QueueForWriting((u8*)binary_data, binary_size * sizeof(u32)); 224 QueueForWriting((u8*)binary_data, binary_size * sizeof(u32));
234 225
235 dvlp.swizzle_patterns_offset = write_offset - dvlp_offset; 226 dvlp.swizzle_info_offset = write_offset - dvlp_offset;
236 dvlp.swizzle_patterns_num_entries = swizzle_size; 227 dvlp.swizzle_info_num_entries = swizzle_size;
237 u32 dummy = 0; 228 u32 dummy = 0;
238 for (unsigned int i = 0; i < swizzle_size; ++i) { 229 for (unsigned int i = 0; i < swizzle_size; ++i) {
239 QueueForWriting((u8*)&swizzle_data[i], sizeof(swizzle_data[i])); 230 QueueForWriting((u8*)&swizzle_data[i], sizeof(swizzle_data[i]));
@@ -265,7 +256,7 @@ static int is_pica_tracing = false;
265void StartPicaTracing() 256void StartPicaTracing()
266{ 257{
267 if (is_pica_tracing) { 258 if (is_pica_tracing) {
268 ERROR_LOG(GPU, "StartPicaTracing called even though tracing already running!"); 259 LOG_WARNING(HW_GPU, "StartPicaTracing called even though tracing already running!");
269 return; 260 return;
270 } 261 }
271 262
@@ -298,7 +289,7 @@ void OnPicaRegWrite(u32 id, u32 value)
298std::unique_ptr<PicaTrace> FinishPicaTracing() 289std::unique_ptr<PicaTrace> FinishPicaTracing()
299{ 290{
300 if (!is_pica_tracing) { 291 if (!is_pica_tracing) {
301 ERROR_LOG(GPU, "FinishPicaTracing called even though tracing already running!"); 292 LOG_WARNING(HW_GPU, "FinishPicaTracing called even though tracing isn't running!");
302 return {}; 293 return {};
303 } 294 }
304 295
@@ -312,6 +303,173 @@ std::unique_ptr<PicaTrace> FinishPicaTracing()
312 return std::move(ret); 303 return std::move(ret);
313} 304}
314 305
306const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const TextureInfo& info, bool disable_alpha) {
307 // Images are split into 8x8 tiles. Each tile is composed of four 4x4 subtiles each
308 // of which is composed of four 2x2 subtiles each of which is composed of four texels.
309 // Each structure is embedded into the next-bigger one in a diagonal pattern, e.g.
310 // texels are laid out in a 2x2 subtile like this:
311 // 2 3
312 // 0 1
313 //
314 // The full 8x8 tile has the texels arranged like this:
315 //
316 // 42 43 46 47 58 59 62 63
317 // 40 41 44 45 56 57 60 61
318 // 34 35 38 39 50 51 54 55
319 // 32 33 36 37 48 49 52 53
320 // 10 11 14 15 26 27 30 31
321 // 08 09 12 13 24 25 28 29
322 // 02 03 06 07 18 19 22 23
323 // 00 01 04 05 16 17 20 21
324
325 const unsigned int block_width = 8;
326 const unsigned int block_height = 8;
327
328 const unsigned int coarse_x = x & ~7;
329 const unsigned int coarse_y = y & ~7;
330
331 // Interleave the lower 3 bits of each coordinate to get the intra-block offsets, which are
332 // arranged in a Z-order curve. More details on the bit manipulation at:
333 // https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/
334 unsigned int i = (x | (y << 8)) & 0x0707; // ---- -210
335 i = (i ^ (i << 2)) & 0x1313; // ---2 --10
336 i = (i ^ (i << 1)) & 0x1515; // ---2 -1-0
337 i = (i | (i >> 7)) & 0x3F;
338
339 source += coarse_y * info.stride;
340 const unsigned int offset = coarse_x * block_height + i;
341
342 switch (info.format) {
343 case Regs::TextureFormat::RGBA8:
344 {
345 const u8* source_ptr = source + offset * 4;
346 return { source_ptr[3], source_ptr[2], source_ptr[1], disable_alpha ? (u8)255 : source_ptr[0] };
347 }
348
349 case Regs::TextureFormat::RGB8:
350 {
351 const u8* source_ptr = source + offset * 3;
352 return { source_ptr[2], source_ptr[1], source_ptr[0], 255 };
353 }
354
355 case Regs::TextureFormat::RGBA5551:
356 {
357 const u16 source_ptr = *(const u16*)(source + offset * 2);
358 u8 r = (source_ptr >> 11) & 0x1F;
359 u8 g = ((source_ptr) >> 6) & 0x1F;
360 u8 b = (source_ptr >> 1) & 0x1F;
361 u8 a = source_ptr & 1;
362 return Math::MakeVec<u8>((r << 3) | (r >> 2), (g << 3) | (g >> 2), (b << 3) | (b >> 2), disable_alpha ? 255 : (a * 255));
363 }
364
365 case Regs::TextureFormat::RGB565:
366 {
367 const u16 source_ptr = *(const u16*)(source + offset * 2);
368 u8 r = (source_ptr >> 11) & 0x1F;
369 u8 g = ((source_ptr) >> 5) & 0x3F;
370 u8 b = (source_ptr) & 0x1F;
371 return Math::MakeVec<u8>((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2), 255);
372 }
373
374 case Regs::TextureFormat::RGBA4:
375 {
376 const u8* source_ptr = source + offset * 2;
377 u8 r = source_ptr[1] >> 4;
378 u8 g = source_ptr[1] & 0xFF;
379 u8 b = source_ptr[0] >> 4;
380 u8 a = source_ptr[0] & 0xFF;
381 r = (r << 4) | r;
382 g = (g << 4) | g;
383 b = (b << 4) | b;
384 a = (a << 4) | a;
385 return { r, g, b, disable_alpha ? (u8)255 : a };
386 }
387
388 case Regs::TextureFormat::IA8:
389 {
390 const u8* source_ptr = source + offset * 2;
391
392 // TODO: component order not verified
393
394 if (disable_alpha) {
395 // Show intensity as red, alpha as green
396 return { source_ptr[0], source_ptr[1], 0, 255 };
397 } else {
398 return { source_ptr[0], source_ptr[0], source_ptr[0], source_ptr[1]};
399 }
400 }
401
402 case Regs::TextureFormat::I8:
403 {
404 const u8* source_ptr = source + offset;
405 return { *source_ptr, *source_ptr, *source_ptr, 255 };
406 }
407
408 case Regs::TextureFormat::A8:
409 {
410 const u8* source_ptr = source + offset;
411
412 if (disable_alpha) {
413 return { *source_ptr, *source_ptr, *source_ptr, 255 };
414 } else {
415 return { 0, 0, 0, *source_ptr };
416 }
417 }
418
419 case Regs::TextureFormat::IA4:
420 {
421 const u8* source_ptr = source + offset / 2;
422
423 // TODO: component order not verified
424
425 u8 i = (*source_ptr) & 0xF;
426 u8 a = ((*source_ptr) & 0xF0) >> 4;
427 a |= a << 4;
428 i |= i << 4;
429
430 if (disable_alpha) {
431 // Show intensity as red, alpha as green
432 return { i, a, 0, 255 };
433 } else {
434 return { i, i, i, a };
435 }
436 }
437
438 case Regs::TextureFormat::A4:
439 {
440 const u8* source_ptr = source + offset / 2;
441
442 // TODO: component order not verified
443
444 u8 a = (coarse_x % 2) ? ((*source_ptr)&0xF) : (((*source_ptr) & 0xF0) >> 4);
445 a |= a << 4;
446
447 if (disable_alpha) {
448 return { *source_ptr, *source_ptr, *source_ptr, 255 };
449 } else {
450 return { 0, 0, 0, *source_ptr };
451 }
452 }
453
454 default:
455 LOG_ERROR(HW_GPU, "Unknown texture format: %x", (u32)info.format);
456 _dbg_assert_(HW_GPU, 0);
457 return {};
458 }
459}
460
461TextureInfo TextureInfo::FromPicaRegister(const Regs::TextureConfig& config,
462 const Regs::TextureFormat& format)
463{
464 TextureInfo info;
465 info.physical_address = config.GetPhysicalAddress();
466 info.width = config.width;
467 info.height = config.height;
468 info.format = format;
469 info.stride = Pica::Regs::NibblesPerPixel(info.format) * info.width / 2;
470 return info;
471}
472
315void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { 473void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) {
316 // NOTE: Permanently enabling this just trashes hard disks for no reason. 474 // NOTE: Permanently enabling this just trashes hard disks for no reason.
317 // Hence, this is currently disabled. 475 // Hence, this is currently disabled.
@@ -341,7 +499,7 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) {
341 // Initialize write structure 499 // Initialize write structure
342 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); 500 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
343 if (png_ptr == nullptr) { 501 if (png_ptr == nullptr) {
344 ERROR_LOG(GPU, "Could not allocate write struct\n"); 502 LOG_ERROR(Debug_GPU, "Could not allocate write struct\n");
345 goto finalise; 503 goto finalise;
346 504
347 } 505 }
@@ -349,13 +507,13 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) {
349 // Initialize info structure 507 // Initialize info structure
350 info_ptr = png_create_info_struct(png_ptr); 508 info_ptr = png_create_info_struct(png_ptr);
351 if (info_ptr == nullptr) { 509 if (info_ptr == nullptr) {
352 ERROR_LOG(GPU, "Could not allocate info struct\n"); 510 LOG_ERROR(Debug_GPU, "Could not allocate info struct\n");
353 goto finalise; 511 goto finalise;
354 } 512 }
355 513
356 // Setup Exception handling 514 // Setup Exception handling
357 if (setjmp(png_jmpbuf(png_ptr))) { 515 if (setjmp(png_jmpbuf(png_ptr))) {
358 ERROR_LOG(GPU, "Error during png creation\n"); 516 LOG_ERROR(Debug_GPU, "Error during png creation\n");
359 goto finalise; 517 goto finalise;
360 } 518 }
361 519
@@ -375,34 +533,22 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) {
375 png_write_info(png_ptr, info_ptr); 533 png_write_info(png_ptr, info_ptr);
376 534
377 buf = new u8[row_stride * texture_config.height]; 535 buf = new u8[row_stride * texture_config.height];
378 for (int y = 0; y < texture_config.height; ++y) { 536 for (unsigned y = 0; y < texture_config.height; ++y) {
379 for (int x = 0; x < texture_config.width; ++x) { 537 for (unsigned x = 0; x < texture_config.width; ++x) {
380 // Cf. rasterizer code for an explanation of this algorithm. 538 TextureInfo info;
381 int texel_index_within_tile = 0; 539 info.width = texture_config.width;
382 for (int block_size_index = 0; block_size_index < 3; ++block_size_index) { 540 info.height = texture_config.height;
383 int sub_tile_width = 1 << block_size_index; 541 info.stride = row_stride;
384 int sub_tile_height = 1 << block_size_index; 542 info.format = registers.texture0_format;
385 543 Math::Vec4<u8> texture_color = LookupTexture(data, x, y, info);
386 int sub_tile_index = (x & sub_tile_width) << block_size_index; 544 buf[3 * x + y * row_stride ] = texture_color.r();
387 sub_tile_index += 2 * ((y & sub_tile_height) << block_size_index); 545 buf[3 * x + y * row_stride + 1] = texture_color.g();
388 texel_index_within_tile += sub_tile_index; 546 buf[3 * x + y * row_stride + 2] = texture_color.b();
389 }
390
391 const int block_width = 8;
392 const int block_height = 8;
393
394 int coarse_x = (x / block_width) * block_width;
395 int coarse_y = (y / block_height) * block_height;
396
397 u8* source_ptr = (u8*)data + coarse_x * block_height * 3 + coarse_y * row_stride + texel_index_within_tile * 3;
398 buf[3 * x + y * row_stride ] = source_ptr[2];
399 buf[3 * x + y * row_stride + 1] = source_ptr[1];
400 buf[3 * x + y * row_stride + 2] = source_ptr[0];
401 } 547 }
402 } 548 }
403 549
404 // Write image data 550 // Write image data
405 for (auto y = 0; y < texture_config.height; ++y) 551 for (unsigned y = 0; y < texture_config.height; ++y)
406 { 552 {
407 u8* row_ptr = (u8*)buf + y * row_stride; 553 u8* row_ptr = (u8*)buf + y * row_stride;
408 u8* ptr = row_ptr; 554 u8* ptr = row_ptr;
@@ -431,26 +577,32 @@ void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages)
431 for (size_t index = 0; index < stages.size(); ++index) { 577 for (size_t index = 0; index < stages.size(); ++index) {
432 const auto& tev_stage = stages[index]; 578 const auto& tev_stage = stages[index];
433 579
434 const std::map<Source, std::string> source_map = { 580 static const std::map<Source, std::string> source_map = {
435 { Source::PrimaryColor, "PrimaryColor" }, 581 { Source::PrimaryColor, "PrimaryColor" },
436 { Source::Texture0, "Texture0" }, 582 { Source::Texture0, "Texture0" },
583 { Source::Texture1, "Texture1" },
584 { Source::Texture2, "Texture2" },
437 { Source::Constant, "Constant" }, 585 { Source::Constant, "Constant" },
438 { Source::Previous, "Previous" }, 586 { Source::Previous, "Previous" },
439 }; 587 };
440 588
441 const std::map<ColorModifier, std::string> color_modifier_map = { 589 static const std::map<ColorModifier, std::string> color_modifier_map = {
442 { ColorModifier::SourceColor, { "%source.rgb" } } 590 { ColorModifier::SourceColor, { "%source.rgb" } },
591 { ColorModifier::SourceAlpha, { "%source.aaa" } },
443 }; 592 };
444 const std::map<AlphaModifier, std::string> alpha_modifier_map = { 593 static const std::map<AlphaModifier, std::string> alpha_modifier_map = {
445 { AlphaModifier::SourceAlpha, "%source.a" } 594 { AlphaModifier::SourceAlpha, "%source.a" },
595 { AlphaModifier::OneMinusSourceAlpha, "(255 - %source.a)" },
446 }; 596 };
447 597
448 std::map<Operation, std::string> combiner_map = { 598 static const std::map<Operation, std::string> combiner_map = {
449 { Operation::Replace, "%source1" }, 599 { Operation::Replace, "%source1" },
450 { Operation::Modulate, "(%source1 * %source2) / 255" }, 600 { Operation::Modulate, "(%source1 * %source2) / 255" },
601 { Operation::Add, "(%source1 + %source2)" },
602 { Operation::Lerp, "lerp(%source1, %source2, %source3)" },
451 }; 603 };
452 604
453 auto ReplacePattern = 605 static auto ReplacePattern =
454 [](const std::string& input, const std::string& pattern, const std::string& replacement) -> std::string { 606 [](const std::string& input, const std::string& pattern, const std::string& replacement) -> std::string {
455 size_t start = input.find(pattern); 607 size_t start = input.find(pattern);
456 if (start == std::string::npos) 608 if (start == std::string::npos)
@@ -460,8 +612,8 @@ void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages)
460 ret.replace(start, pattern.length(), replacement); 612 ret.replace(start, pattern.length(), replacement);
461 return ret; 613 return ret;
462 }; 614 };
463 auto GetColorSourceStr = 615 static auto GetColorSourceStr =
464 [&source_map,&color_modifier_map,&ReplacePattern](const Source& src, const ColorModifier& modifier) { 616 [](const Source& src, const ColorModifier& modifier) {
465 auto src_it = source_map.find(src); 617 auto src_it = source_map.find(src);
466 std::string src_str = "Unknown"; 618 std::string src_str = "Unknown";
467 if (src_it != source_map.end()) 619 if (src_it != source_map.end())
@@ -474,8 +626,8 @@ void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages)
474 626
475 return ReplacePattern(modifier_str, "%source", src_str); 627 return ReplacePattern(modifier_str, "%source", src_str);
476 }; 628 };
477 auto GetColorCombinerStr = 629 static auto GetColorCombinerStr =
478 [&](const Regs::TevStageConfig& tev_stage) { 630 [](const Regs::TevStageConfig& tev_stage) {
479 auto op_it = combiner_map.find(tev_stage.color_op); 631 auto op_it = combiner_map.find(tev_stage.color_op);
480 std::string op_str = "Unknown op (%source1, %source2, %source3)"; 632 std::string op_str = "Unknown op (%source1, %source2, %source3)";
481 if (op_it != combiner_map.end()) 633 if (op_it != combiner_map.end())
@@ -485,8 +637,8 @@ void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages)
485 op_str = ReplacePattern(op_str, "%source2", GetColorSourceStr(tev_stage.color_source2, tev_stage.color_modifier2)); 637 op_str = ReplacePattern(op_str, "%source2", GetColorSourceStr(tev_stage.color_source2, tev_stage.color_modifier2));
486 return ReplacePattern(op_str, "%source3", GetColorSourceStr(tev_stage.color_source3, tev_stage.color_modifier3)); 638 return ReplacePattern(op_str, "%source3", GetColorSourceStr(tev_stage.color_source3, tev_stage.color_modifier3));
487 }; 639 };
488 auto GetAlphaSourceStr = 640 static auto GetAlphaSourceStr =
489 [&source_map,&alpha_modifier_map,&ReplacePattern](const Source& src, const AlphaModifier& modifier) { 641 [](const Source& src, const AlphaModifier& modifier) {
490 auto src_it = source_map.find(src); 642 auto src_it = source_map.find(src);
491 std::string src_str = "Unknown"; 643 std::string src_str = "Unknown";
492 if (src_it != source_map.end()) 644 if (src_it != source_map.end())
@@ -499,8 +651,8 @@ void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages)
499 651
500 return ReplacePattern(modifier_str, "%source", src_str); 652 return ReplacePattern(modifier_str, "%source", src_str);
501 }; 653 };
502 auto GetAlphaCombinerStr = 654 static auto GetAlphaCombinerStr =
503 [&](const Regs::TevStageConfig& tev_stage) { 655 [](const Regs::TevStageConfig& tev_stage) {
504 auto op_it = combiner_map.find(tev_stage.alpha_op); 656 auto op_it = combiner_map.find(tev_stage.alpha_op);
505 std::string op_str = "Unknown op (%source1, %source2, %source3)"; 657 std::string op_str = "Unknown op (%source1, %source2, %source3)";
506 if (op_it != combiner_map.end()) 658 if (op_it != combiner_map.end())
@@ -514,7 +666,7 @@ void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages)
514 stage_info += "Stage " + std::to_string(index) + ": " + GetColorCombinerStr(tev_stage) + " " + GetAlphaCombinerStr(tev_stage) + "\n"; 666 stage_info += "Stage " + std::to_string(index) + ": " + GetColorCombinerStr(tev_stage) + " " + GetAlphaCombinerStr(tev_stage) + "\n";
515 } 667 }
516 668
517 DEBUG_LOG(GPU, "%s", stage_info.c_str()); 669 LOG_TRACE(HW_GPU, "%s", stage_info.c_str());
518} 670}
519 671
520} // namespace 672} // namespace
diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h
index b1558cfae..f361a5385 100644
--- a/src/video_core/debug_utils/debug_utils.h
+++ b/src/video_core/debug_utils/debug_utils.h
@@ -5,13 +5,148 @@
5#pragma once 5#pragma once
6 6
7#include <array> 7#include <array>
8#include <condition_variable>
9#include <list>
10#include <map>
8#include <memory> 11#include <memory>
12#include <mutex>
9#include <vector> 13#include <vector>
10 14
15#include "video_core/math.h"
11#include "video_core/pica.h" 16#include "video_core/pica.h"
12 17
13namespace Pica { 18namespace Pica {
14 19
20class DebugContext {
21public:
22 enum class Event {
23 FirstEvent = 0,
24
25 CommandLoaded = FirstEvent,
26 CommandProcessed,
27 IncomingPrimitiveBatch,
28 FinishedPrimitiveBatch,
29 VertexLoaded,
30
31 NumEvents
32 };
33
34 /**
35 * Inherit from this class to be notified of events registered to some debug context.
36 * Most importantly this is used for our debugger GUI.
37 *
38 * To implement event handling, override the OnPicaBreakPointHit and OnPicaResume methods.
39 * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state access
40 * @todo Evaluate an alternative interface, in which there is only one managing observer and multiple child observers running (by design) on the same thread.
41 */
42 class BreakPointObserver {
43 public:
44 /// Constructs the object such that it observes events of the given DebugContext.
45 BreakPointObserver(std::shared_ptr<DebugContext> debug_context) : context_weak(debug_context) {
46 std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex);
47 debug_context->breakpoint_observers.push_back(this);
48 }
49
50 virtual ~BreakPointObserver() {
51 auto context = context_weak.lock();
52 if (context) {
53 std::unique_lock<std::mutex> lock(context->breakpoint_mutex);
54 context->breakpoint_observers.remove(this);
55
56 // If we are the last observer to be destroyed, tell the debugger context that
57 // it is free to continue. In particular, this is required for a proper Citra
58 // shutdown, when the emulation thread is waiting at a breakpoint.
59 if (context->breakpoint_observers.empty())
60 context->Resume();
61 }
62 }
63
64 /**
65 * Action to perform when a breakpoint was reached.
66 * @param event Type of event which triggered the breakpoint
67 * @param data Optional data pointer (if unused, this is a nullptr)
68 * @note This function will perform nothing unless it is overridden in the child class.
69 */
70 virtual void OnPicaBreakPointHit(Event, void*) {
71 }
72
73 /**
74 * Action to perform when emulation is resumed from a breakpoint.
75 * @note This function will perform nothing unless it is overridden in the child class.
76 */
77 virtual void OnPicaResume() {
78 }
79
80 protected:
81 /**
82 * Weak context pointer. This need not be valid, so when requesting a shared_ptr via
83 * context_weak.lock(), always compare the result against nullptr.
84 */
85 std::weak_ptr<DebugContext> context_weak;
86 };
87
88 /**
89 * Simple structure defining a breakpoint state
90 */
91 struct BreakPoint {
92 bool enabled = false;
93 };
94
95 /**
96 * Static constructor used to create a shared_ptr of a DebugContext.
97 */
98 static std::shared_ptr<DebugContext> Construct() {
99 return std::shared_ptr<DebugContext>(new DebugContext);
100 }
101
102 /**
103 * Used by the emulation core when a given event has happened. If a breakpoint has been set
104 * for this event, OnEvent calls the event handlers of the registered breakpoint observers.
105 * The current thread then is halted until Resume() is called from another thread (or until
106 * emulation is stopped).
107 * @param event Event which has happened
108 * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until Resume() is called.
109 */
110 void OnEvent(Event event, void* data);
111
112 /**
113 * Resume from the current breakpoint.
114 * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock. Calling from any other thread is safe.
115 */
116 void Resume();
117
118 /**
119 * Delete all set breakpoints and resume emulation.
120 */
121 void ClearBreakpoints() {
122 breakpoints.clear();
123 Resume();
124 }
125
126 // TODO: Evaluate if access to these members should be hidden behind a public interface.
127 std::map<Event, BreakPoint> breakpoints;
128 Event active_breakpoint;
129 bool at_breakpoint = false;
130
131private:
132 /**
133 * Private default constructor to make sure people always construct this through Construct()
134 * instead.
135 */
136 DebugContext() = default;
137
138 /// Mutex protecting current breakpoint state and the observer list.
139 std::mutex breakpoint_mutex;
140
141 /// Used by OnEvent to wait for resumption.
142 std::condition_variable resume_from_breakpoint;
143
144 /// List of registered observers
145 std::list<BreakPointObserver*> breakpoint_observers;
146};
147
148extern std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global
149
15namespace DebugUtils { 150namespace DebugUtils {
16 151
17// Simple utility class for dumping geometry data to an OBJ file 152// Simple utility class for dumping geometry data to an OBJ file
@@ -57,6 +192,28 @@ bool IsPicaTracing();
57void OnPicaRegWrite(u32 id, u32 value); 192void OnPicaRegWrite(u32 id, u32 value);
58std::unique_ptr<PicaTrace> FinishPicaTracing(); 193std::unique_ptr<PicaTrace> FinishPicaTracing();
59 194
195struct TextureInfo {
196 PAddr physical_address;
197 int width;
198 int height;
199 int stride;
200 Pica::Regs::TextureFormat format;
201
202 static TextureInfo FromPicaRegister(const Pica::Regs::TextureConfig& config,
203 const Pica::Regs::TextureFormat& format);
204};
205
206/**
207 * Lookup texel located at the given coordinates and return an RGBA vector of its color.
208 * @param source Source pointer to read data from
209 * @param s,t Texture coordinates to read from
210 * @param info TextureInfo object describing the texture setup
211 * @param disable_alpha This is used for debug widgets which use this method to display textures without providing a good way to visualize alpha by themselves. If true, this will return 255 for the alpha component, and either drop the information entirely or store it in an "unused" color channel.
212 * @todo Eventually we should get rid of the disable_alpha parameter.
213 */
214const Math::Vec4<u8> LookupTexture(const u8* source, int s, int t, const TextureInfo& info,
215 bool disable_alpha = false);
216
60void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data); 217void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data);
61 218
62void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages); 219void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages);