diff options
| author | 2018-01-11 20:07:44 -0700 | |
|---|---|---|
| committer | 2018-01-12 19:11:03 -0700 | |
| commit | 1d28b2e142f845773e2b90e267d9632e196a99b9 (patch) | |
| tree | 027a3586a0fc927731afb3711c328c6dafc8551f /src/video_core/debug_utils | |
| parent | Massive removal of unused modules (diff) | |
| download | yuzu-1d28b2e142f845773e2b90e267d9632e196a99b9.tar.gz yuzu-1d28b2e142f845773e2b90e267d9632e196a99b9.tar.xz yuzu-1d28b2e142f845773e2b90e267d9632e196a99b9.zip | |
Remove references to PICA and rasterizers in video_core
Diffstat (limited to 'src/video_core/debug_utils')
| -rw-r--r-- | src/video_core/debug_utils/debug_utils.cpp | 577 | ||||
| -rw-r--r-- | src/video_core/debug_utils/debug_utils.h | 251 |
2 files changed, 0 insertions, 828 deletions
diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp deleted file mode 100644 index 47dbc8cc8..000000000 --- a/src/video_core/debug_utils/debug_utils.cpp +++ /dev/null | |||
| @@ -1,577 +0,0 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include <condition_variable> | ||
| 7 | #include <cstdint> | ||
| 8 | #include <cstring> | ||
| 9 | #include <fstream> | ||
| 10 | #include <map> | ||
| 11 | #include <mutex> | ||
| 12 | #include <stdexcept> | ||
| 13 | #include <string> | ||
| 14 | |||
| 15 | #ifdef HAVE_PNG | ||
| 16 | #include <png.h> | ||
| 17 | #include <setjmp.h> | ||
| 18 | #endif | ||
| 19 | |||
| 20 | #include <nihstro/bit_field.h> | ||
| 21 | #include <nihstro/float24.h> | ||
| 22 | #include <nihstro/shader_binary.h> | ||
| 23 | #include "common/assert.h" | ||
| 24 | #include "common/bit_field.h" | ||
| 25 | #include "common/color.h" | ||
| 26 | #include "common/common_types.h" | ||
| 27 | #include "common/file_util.h" | ||
| 28 | #include "common/logging/log.h" | ||
| 29 | #include "common/math_util.h" | ||
| 30 | #include "common/vector_math.h" | ||
| 31 | #include "video_core/debug_utils/debug_utils.h" | ||
| 32 | #include "video_core/pica_state.h" | ||
| 33 | #include "video_core/pica_types.h" | ||
| 34 | #include "video_core/rasterizer_interface.h" | ||
| 35 | #include "video_core/regs_rasterizer.h" | ||
| 36 | #include "video_core/regs_shader.h" | ||
| 37 | #include "video_core/regs_texturing.h" | ||
| 38 | #include "video_core/renderer_base.h" | ||
| 39 | #include "video_core/shader/shader.h" | ||
| 40 | #include "video_core/texture/texture_decode.h" | ||
| 41 | #include "video_core/utils.h" | ||
| 42 | #include "video_core/video_core.h" | ||
| 43 | |||
| 44 | using nihstro::DVLBHeader; | ||
| 45 | using nihstro::DVLEHeader; | ||
| 46 | using nihstro::DVLPHeader; | ||
| 47 | |||
| 48 | namespace Pica { | ||
| 49 | |||
| 50 | void DebugContext::DoOnEvent(Event event, void* data) { | ||
| 51 | { | ||
| 52 | std::unique_lock<std::mutex> lock(breakpoint_mutex); | ||
| 53 | |||
| 54 | // Commit the rasterizer's caches so framebuffers, render targets, etc. will show on debug | ||
| 55 | // widgets | ||
| 56 | VideoCore::g_renderer->Rasterizer()->FlushAll(); | ||
| 57 | |||
| 58 | // TODO: Should stop the CPU thread here once we multithread emulation. | ||
| 59 | |||
| 60 | active_breakpoint = event; | ||
| 61 | at_breakpoint = true; | ||
| 62 | |||
| 63 | // Tell all observers that we hit a breakpoint | ||
| 64 | for (auto& breakpoint_observer : breakpoint_observers) { | ||
| 65 | breakpoint_observer->OnPicaBreakPointHit(event, data); | ||
| 66 | } | ||
| 67 | |||
| 68 | // Wait until another thread tells us to Resume() | ||
| 69 | resume_from_breakpoint.wait(lock, [&] { return !at_breakpoint; }); | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | void DebugContext::Resume() { | ||
| 74 | { | ||
| 75 | std::lock_guard<std::mutex> lock(breakpoint_mutex); | ||
| 76 | |||
| 77 | // Tell all observers that we are about to resume | ||
| 78 | for (auto& breakpoint_observer : breakpoint_observers) { | ||
| 79 | breakpoint_observer->OnPicaResume(); | ||
| 80 | } | ||
| 81 | |||
| 82 | // Resume the waiting thread (i.e. OnEvent()) | ||
| 83 | at_breakpoint = false; | ||
| 84 | } | ||
| 85 | |||
| 86 | resume_from_breakpoint.notify_one(); | ||
| 87 | } | ||
| 88 | |||
| 89 | std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global | ||
| 90 | |||
| 91 | namespace DebugUtils { | ||
| 92 | |||
| 93 | void DumpShader(const std::string& filename, const ShaderRegs& config, | ||
| 94 | const Shader::ShaderSetup& setup, | ||
| 95 | const RasterizerRegs::VSOutputAttributes* output_attributes) { | ||
| 96 | struct StuffToWrite { | ||
| 97 | const u8* pointer; | ||
| 98 | u32 size; | ||
| 99 | }; | ||
| 100 | std::vector<StuffToWrite> writing_queue; | ||
| 101 | u32 write_offset = 0; | ||
| 102 | |||
| 103 | auto QueueForWriting = [&writing_queue, &write_offset](const u8* pointer, u32 size) { | ||
| 104 | writing_queue.push_back({pointer, size}); | ||
| 105 | u32 old_write_offset = write_offset; | ||
| 106 | write_offset += size; | ||
| 107 | return old_write_offset; | ||
| 108 | }; | ||
| 109 | |||
| 110 | // First off, try to translate Pica state (one enum for output attribute type and component) | ||
| 111 | // into shbin format (separate type and component mask). | ||
| 112 | union OutputRegisterInfo { | ||
| 113 | enum Type : u64 { | ||
| 114 | POSITION = 0, | ||
| 115 | QUATERNION = 1, | ||
| 116 | COLOR = 2, | ||
| 117 | TEXCOORD0 = 3, | ||
| 118 | TEXCOORD1 = 5, | ||
| 119 | TEXCOORD2 = 6, | ||
| 120 | |||
| 121 | VIEW = 8, | ||
| 122 | }; | ||
| 123 | |||
| 124 | BitField<0, 64, u64> hex; | ||
| 125 | |||
| 126 | BitField<0, 16, Type> type; | ||
| 127 | BitField<16, 16, u64> id; | ||
| 128 | BitField<32, 4, u64> component_mask; | ||
| 129 | }; | ||
| 130 | |||
| 131 | // This is put into a try-catch block to make sure we notice unknown configurations. | ||
| 132 | std::vector<OutputRegisterInfo> output_info_table; | ||
| 133 | for (unsigned i = 0; i < 7; ++i) { | ||
| 134 | using OutputAttributes = Pica::RasterizerRegs::VSOutputAttributes; | ||
| 135 | |||
| 136 | // TODO: It's still unclear how the attribute components map to the register! | ||
| 137 | // Once we know that, this code probably will not make much sense anymore. | ||
| 138 | std::map<OutputAttributes::Semantic, std::pair<OutputRegisterInfo::Type, u32>> map = { | ||
| 139 | {OutputAttributes::POSITION_X, {OutputRegisterInfo::POSITION, 1}}, | ||
| 140 | {OutputAttributes::POSITION_Y, {OutputRegisterInfo::POSITION, 2}}, | ||
| 141 | {OutputAttributes::POSITION_Z, {OutputRegisterInfo::POSITION, 4}}, | ||
| 142 | {OutputAttributes::POSITION_W, {OutputRegisterInfo::POSITION, 8}}, | ||
| 143 | {OutputAttributes::QUATERNION_X, {OutputRegisterInfo::QUATERNION, 1}}, | ||
| 144 | {OutputAttributes::QUATERNION_Y, {OutputRegisterInfo::QUATERNION, 2}}, | ||
| 145 | {OutputAttributes::QUATERNION_Z, {OutputRegisterInfo::QUATERNION, 4}}, | ||
| 146 | {OutputAttributes::QUATERNION_W, {OutputRegisterInfo::QUATERNION, 8}}, | ||
| 147 | {OutputAttributes::COLOR_R, {OutputRegisterInfo::COLOR, 1}}, | ||
| 148 | {OutputAttributes::COLOR_G, {OutputRegisterInfo::COLOR, 2}}, | ||
| 149 | {OutputAttributes::COLOR_B, {OutputRegisterInfo::COLOR, 4}}, | ||
| 150 | {OutputAttributes::COLOR_A, {OutputRegisterInfo::COLOR, 8}}, | ||
| 151 | {OutputAttributes::TEXCOORD0_U, {OutputRegisterInfo::TEXCOORD0, 1}}, | ||
| 152 | {OutputAttributes::TEXCOORD0_V, {OutputRegisterInfo::TEXCOORD0, 2}}, | ||
| 153 | {OutputAttributes::TEXCOORD1_U, {OutputRegisterInfo::TEXCOORD1, 1}}, | ||
| 154 | {OutputAttributes::TEXCOORD1_V, {OutputRegisterInfo::TEXCOORD1, 2}}, | ||
| 155 | {OutputAttributes::TEXCOORD2_U, {OutputRegisterInfo::TEXCOORD2, 1}}, | ||
| 156 | {OutputAttributes::TEXCOORD2_V, {OutputRegisterInfo::TEXCOORD2, 2}}, | ||
| 157 | {OutputAttributes::VIEW_X, {OutputRegisterInfo::VIEW, 1}}, | ||
| 158 | {OutputAttributes::VIEW_Y, {OutputRegisterInfo::VIEW, 2}}, | ||
| 159 | {OutputAttributes::VIEW_Z, {OutputRegisterInfo::VIEW, 4}}, | ||
| 160 | }; | ||
| 161 | |||
| 162 | for (const auto& semantic : std::vector<OutputAttributes::Semantic>{ | ||
| 163 | output_attributes[i].map_x, output_attributes[i].map_y, output_attributes[i].map_z, | ||
| 164 | output_attributes[i].map_w}) { | ||
| 165 | if (semantic == OutputAttributes::INVALID) | ||
| 166 | continue; | ||
| 167 | |||
| 168 | try { | ||
| 169 | OutputRegisterInfo::Type type = map.at(semantic).first; | ||
| 170 | u32 component_mask = map.at(semantic).second; | ||
| 171 | |||
| 172 | auto it = std::find_if(output_info_table.begin(), output_info_table.end(), | ||
| 173 | [&i, &type](const OutputRegisterInfo& info) { | ||
| 174 | return info.id == i && info.type == type; | ||
| 175 | }); | ||
| 176 | |||
| 177 | if (it == output_info_table.end()) { | ||
| 178 | output_info_table.emplace_back(); | ||
| 179 | output_info_table.back().type.Assign(type); | ||
| 180 | output_info_table.back().component_mask.Assign(component_mask); | ||
| 181 | output_info_table.back().id.Assign(i); | ||
| 182 | } else { | ||
| 183 | it->component_mask.Assign(it->component_mask | component_mask); | ||
| 184 | } | ||
| 185 | } catch (const std::out_of_range&) { | ||
| 186 | DEBUG_ASSERT_MSG(false, "Unknown output attribute mapping"); | ||
| 187 | LOG_ERROR(HW_GPU, "Unknown output attribute mapping: %03x, %03x, %03x, %03x", | ||
| 188 | (int)output_attributes[i].map_x.Value(), | ||
| 189 | (int)output_attributes[i].map_y.Value(), | ||
| 190 | (int)output_attributes[i].map_z.Value(), | ||
| 191 | (int)output_attributes[i].map_w.Value()); | ||
| 192 | } | ||
| 193 | } | ||
| 194 | } | ||
| 195 | |||
| 196 | struct { | ||
| 197 | DVLBHeader header; | ||
| 198 | u32 dvle_offset; | ||
| 199 | } dvlb{{DVLBHeader::MAGIC_WORD, 1}}; // 1 DVLE | ||
| 200 | |||
| 201 | DVLPHeader dvlp{DVLPHeader::MAGIC_WORD}; | ||
| 202 | DVLEHeader dvle{DVLEHeader::MAGIC_WORD}; | ||
| 203 | |||
| 204 | QueueForWriting(reinterpret_cast<const u8*>(&dvlb), sizeof(dvlb)); | ||
| 205 | u32 dvlp_offset = QueueForWriting(reinterpret_cast<const u8*>(&dvlp), sizeof(dvlp)); | ||
| 206 | dvlb.dvle_offset = QueueForWriting(reinterpret_cast<const u8*>(&dvle), sizeof(dvle)); | ||
| 207 | |||
| 208 | // TODO: Reduce the amount of binary code written to relevant portions | ||
| 209 | dvlp.binary_offset = write_offset - dvlp_offset; | ||
| 210 | dvlp.binary_size_words = static_cast<uint32_t>(setup.program_code.size()); | ||
| 211 | QueueForWriting(reinterpret_cast<const u8*>(setup.program_code.data()), | ||
| 212 | static_cast<u32>(setup.program_code.size()) * sizeof(u32)); | ||
| 213 | |||
| 214 | dvlp.swizzle_info_offset = write_offset - dvlp_offset; | ||
| 215 | dvlp.swizzle_info_num_entries = static_cast<uint32_t>(setup.swizzle_data.size()); | ||
| 216 | u32 dummy = 0; | ||
| 217 | for (unsigned int i = 0; i < setup.swizzle_data.size(); ++i) { | ||
| 218 | QueueForWriting(reinterpret_cast<const u8*>(&setup.swizzle_data[i]), | ||
| 219 | sizeof(setup.swizzle_data[i])); | ||
| 220 | QueueForWriting(reinterpret_cast<const u8*>(&dummy), sizeof(dummy)); | ||
| 221 | } | ||
| 222 | |||
| 223 | dvle.main_offset_words = config.main_offset; | ||
| 224 | dvle.output_register_table_offset = write_offset - dvlb.dvle_offset; | ||
| 225 | dvle.output_register_table_size = static_cast<u32>(output_info_table.size()); | ||
| 226 | QueueForWriting(reinterpret_cast<const u8*>(output_info_table.data()), | ||
| 227 | static_cast<u32>(output_info_table.size() * sizeof(OutputRegisterInfo))); | ||
| 228 | |||
| 229 | // TODO: Create a label table for "main" | ||
| 230 | |||
| 231 | std::vector<nihstro::ConstantInfo> constant_table; | ||
| 232 | for (unsigned i = 0; i < setup.uniforms.b.size(); ++i) { | ||
| 233 | nihstro::ConstantInfo constant; | ||
| 234 | memset(&constant, 0, sizeof(constant)); | ||
| 235 | constant.type = nihstro::ConstantInfo::Bool; | ||
| 236 | constant.regid = i; | ||
| 237 | constant.b = setup.uniforms.b[i]; | ||
| 238 | constant_table.emplace_back(constant); | ||
| 239 | } | ||
| 240 | for (unsigned i = 0; i < setup.uniforms.i.size(); ++i) { | ||
| 241 | nihstro::ConstantInfo constant; | ||
| 242 | memset(&constant, 0, sizeof(constant)); | ||
| 243 | constant.type = nihstro::ConstantInfo::Int; | ||
| 244 | constant.regid = i; | ||
| 245 | constant.i.x = setup.uniforms.i[i].x; | ||
| 246 | constant.i.y = setup.uniforms.i[i].y; | ||
| 247 | constant.i.z = setup.uniforms.i[i].z; | ||
| 248 | constant.i.w = setup.uniforms.i[i].w; | ||
| 249 | constant_table.emplace_back(constant); | ||
| 250 | } | ||
| 251 | for (unsigned i = 0; i < sizeof(setup.uniforms.f) / sizeof(setup.uniforms.f[0]); ++i) { | ||
| 252 | nihstro::ConstantInfo constant; | ||
| 253 | memset(&constant, 0, sizeof(constant)); | ||
| 254 | constant.type = nihstro::ConstantInfo::Float; | ||
| 255 | constant.regid = i; | ||
| 256 | constant.f.x = nihstro::to_float24(setup.uniforms.f[i].x.ToFloat32()); | ||
| 257 | constant.f.y = nihstro::to_float24(setup.uniforms.f[i].y.ToFloat32()); | ||
| 258 | constant.f.z = nihstro::to_float24(setup.uniforms.f[i].z.ToFloat32()); | ||
| 259 | constant.f.w = nihstro::to_float24(setup.uniforms.f[i].w.ToFloat32()); | ||
| 260 | |||
| 261 | // Store constant if it's different from zero.. | ||
| 262 | if (setup.uniforms.f[i].x.ToFloat32() != 0.0 || setup.uniforms.f[i].y.ToFloat32() != 0.0 || | ||
| 263 | setup.uniforms.f[i].z.ToFloat32() != 0.0 || setup.uniforms.f[i].w.ToFloat32() != 0.0) | ||
| 264 | constant_table.emplace_back(constant); | ||
| 265 | } | ||
| 266 | dvle.constant_table_offset = write_offset - dvlb.dvle_offset; | ||
| 267 | dvle.constant_table_size = static_cast<uint32_t>(constant_table.size()); | ||
| 268 | for (const auto& constant : constant_table) { | ||
| 269 | QueueForWriting(reinterpret_cast<const u8*>(&constant), sizeof(constant)); | ||
| 270 | } | ||
| 271 | |||
| 272 | // Write data to file | ||
| 273 | std::ofstream file(filename, std::ios_base::out | std::ios_base::binary); | ||
| 274 | |||
| 275 | for (const auto& chunk : writing_queue) { | ||
| 276 | file.write(reinterpret_cast<const char*>(chunk.pointer), chunk.size); | ||
| 277 | } | ||
| 278 | } | ||
| 279 | |||
| 280 | static std::unique_ptr<PicaTrace> pica_trace; | ||
| 281 | static std::mutex pica_trace_mutex; | ||
| 282 | bool g_is_pica_tracing = false; | ||
| 283 | |||
| 284 | void StartPicaTracing() { | ||
| 285 | if (g_is_pica_tracing) { | ||
| 286 | LOG_WARNING(HW_GPU, "StartPicaTracing called even though tracing already running!"); | ||
| 287 | return; | ||
| 288 | } | ||
| 289 | |||
| 290 | std::lock_guard<std::mutex> lock(pica_trace_mutex); | ||
| 291 | pica_trace = std::make_unique<PicaTrace>(); | ||
| 292 | |||
| 293 | g_is_pica_tracing = true; | ||
| 294 | } | ||
| 295 | |||
| 296 | void OnPicaRegWrite(PicaTrace::Write write) { | ||
| 297 | std::lock_guard<std::mutex> lock(pica_trace_mutex); | ||
| 298 | |||
| 299 | if (!g_is_pica_tracing) | ||
| 300 | return; | ||
| 301 | |||
| 302 | pica_trace->writes.push_back(write); | ||
| 303 | } | ||
| 304 | |||
| 305 | std::unique_ptr<PicaTrace> FinishPicaTracing() { | ||
| 306 | if (!g_is_pica_tracing) { | ||
| 307 | LOG_WARNING(HW_GPU, "FinishPicaTracing called even though tracing isn't running!"); | ||
| 308 | return {}; | ||
| 309 | } | ||
| 310 | |||
| 311 | // signalize that no further tracing should be performed | ||
| 312 | g_is_pica_tracing = false; | ||
| 313 | |||
| 314 | // Wait until running tracing is finished | ||
| 315 | std::lock_guard<std::mutex> lock(pica_trace_mutex); | ||
| 316 | std::unique_ptr<PicaTrace> ret(std::move(pica_trace)); | ||
| 317 | |||
| 318 | return ret; | ||
| 319 | } | ||
| 320 | |||
| 321 | #ifdef HAVE_PNG | ||
| 322 | // Adapter functions to libpng to write/flush to File::IOFile instances. | ||
| 323 | static void WriteIOFile(png_structp png_ptr, png_bytep data, png_size_t length) { | ||
| 324 | auto* fp = static_cast<FileUtil::IOFile*>(png_get_io_ptr(png_ptr)); | ||
| 325 | if (!fp->WriteBytes(data, length)) | ||
| 326 | png_error(png_ptr, "Failed to write to output PNG file."); | ||
| 327 | } | ||
| 328 | |||
| 329 | static void FlushIOFile(png_structp png_ptr) { | ||
| 330 | auto* fp = static_cast<FileUtil::IOFile*>(png_get_io_ptr(png_ptr)); | ||
| 331 | if (!fp->Flush()) | ||
| 332 | png_error(png_ptr, "Failed to flush to output PNG file."); | ||
| 333 | } | ||
| 334 | #endif | ||
| 335 | |||
| 336 | void DumpTexture(const TexturingRegs::TextureConfig& texture_config, u8* data) { | ||
| 337 | #ifndef HAVE_PNG | ||
| 338 | return; | ||
| 339 | #else | ||
| 340 | if (!data) | ||
| 341 | return; | ||
| 342 | |||
| 343 | // Write data to file | ||
| 344 | static int dump_index = 0; | ||
| 345 | std::string filename = | ||
| 346 | std::string("texture_dump") + std::to_string(++dump_index) + std::string(".png"); | ||
| 347 | u32 row_stride = texture_config.width * 3; | ||
| 348 | |||
| 349 | u8* buf; | ||
| 350 | |||
| 351 | char title[] = "Citra texture dump"; | ||
| 352 | char title_key[] = "Title"; | ||
| 353 | png_structp png_ptr = nullptr; | ||
| 354 | png_infop info_ptr = nullptr; | ||
| 355 | |||
| 356 | // Open file for writing (binary mode) | ||
| 357 | FileUtil::IOFile fp(filename, "wb"); | ||
| 358 | |||
| 359 | // Initialize write structure | ||
| 360 | png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); | ||
| 361 | if (png_ptr == nullptr) { | ||
| 362 | LOG_ERROR(Debug_GPU, "Could not allocate write struct"); | ||
| 363 | goto finalise; | ||
| 364 | } | ||
| 365 | |||
| 366 | // Initialize info structure | ||
| 367 | info_ptr = png_create_info_struct(png_ptr); | ||
| 368 | if (info_ptr == nullptr) { | ||
| 369 | LOG_ERROR(Debug_GPU, "Could not allocate info struct"); | ||
| 370 | goto finalise; | ||
| 371 | } | ||
| 372 | |||
| 373 | // Setup Exception handling | ||
| 374 | if (setjmp(png_jmpbuf(png_ptr))) { | ||
| 375 | LOG_ERROR(Debug_GPU, "Error during png creation"); | ||
| 376 | goto finalise; | ||
| 377 | } | ||
| 378 | |||
| 379 | png_set_write_fn(png_ptr, static_cast<void*>(&fp), WriteIOFile, FlushIOFile); | ||
| 380 | |||
| 381 | // Write header (8 bit color depth) | ||
| 382 | png_set_IHDR(png_ptr, info_ptr, texture_config.width, texture_config.height, 8, | ||
| 383 | PNG_COLOR_TYPE_RGB /*_ALPHA*/, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, | ||
| 384 | PNG_FILTER_TYPE_BASE); | ||
| 385 | |||
| 386 | png_text title_text; | ||
| 387 | title_text.compression = PNG_TEXT_COMPRESSION_NONE; | ||
| 388 | title_text.key = title_key; | ||
| 389 | title_text.text = title; | ||
| 390 | png_set_text(png_ptr, info_ptr, &title_text, 1); | ||
| 391 | |||
| 392 | png_write_info(png_ptr, info_ptr); | ||
| 393 | |||
| 394 | buf = new u8[row_stride * texture_config.height]; | ||
| 395 | for (unsigned y = 0; y < texture_config.height; ++y) { | ||
| 396 | for (unsigned x = 0; x < texture_config.width; ++x) { | ||
| 397 | Pica::Texture::TextureInfo info; | ||
| 398 | info.width = texture_config.width; | ||
| 399 | info.height = texture_config.height; | ||
| 400 | info.stride = row_stride; | ||
| 401 | info.format = g_state.regs.texturing.texture0_format; | ||
| 402 | Math::Vec4<u8> texture_color = Pica::Texture::LookupTexture(data, x, y, info); | ||
| 403 | buf[3 * x + y * row_stride] = texture_color.r(); | ||
| 404 | buf[3 * x + y * row_stride + 1] = texture_color.g(); | ||
| 405 | buf[3 * x + y * row_stride + 2] = texture_color.b(); | ||
| 406 | } | ||
| 407 | } | ||
| 408 | |||
| 409 | // Write image data | ||
| 410 | for (unsigned y = 0; y < texture_config.height; ++y) { | ||
| 411 | u8* row_ptr = (u8*)buf + y * row_stride; | ||
| 412 | png_write_row(png_ptr, row_ptr); | ||
| 413 | } | ||
| 414 | |||
| 415 | delete[] buf; | ||
| 416 | |||
| 417 | // End write | ||
| 418 | png_write_end(png_ptr, nullptr); | ||
| 419 | |||
| 420 | finalise: | ||
| 421 | if (info_ptr != nullptr) | ||
| 422 | png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); | ||
| 423 | if (png_ptr != nullptr) | ||
| 424 | png_destroy_write_struct(&png_ptr, (png_infopp) nullptr); | ||
| 425 | #endif | ||
| 426 | } | ||
| 427 | |||
| 428 | static std::string ReplacePattern(const std::string& input, const std::string& pattern, | ||
| 429 | const std::string& replacement) { | ||
| 430 | size_t start = input.find(pattern); | ||
| 431 | if (start == std::string::npos) | ||
| 432 | return input; | ||
| 433 | |||
| 434 | std::string ret = input; | ||
| 435 | ret.replace(start, pattern.length(), replacement); | ||
| 436 | return ret; | ||
| 437 | } | ||
| 438 | |||
| 439 | static std::string GetTevStageConfigSourceString( | ||
| 440 | const TexturingRegs::TevStageConfig::Source& source) { | ||
| 441 | |||
| 442 | using Source = TexturingRegs::TevStageConfig::Source; | ||
| 443 | static const std::map<Source, std::string> source_map = { | ||
| 444 | {Source::PrimaryColor, "PrimaryColor"}, | ||
| 445 | {Source::PrimaryFragmentColor, "PrimaryFragmentColor"}, | ||
| 446 | {Source::SecondaryFragmentColor, "SecondaryFragmentColor"}, | ||
| 447 | {Source::Texture0, "Texture0"}, | ||
| 448 | {Source::Texture1, "Texture1"}, | ||
| 449 | {Source::Texture2, "Texture2"}, | ||
| 450 | {Source::Texture3, "Texture3"}, | ||
| 451 | {Source::PreviousBuffer, "PreviousBuffer"}, | ||
| 452 | {Source::Constant, "Constant"}, | ||
| 453 | {Source::Previous, "Previous"}, | ||
| 454 | }; | ||
| 455 | |||
| 456 | const auto src_it = source_map.find(source); | ||
| 457 | if (src_it == source_map.end()) | ||
| 458 | return "Unknown"; | ||
| 459 | |||
| 460 | return src_it->second; | ||
| 461 | } | ||
| 462 | |||
| 463 | static std::string GetTevStageConfigColorSourceString( | ||
| 464 | const TexturingRegs::TevStageConfig::Source& source, | ||
| 465 | const TexturingRegs::TevStageConfig::ColorModifier modifier) { | ||
| 466 | |||
| 467 | using ColorModifier = TexturingRegs::TevStageConfig::ColorModifier; | ||
| 468 | static const std::map<ColorModifier, std::string> color_modifier_map = { | ||
| 469 | {ColorModifier::SourceColor, "%source.rgb"}, | ||
| 470 | {ColorModifier::OneMinusSourceColor, "(1.0 - %source.rgb)"}, | ||
| 471 | {ColorModifier::SourceAlpha, "%source.aaa"}, | ||
| 472 | {ColorModifier::OneMinusSourceAlpha, "(1.0 - %source.aaa)"}, | ||
| 473 | {ColorModifier::SourceRed, "%source.rrr"}, | ||
| 474 | {ColorModifier::OneMinusSourceRed, "(1.0 - %source.rrr)"}, | ||
| 475 | {ColorModifier::SourceGreen, "%source.ggg"}, | ||
| 476 | {ColorModifier::OneMinusSourceGreen, "(1.0 - %source.ggg)"}, | ||
| 477 | {ColorModifier::SourceBlue, "%source.bbb"}, | ||
| 478 | {ColorModifier::OneMinusSourceBlue, "(1.0 - %source.bbb)"}, | ||
| 479 | }; | ||
| 480 | |||
| 481 | auto src_str = GetTevStageConfigSourceString(source); | ||
| 482 | auto modifier_it = color_modifier_map.find(modifier); | ||
| 483 | std::string modifier_str = "%source.????"; | ||
| 484 | if (modifier_it != color_modifier_map.end()) | ||
| 485 | modifier_str = modifier_it->second; | ||
| 486 | |||
| 487 | return ReplacePattern(modifier_str, "%source", src_str); | ||
| 488 | } | ||
| 489 | |||
| 490 | static std::string GetTevStageConfigAlphaSourceString( | ||
| 491 | const TexturingRegs::TevStageConfig::Source& source, | ||
| 492 | const TexturingRegs::TevStageConfig::AlphaModifier modifier) { | ||
| 493 | |||
| 494 | using AlphaModifier = TexturingRegs::TevStageConfig::AlphaModifier; | ||
| 495 | static const std::map<AlphaModifier, std::string> alpha_modifier_map = { | ||
| 496 | {AlphaModifier::SourceAlpha, "%source.a"}, | ||
| 497 | {AlphaModifier::OneMinusSourceAlpha, "(1.0 - %source.a)"}, | ||
| 498 | {AlphaModifier::SourceRed, "%source.r"}, | ||
| 499 | {AlphaModifier::OneMinusSourceRed, "(1.0 - %source.r)"}, | ||
| 500 | {AlphaModifier::SourceGreen, "%source.g"}, | ||
| 501 | {AlphaModifier::OneMinusSourceGreen, "(1.0 - %source.g)"}, | ||
| 502 | {AlphaModifier::SourceBlue, "%source.b"}, | ||
| 503 | {AlphaModifier::OneMinusSourceBlue, "(1.0 - %source.b)"}, | ||
| 504 | }; | ||
| 505 | |||
| 506 | auto src_str = GetTevStageConfigSourceString(source); | ||
| 507 | auto modifier_it = alpha_modifier_map.find(modifier); | ||
| 508 | std::string modifier_str = "%source.????"; | ||
| 509 | if (modifier_it != alpha_modifier_map.end()) | ||
| 510 | modifier_str = modifier_it->second; | ||
| 511 | |||
| 512 | return ReplacePattern(modifier_str, "%source", src_str); | ||
| 513 | } | ||
| 514 | |||
| 515 | static std::string GetTevStageConfigOperationString( | ||
| 516 | const TexturingRegs::TevStageConfig::Operation& operation) { | ||
| 517 | |||
| 518 | using Operation = TexturingRegs::TevStageConfig::Operation; | ||
| 519 | static const std::map<Operation, std::string> combiner_map = { | ||
| 520 | {Operation::Replace, "%source1"}, | ||
| 521 | {Operation::Modulate, "(%source1 * %source2)"}, | ||
| 522 | {Operation::Add, "(%source1 + %source2)"}, | ||
| 523 | {Operation::AddSigned, "(%source1 + %source2) - 0.5"}, | ||
| 524 | {Operation::Lerp, "lerp(%source1, %source2, %source3)"}, | ||
| 525 | {Operation::Subtract, "(%source1 - %source2)"}, | ||
| 526 | {Operation::Dot3_RGB, "dot(%source1, %source2)"}, | ||
| 527 | {Operation::MultiplyThenAdd, "((%source1 * %source2) + %source3)"}, | ||
| 528 | {Operation::AddThenMultiply, "((%source1 + %source2) * %source3)"}, | ||
| 529 | }; | ||
| 530 | |||
| 531 | const auto op_it = combiner_map.find(operation); | ||
| 532 | if (op_it == combiner_map.end()) | ||
| 533 | return "Unknown op (%source1, %source2, %source3)"; | ||
| 534 | |||
| 535 | return op_it->second; | ||
| 536 | } | ||
| 537 | |||
| 538 | std::string GetTevStageConfigColorCombinerString(const TexturingRegs::TevStageConfig& tev_stage) { | ||
| 539 | auto op_str = GetTevStageConfigOperationString(tev_stage.color_op); | ||
| 540 | op_str = ReplacePattern( | ||
| 541 | op_str, "%source1", | ||
| 542 | GetTevStageConfigColorSourceString(tev_stage.color_source1, tev_stage.color_modifier1)); | ||
| 543 | op_str = ReplacePattern( | ||
| 544 | op_str, "%source2", | ||
| 545 | GetTevStageConfigColorSourceString(tev_stage.color_source2, tev_stage.color_modifier2)); | ||
| 546 | return ReplacePattern( | ||
| 547 | op_str, "%source3", | ||
| 548 | GetTevStageConfigColorSourceString(tev_stage.color_source3, tev_stage.color_modifier3)); | ||
| 549 | } | ||
| 550 | |||
| 551 | std::string GetTevStageConfigAlphaCombinerString(const TexturingRegs::TevStageConfig& tev_stage) { | ||
| 552 | auto op_str = GetTevStageConfigOperationString(tev_stage.alpha_op); | ||
| 553 | op_str = ReplacePattern( | ||
| 554 | op_str, "%source1", | ||
| 555 | GetTevStageConfigAlphaSourceString(tev_stage.alpha_source1, tev_stage.alpha_modifier1)); | ||
| 556 | op_str = ReplacePattern( | ||
| 557 | op_str, "%source2", | ||
| 558 | GetTevStageConfigAlphaSourceString(tev_stage.alpha_source2, tev_stage.alpha_modifier2)); | ||
| 559 | return ReplacePattern( | ||
| 560 | op_str, "%source3", | ||
| 561 | GetTevStageConfigAlphaSourceString(tev_stage.alpha_source3, tev_stage.alpha_modifier3)); | ||
| 562 | } | ||
| 563 | |||
| 564 | void DumpTevStageConfig(const std::array<TexturingRegs::TevStageConfig, 6>& stages) { | ||
| 565 | std::string stage_info = "Tev setup:\n"; | ||
| 566 | for (size_t index = 0; index < stages.size(); ++index) { | ||
| 567 | const auto& tev_stage = stages[index]; | ||
| 568 | stage_info += "Stage " + std::to_string(index) + ": " + | ||
| 569 | GetTevStageConfigColorCombinerString(tev_stage) + " " + | ||
| 570 | GetTevStageConfigAlphaCombinerString(tev_stage) + "\n"; | ||
| 571 | } | ||
| 572 | LOG_TRACE(HW_GPU, "%s", stage_info.c_str()); | ||
| 573 | } | ||
| 574 | |||
| 575 | } // namespace | ||
| 576 | |||
| 577 | } // namespace | ||
diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h deleted file mode 100644 index c1f29c527..000000000 --- a/src/video_core/debug_utils/debug_utils.h +++ /dev/null | |||
| @@ -1,251 +0,0 @@ | |||
| 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 <algorithm> | ||
| 8 | #include <array> | ||
| 9 | #include <condition_variable> | ||
| 10 | #include <iterator> | ||
| 11 | #include <list> | ||
| 12 | #include <map> | ||
| 13 | #include <memory> | ||
| 14 | #include <mutex> | ||
| 15 | #include <string> | ||
| 16 | #include <utility> | ||
| 17 | #include <vector> | ||
| 18 | #include "common/common_types.h" | ||
| 19 | #include "common/vector_math.h" | ||
| 20 | #include "video_core/regs_rasterizer.h" | ||
| 21 | #include "video_core/regs_shader.h" | ||
| 22 | #include "video_core/regs_texturing.h" | ||
| 23 | |||
| 24 | namespace CiTrace { | ||
| 25 | class Recorder; | ||
| 26 | } | ||
| 27 | |||
| 28 | namespace Pica { | ||
| 29 | |||
| 30 | namespace Shader { | ||
| 31 | struct ShaderSetup; | ||
| 32 | } | ||
| 33 | |||
| 34 | class DebugContext { | ||
| 35 | public: | ||
| 36 | enum class Event { | ||
| 37 | FirstEvent = 0, | ||
| 38 | |||
| 39 | PicaCommandLoaded = FirstEvent, | ||
| 40 | PicaCommandProcessed, | ||
| 41 | IncomingPrimitiveBatch, | ||
| 42 | FinishedPrimitiveBatch, | ||
| 43 | VertexShaderInvocation, | ||
| 44 | IncomingDisplayTransfer, | ||
| 45 | GSPCommandProcessed, | ||
| 46 | BufferSwapped, | ||
| 47 | |||
| 48 | NumEvents | ||
| 49 | }; | ||
| 50 | |||
| 51 | /** | ||
| 52 | * Inherit from this class to be notified of events registered to some debug context. | ||
| 53 | * Most importantly this is used for our debugger GUI. | ||
| 54 | * | ||
| 55 | * To implement event handling, override the OnPicaBreakPointHit and OnPicaResume methods. | ||
| 56 | * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state | ||
| 57 | * access | ||
| 58 | * @todo Evaluate an alternative interface, in which there is only one managing observer and | ||
| 59 | * multiple child observers running (by design) on the same thread. | ||
| 60 | */ | ||
| 61 | class BreakPointObserver { | ||
| 62 | public: | ||
| 63 | /// Constructs the object such that it observes events of the given DebugContext. | ||
| 64 | BreakPointObserver(std::shared_ptr<DebugContext> debug_context) | ||
| 65 | : context_weak(debug_context) { | ||
| 66 | std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex); | ||
| 67 | debug_context->breakpoint_observers.push_back(this); | ||
| 68 | } | ||
| 69 | |||
| 70 | virtual ~BreakPointObserver() { | ||
| 71 | auto context = context_weak.lock(); | ||
| 72 | if (context) { | ||
| 73 | std::unique_lock<std::mutex> lock(context->breakpoint_mutex); | ||
| 74 | context->breakpoint_observers.remove(this); | ||
| 75 | |||
| 76 | // If we are the last observer to be destroyed, tell the debugger context that | ||
| 77 | // it is free to continue. In particular, this is required for a proper Citra | ||
| 78 | // shutdown, when the emulation thread is waiting at a breakpoint. | ||
| 79 | if (context->breakpoint_observers.empty()) | ||
| 80 | context->Resume(); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | /** | ||
| 85 | * Action to perform when a breakpoint was reached. | ||
| 86 | * @param event Type of event which triggered the breakpoint | ||
| 87 | * @param data Optional data pointer (if unused, this is a nullptr) | ||
| 88 | * @note This function will perform nothing unless it is overridden in the child class. | ||
| 89 | */ | ||
| 90 | virtual void OnPicaBreakPointHit(Event event, void* data) {} | ||
| 91 | |||
| 92 | /** | ||
| 93 | * Action to perform when emulation is resumed from a breakpoint. | ||
| 94 | * @note This function will perform nothing unless it is overridden in the child class. | ||
| 95 | */ | ||
| 96 | virtual void OnPicaResume() {} | ||
| 97 | |||
| 98 | protected: | ||
| 99 | /** | ||
| 100 | * Weak context pointer. This need not be valid, so when requesting a shared_ptr via | ||
| 101 | * context_weak.lock(), always compare the result against nullptr. | ||
| 102 | */ | ||
| 103 | std::weak_ptr<DebugContext> context_weak; | ||
| 104 | }; | ||
| 105 | |||
| 106 | /** | ||
| 107 | * Simple structure defining a breakpoint state | ||
| 108 | */ | ||
| 109 | struct BreakPoint { | ||
| 110 | bool enabled = false; | ||
| 111 | }; | ||
| 112 | |||
| 113 | /** | ||
| 114 | * Static constructor used to create a shared_ptr of a DebugContext. | ||
| 115 | */ | ||
| 116 | static std::shared_ptr<DebugContext> Construct() { | ||
| 117 | return std::shared_ptr<DebugContext>(new DebugContext); | ||
| 118 | } | ||
| 119 | |||
| 120 | /** | ||
| 121 | * Used by the emulation core when a given event has happened. If a breakpoint has been set | ||
| 122 | * for this event, OnEvent calls the event handlers of the registered breakpoint observers. | ||
| 123 | * The current thread then is halted until Resume() is called from another thread (or until | ||
| 124 | * emulation is stopped). | ||
| 125 | * @param event Event which has happened | ||
| 126 | * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until | ||
| 127 | * Resume() is called. | ||
| 128 | */ | ||
| 129 | void OnEvent(Event event, void* data) { | ||
| 130 | // This check is left in the header to allow the compiler to inline it. | ||
| 131 | if (!breakpoints[(int)event].enabled) | ||
| 132 | return; | ||
| 133 | // For the rest of event handling, call a separate function. | ||
| 134 | DoOnEvent(event, data); | ||
| 135 | } | ||
| 136 | |||
| 137 | void DoOnEvent(Event event, void* data); | ||
| 138 | |||
| 139 | /** | ||
| 140 | * Resume from the current breakpoint. | ||
| 141 | * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock. | ||
| 142 | * Calling from any other thread is safe. | ||
| 143 | */ | ||
| 144 | void Resume(); | ||
| 145 | |||
| 146 | /** | ||
| 147 | * Delete all set breakpoints and resume emulation. | ||
| 148 | */ | ||
| 149 | void ClearBreakpoints() { | ||
| 150 | for (auto& bp : breakpoints) { | ||
| 151 | bp.enabled = false; | ||
| 152 | } | ||
| 153 | Resume(); | ||
| 154 | } | ||
| 155 | |||
| 156 | // TODO: Evaluate if access to these members should be hidden behind a public interface. | ||
| 157 | std::array<BreakPoint, (int)Event::NumEvents> breakpoints; | ||
| 158 | Event active_breakpoint; | ||
| 159 | bool at_breakpoint = false; | ||
| 160 | |||
| 161 | std::shared_ptr<CiTrace::Recorder> recorder = nullptr; | ||
| 162 | |||
| 163 | private: | ||
| 164 | /** | ||
| 165 | * Private default constructor to make sure people always construct this through Construct() | ||
| 166 | * instead. | ||
| 167 | */ | ||
| 168 | DebugContext() = default; | ||
| 169 | |||
| 170 | /// Mutex protecting current breakpoint state and the observer list. | ||
| 171 | std::mutex breakpoint_mutex; | ||
| 172 | |||
| 173 | /// Used by OnEvent to wait for resumption. | ||
| 174 | std::condition_variable resume_from_breakpoint; | ||
| 175 | |||
| 176 | /// List of registered observers | ||
| 177 | std::list<BreakPointObserver*> breakpoint_observers; | ||
| 178 | }; | ||
| 179 | |||
| 180 | extern std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global | ||
| 181 | |||
| 182 | namespace DebugUtils { | ||
| 183 | |||
| 184 | #define PICA_DUMP_TEXTURES 0 | ||
| 185 | #define PICA_LOG_TEV 0 | ||
| 186 | |||
| 187 | void DumpShader(const std::string& filename, const ShaderRegs& config, | ||
| 188 | const Shader::ShaderSetup& setup, | ||
| 189 | const RasterizerRegs::VSOutputAttributes* output_attributes); | ||
| 190 | |||
| 191 | // Utility class to log Pica commands. | ||
| 192 | struct PicaTrace { | ||
| 193 | struct Write { | ||
| 194 | u16 cmd_id; | ||
| 195 | u16 mask; | ||
| 196 | u32 value; | ||
| 197 | }; | ||
| 198 | std::vector<Write> writes; | ||
| 199 | }; | ||
| 200 | |||
| 201 | extern bool g_is_pica_tracing; | ||
| 202 | |||
| 203 | void StartPicaTracing(); | ||
| 204 | inline bool IsPicaTracing() { | ||
| 205 | return g_is_pica_tracing; | ||
| 206 | } | ||
| 207 | void OnPicaRegWrite(PicaTrace::Write write); | ||
| 208 | std::unique_ptr<PicaTrace> FinishPicaTracing(); | ||
| 209 | |||
| 210 | void DumpTexture(const TexturingRegs::TextureConfig& texture_config, u8* data); | ||
| 211 | |||
| 212 | std::string GetTevStageConfigColorCombinerString(const TexturingRegs::TevStageConfig& tev_stage); | ||
| 213 | std::string GetTevStageConfigAlphaCombinerString(const TexturingRegs::TevStageConfig& tev_stage); | ||
| 214 | |||
| 215 | /// Dumps the Tev stage config to log at trace level | ||
| 216 | void DumpTevStageConfig(const std::array<TexturingRegs::TevStageConfig, 6>& stages); | ||
| 217 | |||
| 218 | /** | ||
| 219 | * Used in the vertex loader to merge access records. TODO: Investigate if actually useful. | ||
| 220 | */ | ||
| 221 | class MemoryAccessTracker { | ||
| 222 | /// Combine overlapping and close ranges | ||
| 223 | void SimplifyRanges() { | ||
| 224 | for (auto it = ranges.begin(); it != ranges.end(); ++it) { | ||
| 225 | // NOTE: We add 32 to the range end address to make sure "close" ranges are combined, | ||
| 226 | // too | ||
| 227 | auto it2 = std::next(it); | ||
| 228 | while (it2 != ranges.end() && it->first + it->second + 32 >= it2->first) { | ||
| 229 | it->second = std::max(it->second, it2->first + it2->second - it->first); | ||
| 230 | it2 = ranges.erase(it2); | ||
| 231 | } | ||
| 232 | } | ||
| 233 | } | ||
| 234 | |||
| 235 | public: | ||
| 236 | /// Record a particular memory access in the list | ||
| 237 | void AddAccess(u32 paddr, u32 size) { | ||
| 238 | // Create new range or extend existing one | ||
| 239 | ranges[paddr] = std::max(ranges[paddr], size); | ||
| 240 | |||
| 241 | // Simplify ranges... | ||
| 242 | SimplifyRanges(); | ||
| 243 | } | ||
| 244 | |||
| 245 | /// Map of accessed ranges (mapping start address to range size) | ||
| 246 | std::map<u32, u32> ranges; | ||
| 247 | }; | ||
| 248 | |||
| 249 | } // namespace | ||
| 250 | |||
| 251 | } // namespace | ||