diff --git a/src/citra/config.cpp b/src/citra/config.cpp index b9d6441bec..2bf0dff35e 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -58,7 +58,8 @@ void Config::ReadValues() { // Core Settings::values.cpu_core = glfw_config->GetInteger("Core", "cpu_core", Core::CPU_Interpreter); - Settings::values.gpu_refresh_rate = glfw_config->GetInteger("Core", "gpu_refresh_rate", 60); + Settings::values.gpu_refresh_rate = glfw_config->GetInteger("Core", "gpu_refresh_rate", 30); + Settings::values.frame_skip = glfw_config->GetInteger("Core", "frame_skip", 0); // Data Storage Settings::values.use_virtual_sd = glfw_config->GetBoolean("Data Storage", "use_virtual_sd", true); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index a281c536f2..f41020f7b4 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -28,7 +28,8 @@ pad_sright = [Core] cpu_core = ## 0: Interpreter (default), 1: FastInterpreter (experimental) -gpu_refresh_rate = ## 60 (default) +gpu_refresh_rate = ## 30 (default) +frame_skip = ## 0: No frameskip (default), 1 : 2x frameskip, 2 : 4x frameskip, etc. [Data Storage] use_virtual_sd = diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index 0fea8e4f9c..1596c08d72 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -44,7 +44,8 @@ void Config::ReadValues() { qt_config->beginGroup("Core"); Settings::values.cpu_core = qt_config->value("cpu_core", Core::CPU_Interpreter).toInt(); - Settings::values.gpu_refresh_rate = qt_config->value("gpu_refresh_rate", 60).toInt(); + Settings::values.gpu_refresh_rate = qt_config->value("gpu_refresh_rate", 30).toInt(); + Settings::values.frame_skip = qt_config->value("frame_skip", 0).toInt(); qt_config->endGroup(); qt_config->beginGroup("Data Storage"); @@ -80,6 +81,7 @@ void Config::SaveValues() { qt_config->beginGroup("Core"); qt_config->setValue("cpu_core", Settings::values.cpu_core); qt_config->setValue("gpu_refresh_rate", Settings::values.gpu_refresh_rate); + qt_config->setValue("frame_skip", Settings::values.frame_skip); qt_config->endGroup(); qt_config->beginGroup("Data Storage"); diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index 7e70b34c18..dd619cb16c 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -21,10 +21,14 @@ namespace GPU { Regs g_regs; -static u64 frame_ticks = 0; ///< 268MHz / 60 frames per second -static u32 cur_line = 0; ///< Current vertical screen line -static u64 last_frame_ticks = 0; ///< CPU tick count from last frame -static u64 last_update_tick = 0; ///< CPU ticl count from last GPU update +bool g_skip_frame = false; ///< True if the current frame was skipped + +static u64 frame_ticks = 0; ///< 268MHz / gpu_refresh_rate frames per second +static u64 line_ticks = 0; ///< Number of ticks for a screen line +static u32 cur_line = 0; ///< Current screen line +static u64 last_update_tick = 0; ///< CPU ticl count from last GPU update +static u64 frame_count = 0; ///< Number of frames drawn +static bool last_skip_frame = false; ///< True if the last frame was skipped template inline void Read(T &var, const u32 raw_addr) { @@ -178,38 +182,40 @@ template void Write(u32 addr, const u8 data); void Update() { auto& framebuffer_top = g_regs.framebuffer_config[0]; - // Update the frame after a certain number of CPU ticks have elapsed. This assumes that the - // active frame in memory is always complete to render. There also may be issues with this - // becoming out-of-synch with GSP synchrinization code (as follows). At this time, this seems to - // be the most effective solution for both homebrew and retail applications. With retail, this - // could be moved below (and probably would guarantee more accurate synchronization). However, - // primitive homebrew relies on a vertical blank interrupt to happen inevitably (regardless of a - // threading reschedule). - - if ((Core::g_app_core->GetTicks() - last_frame_ticks) > (GPU::frame_ticks)) { - VideoCore::g_renderer->SwapBuffers(); - last_frame_ticks = Core::g_app_core->GetTicks(); - } - // Synchronize GPU on a thread reschedule: Because we cannot accurately predict a vertical // blank, we need to simulate it. Based on testing, it seems that retail applications work more // accurately when this is signalled between thread switches. if (HLE::g_reschedule) { u64 current_ticks = Core::g_app_core->GetTicks(); - u64 line_ticks = (GPU::frame_ticks / framebuffer_top.height) * 16; + u32 num_lines = static_cast((current_ticks - last_update_tick) / line_ticks); - //// Synchronize line... - if ((current_ticks - last_update_tick) >= line_ticks) { + // Synchronize line... + if (num_lines > 0) { GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC0); - cur_line++; - last_update_tick += line_ticks; + cur_line += num_lines; + last_update_tick += (num_lines * line_ticks); } // Synchronize frame... if (cur_line >= framebuffer_top.height) { cur_line = 0; - VideoCore::g_renderer->SwapBuffers(); + frame_count++; + last_skip_frame = g_skip_frame; + g_skip_frame = (frame_count & Settings::values.frame_skip) != 0; + + // Swap buffers based on the frameskip mode, which is a little bit tricky. When + // a frame is being skipped, nothing is being rendered to the internal framebuffer(s). + // So, we should only swap frames if the last frame was rendered. The rules are: + // - If frameskip == 0 (disabled), always swap buffers + // - If frameskip == 1, swap buffers every other frame (starting from the first frame) + // - If frameskip > 1, swap buffers every frameskip^n frames (starting from the second frame) + + if ((((Settings::values.frame_skip != 1) ^ last_skip_frame) && last_skip_frame != g_skip_frame) || + Settings::values.frame_skip == 0) { + VideoCore::g_renderer->SwapBuffers(); + } + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PDC1); } } @@ -217,10 +223,6 @@ void Update() { /// Initialize hardware void Init() { - frame_ticks = 268123480 / Settings::values.gpu_refresh_rate; - cur_line = 0; - last_update_tick = last_frame_ticks = Core::g_app_core->GetTicks(); - auto& framebuffer_top = g_regs.framebuffer_config[0]; auto& framebuffer_sub = g_regs.framebuffer_config[1]; @@ -249,6 +251,13 @@ void Init() { framebuffer_sub.color_format = Regs::PixelFormat::RGB8; framebuffer_sub.active_fb = 0; + frame_ticks = 268123480 / Settings::values.gpu_refresh_rate; + line_ticks = (GPU::frame_ticks / framebuffer_top.height); + cur_line = 0; + last_update_tick = Core::g_app_core->GetTicks(); + last_skip_frame = false; + g_skip_frame = false; + LOG_DEBUG(HW_GPU, "initialized OK"); } diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h index 68f11bfcb6..292f496c1e 100644 --- a/src/core/hw/gpu.h +++ b/src/core/hw/gpu.h @@ -241,6 +241,7 @@ ASSERT_REG_POSITION(command_processor_config, 0x00638); static_assert(sizeof(Regs) == 0x1000 * sizeof(u32), "Invalid total size of register set"); extern Regs g_regs; +extern bool g_skip_frame; template void Read(T &var, const u32 addr); diff --git a/src/core/settings.h b/src/core/settings.h index 4808872aed..4b89288474 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -31,6 +31,7 @@ struct Values { // Core int cpu_core; int gpu_refresh_rate; + int frame_skip; // Data Storage bool use_virtual_sd; diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 2083357fe5..9602779f4e 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -9,6 +9,7 @@ #include "primitive_assembly.h" #include "vertex_shader.h" #include "core/hle/service/gsp_gpu.h" +#include "core/hw/gpu.h" #include "debug_utils/debug_utils.h" @@ -31,6 +32,10 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { if (id >= registers.NumIds()) return; + // If we're skipping this frame, only allow trigger IRQ + if (GPU::g_skip_frame && id != PICA_REG_INDEX(trigger_irq)) + return; + // TODO: Figure out how register masking acts on e.g. vs_uniform_setup.set_value u32 old_value = registers[id]; registers[id] = (old_value & ~mask) | (value & mask);