diff options
Diffstat (limited to 'src/core')
| -rw-r--r-- | src/core/hle/service/gsp.cpp | 36 | ||||
| -rw-r--r-- | src/core/hw/gpu.cpp | 47 | ||||
| -rw-r--r-- | src/core/hw/gpu.h | 297 |
3 files changed, 230 insertions, 150 deletions
diff --git a/src/core/hle/service/gsp.cpp b/src/core/hle/service/gsp.cpp index e241b31c8..08e65612e 100644 --- a/src/core/hle/service/gsp.cpp +++ b/src/core/hle/service/gsp.cpp | |||
| @@ -173,11 +173,11 @@ void ExecuteCommand(const Command& command) { | |||
| 173 | case CommandId::SET_COMMAND_LIST_LAST: | 173 | case CommandId::SET_COMMAND_LIST_LAST: |
| 174 | { | 174 | { |
| 175 | auto& params = command.set_command_list_last; | 175 | auto& params = command.set_command_list_last; |
| 176 | WriteGPURegister(GPU::Regs::CommandProcessor + 2, params.address >> 3); | 176 | WriteGPURegister(GPU_REG_INDEX(command_processor_config.address), params.address >> 3); |
| 177 | WriteGPURegister(GPU::Regs::CommandProcessor, params.size >> 3); | 177 | WriteGPURegister(GPU_REG_INDEX(command_processor_config.size), params.size >> 3); |
| 178 | 178 | ||
| 179 | // TODO: Not sure if we are supposed to always write this .. seems to trigger processing though | 179 | // TODO: Not sure if we are supposed to always write this .. seems to trigger processing though |
| 180 | WriteGPURegister(GPU::Regs::CommandProcessor + 4, 1); | 180 | WriteGPURegister(GPU_REG_INDEX(command_processor_config.trigger), 1); |
| 181 | 181 | ||
| 182 | // TODO: Move this to GPU | 182 | // TODO: Move this to GPU |
| 183 | // TODO: Not sure what units the size is measured in | 183 | // TODO: Not sure what units the size is measured in |
| @@ -193,15 +193,15 @@ void ExecuteCommand(const Command& command) { | |||
| 193 | case CommandId::SET_MEMORY_FILL: | 193 | case CommandId::SET_MEMORY_FILL: |
| 194 | { | 194 | { |
| 195 | auto& params = command.memory_fill; | 195 | auto& params = command.memory_fill; |
| 196 | WriteGPURegister(GPU::Regs::MemoryFill, params.start1 >> 3); | 196 | WriteGPURegister(GPU_REG_INDEX(memory_fill_config[0].address_start), params.start1 >> 3); |
| 197 | WriteGPURegister(GPU::Regs::MemoryFill + 1, params.end1 >> 3); | 197 | WriteGPURegister(GPU_REG_INDEX(memory_fill_config[0].address_end), params.end1 >> 3); |
| 198 | WriteGPURegister(GPU::Regs::MemoryFill + 2, params.end1 - params.start1); | 198 | WriteGPURegister(GPU_REG_INDEX(memory_fill_config[0].size), params.end1 - params.start1); |
| 199 | WriteGPURegister(GPU::Regs::MemoryFill + 3, params.value1); | 199 | WriteGPURegister(GPU_REG_INDEX(memory_fill_config[0].value), params.value1); |
| 200 | 200 | ||
| 201 | WriteGPURegister(GPU::Regs::MemoryFill + 4, params.start2 >> 3); | 201 | WriteGPURegister(GPU_REG_INDEX(memory_fill_config[1].address_start), params.start2 >> 3); |
| 202 | WriteGPURegister(GPU::Regs::MemoryFill + 5, params.end2 >> 3); | 202 | WriteGPURegister(GPU_REG_INDEX(memory_fill_config[1].address_end), params.end2 >> 3); |
| 203 | WriteGPURegister(GPU::Regs::MemoryFill + 6, params.end2 - params.start2); | 203 | WriteGPURegister(GPU_REG_INDEX(memory_fill_config[1].size), params.end2 - params.start2); |
| 204 | WriteGPURegister(GPU::Regs::MemoryFill + 7, params.value2); | 204 | WriteGPURegister(GPU_REG_INDEX(memory_fill_config[1].value), params.value2); |
| 205 | break; | 205 | break; |
| 206 | } | 206 | } |
| 207 | 207 | ||
| @@ -220,15 +220,15 @@ void ExecuteCommand(const Command& command) { | |||
| 220 | case CommandId::SET_TEXTURE_COPY: | 220 | case CommandId::SET_TEXTURE_COPY: |
| 221 | { | 221 | { |
| 222 | auto& params = command.image_copy; | 222 | auto& params = command.image_copy; |
| 223 | WriteGPURegister(GPU::Regs::DisplayTransfer, params.in_buffer_address >> 3); | 223 | WriteGPURegister(GPU_REG_INDEX(display_transfer_config.input_address), params.in_buffer_address >> 3); |
| 224 | WriteGPURegister(GPU::Regs::DisplayTransfer + 1, params.out_buffer_address >> 3); | 224 | WriteGPURegister(GPU_REG_INDEX(display_transfer_config.output_address), params.out_buffer_address >> 3); |
| 225 | WriteGPURegister(GPU::Regs::DisplayTransfer + 3, params.in_buffer_size); | 225 | WriteGPURegister(GPU_REG_INDEX(display_transfer_config.input_size), params.in_buffer_size); |
| 226 | WriteGPURegister(GPU::Regs::DisplayTransfer + 2, params.out_buffer_size); | 226 | WriteGPURegister(GPU_REG_INDEX(display_transfer_config.output_size), params.out_buffer_size); |
| 227 | WriteGPURegister(GPU::Regs::DisplayTransfer + 4, params.flags); | 227 | WriteGPURegister(GPU_REG_INDEX(display_transfer_config.flags), params.flags); |
| 228 | 228 | ||
| 229 | // TODO: Should this only be ORed with 1 for texture copies? | 229 | // TODO: Should this only be ORed with 1 for texture copies? |
| 230 | // trigger transfer | 230 | // trigger transfer |
| 231 | WriteGPURegister(GPU::Regs::DisplayTransfer + 6, 1); | 231 | WriteGPURegister(GPU_REG_INDEX(display_transfer_config.trigger), 1); |
| 232 | break; | 232 | break; |
| 233 | } | 233 | } |
| 234 | 234 | ||
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index d94c2329b..fd40f8ac0 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp | |||
| @@ -19,7 +19,7 @@ | |||
| 19 | 19 | ||
| 20 | namespace GPU { | 20 | namespace GPU { |
| 21 | 21 | ||
| 22 | RegisterSet<u32, Regs> g_regs; | 22 | Regs g_regs; |
| 23 | 23 | ||
| 24 | u32 g_cur_line = 0; ///< Current vertical screen line | 24 | u32 g_cur_line = 0; ///< Current vertical screen line |
| 25 | u64 g_last_line_ticks = 0; ///< CPU tick count from last vertical screen line | 25 | u64 g_last_line_ticks = 0; ///< CPU tick count from last vertical screen line |
| @@ -32,8 +32,8 @@ void SetFramebufferLocation(const FramebufferLocation mode) { | |||
| 32 | switch (mode) { | 32 | switch (mode) { |
| 33 | case FRAMEBUFFER_LOCATION_FCRAM: | 33 | case FRAMEBUFFER_LOCATION_FCRAM: |
| 34 | { | 34 | { |
| 35 | auto& framebuffer_top = g_regs.Get<Regs::FramebufferTop>(); | 35 | auto& framebuffer_top = g_regs.framebuffer_config[0]; |
| 36 | auto& framebuffer_sub = g_regs.Get<Regs::FramebufferBottom>(); | 36 | auto& framebuffer_sub = g_regs.framebuffer_config[1]; |
| 37 | 37 | ||
| 38 | framebuffer_top.address_left1 = PADDR_TOP_LEFT_FRAME1; | 38 | framebuffer_top.address_left1 = PADDR_TOP_LEFT_FRAME1; |
| 39 | framebuffer_top.address_left2 = PADDR_TOP_LEFT_FRAME2; | 39 | framebuffer_top.address_left2 = PADDR_TOP_LEFT_FRAME2; |
| @@ -48,8 +48,8 @@ void SetFramebufferLocation(const FramebufferLocation mode) { | |||
| 48 | 48 | ||
| 49 | case FRAMEBUFFER_LOCATION_VRAM: | 49 | case FRAMEBUFFER_LOCATION_VRAM: |
| 50 | { | 50 | { |
| 51 | auto& framebuffer_top = g_regs.Get<Regs::FramebufferTop>(); | 51 | auto& framebuffer_top = g_regs.framebuffer_config[0]; |
| 52 | auto& framebuffer_sub = g_regs.Get<Regs::FramebufferBottom>(); | 52 | auto& framebuffer_sub = g_regs.framebuffer_config[1]; |
| 53 | 53 | ||
| 54 | framebuffer_top.address_left1 = PADDR_VRAM_TOP_LEFT_FRAME1; | 54 | framebuffer_top.address_left1 = PADDR_VRAM_TOP_LEFT_FRAME1; |
| 55 | framebuffer_top.address_left2 = PADDR_VRAM_TOP_LEFT_FRAME2; | 55 | framebuffer_top.address_left2 = PADDR_VRAM_TOP_LEFT_FRAME2; |
| @@ -107,13 +107,12 @@ inline void Read(T &var, const u32 raw_addr) { | |||
| 107 | int index = addr / 4; | 107 | int index = addr / 4; |
| 108 | 108 | ||
| 109 | // Reads other than u32 are untested, so I'd rather have them abort than silently fail | 109 | // Reads other than u32 are untested, so I'd rather have them abort than silently fail |
| 110 | if (index >= Regs::NumIds || !std::is_same<T,u32>::value) | 110 | if (index >= Regs::NumIds() || !std::is_same<T,u32>::value) { |
| 111 | { | ||
| 112 | ERROR_LOG(GPU, "unknown Read%d @ 0x%08X", sizeof(var) * 8, addr); | 111 | ERROR_LOG(GPU, "unknown Read%d @ 0x%08X", sizeof(var) * 8, addr); |
| 113 | return; | 112 | return; |
| 114 | } | 113 | } |
| 115 | 114 | ||
| 116 | var = g_regs[static_cast<Regs::Id>(addr / 4)]; | 115 | var = g_regs[addr / 4]; |
| 117 | } | 116 | } |
| 118 | 117 | ||
| 119 | template <typename T> | 118 | template <typename T> |
| @@ -122,22 +121,22 @@ inline void Write(u32 addr, const T data) { | |||
| 122 | int index = addr / 4; | 121 | int index = addr / 4; |
| 123 | 122 | ||
| 124 | // Writes other than u32 are untested, so I'd rather have them abort than silently fail | 123 | // Writes other than u32 are untested, so I'd rather have them abort than silently fail |
| 125 | if (index >= Regs::NumIds || !std::is_same<T,u32>::value) | 124 | if (index >= Regs::NumIds() || !std::is_same<T,u32>::value) { |
| 126 | { | ||
| 127 | ERROR_LOG(GPU, "unknown Write%d 0x%08X @ 0x%08X", sizeof(data) * 8, data, addr); | 125 | ERROR_LOG(GPU, "unknown Write%d 0x%08X @ 0x%08X", sizeof(data) * 8, data, addr); |
| 128 | return; | 126 | return; |
| 129 | } | 127 | } |
| 130 | 128 | ||
| 131 | g_regs[static_cast<Regs::Id>(index)] = data; | 129 | g_regs[index] = data; |
| 132 | 130 | ||
| 133 | switch (static_cast<Regs::Id>(index)) { | 131 | switch (index) { |
| 134 | 132 | ||
| 135 | // Memory fills are triggered once the fill value is written. | 133 | // Memory fills are triggered once the fill value is written. |
| 136 | // NOTE: This is not verified. | 134 | // NOTE: This is not verified. |
| 137 | case Regs::MemoryFill + 3: | 135 | case GPU_REG_INDEX_WORKAROUND(memory_fill_config[0].value, 0x00004 + 0x3): |
| 138 | case Regs::MemoryFill + 7: | 136 | case GPU_REG_INDEX_WORKAROUND(memory_fill_config[1].value, 0x00008 + 0x3): |
| 139 | { | 137 | { |
| 140 | const auto& config = g_regs.Get<Regs::MemoryFill>(static_cast<Regs::Id>(index - 3)); | 138 | const bool is_second_filler = (index != GPU_REG_INDEX(memory_fill_config[0].value)); |
| 139 | const auto& config = g_regs.memory_fill_config[is_second_filler]; | ||
| 141 | 140 | ||
| 142 | // TODO: Not sure if this check should be done at GSP level instead | 141 | // TODO: Not sure if this check should be done at GSP level instead |
| 143 | if (config.address_start) { | 142 | if (config.address_start) { |
| @@ -152,9 +151,9 @@ inline void Write(u32 addr, const T data) { | |||
| 152 | break; | 151 | break; |
| 153 | } | 152 | } |
| 154 | 153 | ||
| 155 | case Regs::DisplayTransfer + 6: | 154 | case GPU_REG_INDEX(display_transfer_config.trigger): |
| 156 | { | 155 | { |
| 157 | const auto& config = g_regs.Get<Regs::DisplayTransfer>(); | 156 | const auto& config = g_regs.display_transfer_config; |
| 158 | if (config.trigger & 1) { | 157 | if (config.trigger & 1) { |
| 159 | u8* source_pointer = Memory::GetPointer(config.GetPhysicalInputAddress()); | 158 | u8* source_pointer = Memory::GetPointer(config.GetPhysicalInputAddress()); |
| 160 | u8* dest_pointer = Memory::GetPointer(config.GetPhysicalOutputAddress()); | 159 | u8* dest_pointer = Memory::GetPointer(config.GetPhysicalOutputAddress()); |
| @@ -221,13 +220,13 @@ inline void Write(u32 addr, const T data) { | |||
| 221 | break; | 220 | break; |
| 222 | } | 221 | } |
| 223 | 222 | ||
| 224 | case Regs::CommandProcessor + 4: | 223 | case GPU_REG_INDEX(command_processor_config.trigger): |
| 225 | { | 224 | { |
| 226 | const auto& config = g_regs.Get<Regs::CommandProcessor>(); | 225 | const auto& config = g_regs.command_processor_config; |
| 227 | if (config.trigger & 1) | 226 | if (config.trigger & 1) |
| 228 | { | 227 | { |
| 229 | // u32* buffer = (u32*)Memory::GetPointer(config.address << 3); | 228 | // u32* buffer = (u32*)Memory::GetPointer(config.GetPhysicalAddress()); |
| 230 | ERROR_LOG(GPU, "Beginning 0x%08x bytes of commands from address 0x%08x", config.size, config.address << 3); | 229 | ERROR_LOG(GPU, "Beginning 0x%08x bytes of commands from address 0x%08x", config.size, config.GetPhysicalAddress()); |
| 231 | // TODO: Process command list! | 230 | // TODO: Process command list! |
| 232 | } | 231 | } |
| 233 | break; | 232 | break; |
| @@ -252,7 +251,7 @@ template void Write<u8>(u32 addr, const u8 data); | |||
| 252 | 251 | ||
| 253 | /// Update hardware | 252 | /// Update hardware |
| 254 | void Update() { | 253 | void Update() { |
| 255 | auto& framebuffer_top = g_regs.Get<Regs::FramebufferTop>(); | 254 | auto& framebuffer_top = g_regs.framebuffer_config[0]; |
| 256 | u64 current_ticks = Core::g_app_core->GetTicks(); | 255 | u64 current_ticks = Core::g_app_core->GetTicks(); |
| 257 | 256 | ||
| 258 | // Synchronize line... | 257 | // Synchronize line... |
| @@ -280,8 +279,8 @@ void Init() { | |||
| 280 | // SetFramebufferLocation(FRAMEBUFFER_LOCATION_FCRAM); | 279 | // SetFramebufferLocation(FRAMEBUFFER_LOCATION_FCRAM); |
| 281 | SetFramebufferLocation(FRAMEBUFFER_LOCATION_VRAM); | 280 | SetFramebufferLocation(FRAMEBUFFER_LOCATION_VRAM); |
| 282 | 281 | ||
| 283 | auto& framebuffer_top = g_regs.Get<Regs::FramebufferTop>(); | 282 | auto& framebuffer_top = g_regs.framebuffer_config[0]; |
| 284 | auto& framebuffer_sub = g_regs.Get<Regs::FramebufferBottom>(); | 283 | auto& framebuffer_sub = g_regs.framebuffer_config[1]; |
| 285 | // TODO: Width should be 240 instead? | 284 | // TODO: Width should be 240 instead? |
| 286 | framebuffer_top.width = 480; | 285 | framebuffer_top.width = 480; |
| 287 | framebuffer_top.height = 400; | 286 | framebuffer_top.height = 400; |
diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h index 42f18a0e7..3065da891 100644 --- a/src/core/hw/gpu.h +++ b/src/core/hw/gpu.h | |||
| @@ -4,32 +4,57 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <cstddef> | ||
| 8 | |||
| 7 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 8 | #include "common/bit_field.h" | 10 | #include "common/bit_field.h" |
| 9 | #include "common/register_set.h" | ||
| 10 | 11 | ||
| 11 | namespace GPU { | 12 | namespace GPU { |
| 12 | 13 | ||
| 13 | static const u32 kFrameCycles = 268123480 / 60; ///< 268MHz / 60 frames per second | 14 | static const u32 kFrameCycles = 268123480 / 60; ///< 268MHz / 60 frames per second |
| 14 | static const u32 kFrameTicks = kFrameCycles / 3; ///< Approximate number of instructions/frame | 15 | static const u32 kFrameTicks = kFrameCycles / 3; ///< Approximate number of instructions/frame |
| 15 | 16 | ||
| 17 | // Returns index corresponding to the Regs member labeled by field_name | ||
| 18 | // TODO: Due to Visual studio bug 209229, offsetof does not return constant expressions | ||
| 19 | // when used with array elements (e.g. GPU_REG_INDEX(memory_fill_config[0])). | ||
| 20 | // For details cf. https://connect.microsoft.com/VisualStudio/feedback/details/209229/offsetof-does-not-produce-a-constant-expression-for-array-members | ||
| 21 | // Hopefully, this will be fixed sometime in the future. | ||
| 22 | // For lack of better alternatives, we currently hardcode the offsets when constant | ||
| 23 | // expressions are needed via GPU_REG_INDEX_WORKAROUND (on sane compilers, static_asserts | ||
| 24 | // will then make sure the offsets indeed match the automatically calculated ones). | ||
| 25 | #define GPU_REG_INDEX(field_name) (offsetof(GPU::Regs, field_name) / sizeof(u32)) | ||
| 26 | #if defined(_MSC_VER) | ||
| 27 | #define GPU_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) (backup_workaround_index) | ||
| 28 | #else | ||
| 29 | // NOTE: Yeah, hacking in a static_assert here just to workaround the lacking MSVC compiler | ||
| 30 | // really is this annoying. This macro just forwards its first argument to GPU_REG_INDEX | ||
| 31 | // and then performs a (no-op) cast to size_t iff the second argument matches the expected | ||
| 32 | // field offset. Otherwise, the compiler will fail to compile this code. | ||
| 33 | #define GPU_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) \ | ||
| 34 | ((typename std::enable_if<backup_workaround_index == GPU_REG_INDEX(field_name), size_t>::type)GPU_REG_INDEX(field_name)) | ||
| 35 | #endif | ||
| 36 | |||
| 16 | // MMIO region 0x1EFxxxxx | 37 | // MMIO region 0x1EFxxxxx |
| 17 | struct Regs { | 38 | struct Regs { |
| 18 | enum Id : u32 { | ||
| 19 | MemoryFill = 0x00004, // + 5,6,7; second block at 8-11 | ||
| 20 | |||
| 21 | FramebufferTop = 0x00117, // + 11a,11b,11c,11d(?),11e...126 | ||
| 22 | FramebufferBottom = 0x00157, // + 15a,15b,15c,15d(?),15e...166 | ||
| 23 | |||
| 24 | DisplayTransfer = 0x00300, // + 301,302,303,304,305,306 | ||
| 25 | |||
| 26 | CommandProcessor = 0x00638, // + 63a,63c | ||
| 27 | 39 | ||
| 28 | NumIds = 0x01000 | 40 | // helper macro to properly align structure members. |
| 29 | }; | 41 | // Calling INSERT_PADDING_WORDS will add a new member variable with a name like "pad121", |
| 30 | 42 | // depending on the current source line to make sure variable names are unique. | |
| 31 | template<Id id> | 43 | #define INSERT_PADDING_WORDS_HELPER1(x, y) x ## y |
| 32 | struct Struct; | 44 | #define INSERT_PADDING_WORDS_HELPER2(x, y) INSERT_PADDING_WORDS_HELPER1(x, y) |
| 45 | #define INSERT_PADDING_WORDS(num_words) u32 INSERT_PADDING_WORDS_HELPER2(pad, __LINE__)[(num_words)]; | ||
| 46 | |||
| 47 | // helper macro to make sure the defined structures are of the expected size. | ||
| 48 | #if defined(_MSC_VER) | ||
| 49 | // TODO: MSVC does not support using sizeof() on non-static data members even though this | ||
| 50 | // is technically allowed since C++11. This macro should be enabled once MSVC adds | ||
| 51 | // support for that. | ||
| 52 | #define ASSERT_MEMBER_SIZE(name, size_in_bytes) | ||
| 53 | #else | ||
| 54 | #define ASSERT_MEMBER_SIZE(name, size_in_bytes) \ | ||
| 55 | static_assert(sizeof(name) == size_in_bytes, \ | ||
| 56 | "Structure size and register block length don't match"); | ||
| 57 | #endif | ||
| 33 | 58 | ||
| 34 | enum class FramebufferFormat : u32 { | 59 | enum class FramebufferFormat : u32 { |
| 35 | RGBA8 = 0, | 60 | RGBA8 = 0, |
| @@ -38,135 +63,191 @@ struct Regs { | |||
| 38 | RGB5A1 = 3, | 63 | RGB5A1 = 3, |
| 39 | RGBA4 = 4, | 64 | RGBA4 = 4, |
| 40 | }; | 65 | }; |
| 41 | }; | ||
| 42 | 66 | ||
| 43 | template<> | 67 | INSERT_PADDING_WORDS(0x4); |
| 44 | struct Regs::Struct<Regs::MemoryFill> { | ||
| 45 | u32 address_start; | ||
| 46 | u32 address_end; // ? | ||
| 47 | u32 size; | ||
| 48 | u32 value; // ? | ||
| 49 | 68 | ||
| 50 | inline u32 GetStartAddress() const { | 69 | struct { |
| 51 | return address_start * 8; | 70 | u32 address_start; |
| 52 | } | 71 | u32 address_end; // ? |
| 72 | u32 size; | ||
| 73 | u32 value; // ? | ||
| 53 | 74 | ||
| 54 | inline u32 GetEndAddress() const { | 75 | inline u32 GetStartAddress() const { |
| 55 | return address_end * 8; | 76 | return DecodeAddressRegister(address_start); |
| 56 | } | 77 | } |
| 57 | }; | ||
| 58 | static_assert(sizeof(Regs::Struct<Regs::MemoryFill>) == 0x10, "Structure size and register block length don't match"); | ||
| 59 | 78 | ||
| 60 | template<> | 79 | inline u32 GetEndAddress() const { |
| 61 | struct Regs::Struct<Regs::FramebufferTop> { | 80 | return DecodeAddressRegister(address_end); |
| 62 | using Format = Regs::FramebufferFormat; | 81 | } |
| 82 | } memory_fill_config[2]; | ||
| 83 | ASSERT_MEMBER_SIZE(memory_fill_config[0], 0x10); | ||
| 63 | 84 | ||
| 64 | union { | 85 | INSERT_PADDING_WORDS(0x10b); |
| 65 | u32 size; | ||
| 66 | 86 | ||
| 67 | BitField< 0, 16, u32> width; | 87 | struct { |
| 68 | BitField<16, 16, u32> height; | 88 | using Format = Regs::FramebufferFormat; |
| 69 | }; | ||
| 70 | 89 | ||
| 71 | u32 pad0[2]; | 90 | union { |
| 91 | u32 size; | ||
| 72 | 92 | ||
| 73 | u32 address_left1; | 93 | BitField< 0, 16, u32> width; |
| 74 | u32 address_left2; | 94 | BitField<16, 16, u32> height; |
| 95 | }; | ||
| 75 | 96 | ||
| 76 | union { | 97 | INSERT_PADDING_WORDS(0x2); |
| 77 | u32 format; | ||
| 78 | 98 | ||
| 79 | BitField< 0, 3, Format> color_format; | 99 | u32 address_left1; |
| 80 | }; | 100 | u32 address_left2; |
| 81 | 101 | ||
| 82 | u32 pad1; | 102 | union { |
| 103 | u32 format; | ||
| 83 | 104 | ||
| 84 | union { | 105 | BitField< 0, 3, Format> color_format; |
| 85 | u32 active_fb; | 106 | }; |
| 86 | 107 | ||
| 87 | // 0: Use parameters ending with "1" | 108 | INSERT_PADDING_WORDS(0x1); |
| 88 | // 1: Use parameters ending with "2" | ||
| 89 | BitField<0, 1, u32> second_fb_active; | ||
| 90 | }; | ||
| 91 | 109 | ||
| 92 | u32 pad2[5]; | 110 | union { |
| 111 | u32 active_fb; | ||
| 93 | 112 | ||
| 94 | // Distance between two pixel rows, in bytes | 113 | // 0: Use parameters ending with "1" |
| 95 | u32 stride; | 114 | // 1: Use parameters ending with "2" |
| 115 | BitField<0, 1, u32> second_fb_active; | ||
| 116 | }; | ||
| 96 | 117 | ||
| 97 | u32 address_right1; | 118 | INSERT_PADDING_WORDS(0x5); |
| 98 | u32 address_right2; | ||
| 99 | }; | ||
| 100 | 119 | ||
| 101 | template<> | 120 | // Distance between two pixel rows, in bytes |
| 102 | struct Regs::Struct<Regs::FramebufferBottom> : public Regs::Struct<Regs::FramebufferTop> { | 121 | u32 stride; |
| 103 | }; | ||
| 104 | static_assert(sizeof(Regs::Struct<Regs::FramebufferTop>) == 0x40, "Structure size and register block length don't match"); | ||
| 105 | 122 | ||
| 106 | template<> | 123 | u32 address_right1; |
| 107 | struct Regs::Struct<Regs::DisplayTransfer> { | 124 | u32 address_right2; |
| 108 | using Format = Regs::FramebufferFormat; | ||
| 109 | 125 | ||
| 110 | u32 input_address; | 126 | INSERT_PADDING_WORDS(0x30); |
| 111 | u32 output_address; | 127 | } framebuffer_config[2]; |
| 128 | ASSERT_MEMBER_SIZE(framebuffer_config[0], 0x100); | ||
| 112 | 129 | ||
| 113 | inline u32 GetPhysicalInputAddress() const { | 130 | INSERT_PADDING_WORDS(0x169); |
| 114 | return input_address * 8; | ||
| 115 | } | ||
| 116 | 131 | ||
| 117 | inline u32 GetPhysicalOutputAddress() const { | 132 | struct { |
| 118 | return output_address * 8; | 133 | using Format = Regs::FramebufferFormat; |
| 119 | } | ||
| 120 | 134 | ||
| 121 | union { | 135 | u32 input_address; |
| 122 | u32 output_size; | 136 | u32 output_address; |
| 123 | 137 | ||
| 124 | BitField< 0, 16, u32> output_width; | 138 | inline u32 GetPhysicalInputAddress() const { |
| 125 | BitField<16, 16, u32> output_height; | 139 | return DecodeAddressRegister(input_address); |
| 126 | }; | 140 | } |
| 127 | 141 | ||
| 128 | union { | 142 | inline u32 GetPhysicalOutputAddress() const { |
| 129 | u32 input_size; | 143 | return DecodeAddressRegister(output_address); |
| 144 | } | ||
| 130 | 145 | ||
| 131 | BitField< 0, 16, u32> input_width; | 146 | union { |
| 132 | BitField<16, 16, u32> input_height; | 147 | u32 output_size; |
| 133 | }; | ||
| 134 | 148 | ||
| 135 | union { | 149 | BitField< 0, 16, u32> output_width; |
| 136 | u32 flags; | 150 | BitField<16, 16, u32> output_height; |
| 151 | }; | ||
| 137 | 152 | ||
| 138 | BitField< 0, 1, u32> flip_data; // flips input data horizontally (TODO) if true | 153 | union { |
| 139 | BitField< 8, 3, Format> input_format; | 154 | u32 input_size; |
| 140 | BitField<12, 3, Format> output_format; | ||
| 141 | BitField<16, 1, u32> output_tiled; // stores output in a tiled format | ||
| 142 | }; | ||
| 143 | 155 | ||
| 144 | u32 unknown; | 156 | BitField< 0, 16, u32> input_width; |
| 157 | BitField<16, 16, u32> input_height; | ||
| 158 | }; | ||
| 145 | 159 | ||
| 146 | // it seems that writing to this field triggers the display transfer | 160 | union { |
| 147 | u32 trigger; | 161 | u32 flags; |
| 148 | }; | ||
| 149 | static_assert(sizeof(Regs::Struct<Regs::DisplayTransfer>) == 0x1C, "Structure size and register block length don't match"); | ||
| 150 | 162 | ||
| 151 | template<> | 163 | BitField< 0, 1, u32> flip_data; // flips input data horizontally (TODO) if true |
| 152 | struct Regs::Struct<Regs::CommandProcessor> { | 164 | BitField< 8, 3, Format> input_format; |
| 153 | // command list size | 165 | BitField<12, 3, Format> output_format; |
| 154 | u32 size; | 166 | BitField<16, 1, u32> output_tiled; // stores output in a tiled format |
| 167 | }; | ||
| 155 | 168 | ||
| 156 | u32 pad0; | 169 | INSERT_PADDING_WORDS(0x1); |
| 157 | 170 | ||
| 158 | // command list address | 171 | // it seems that writing to this field triggers the display transfer |
| 159 | u32 address; | 172 | u32 trigger; |
| 173 | } display_transfer_config; | ||
| 174 | ASSERT_MEMBER_SIZE(display_transfer_config, 0x1c); | ||
| 160 | 175 | ||
| 161 | u32 pad1; | 176 | INSERT_PADDING_WORDS(0x331); |
| 162 | 177 | ||
| 163 | // it seems that writing to this field triggers command list processing | 178 | struct { |
| 164 | u32 trigger; | 179 | // command list size |
| 165 | }; | 180 | u32 size; |
| 166 | static_assert(sizeof(Regs::Struct<Regs::CommandProcessor>) == 0x14, "Structure size and register block length don't match"); | 181 | |
| 182 | INSERT_PADDING_WORDS(0x1); | ||
| 183 | |||
| 184 | // command list address | ||
| 185 | u32 address; | ||
| 186 | |||
| 187 | INSERT_PADDING_WORDS(0x1); | ||
| 188 | |||
| 189 | // it seems that writing to this field triggers command list processing | ||
| 190 | u32 trigger; | ||
| 167 | 191 | ||
| 192 | inline u32 GetPhysicalAddress() const { | ||
| 193 | return DecodeAddressRegister(address); | ||
| 194 | } | ||
| 195 | } command_processor_config; | ||
| 196 | ASSERT_MEMBER_SIZE(command_processor_config, 0x14); | ||
| 168 | 197 | ||
| 169 | extern RegisterSet<u32, Regs> g_regs; | 198 | INSERT_PADDING_WORDS(0x9c3); |
| 199 | |||
| 200 | #undef INSERT_PADDING_WORDS_HELPER1 | ||
| 201 | #undef INSERT_PADDING_WORDS_HELPER2 | ||
| 202 | #undef INSERT_PADDING_WORDS | ||
| 203 | |||
| 204 | static inline int NumIds() { | ||
| 205 | return sizeof(Regs) / sizeof(u32); | ||
| 206 | } | ||
| 207 | |||
| 208 | u32& operator [] (int index) const { | ||
| 209 | u32* content = (u32*)this; | ||
| 210 | return content[index]; | ||
| 211 | } | ||
| 212 | |||
| 213 | u32& operator [] (int index) { | ||
| 214 | u32* content = (u32*)this; | ||
| 215 | return content[index]; | ||
| 216 | } | ||
| 217 | |||
| 218 | private: | ||
| 219 | /* | ||
| 220 | * Most physical addresses which GPU registers refer to are 8-byte aligned. | ||
| 221 | * This function should be used to get the address from a raw register value. | ||
| 222 | */ | ||
| 223 | static inline u32 DecodeAddressRegister(u32 register_value) { | ||
| 224 | return register_value * 8; | ||
| 225 | } | ||
| 226 | }; | ||
| 227 | static_assert(std::is_standard_layout<Regs>::value, "Structure does not use standard layout"); | ||
| 228 | |||
| 229 | // TODO: MSVC does not support using offsetof() on non-static data members even though this | ||
| 230 | // is technically allowed since C++11. This macro should be enabled once MSVC adds | ||
| 231 | // support for that. | ||
| 232 | #ifndef _MSC_VER | ||
| 233 | #define ASSERT_REG_POSITION(field_name, position) \ | ||
| 234 | static_assert(offsetof(Regs, field_name) == position * 4, \ | ||
| 235 | "Field "#field_name" has invalid position") | ||
| 236 | |||
| 237 | ASSERT_REG_POSITION(memory_fill_config[0], 0x00004); | ||
| 238 | ASSERT_REG_POSITION(memory_fill_config[1], 0x00008); | ||
| 239 | ASSERT_REG_POSITION(framebuffer_config[0], 0x00117); | ||
| 240 | ASSERT_REG_POSITION(framebuffer_config[1], 0x00157); | ||
| 241 | ASSERT_REG_POSITION(display_transfer_config, 0x00300); | ||
| 242 | ASSERT_REG_POSITION(command_processor_config, 0x00638); | ||
| 243 | |||
| 244 | #undef ASSERT_REG_POSITION | ||
| 245 | #endif // !defined(_MSC_VER) | ||
| 246 | |||
| 247 | // The total number of registers is chosen arbitrarily, but let's make sure it's not some odd value anyway. | ||
| 248 | static_assert(sizeof(Regs) == 0x1000 * sizeof(u32), "Invalid total size of register set"); | ||
| 249 | |||
| 250 | extern Regs g_regs; | ||
| 170 | 251 | ||
| 171 | enum { | 252 | enum { |
| 172 | TOP_ASPECT_X = 0x5, | 253 | TOP_ASPECT_X = 0x5, |