summaryrefslogtreecommitdiff
path: root/src/video_core/debug_utils
diff options
context:
space:
mode:
authorGravatar bunnei2014-08-25 16:12:10 -0400
committerGravatar bunnei2014-08-25 16:12:10 -0400
commit97fd8fc38d4f9c288779cddb06538860124c6263 (patch)
treebc99e0fceaae732f9c8d4831fcdb8f661b49ccb8 /src/video_core/debug_utils
parentMerge pull request #75 from xsacha/qt5 (diff)
parentPica/Rasterizer: Clarify a TODO. (diff)
downloadyuzu-97fd8fc38d4f9c288779cddb06538860124c6263.tar.gz
yuzu-97fd8fc38d4f9c288779cddb06538860124c6263.tar.xz
yuzu-97fd8fc38d4f9c288779cddb06538860124c6263.zip
Merge pull request #50 from neobrain/pica
Further work on Pica emulation
Diffstat (limited to 'src/video_core/debug_utils')
-rw-r--r--src/video_core/debug_utils/debug_utils.cpp522
-rw-r--r--src/video_core/debug_utils/debug_utils.h66
2 files changed, 588 insertions, 0 deletions
diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp
new file mode 100644
index 000000000..48e6dd182
--- /dev/null
+++ b/src/video_core/debug_utils/debug_utils.cpp
@@ -0,0 +1,522 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <map>
7#include <fstream>
8#include <mutex>
9#include <string>
10
11#ifdef HAVE_PNG
12#include <png.h>
13#endif
14
15#include "common/file_util.h"
16
17#include "video_core/pica.h"
18
19#include "debug_utils.h"
20
21namespace Pica {
22
23namespace DebugUtils {
24
25void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) {
26 vertices.push_back(v0);
27 vertices.push_back(v1);
28 vertices.push_back(v2);
29
30 int num_vertices = vertices.size();
31 faces.push_back({ num_vertices-3, num_vertices-2, num_vertices-1 });
32}
33
34void GeometryDumper::Dump() {
35 // NOTE: Permanently enabling this just trashes the hard disk for no reason.
36 // Hence, this is currently disabled.
37 return;
38
39 static int index = 0;
40 std::string filename = std::string("geometry_dump") + std::to_string(++index) + ".obj";
41
42 std::ofstream file(filename);
43
44 for (const auto& vertex : vertices) {
45 file << "v " << vertex.pos[0]
46 << " " << vertex.pos[1]
47 << " " << vertex.pos[2] << std::endl;
48 }
49
50 for (const Face& face : faces) {
51 file << "f " << 1+face.index[0]
52 << " " << 1+face.index[1]
53 << " " << 1+face.index[2] << std::endl;
54 }
55}
56
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
117void DumpShader(const u32* binary_data, u32 binary_size, const u32* swizzle_data, u32 swizzle_size,
118 u32 main_offset, const Regs::VSOutputAttributes* output_attributes)
119{
120 // NOTE: Permanently enabling this just trashes hard disks for no reason.
121 // Hence, this is currently disabled.
122 return;
123
124 struct StuffToWrite {
125 u8* pointer;
126 u32 size;
127 };
128 std::vector<StuffToWrite> writing_queue;
129 u32 write_offset = 0;
130
131 auto QueueForWriting = [&writing_queue,&write_offset](u8* pointer, u32 size) {
132 writing_queue.push_back({pointer, size});
133 u32 old_write_offset = write_offset;
134 write_offset += size;
135 return old_write_offset;
136 };
137
138 // First off, try to translate Pica state (one enum for output attribute type and component)
139 // into shbin format (separate type and component mask).
140 union OutputRegisterInfo {
141 enum Type : u64 {
142 POSITION = 0,
143 COLOR = 2,
144 TEXCOORD0 = 3,
145 TEXCOORD1 = 5,
146 TEXCOORD2 = 6,
147 };
148
149 BitField< 0, 64, u64> hex;
150
151 BitField< 0, 16, Type> type;
152 BitField<16, 16, u64> id;
153 BitField<32, 4, u64> component_mask;
154 };
155
156 // This is put into a try-catch block to make sure we notice unknown configurations.
157 std::vector<OutputRegisterInfo> output_info_table;
158 for (int i = 0; i < 7; ++i) {
159 using OutputAttributes = Pica::Regs::VSOutputAttributes;
160
161 // TODO: It's still unclear how the attribute components map to the register!
162 // Once we know that, this code probably will not make much sense anymore.
163 std::map<OutputAttributes::Semantic, std::pair<OutputRegisterInfo::Type, u32> > map = {
164 { OutputAttributes::POSITION_X, { OutputRegisterInfo::POSITION, 1} },
165 { OutputAttributes::POSITION_Y, { OutputRegisterInfo::POSITION, 2} },
166 { OutputAttributes::POSITION_Z, { OutputRegisterInfo::POSITION, 4} },
167 { OutputAttributes::POSITION_W, { OutputRegisterInfo::POSITION, 8} },
168 { OutputAttributes::COLOR_R, { OutputRegisterInfo::COLOR, 1} },
169 { OutputAttributes::COLOR_G, { OutputRegisterInfo::COLOR, 2} },
170 { OutputAttributes::COLOR_B, { OutputRegisterInfo::COLOR, 4} },
171 { OutputAttributes::COLOR_A, { OutputRegisterInfo::COLOR, 8} },
172 { OutputAttributes::TEXCOORD0_U, { OutputRegisterInfo::TEXCOORD0, 1} },
173 { OutputAttributes::TEXCOORD0_V, { OutputRegisterInfo::TEXCOORD0, 2} },
174 { OutputAttributes::TEXCOORD1_U, { OutputRegisterInfo::TEXCOORD1, 1} },
175 { OutputAttributes::TEXCOORD1_V, { OutputRegisterInfo::TEXCOORD1, 2} },
176 { OutputAttributes::TEXCOORD2_U, { OutputRegisterInfo::TEXCOORD2, 1} },
177 { OutputAttributes::TEXCOORD2_V, { OutputRegisterInfo::TEXCOORD2, 2} }
178 };
179
180 for (const auto& semantic : std::vector<OutputAttributes::Semantic>{
181 output_attributes[i].map_x,
182 output_attributes[i].map_y,
183 output_attributes[i].map_z,
184 output_attributes[i].map_w }) {
185 if (semantic == OutputAttributes::INVALID)
186 continue;
187
188 try {
189 OutputRegisterInfo::Type type = map.at(semantic).first;
190 u32 component_mask = map.at(semantic).second;
191
192 auto it = std::find_if(output_info_table.begin(), output_info_table.end(),
193 [&i, &type](const OutputRegisterInfo& info) {
194 return info.id == i && info.type == type;
195 }
196 );
197
198 if (it == output_info_table.end()) {
199 output_info_table.push_back({});
200 output_info_table.back().type = type;
201 output_info_table.back().component_mask = component_mask;
202 output_info_table.back().id = i;
203 } else {
204 it->component_mask = it->component_mask | component_mask;
205 }
206 } catch (const std::out_of_range& oor) {
207 _dbg_assert_msg_(GPU, 0, "Unknown output attribute mapping");
208 ERROR_LOG(GPU, "Unknown output attribute mapping: %03x, %03x, %03x, %03x",
209 (int)output_attributes[i].map_x.Value(),
210 (int)output_attributes[i].map_y.Value(),
211 (int)output_attributes[i].map_z.Value(),
212 (int)output_attributes[i].map_w.Value());
213 }
214 }
215 }
216
217
218 struct {
219 DVLBHeader header;
220 u32 dvle_offset;
221 } dvlb{ {DVLBHeader::MAGIC_WORD, 1 } }; // 1 DVLE
222
223 DVLPHeader dvlp{ DVLPHeader::MAGIC_WORD };
224 DVLEHeader dvle{ DVLEHeader::MAGIC_WORD };
225
226 QueueForWriting((u8*)&dvlb, sizeof(dvlb));
227 u32 dvlp_offset = QueueForWriting((u8*)&dvlp, sizeof(dvlp));
228 dvlb.dvle_offset = QueueForWriting((u8*)&dvle, sizeof(dvle));
229
230 // TODO: Reduce the amount of binary code written to relevant portions
231 dvlp.binary_offset = write_offset - dvlp_offset;
232 dvlp.binary_size_words = binary_size;
233 QueueForWriting((u8*)binary_data, binary_size * sizeof(u32));
234
235 dvlp.swizzle_patterns_offset = write_offset - dvlp_offset;
236 dvlp.swizzle_patterns_num_entries = swizzle_size;
237 u32 dummy = 0;
238 for (int i = 0; i < swizzle_size; ++i) {
239 QueueForWriting((u8*)&swizzle_data[i], sizeof(swizzle_data[i]));
240 QueueForWriting((u8*)&dummy, sizeof(dummy));
241 }
242
243 dvle.main_offset_words = main_offset;
244 dvle.output_register_table_offset = write_offset - dvlb.dvle_offset;
245 dvle.output_register_table_size = output_info_table.size();
246 QueueForWriting((u8*)output_info_table.data(), output_info_table.size() * sizeof(OutputRegisterInfo));
247
248 // TODO: Create a label table for "main"
249
250
251 // Write data to file
252 static int dump_index = 0;
253 std::string filename = std::string("shader_dump") + std::to_string(++dump_index) + std::string(".shbin");
254 std::ofstream file(filename, std::ios_base::out | std::ios_base::binary);
255
256 for (auto& chunk : writing_queue) {
257 file.write((char*)chunk.pointer, chunk.size);
258 }
259}
260
261static std::unique_ptr<PicaTrace> pica_trace;
262static std::mutex pica_trace_mutex;
263static int is_pica_tracing = false;
264
265void StartPicaTracing()
266{
267 if (is_pica_tracing) {
268 ERROR_LOG(GPU, "StartPicaTracing called even though tracing already running!");
269 return;
270 }
271
272 pica_trace_mutex.lock();
273 pica_trace = std::unique_ptr<PicaTrace>(new PicaTrace);
274
275 is_pica_tracing = true;
276 pica_trace_mutex.unlock();
277}
278
279bool IsPicaTracing()
280{
281 return is_pica_tracing;
282}
283
284void OnPicaRegWrite(u32 id, u32 value)
285{
286 // Double check for is_pica_tracing to avoid pointless locking overhead
287 if (!is_pica_tracing)
288 return;
289
290 std::unique_lock<std::mutex> lock(pica_trace_mutex);
291
292 if (!is_pica_tracing)
293 return;
294
295 pica_trace->writes.push_back({id, value});
296}
297
298std::unique_ptr<PicaTrace> FinishPicaTracing()
299{
300 if (!is_pica_tracing) {
301 ERROR_LOG(GPU, "FinishPicaTracing called even though tracing already running!");
302 return {};
303 }
304
305 // signalize that no further tracing should be performed
306 is_pica_tracing = false;
307
308 // Wait until running tracing is finished
309 pica_trace_mutex.lock();
310 std::unique_ptr<PicaTrace> ret(std::move(pica_trace));
311 pica_trace_mutex.unlock();
312 return std::move(ret);
313}
314
315void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) {
316 // NOTE: Permanently enabling this just trashes hard disks for no reason.
317 // Hence, this is currently disabled.
318 return;
319
320#ifndef HAVE_PNG
321 return;
322#else
323 if (!data)
324 return;
325
326 // Write data to file
327 static int dump_index = 0;
328 std::string filename = std::string("texture_dump") + std::to_string(++dump_index) + std::string(".png");
329 u32 row_stride = texture_config.width * 3;
330
331 u8* buf;
332
333 char title[] = "Citra texture dump";
334 char title_key[] = "Title";
335 png_structp png_ptr = nullptr;
336 png_infop info_ptr = nullptr;
337
338 // Open file for writing (binary mode)
339 File::IOFile fp(filename, "wb");
340
341 // Initialize write structure
342 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
343 if (png_ptr == nullptr) {
344 ERROR_LOG(GPU, "Could not allocate write struct\n");
345 goto finalise;
346
347 }
348
349 // Initialize info structure
350 info_ptr = png_create_info_struct(png_ptr);
351 if (info_ptr == nullptr) {
352 ERROR_LOG(GPU, "Could not allocate info struct\n");
353 goto finalise;
354 }
355
356 // Setup Exception handling
357 if (setjmp(png_jmpbuf(png_ptr))) {
358 ERROR_LOG(GPU, "Error during png creation\n");
359 goto finalise;
360 }
361
362 png_init_io(png_ptr, fp.GetHandle());
363
364 // Write header (8 bit colour depth)
365 png_set_IHDR(png_ptr, info_ptr, texture_config.width, texture_config.height,
366 8, PNG_COLOR_TYPE_RGB /*_ALPHA*/, PNG_INTERLACE_NONE,
367 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
368
369 png_text title_text;
370 title_text.compression = PNG_TEXT_COMPRESSION_NONE;
371 title_text.key = title_key;
372 title_text.text = title;
373 png_set_text(png_ptr, info_ptr, &title_text, 1);
374
375 png_write_info(png_ptr, info_ptr);
376
377 buf = new u8[row_stride * texture_config.height];
378 for (int y = 0; y < texture_config.height; ++y) {
379 for (int x = 0; x < texture_config.width; ++x) {
380 // Cf. rasterizer code for an explanation of this algorithm.
381 int texel_index_within_tile = 0;
382 for (int block_size_index = 0; block_size_index < 3; ++block_size_index) {
383 int sub_tile_width = 1 << block_size_index;
384 int sub_tile_height = 1 << block_size_index;
385
386 int sub_tile_index = (x & sub_tile_width) << block_size_index;
387 sub_tile_index += 2 * ((y & sub_tile_height) << block_size_index);
388 texel_index_within_tile += sub_tile_index;
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 }
402 }
403
404 // Write image data
405 for (auto y = 0; y < texture_config.height; ++y)
406 {
407 u8* row_ptr = (u8*)buf + y * row_stride;
408 u8* ptr = row_ptr;
409 png_write_row(png_ptr, row_ptr);
410 }
411
412 delete[] buf;
413
414 // End write
415 png_write_end(png_ptr, nullptr);
416
417finalise:
418 if (info_ptr != nullptr) png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
419 if (png_ptr != nullptr) png_destroy_write_struct(&png_ptr, (png_infopp)nullptr);
420#endif
421}
422
423void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages)
424{
425 using Source = Pica::Regs::TevStageConfig::Source;
426 using ColorModifier = Pica::Regs::TevStageConfig::ColorModifier;
427 using AlphaModifier = Pica::Regs::TevStageConfig::AlphaModifier;
428 using Operation = Pica::Regs::TevStageConfig::Operation;
429
430 std::string stage_info = "Tev setup:\n";
431 for (int index = 0; index < stages.size(); ++index) {
432 const auto& tev_stage = stages[index];
433
434 const std::map<Source, std::string> source_map = {
435 { Source::PrimaryColor, "PrimaryColor" },
436 { Source::Texture0, "Texture0" },
437 { Source::Constant, "Constant" },
438 { Source::Previous, "Previous" },
439 };
440
441 const std::map<ColorModifier, std::string> color_modifier_map = {
442 { ColorModifier::SourceColor, { "%source.rgb" } }
443 };
444 const std::map<AlphaModifier, std::string> alpha_modifier_map = {
445 { AlphaModifier::SourceAlpha, "%source.a" }
446 };
447
448 std::map<Operation, std::string> combiner_map = {
449 { Operation::Replace, "%source1" },
450 { Operation::Modulate, "(%source1 * %source2) / 255" },
451 };
452
453 auto ReplacePattern =
454 [](const std::string& input, const std::string& pattern, const std::string& replacement) -> std::string {
455 size_t start = input.find(pattern);
456 if (start == std::string::npos)
457 return input;
458
459 std::string ret = input;
460 ret.replace(start, pattern.length(), replacement);
461 return ret;
462 };
463 auto GetColorSourceStr =
464 [&source_map,&color_modifier_map,&ReplacePattern](const Source& src, const ColorModifier& modifier) {
465 auto src_it = source_map.find(src);
466 std::string src_str = "Unknown";
467 if (src_it != source_map.end())
468 src_str = src_it->second;
469
470 auto modifier_it = color_modifier_map.find(modifier);
471 std::string modifier_str = "%source.????";
472 if (modifier_it != color_modifier_map.end())
473 modifier_str = modifier_it->second;
474
475 return ReplacePattern(modifier_str, "%source", src_str);
476 };
477 auto GetColorCombinerStr =
478 [&](const Regs::TevStageConfig& tev_stage) {
479 auto op_it = combiner_map.find(tev_stage.color_op);
480 std::string op_str = "Unknown op (%source1, %source2, %source3)";
481 if (op_it != combiner_map.end())
482 op_str = op_it->second;
483
484 op_str = ReplacePattern(op_str, "%source1", GetColorSourceStr(tev_stage.color_source1, tev_stage.color_modifier1));
485 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));
487 };
488 auto GetAlphaSourceStr =
489 [&source_map,&alpha_modifier_map,&ReplacePattern](const Source& src, const AlphaModifier& modifier) {
490 auto src_it = source_map.find(src);
491 std::string src_str = "Unknown";
492 if (src_it != source_map.end())
493 src_str = src_it->second;
494
495 auto modifier_it = alpha_modifier_map.find(modifier);
496 std::string modifier_str = "%source.????";
497 if (modifier_it != alpha_modifier_map.end())
498 modifier_str = modifier_it->second;
499
500 return ReplacePattern(modifier_str, "%source", src_str);
501 };
502 auto GetAlphaCombinerStr =
503 [&](const Regs::TevStageConfig& tev_stage) {
504 auto op_it = combiner_map.find(tev_stage.alpha_op);
505 std::string op_str = "Unknown op (%source1, %source2, %source3)";
506 if (op_it != combiner_map.end())
507 op_str = op_it->second;
508
509 op_str = ReplacePattern(op_str, "%source1", GetAlphaSourceStr(tev_stage.alpha_source1, tev_stage.alpha_modifier1));
510 op_str = ReplacePattern(op_str, "%source2", GetAlphaSourceStr(tev_stage.alpha_source2, tev_stage.alpha_modifier2));
511 return ReplacePattern(op_str, "%source3", GetAlphaSourceStr(tev_stage.alpha_source3, tev_stage.alpha_modifier3));
512 };
513
514 stage_info += "Stage " + std::to_string(index) + ": " + GetColorCombinerStr(tev_stage) + " " + GetAlphaCombinerStr(tev_stage) + "\n";
515 }
516
517 DEBUG_LOG(GPU, "%s", stage_info.c_str());
518}
519
520} // namespace
521
522} // namespace
diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h
new file mode 100644
index 000000000..8b1499bf2
--- /dev/null
+++ b/src/video_core/debug_utils/debug_utils.h
@@ -0,0 +1,66 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <memory>
9#include <vector>
10
11#include "video_core/pica.h"
12
13namespace Pica {
14
15namespace DebugUtils {
16
17// Simple utility class for dumping geometry data to an OBJ file
18class GeometryDumper {
19public:
20 struct Vertex {
21 std::array<float,3> pos;
22 };
23
24 void AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2);
25
26 void Dump();
27
28private:
29 struct Face {
30 int index[3];
31 };
32
33 std::vector<Vertex> vertices;
34 std::vector<Face> faces;
35};
36
37void DumpShader(const u32* binary_data, u32 binary_size, const u32* swizzle_data, u32 swizzle_size,
38 u32 main_offset, const Regs::VSOutputAttributes* output_attributes);
39
40
41// Utility class to log Pica commands.
42struct PicaTrace {
43 struct Write : public std::pair<u32,u32> {
44 Write(u32 id, u32 value) : std::pair<u32,u32>(id, value) {}
45
46 u32& Id() { return first; }
47 const u32& Id() const { return first; }
48
49 u32& Value() { return second; }
50 const u32& Value() const { return second; }
51 };
52 std::vector<Write> writes;
53};
54
55void StartPicaTracing();
56bool IsPicaTracing();
57void OnPicaRegWrite(u32 id, u32 value);
58std::unique_ptr<PicaTrace> FinishPicaTracing();
59
60void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data);
61
62void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages);
63
64} // namespace
65
66} // namespace