diff options
| author | 2014-08-25 16:12:10 -0400 | |
|---|---|---|
| committer | 2014-08-25 16:12:10 -0400 | |
| commit | 97fd8fc38d4f9c288779cddb06538860124c6263 (patch) | |
| tree | bc99e0fceaae732f9c8d4831fcdb8f661b49ccb8 /src/video_core/debug_utils | |
| parent | Merge pull request #75 from xsacha/qt5 (diff) | |
| parent | Pica/Rasterizer: Clarify a TODO. (diff) | |
| download | yuzu-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.cpp | 522 | ||||
| -rw-r--r-- | src/video_core/debug_utils/debug_utils.h | 66 |
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 | |||
| 21 | namespace Pica { | ||
| 22 | |||
| 23 | namespace DebugUtils { | ||
| 24 | |||
| 25 | void 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 | |||
| 34 | void 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) | ||
| 58 | struct 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 | }; | ||
| 67 | static_assert(sizeof(DVLBHeader) == 0x8, "Incorrect structure size"); | ||
| 68 | |||
| 69 | struct 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 | }; | ||
| 82 | static_assert(sizeof(DVLPHeader) == 0x1C, "Incorrect structure size"); | ||
| 83 | |||
| 84 | struct 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 | }; | ||
| 114 | static_assert(sizeof(DVLEHeader) == 0x40, "Incorrect structure size"); | ||
| 115 | #pragma pack() | ||
| 116 | |||
| 117 | void 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 | |||
| 261 | static std::unique_ptr<PicaTrace> pica_trace; | ||
| 262 | static std::mutex pica_trace_mutex; | ||
| 263 | static int is_pica_tracing = false; | ||
| 264 | |||
| 265 | void 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 | |||
| 279 | bool IsPicaTracing() | ||
| 280 | { | ||
| 281 | return is_pica_tracing; | ||
| 282 | } | ||
| 283 | |||
| 284 | void 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 | |||
| 298 | std::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 | |||
| 315 | void 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 | |||
| 417 | finalise: | ||
| 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 | |||
| 423 | void 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 | |||
| 13 | namespace Pica { | ||
| 14 | |||
| 15 | namespace DebugUtils { | ||
| 16 | |||
| 17 | // Simple utility class for dumping geometry data to an OBJ file | ||
| 18 | class GeometryDumper { | ||
| 19 | public: | ||
| 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 | |||
| 28 | private: | ||
| 29 | struct Face { | ||
| 30 | int index[3]; | ||
| 31 | }; | ||
| 32 | |||
| 33 | std::vector<Vertex> vertices; | ||
| 34 | std::vector<Face> faces; | ||
| 35 | }; | ||
| 36 | |||
| 37 | void 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. | ||
| 42 | struct 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 | |||
| 55 | void StartPicaTracing(); | ||
| 56 | bool IsPicaTracing(); | ||
| 57 | void OnPicaRegWrite(u32 id, u32 value); | ||
| 58 | std::unique_ptr<PicaTrace> FinishPicaTracing(); | ||
| 59 | |||
| 60 | void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data); | ||
| 61 | |||
| 62 | void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages); | ||
| 63 | |||
| 64 | } // namespace | ||
| 65 | |||
| 66 | } // namespace | ||