summaryrefslogtreecommitdiff
path: root/src/core/hw/gpu.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/hw/gpu.cpp')
-rw-r--r--src/core/hw/gpu.cpp129
1 files changed, 66 insertions, 63 deletions
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp
index 3b730a0de..58eec3005 100644
--- a/src/core/hw/gpu.cpp
+++ b/src/core/hw/gpu.cpp
@@ -9,6 +9,7 @@
9#include "core/settings.h" 9#include "core/settings.h"
10#include "core/core.h" 10#include "core/core.h"
11#include "core/mem_map.h" 11#include "core/mem_map.h"
12#include "core/core_timing.h"
12 13
13#include "core/hle/hle.h" 14#include "core/hle/hle.h"
14#include "core/hle/service/gsp_gpu.h" 15#include "core/hle/service/gsp_gpu.h"
@@ -24,14 +25,17 @@ namespace GPU {
24 25
25Regs g_regs; 26Regs g_regs;
26 27
27bool g_skip_frame = false; ///< True if the current frame was skipped 28/// True if the current frame was skipped
29bool g_skip_frame = false;
28 30
29static u64 frame_ticks = 0; ///< 268MHz / gpu_refresh_rate frames per second 31/// 268MHz / gpu_refresh_rate frames per second
30static u64 line_ticks = 0; ///< Number of ticks for a screen line 32static u64 frame_ticks;
31static u32 cur_line = 0; ///< Current screen line 33/// Event id for CoreTiming
32static u64 last_update_tick = 0; ///< CPU ticl count from last GPU update 34static int vblank_event;
33static u64 frame_count = 0; ///< Number of frames drawn 35/// Total number of frames drawn
34static bool last_skip_frame = false; ///< True if the last frame was skipped 36static u64 frame_count;
37/// True if the last frame was skipped
38static bool last_skip_frame = false;
35 39
36template <typename T> 40template <typename T>
37inline void Read(T &var, const u32 raw_addr) { 41inline void Read(T &var, const u32 raw_addr) {
@@ -79,6 +83,12 @@ inline void Write(u32 addr, const T data) {
79 *ptr = bswap32(config.value); // TODO: This is just a workaround to missing framebuffer format emulation 83 *ptr = bswap32(config.value); // TODO: This is just a workaround to missing framebuffer format emulation
80 84
81 LOG_TRACE(HW_GPU, "MemoryFill from 0x%08x to 0x%08x", config.GetStartAddress(), config.GetEndAddress()); 85 LOG_TRACE(HW_GPU, "MemoryFill from 0x%08x to 0x%08x", config.GetStartAddress(), config.GetEndAddress());
86
87 if (!is_second_filler) {
88 GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PSC0);
89 } else {
90 GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PSC1);
91 }
82 } 92 }
83 break; 93 break;
84 } 94 }
@@ -90,22 +100,25 @@ inline void Write(u32 addr, const T data) {
90 u8* source_pointer = Memory::GetPointer(Memory::PhysicalToVirtualAddress(config.GetPhysicalInputAddress())); 100 u8* source_pointer = Memory::GetPointer(Memory::PhysicalToVirtualAddress(config.GetPhysicalInputAddress()));
91 u8* dest_pointer = Memory::GetPointer(Memory::PhysicalToVirtualAddress(config.GetPhysicalOutputAddress())); 101 u8* dest_pointer = Memory::GetPointer(Memory::PhysicalToVirtualAddress(config.GetPhysicalOutputAddress()));
92 102
103 // Cheap emulation of horizontal scaling: Just skip each second pixel of the
104 // input framebuffer. We keep track of this in the pixel_skip variable.
105 unsigned pixel_skip = (config.scale_horizontally != 0) ? 2 : 1;
106
107 u32 output_width = config.output_width / pixel_skip;
108
93 for (u32 y = 0; y < config.output_height; ++y) { 109 for (u32 y = 0; y < config.output_height; ++y) {
94 // TODO: Why does the register seem to hold twice the framebuffer width? 110 // TODO: Why does the register seem to hold twice the framebuffer width?
95 for (u32 x = 0; x < config.output_width; ++x) { 111
112 for (u32 x = 0; x < output_width; ++x) {
96 struct { 113 struct {
97 int r, g, b, a; 114 int r, g, b, a;
98 } source_color = { 0, 0, 0, 0 }; 115 } source_color = { 0, 0, 0, 0 };
99 116
100 // Cheap emulation of horizontal scaling: Just skip each second pixel of the
101 // input framebuffer. We keep track of this in the pixel_skip variable.
102 unsigned pixel_skip = (config.scale_horizontally != 0) ? 2 : 1;
103
104 switch (config.input_format) { 117 switch (config.input_format) {
105 case Regs::PixelFormat::RGBA8: 118 case Regs::PixelFormat::RGBA8:
106 { 119 {
107 // TODO: Most likely got the component order messed up. 120 // TODO: Most likely got the component order messed up.
108 u8* srcptr = source_pointer + x * 4 * pixel_skip + y * config.input_width * 4 * pixel_skip; 121 u8* srcptr = source_pointer + (x * pixel_skip + y * config.input_width) * 4;
109 source_color.r = srcptr[0]; // blue 122 source_color.r = srcptr[0]; // blue
110 source_color.g = srcptr[1]; // green 123 source_color.g = srcptr[1]; // green
111 source_color.b = srcptr[2]; // red 124 source_color.b = srcptr[2]; // red
@@ -133,7 +146,7 @@ inline void Write(u32 addr, const T data) {
133 case Regs::PixelFormat::RGB8: 146 case Regs::PixelFormat::RGB8:
134 { 147 {
135 // TODO: Most likely got the component order messed up. 148 // TODO: Most likely got the component order messed up.
136 u8* dstptr = dest_pointer + x * 3 + y * config.output_width * 3; 149 u8* dstptr = dest_pointer + (x + y * output_width) * 3;
137 dstptr[0] = source_color.r; // blue 150 dstptr[0] = source_color.r; // blue
138 dstptr[1] = source_color.g; // green 151 dstptr[1] = source_color.g; // green
139 dstptr[2] = source_color.b; // red 152 dstptr[2] = source_color.b; // red
@@ -148,10 +161,12 @@ inline void Write(u32 addr, const T data) {
148 } 161 }
149 162
150 LOG_TRACE(HW_GPU, "DisplayTriggerTransfer: 0x%08x bytes from 0x%08x(%ux%u)-> 0x%08x(%ux%u), dst format %x", 163 LOG_TRACE(HW_GPU, "DisplayTriggerTransfer: 0x%08x bytes from 0x%08x(%ux%u)-> 0x%08x(%ux%u), dst format %x",
151 config.output_height * config.output_width * 4, 164 config.output_height * output_width * 4,
152 config.GetPhysicalInputAddress(), (u32)config.input_width, (u32)config.input_height, 165 config.GetPhysicalInputAddress(), (u32)config.input_width, (u32)config.input_height,
153 config.GetPhysicalOutputAddress(), (u32)config.output_width, (u32)config.output_height, 166 config.GetPhysicalOutputAddress(), (u32)output_width, (u32)config.output_height,
154 config.output_format.Value()); 167 config.output_format.Value());
168
169 GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF);
155 } 170 }
156 break; 171 break;
157 } 172 }
@@ -186,51 +201,39 @@ template void Write<u16>(u32 addr, const u16 data);
186template void Write<u8>(u32 addr, const u8 data); 201template void Write<u8>(u32 addr, const u8 data);
187 202
188/// Update hardware 203/// Update hardware
189void Update() { 204static void VBlankCallback(u64 userdata, int cycles_late) {
190 auto& framebuffer_top = g_regs.framebuffer_config[0]; 205 auto& framebuffer_top = g_regs.framebuffer_config[0];
191 206
192 // Synchronize GPU on a thread reschedule: Because we cannot accurately predict a vertical 207 frame_count++;
193 // blank, we need to simulate it. Based on testing, it seems that retail applications work more 208 last_skip_frame = g_skip_frame;
194 // accurately when this is signalled between thread switches. 209 g_skip_frame = (frame_count & Settings::values.frame_skip) != 0;
195 210
196 if (HLE::g_reschedule) { 211 // Swap buffers based on the frameskip mode, which is a little bit tricky. When
197 u64 current_ticks = Core::g_app_core->GetTicks(); 212 // a frame is being skipped, nothing is being rendered to the internal framebuffer(s).
198 u32 num_lines = static_cast<u32>((current_ticks - last_update_tick) / line_ticks); 213 // So, we should only swap frames if the last frame was rendered. The rules are:
199 214 // - If frameskip == 0 (disabled), always swap buffers
200 // Synchronize line... 215 // - If frameskip == 1, swap buffers every other frame (starting from the first frame)
201 if (num_lines > 0) { 216 // - If frameskip > 1, swap buffers every frameskip^n frames (starting from the second frame)
202 GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC0); 217 if ((((Settings::values.frame_skip != 1) ^ last_skip_frame) && last_skip_frame != g_skip_frame) ||
203 cur_line += num_lines; 218 Settings::values.frame_skip == 0) {
204 last_update_tick += (num_lines * line_ticks); 219 VideoCore::g_renderer->SwapBuffers();
205 }
206
207 // Synchronize frame...
208 if (cur_line >= framebuffer_top.height) {
209 cur_line = 0;
210 frame_count++;
211 last_skip_frame = g_skip_frame;
212 g_skip_frame = (frame_count & Settings::values.frame_skip) != 0;
213
214 // Swap buffers based on the frameskip mode, which is a little bit tricky. When
215 // a frame is being skipped, nothing is being rendered to the internal framebuffer(s).
216 // So, we should only swap frames if the last frame was rendered. The rules are:
217 // - If frameskip == 0 (disabled), always swap buffers
218 // - If frameskip == 1, swap buffers every other frame (starting from the first frame)
219 // - If frameskip > 1, swap buffers every frameskip^n frames (starting from the second frame)
220 if ((((Settings::values.frame_skip != 1) ^ last_skip_frame) && last_skip_frame != g_skip_frame) ||
221 Settings::values.frame_skip == 0) {
222 VideoCore::g_renderer->SwapBuffers();
223 }
224
225 // Signal to GSP that GPU interrupt has occurred
226 GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC1);
227
228 // TODO(bunnei): Fake a DSP interrupt on each frame. This does not belong here, but
229 // until we can emulate DSP interrupts, this is probably the only reasonable place to do
230 // this. Certain games expect this to be periodically signaled.
231 DSP_DSP::SignalInterrupt();
232 }
233 } 220 }
221
222 // Signal to GSP that GPU interrupt has occurred
223 // TODO(yuriks): hwtest to determine if PDC0 is for the Top screen and PDC1 for the Sub
224 // screen, or if both use the same interrupts and these two instead determine the
225 // beginning and end of the VBlank period. If needed, split the interrupt firing into
226 // two different intervals.
227 GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC0);
228 GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC1);
229
230 // TODO(bunnei): Fake a DSP interrupt on each frame. This does not belong here, but
231 // until we can emulate DSP interrupts, this is probably the only reasonable place to do
232 // this. Certain games expect this to be periodically signaled.
233 DSP_DSP::SignalInterrupt();
234
235 // Reschedule recurrent event
236 CoreTiming::ScheduleEvent(frame_ticks - cycles_late, vblank_event);
234} 237}
235 238
236/// Initialize hardware 239/// Initialize hardware
@@ -247,8 +250,8 @@ void Init() {
247 framebuffer_top.address_right1 = 0x18273000; 250 framebuffer_top.address_right1 = 0x18273000;
248 framebuffer_top.address_right2 = 0x182B9800; 251 framebuffer_top.address_right2 = 0x182B9800;
249 framebuffer_sub.address_left1 = 0x1848F000; 252 framebuffer_sub.address_left1 = 0x1848F000;
250 //framebuffer_sub.address_left2 = unknown; 253 framebuffer_sub.address_left2 = 0x184C7800;
251 framebuffer_sub.address_right1 = 0x184C7800; 254 //framebuffer_sub.address_right1 = unknown;
252 //framebuffer_sub.address_right2 = unknown; 255 //framebuffer_sub.address_right2 = unknown;
253 256
254 framebuffer_top.width = 240; 257 framebuffer_top.width = 240;
@@ -264,12 +267,12 @@ void Init() {
264 framebuffer_sub.active_fb = 0; 267 framebuffer_sub.active_fb = 0;
265 268
266 frame_ticks = 268123480 / Settings::values.gpu_refresh_rate; 269 frame_ticks = 268123480 / Settings::values.gpu_refresh_rate;
267 line_ticks = (GPU::frame_ticks / framebuffer_top.height);
268 cur_line = 0;
269 last_update_tick = Core::g_app_core->GetTicks();
270 last_skip_frame = false; 270 last_skip_frame = false;
271 g_skip_frame = false; 271 g_skip_frame = false;
272 272
273 vblank_event = CoreTiming::RegisterEvent("GPU::VBlankCallback", VBlankCallback);
274 CoreTiming::ScheduleEvent(frame_ticks, vblank_event);
275
273 LOG_DEBUG(HW_GPU, "initialized OK"); 276 LOG_DEBUG(HW_GPU, "initialized OK");
274} 277}
275 278