debug_utils.cpp
1 // Copyright 2014 Citra Emulator Project 2 // Licensed under GPLv2 3 // Refer to the license.txt file included. 4 5 #include <cstdint> 6 #include <cstring> 7 #include <fstream> 8 #include <stdexcept> 9 #include <nihstro/bit_field.h> 10 #include <nihstro/float24.h> 11 #include <nihstro/shader_binary.h> 12 #include "common/assert.h" 13 #include "common/bit_field.h" 14 #include "common/vector_math.h" 15 #include "core/core.h" 16 #include "video_core/debug_utils/debug_utils.h" 17 #include "video_core/gpu.h" 18 #include "video_core/pica/regs_shader.h" 19 #include "video_core/pica/shader_setup.h" 20 #include "video_core/renderer_base.h" 21 22 using nihstro::DVLBHeader; 23 using nihstro::DVLEHeader; 24 using nihstro::DVLPHeader; 25 26 namespace Pica { 27 28 void DebugContext::DoOnEvent(Event event, const void* data) { 29 { 30 std::unique_lock lock{breakpoint_mutex}; 31 32 // Commit the rasterizer's caches so framebuffers, render targets, etc. will show on debug 33 // widgets 34 Core::System::GetInstance().GPU().Renderer().Rasterizer()->FlushAll(); 35 36 // TODO: Should stop the CPU thread here once we multithread emulation. 37 38 active_breakpoint = event; 39 at_breakpoint = true; 40 41 // Tell all observers that we hit a breakpoint 42 for (auto& breakpoint_observer : breakpoint_observers) { 43 breakpoint_observer->OnPicaBreakPointHit(event, data); 44 } 45 46 // Wait until another thread tells us to Resume() 47 resume_from_breakpoint.wait(lock, [&] { return !at_breakpoint; }); 48 } 49 } 50 51 void DebugContext::Resume() { 52 { 53 std::lock_guard lock{breakpoint_mutex}; 54 55 // Tell all observers that we are about to resume 56 for (auto& breakpoint_observer : breakpoint_observers) { 57 breakpoint_observer->OnPicaResume(); 58 } 59 60 // Resume the waiting thread (i.e. OnEvent()) 61 at_breakpoint = false; 62 } 63 64 resume_from_breakpoint.notify_one(); 65 } 66 67 std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global 68 69 namespace DebugUtils { 70 71 void DumpShader(const std::string& filename, const ShaderRegs& config, const ShaderSetup& setup, 72 const RasterizerRegs::VSOutputAttributes* output_attributes) { 73 struct StuffToWrite { 74 const u8* pointer; 75 u32 size; 76 }; 77 std::vector<StuffToWrite> writing_queue; 78 u32 write_offset = 0; 79 80 auto QueueForWriting = [&writing_queue, &write_offset](const u8* pointer, u32 size) { 81 writing_queue.push_back({pointer, size}); 82 u32 old_write_offset = write_offset; 83 write_offset += size; 84 return old_write_offset; 85 }; 86 87 // First off, try to translate Pica state (one enum for output attribute type and component) 88 // into shbin format (separate type and component mask). 89 union OutputRegisterInfo { 90 enum Type : u64 { 91 POSITION = 0, 92 QUATERNION = 1, 93 COLOR = 2, 94 TEXCOORD0 = 3, 95 TEXCOORD1 = 5, 96 TEXCOORD2 = 6, 97 98 VIEW = 8, 99 }; 100 101 BitField<0, 64, u64> hex; 102 103 BitField<0, 16, Type> type; 104 BitField<16, 16, u64> id; 105 BitField<32, 4, u64> component_mask; 106 }; 107 108 // This is put into a try-catch block to make sure we notice unknown configurations. 109 std::vector<OutputRegisterInfo> output_info_table; 110 for (unsigned i = 0; i < 7; ++i) { 111 using OutputAttributes = Pica::RasterizerRegs::VSOutputAttributes; 112 113 // TODO: It's still unclear how the attribute components map to the register! 114 // Once we know that, this code probably will not make much sense anymore. 115 std::map<OutputAttributes::Semantic, std::pair<OutputRegisterInfo::Type, u32>> map = { 116 {OutputAttributes::POSITION_X, {OutputRegisterInfo::POSITION, 1}}, 117 {OutputAttributes::POSITION_Y, {OutputRegisterInfo::POSITION, 2}}, 118 {OutputAttributes::POSITION_Z, {OutputRegisterInfo::POSITION, 4}}, 119 {OutputAttributes::POSITION_W, {OutputRegisterInfo::POSITION, 8}}, 120 {OutputAttributes::QUATERNION_X, {OutputRegisterInfo::QUATERNION, 1}}, 121 {OutputAttributes::QUATERNION_Y, {OutputRegisterInfo::QUATERNION, 2}}, 122 {OutputAttributes::QUATERNION_Z, {OutputRegisterInfo::QUATERNION, 4}}, 123 {OutputAttributes::QUATERNION_W, {OutputRegisterInfo::QUATERNION, 8}}, 124 {OutputAttributes::COLOR_R, {OutputRegisterInfo::COLOR, 1}}, 125 {OutputAttributes::COLOR_G, {OutputRegisterInfo::COLOR, 2}}, 126 {OutputAttributes::COLOR_B, {OutputRegisterInfo::COLOR, 4}}, 127 {OutputAttributes::COLOR_A, {OutputRegisterInfo::COLOR, 8}}, 128 {OutputAttributes::TEXCOORD0_U, {OutputRegisterInfo::TEXCOORD0, 1}}, 129 {OutputAttributes::TEXCOORD0_V, {OutputRegisterInfo::TEXCOORD0, 2}}, 130 {OutputAttributes::TEXCOORD1_U, {OutputRegisterInfo::TEXCOORD1, 1}}, 131 {OutputAttributes::TEXCOORD1_V, {OutputRegisterInfo::TEXCOORD1, 2}}, 132 {OutputAttributes::TEXCOORD2_U, {OutputRegisterInfo::TEXCOORD2, 1}}, 133 {OutputAttributes::TEXCOORD2_V, {OutputRegisterInfo::TEXCOORD2, 2}}, 134 {OutputAttributes::VIEW_X, {OutputRegisterInfo::VIEW, 1}}, 135 {OutputAttributes::VIEW_Y, {OutputRegisterInfo::VIEW, 2}}, 136 {OutputAttributes::VIEW_Z, {OutputRegisterInfo::VIEW, 4}}, 137 }; 138 139 for (const auto& semantic : std::vector<OutputAttributes::Semantic>{ 140 output_attributes[i].map_x, output_attributes[i].map_y, output_attributes[i].map_z, 141 output_attributes[i].map_w}) { 142 if (semantic == OutputAttributes::INVALID) 143 continue; 144 145 try { 146 OutputRegisterInfo::Type type = map.at(semantic).first; 147 u32 component_mask = map.at(semantic).second; 148 149 auto it = std::find_if(output_info_table.begin(), output_info_table.end(), 150 [&i, &type](const OutputRegisterInfo& info) { 151 return info.id == i && info.type == type; 152 }); 153 154 if (it == output_info_table.end()) { 155 output_info_table.emplace_back(); 156 output_info_table.back().type.Assign(type); 157 output_info_table.back().component_mask.Assign(component_mask); 158 output_info_table.back().id.Assign(i); 159 } else { 160 it->component_mask.Assign(it->component_mask | component_mask); 161 } 162 } catch (const std::out_of_range&) { 163 DEBUG_ASSERT_MSG(false, "Unknown output attribute mapping"); 164 LOG_ERROR(HW_GPU, 165 "Unknown output attribute mapping: {:03x}, {:03x}, {:03x}, {:03x}", 166 (int)output_attributes[i].map_x.Value(), 167 (int)output_attributes[i].map_y.Value(), 168 (int)output_attributes[i].map_z.Value(), 169 (int)output_attributes[i].map_w.Value()); 170 } 171 } 172 } 173 174 struct { 175 DVLBHeader header; 176 u32 dvle_offset; 177 } dvlb{{DVLBHeader::MAGIC_WORD, 1}}; // 1 DVLE 178 179 DVLPHeader dvlp{DVLPHeader::MAGIC_WORD}; 180 DVLEHeader dvle{DVLEHeader::MAGIC_WORD}; 181 182 QueueForWriting(reinterpret_cast<const u8*>(&dvlb), sizeof(dvlb)); 183 u32 dvlp_offset = QueueForWriting(reinterpret_cast<const u8*>(&dvlp), sizeof(dvlp)); 184 dvlb.dvle_offset = QueueForWriting(reinterpret_cast<const u8*>(&dvle), sizeof(dvle)); 185 186 // TODO: Reduce the amount of binary code written to relevant portions 187 dvlp.binary_offset = write_offset - dvlp_offset; 188 dvlp.binary_size_words = static_cast<uint32_t>(setup.program_code.size()); 189 QueueForWriting(reinterpret_cast<const u8*>(setup.program_code.data()), 190 static_cast<u32>(setup.program_code.size()) * sizeof(u32)); 191 192 dvlp.swizzle_info_offset = write_offset - dvlp_offset; 193 dvlp.swizzle_info_num_entries = static_cast<uint32_t>(setup.swizzle_data.size()); 194 u32 dummy = 0; 195 for (unsigned int i = 0; i < setup.swizzle_data.size(); ++i) { 196 QueueForWriting(reinterpret_cast<const u8*>(&setup.swizzle_data[i]), 197 sizeof(setup.swizzle_data[i])); 198 QueueForWriting(reinterpret_cast<const u8*>(&dummy), sizeof(dummy)); 199 } 200 201 dvle.main_offset_words = config.main_offset; 202 dvle.output_register_table_offset = write_offset - dvlb.dvle_offset; 203 dvle.output_register_table_size = static_cast<u32>(output_info_table.size()); 204 QueueForWriting(reinterpret_cast<const u8*>(output_info_table.data()), 205 static_cast<u32>(output_info_table.size() * sizeof(OutputRegisterInfo))); 206 207 // TODO: Create a label table for "main" 208 209 std::vector<nihstro::ConstantInfo> constant_table; 210 for (unsigned i = 0; i < setup.uniforms.b.size(); ++i) { 211 nihstro::ConstantInfo constant; 212 std::memset(&constant, 0, sizeof(constant)); 213 constant.type = nihstro::ConstantInfo::Bool; 214 constant.regid = i; 215 constant.b = setup.uniforms.b[i]; 216 constant_table.emplace_back(constant); 217 } 218 for (unsigned i = 0; i < setup.uniforms.i.size(); ++i) { 219 nihstro::ConstantInfo constant; 220 std::memset(&constant, 0, sizeof(constant)); 221 constant.type = nihstro::ConstantInfo::Int; 222 constant.regid = i; 223 constant.i.x = setup.uniforms.i[i].x; 224 constant.i.y = setup.uniforms.i[i].y; 225 constant.i.z = setup.uniforms.i[i].z; 226 constant.i.w = setup.uniforms.i[i].w; 227 constant_table.emplace_back(constant); 228 } 229 for (unsigned i = 0; i < sizeof(setup.uniforms.f) / sizeof(setup.uniforms.f[0]); ++i) { 230 nihstro::ConstantInfo constant; 231 std::memset(&constant, 0, sizeof(constant)); 232 constant.type = nihstro::ConstantInfo::Float; 233 constant.regid = i; 234 constant.f.x = nihstro::to_float24(setup.uniforms.f[i].x.ToFloat32()); 235 constant.f.y = nihstro::to_float24(setup.uniforms.f[i].y.ToFloat32()); 236 constant.f.z = nihstro::to_float24(setup.uniforms.f[i].z.ToFloat32()); 237 constant.f.w = nihstro::to_float24(setup.uniforms.f[i].w.ToFloat32()); 238 239 // Store constant if it's different from zero.. 240 if (setup.uniforms.f[i].x.ToFloat32() != 0.0 || setup.uniforms.f[i].y.ToFloat32() != 0.0 || 241 setup.uniforms.f[i].z.ToFloat32() != 0.0 || setup.uniforms.f[i].w.ToFloat32() != 0.0) 242 constant_table.emplace_back(constant); 243 } 244 dvle.constant_table_offset = write_offset - dvlb.dvle_offset; 245 dvle.constant_table_size = static_cast<uint32_t>(constant_table.size()); 246 for (const auto& constant : constant_table) { 247 QueueForWriting(reinterpret_cast<const u8*>(&constant), sizeof(constant)); 248 } 249 250 // Write data to file 251 std::ofstream file(filename, std::ios_base::out | std::ios_base::binary); 252 253 for (const auto& chunk : writing_queue) { 254 file.write(reinterpret_cast<const char*>(chunk.pointer), chunk.size); 255 } 256 } 257 258 static std::unique_ptr<PicaTrace> pica_trace; 259 static std::mutex pica_trace_mutex; 260 bool g_is_pica_tracing = false; 261 262 void StartPicaTracing() { 263 if (g_is_pica_tracing) { 264 LOG_WARNING(HW_GPU, "StartPicaTracing called even though tracing already running!"); 265 return; 266 } 267 268 std::lock_guard lock(pica_trace_mutex); 269 pica_trace = std::make_unique<PicaTrace>(); 270 271 g_is_pica_tracing = true; 272 } 273 274 void OnPicaRegWrite(u16 cmd_id, u16 mask, u32 value) { 275 std::lock_guard lock(pica_trace_mutex); 276 277 if (!g_is_pica_tracing) 278 return; 279 280 pica_trace->writes.push_back(PicaTrace::Write{cmd_id, mask, value}); 281 } 282 283 std::unique_ptr<PicaTrace> FinishPicaTracing() { 284 if (!g_is_pica_tracing) { 285 LOG_WARNING(HW_GPU, "FinishPicaTracing called even though tracing isn't running!"); 286 return {}; 287 } 288 289 // signalize that no further tracing should be performed 290 g_is_pica_tracing = false; 291 292 // Wait until running tracing is finished 293 std::lock_guard lock(pica_trace_mutex); 294 std::unique_ptr<PicaTrace> ret(std::move(pica_trace)); 295 296 return ret; 297 } 298 299 } // namespace DebugUtils 300 301 } // namespace Pica