diff options
| author | 2014-12-09 12:14:05 -0500 | |
|---|---|---|
| committer | 2014-12-09 12:14:05 -0500 | |
| commit | 360f68419db55de15a996feb1874d1b0b82c4661 (patch) | |
| tree | 816d9a88deded411908e6d9aa22a648416c41469 /src/video_core | |
| parent | Merge pull request #217 from archshift/cmd_buff (diff) | |
| parent | More cleanups. (diff) | |
| download | yuzu-360f68419db55de15a996feb1874d1b0b82c4661.tar.gz yuzu-360f68419db55de15a996feb1874d1b0b82c4661.tar.xz yuzu-360f68419db55de15a996feb1874d1b0b82c4661.zip | |
Merge pull request #218 from neobrain/pica_debugger
Pica debugger improvements
Diffstat (limited to 'src/video_core')
| -rw-r--r-- | src/video_core/command_processor.cpp | 13 | ||||
| -rw-r--r-- | src/video_core/debug_utils/debug_utils.cpp | 110 | ||||
| -rw-r--r-- | src/video_core/debug_utils/debug_utils.h | 146 | ||||
| -rw-r--r-- | src/video_core/pica.h | 28 |
4 files changed, 269 insertions, 28 deletions
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 8a6ba2560..298b04c51 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp | |||
| @@ -34,6 +34,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 34 | u32 old_value = registers[id]; | 34 | u32 old_value = registers[id]; |
| 35 | registers[id] = (old_value & ~mask) | (value & mask); | 35 | registers[id] = (old_value & ~mask) | (value & mask); |
| 36 | 36 | ||
| 37 | if (g_debug_context) | ||
| 38 | g_debug_context->OnEvent(DebugContext::Event::CommandLoaded, reinterpret_cast<void*>(&id)); | ||
| 39 | |||
| 37 | DebugUtils::OnPicaRegWrite(id, registers[id]); | 40 | DebugUtils::OnPicaRegWrite(id, registers[id]); |
| 38 | 41 | ||
| 39 | switch(id) { | 42 | switch(id) { |
| @@ -43,6 +46,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 43 | { | 46 | { |
| 44 | DebugUtils::DumpTevStageConfig(registers.GetTevStages()); | 47 | DebugUtils::DumpTevStageConfig(registers.GetTevStages()); |
| 45 | 48 | ||
| 49 | if (g_debug_context) | ||
| 50 | g_debug_context->OnEvent(DebugContext::Event::IncomingPrimitiveBatch, nullptr); | ||
| 51 | |||
| 46 | const auto& attribute_config = registers.vertex_attributes; | 52 | const auto& attribute_config = registers.vertex_attributes; |
| 47 | const u8* const base_address = Memory::GetPointer(attribute_config.GetBaseAddress()); | 53 | const u8* const base_address = Memory::GetPointer(attribute_config.GetBaseAddress()); |
| 48 | 54 | ||
| @@ -132,6 +138,10 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 132 | clipper_primitive_assembler.SubmitVertex(output, Clipper::ProcessTriangle); | 138 | clipper_primitive_assembler.SubmitVertex(output, Clipper::ProcessTriangle); |
| 133 | } | 139 | } |
| 134 | geometry_dumper.Dump(); | 140 | geometry_dumper.Dump(); |
| 141 | |||
| 142 | if (g_debug_context) | ||
| 143 | g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr); | ||
| 144 | |||
| 135 | break; | 145 | break; |
| 136 | } | 146 | } |
| 137 | 147 | ||
| @@ -229,6 +239,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 229 | default: | 239 | default: |
| 230 | break; | 240 | break; |
| 231 | } | 241 | } |
| 242 | |||
| 243 | if (g_debug_context) | ||
| 244 | g_debug_context->OnEvent(DebugContext::Event::CommandProcessed, reinterpret_cast<void*>(&id)); | ||
| 232 | } | 245 | } |
| 233 | 246 | ||
| 234 | static std::ptrdiff_t ExecuteCommandBlock(const u32* first_command_word) { | 247 | static std::ptrdiff_t ExecuteCommandBlock(const u32* first_command_word) { |
diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index 8a5f11424..71b03f31c 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp | |||
| @@ -3,6 +3,8 @@ | |||
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <algorithm> | 5 | #include <algorithm> |
| 6 | #include <condition_variable> | ||
| 7 | #include <list> | ||
| 6 | #include <map> | 8 | #include <map> |
| 7 | #include <fstream> | 9 | #include <fstream> |
| 8 | #include <mutex> | 10 | #include <mutex> |
| @@ -12,14 +14,56 @@ | |||
| 12 | #include <png.h> | 14 | #include <png.h> |
| 13 | #endif | 15 | #endif |
| 14 | 16 | ||
| 17 | #include "common/log.h" | ||
| 15 | #include "common/file_util.h" | 18 | #include "common/file_util.h" |
| 16 | 19 | ||
| 20 | #include "video_core/math.h" | ||
| 17 | #include "video_core/pica.h" | 21 | #include "video_core/pica.h" |
| 18 | 22 | ||
| 19 | #include "debug_utils.h" | 23 | #include "debug_utils.h" |
| 20 | 24 | ||
| 21 | namespace Pica { | 25 | namespace Pica { |
| 22 | 26 | ||
| 27 | void DebugContext::OnEvent(Event event, void* data) { | ||
| 28 | if (!breakpoints[event].enabled) | ||
| 29 | return; | ||
| 30 | |||
| 31 | { | ||
| 32 | std::unique_lock<std::mutex> lock(breakpoint_mutex); | ||
| 33 | |||
| 34 | // TODO: Should stop the CPU thread here once we multithread emulation. | ||
| 35 | |||
| 36 | active_breakpoint = event; | ||
| 37 | at_breakpoint = true; | ||
| 38 | |||
| 39 | // Tell all observers that we hit a breakpoint | ||
| 40 | for (auto& breakpoint_observer : breakpoint_observers) { | ||
| 41 | breakpoint_observer->OnPicaBreakPointHit(event, data); | ||
| 42 | } | ||
| 43 | |||
| 44 | // Wait until another thread tells us to Resume() | ||
| 45 | resume_from_breakpoint.wait(lock, [&]{ return !at_breakpoint; }); | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | void DebugContext::Resume() { | ||
| 50 | { | ||
| 51 | std::unique_lock<std::mutex> lock(breakpoint_mutex); | ||
| 52 | |||
| 53 | // Tell all observers that we are about to resume | ||
| 54 | for (auto& breakpoint_observer : breakpoint_observers) { | ||
| 55 | breakpoint_observer->OnPicaResume(); | ||
| 56 | } | ||
| 57 | |||
| 58 | // Resume the waiting thread (i.e. OnEvent()) | ||
| 59 | at_breakpoint = false; | ||
| 60 | } | ||
| 61 | |||
| 62 | resume_from_breakpoint.notify_one(); | ||
| 63 | } | ||
| 64 | |||
| 65 | std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global | ||
| 66 | |||
| 23 | namespace DebugUtils { | 67 | namespace DebugUtils { |
| 24 | 68 | ||
| 25 | void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) { | 69 | void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) { |
| @@ -312,6 +356,42 @@ std::unique_ptr<PicaTrace> FinishPicaTracing() | |||
| 312 | return std::move(ret); | 356 | return std::move(ret); |
| 313 | } | 357 | } |
| 314 | 358 | ||
| 359 | const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const TextureInfo& info) { | ||
| 360 | _dbg_assert_(GPU, info.format == Pica::Regs::TextureFormat::RGB8); | ||
| 361 | |||
| 362 | // Cf. rasterizer code for an explanation of this algorithm. | ||
| 363 | int texel_index_within_tile = 0; | ||
| 364 | for (int block_size_index = 0; block_size_index < 3; ++block_size_index) { | ||
| 365 | int sub_tile_width = 1 << block_size_index; | ||
| 366 | int sub_tile_height = 1 << block_size_index; | ||
| 367 | |||
| 368 | int sub_tile_index = (x & sub_tile_width) << block_size_index; | ||
| 369 | sub_tile_index += 2 * ((y & sub_tile_height) << block_size_index); | ||
| 370 | texel_index_within_tile += sub_tile_index; | ||
| 371 | } | ||
| 372 | |||
| 373 | const int block_width = 8; | ||
| 374 | const int block_height = 8; | ||
| 375 | |||
| 376 | int coarse_x = (x / block_width) * block_width; | ||
| 377 | int coarse_y = (y / block_height) * block_height; | ||
| 378 | |||
| 379 | const u8* source_ptr = source + coarse_x * block_height * 3 + coarse_y * info.stride + texel_index_within_tile * 3; | ||
| 380 | return { source_ptr[2], source_ptr[1], source_ptr[0], 255 }; | ||
| 381 | } | ||
| 382 | |||
| 383 | TextureInfo TextureInfo::FromPicaRegister(const Regs::TextureConfig& config, | ||
| 384 | const Regs::TextureFormat& format) | ||
| 385 | { | ||
| 386 | TextureInfo info; | ||
| 387 | info.address = config.GetPhysicalAddress(); | ||
| 388 | info.width = config.width; | ||
| 389 | info.height = config.height; | ||
| 390 | info.format = format; | ||
| 391 | info.stride = Pica::Regs::BytesPerPixel(info.format) * info.width; | ||
| 392 | return info; | ||
| 393 | } | ||
| 394 | |||
| 315 | void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { | 395 | void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { |
| 316 | // NOTE: Permanently enabling this just trashes hard disks for no reason. | 396 | // NOTE: Permanently enabling this just trashes hard disks for no reason. |
| 317 | // Hence, this is currently disabled. | 397 | // Hence, this is currently disabled. |
| @@ -377,27 +457,15 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { | |||
| 377 | buf = new u8[row_stride * texture_config.height]; | 457 | buf = new u8[row_stride * texture_config.height]; |
| 378 | for (unsigned y = 0; y < texture_config.height; ++y) { | 458 | for (unsigned y = 0; y < texture_config.height; ++y) { |
| 379 | for (unsigned x = 0; x < texture_config.width; ++x) { | 459 | for (unsigned x = 0; x < texture_config.width; ++x) { |
| 380 | // Cf. rasterizer code for an explanation of this algorithm. | 460 | TextureInfo info; |
| 381 | int texel_index_within_tile = 0; | 461 | info.width = texture_config.width; |
| 382 | for (int block_size_index = 0; block_size_index < 3; ++block_size_index) { | 462 | info.height = texture_config.height; |
| 383 | int sub_tile_width = 1 << block_size_index; | 463 | info.stride = row_stride; |
| 384 | int sub_tile_height = 1 << block_size_index; | 464 | info.format = registers.texture0_format; |
| 385 | 465 | Math::Vec4<u8> texture_color = LookupTexture(data, x, y, info); | |
| 386 | int sub_tile_index = (x & sub_tile_width) << block_size_index; | 466 | buf[3 * x + y * row_stride ] = texture_color.r(); |
| 387 | sub_tile_index += 2 * ((y & sub_tile_height) << block_size_index); | 467 | buf[3 * x + y * row_stride + 1] = texture_color.g(); |
| 388 | texel_index_within_tile += sub_tile_index; | 468 | buf[3 * x + y * row_stride + 2] = texture_color.b(); |
| 389 | } | ||
| 390 | |||
| 391 | const int block_width = 8; | ||
| 392 | const int block_height = 8; | ||
| 393 | |||
| 394 | int coarse_x = (x / block_width) * block_width; | ||
| 395 | int coarse_y = (y / block_height) * block_height; | ||
| 396 | |||
| 397 | u8* source_ptr = (u8*)data + coarse_x * block_height * 3 + coarse_y * row_stride + texel_index_within_tile * 3; | ||
| 398 | buf[3 * x + y * row_stride ] = source_ptr[2]; | ||
| 399 | buf[3 * x + y * row_stride + 1] = source_ptr[1]; | ||
| 400 | buf[3 * x + y * row_stride + 2] = source_ptr[0]; | ||
| 401 | } | 469 | } |
| 402 | } | 470 | } |
| 403 | 471 | ||
diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index b1558cfae..51f14f12f 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h | |||
| @@ -5,13 +5,147 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <array> | 7 | #include <array> |
| 8 | #include <condition_variable> | ||
| 9 | #include <list> | ||
| 10 | #include <map> | ||
| 8 | #include <memory> | 11 | #include <memory> |
| 12 | #include <mutex> | ||
| 9 | #include <vector> | 13 | #include <vector> |
| 10 | 14 | ||
| 15 | #include "video_core/math.h" | ||
| 11 | #include "video_core/pica.h" | 16 | #include "video_core/pica.h" |
| 12 | 17 | ||
| 13 | namespace Pica { | 18 | namespace Pica { |
| 14 | 19 | ||
| 20 | class DebugContext { | ||
| 21 | public: | ||
| 22 | enum class Event { | ||
| 23 | FirstEvent = 0, | ||
| 24 | |||
| 25 | CommandLoaded = FirstEvent, | ||
| 26 | CommandProcessed, | ||
| 27 | IncomingPrimitiveBatch, | ||
| 28 | FinishedPrimitiveBatch, | ||
| 29 | |||
| 30 | NumEvents | ||
| 31 | }; | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Inherit from this class to be notified of events registered to some debug context. | ||
| 35 | * Most importantly this is used for our debugger GUI. | ||
| 36 | * | ||
| 37 | * To implement event handling, override the OnPicaBreakPointHit and OnPicaResume methods. | ||
| 38 | * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state access | ||
| 39 | * @todo Evaluate an alternative interface, in which there is only one managing observer and multiple child observers running (by design) on the same thread. | ||
| 40 | */ | ||
| 41 | class BreakPointObserver { | ||
| 42 | public: | ||
| 43 | /// Constructs the object such that it observes events of the given DebugContext. | ||
| 44 | BreakPointObserver(std::shared_ptr<DebugContext> debug_context) : context_weak(debug_context) { | ||
| 45 | std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex); | ||
| 46 | debug_context->breakpoint_observers.push_back(this); | ||
| 47 | } | ||
| 48 | |||
| 49 | virtual ~BreakPointObserver() { | ||
| 50 | auto context = context_weak.lock(); | ||
| 51 | if (context) { | ||
| 52 | std::unique_lock<std::mutex> lock(context->breakpoint_mutex); | ||
| 53 | context->breakpoint_observers.remove(this); | ||
| 54 | |||
| 55 | // If we are the last observer to be destroyed, tell the debugger context that | ||
| 56 | // it is free to continue. In particular, this is required for a proper Citra | ||
| 57 | // shutdown, when the emulation thread is waiting at a breakpoint. | ||
| 58 | if (context->breakpoint_observers.empty()) | ||
| 59 | context->Resume(); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | /** | ||
| 64 | * Action to perform when a breakpoint was reached. | ||
| 65 | * @param event Type of event which triggered the breakpoint | ||
| 66 | * @param data Optional data pointer (if unused, this is a nullptr) | ||
| 67 | * @note This function will perform nothing unless it is overridden in the child class. | ||
| 68 | */ | ||
| 69 | virtual void OnPicaBreakPointHit(Event, void*) { | ||
| 70 | } | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Action to perform when emulation is resumed from a breakpoint. | ||
| 74 | * @note This function will perform nothing unless it is overridden in the child class. | ||
| 75 | */ | ||
| 76 | virtual void OnPicaResume() { | ||
| 77 | } | ||
| 78 | |||
| 79 | protected: | ||
| 80 | /** | ||
| 81 | * Weak context pointer. This need not be valid, so when requesting a shared_ptr via | ||
| 82 | * context_weak.lock(), always compare the result against nullptr. | ||
| 83 | */ | ||
| 84 | std::weak_ptr<DebugContext> context_weak; | ||
| 85 | }; | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Simple structure defining a breakpoint state | ||
| 89 | */ | ||
| 90 | struct BreakPoint { | ||
| 91 | bool enabled = false; | ||
| 92 | }; | ||
| 93 | |||
| 94 | /** | ||
| 95 | * Static constructor used to create a shared_ptr of a DebugContext. | ||
| 96 | */ | ||
| 97 | static std::shared_ptr<DebugContext> Construct() { | ||
| 98 | return std::shared_ptr<DebugContext>(new DebugContext); | ||
| 99 | } | ||
| 100 | |||
| 101 | /** | ||
| 102 | * Used by the emulation core when a given event has happened. If a breakpoint has been set | ||
| 103 | * for this event, OnEvent calls the event handlers of the registered breakpoint observers. | ||
| 104 | * The current thread then is halted until Resume() is called from another thread (or until | ||
| 105 | * emulation is stopped). | ||
| 106 | * @param event Event which has happened | ||
| 107 | * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until Resume() is called. | ||
| 108 | */ | ||
| 109 | void OnEvent(Event event, void* data); | ||
| 110 | |||
| 111 | /** | ||
| 112 | * Resume from the current breakpoint. | ||
| 113 | * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock. Calling from any other thread is safe. | ||
| 114 | */ | ||
| 115 | void Resume(); | ||
| 116 | |||
| 117 | /** | ||
| 118 | * Delete all set breakpoints and resume emulation. | ||
| 119 | */ | ||
| 120 | void ClearBreakpoints() { | ||
| 121 | breakpoints.clear(); | ||
| 122 | Resume(); | ||
| 123 | } | ||
| 124 | |||
| 125 | // TODO: Evaluate if access to these members should be hidden behind a public interface. | ||
| 126 | std::map<Event, BreakPoint> breakpoints; | ||
| 127 | Event active_breakpoint; | ||
| 128 | bool at_breakpoint = false; | ||
| 129 | |||
| 130 | private: | ||
| 131 | /** | ||
| 132 | * Private default constructor to make sure people always construct this through Construct() | ||
| 133 | * instead. | ||
| 134 | */ | ||
| 135 | DebugContext() = default; | ||
| 136 | |||
| 137 | /// Mutex protecting current breakpoint state and the observer list. | ||
| 138 | std::mutex breakpoint_mutex; | ||
| 139 | |||
| 140 | /// Used by OnEvent to wait for resumption. | ||
| 141 | std::condition_variable resume_from_breakpoint; | ||
| 142 | |||
| 143 | /// List of registered observers | ||
| 144 | std::list<BreakPointObserver*> breakpoint_observers; | ||
| 145 | }; | ||
| 146 | |||
| 147 | extern std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global | ||
| 148 | |||
| 15 | namespace DebugUtils { | 149 | namespace DebugUtils { |
| 16 | 150 | ||
| 17 | // Simple utility class for dumping geometry data to an OBJ file | 151 | // Simple utility class for dumping geometry data to an OBJ file |
| @@ -57,6 +191,18 @@ bool IsPicaTracing(); | |||
| 57 | void OnPicaRegWrite(u32 id, u32 value); | 191 | void OnPicaRegWrite(u32 id, u32 value); |
| 58 | std::unique_ptr<PicaTrace> FinishPicaTracing(); | 192 | std::unique_ptr<PicaTrace> FinishPicaTracing(); |
| 59 | 193 | ||
| 194 | struct TextureInfo { | ||
| 195 | unsigned int address; | ||
| 196 | int width; | ||
| 197 | int height; | ||
| 198 | int stride; | ||
| 199 | Pica::Regs::TextureFormat format; | ||
| 200 | |||
| 201 | static TextureInfo FromPicaRegister(const Pica::Regs::TextureConfig& config, | ||
| 202 | const Pica::Regs::TextureFormat& format); | ||
| 203 | }; | ||
| 204 | |||
| 205 | const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const TextureInfo& info); | ||
| 60 | void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data); | 206 | void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data); |
| 61 | 207 | ||
| 62 | void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages); | 208 | void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages); |
diff --git a/src/video_core/pica.h b/src/video_core/pica.h index 5fe15a218..e7ca38978 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h | |||
| @@ -109,7 +109,7 @@ struct Regs { | |||
| 109 | 109 | ||
| 110 | u32 address; | 110 | u32 address; |
| 111 | 111 | ||
| 112 | u32 GetPhysicalAddress() { | 112 | u32 GetPhysicalAddress() const { |
| 113 | return DecodeAddressRegister(address) - Memory::FCRAM_PADDR + Memory::HEAP_GSP_VADDR; | 113 | return DecodeAddressRegister(address) - Memory::FCRAM_PADDR + Memory::HEAP_GSP_VADDR; |
| 114 | } | 114 | } |
| 115 | 115 | ||
| @@ -130,7 +130,26 @@ struct Regs { | |||
| 130 | // Seems like they are luminance formats and compressed textures. | 130 | // Seems like they are luminance formats and compressed textures. |
| 131 | }; | 131 | }; |
| 132 | 132 | ||
| 133 | BitField<0, 1, u32> texturing_enable; | 133 | static unsigned BytesPerPixel(TextureFormat format) { |
| 134 | switch (format) { | ||
| 135 | case TextureFormat::RGBA8: | ||
| 136 | return 4; | ||
| 137 | |||
| 138 | case TextureFormat::RGB8: | ||
| 139 | return 3; | ||
| 140 | |||
| 141 | case TextureFormat::RGBA5551: | ||
| 142 | case TextureFormat::RGB565: | ||
| 143 | case TextureFormat::RGBA4: | ||
| 144 | return 2; | ||
| 145 | |||
| 146 | default: | ||
| 147 | // placeholder for yet unknown formats | ||
| 148 | return 1; | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | BitField< 0, 1, u32> texturing_enable; | ||
| 134 | TextureConfig texture0; | 153 | TextureConfig texture0; |
| 135 | INSERT_PADDING_WORDS(0x8); | 154 | INSERT_PADDING_WORDS(0x8); |
| 136 | BitField<0, 4, TextureFormat> texture0_format; | 155 | BitField<0, 4, TextureFormat> texture0_format; |
| @@ -517,10 +536,6 @@ struct Regs { | |||
| 517 | static std::string GetCommandName(int index) { | 536 | static std::string GetCommandName(int index) { |
| 518 | std::map<u32, std::string> map; | 537 | std::map<u32, std::string> map; |
| 519 | 538 | ||
| 520 | // TODO: MSVC does not support using offsetof() on non-static data members even though this | ||
| 521 | // is technically allowed since C++11. Hence, this functionality is disabled until | ||
| 522 | // MSVC properly supports it. | ||
| 523 | #ifndef _MSC_VER | ||
| 524 | Regs regs; | 539 | Regs regs; |
| 525 | #define ADD_FIELD(name) \ | 540 | #define ADD_FIELD(name) \ |
| 526 | do { \ | 541 | do { \ |
| @@ -557,7 +572,6 @@ struct Regs { | |||
| 557 | ADD_FIELD(vs_swizzle_patterns); | 572 | ADD_FIELD(vs_swizzle_patterns); |
| 558 | 573 | ||
| 559 | #undef ADD_FIELD | 574 | #undef ADD_FIELD |
| 560 | #endif // _MSC_VER | ||
| 561 | 575 | ||
| 562 | // Return empty string if no match is found | 576 | // Return empty string if no match is found |
| 563 | return map[index]; | 577 | return map[index]; |