mirror of
https://git.uzuy-edge.org/Uzuy-Edge/Uzuy
synced 2024-11-23 11:09:47 +00:00
feat(renderer): Add frame skipping and interpolation logic
- Further Implemented frame skipping functionality to improve performance on lower-end devices by skipping alternate frames. - Added interpolation logic to create additional frames, resulting in smoother gameplay when frame interpolation is enabled. - Introduced new settings for toggling frame skipping and interpolation in the renderer configuration.
This commit is contained in:
parent
9a34d40523
commit
308cc609c5
@ -29,7 +29,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||||||
SHOW_THERMAL_OVERLAY("show_thermal_overlay"),
|
SHOW_THERMAL_OVERLAY("show_thermal_overlay"),
|
||||||
CORE_USE_MULTI_CORE("use_multi_core"),
|
CORE_USE_MULTI_CORE("use_multi_core"),
|
||||||
SYNC_CORE_SPEED("sync_core_speed"),
|
SYNC_CORE_SPEED("sync_core_speed"),
|
||||||
FRAME_SKIPPING("frame_skipping");
|
FRAME_SKIPPING("frame_skipping"),
|
||||||
|
FRAME_INTERPOLATION("frame_interpolation");
|
||||||
|
|
||||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
||||||
NativeConfig.getBoolean(key, needsGlobal)
|
NativeConfig.getBoolean(key, needsGlobal)
|
||||||
|
@ -177,6 +177,7 @@ private fun addPhoenixHacksSubmenu(sl: ArrayList<SettingsItem>) {
|
|||||||
|
|
||||||
// Add settings using keys directly
|
// Add settings using keys directly
|
||||||
add(BooleanSetting.FRAME_SKIPPING.key)
|
add(BooleanSetting.FRAME_SKIPPING.key)
|
||||||
|
add(BooleanSetting.FRAME_INTERPOLATION.key)
|
||||||
add(BooleanSetting.CORE_USE_MULTI_CORE.key)
|
add(BooleanSetting.CORE_USE_MULTI_CORE.key)
|
||||||
add(BooleanSetting.SYNC_CORE_SPEED.key)
|
add(BooleanSetting.SYNC_CORE_SPEED.key)
|
||||||
add(IntSetting.RENDERER_SHADER_BACKEND.key)
|
add(IntSetting.RENDERER_SHADER_BACKEND.key)
|
||||||
@ -187,6 +188,25 @@ private fun addPhoenixHacksSubmenu(sl: ArrayList<SettingsItem>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val useInterpolationSetting = object : AbstractBooleanSetting {
|
||||||
|
override val key = BooleanSetting.FRAME_INTERPOLATION.key
|
||||||
|
|
||||||
|
override fun getBoolean(needsGlobal: Boolean): Boolean {
|
||||||
|
return BooleanSetting.FRAME_INTERPOLATION.getBoolean(needsGlobal)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setBoolean(value: Boolean) {
|
||||||
|
BooleanSetting.FRAME_INTERPOLATION.setBoolean(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val defaultValue = BooleanSetting.FRAME_INTERPOLATION.defaultValue
|
||||||
|
|
||||||
|
override fun getValueAsString(needsGlobal: Boolean): String =
|
||||||
|
BooleanSetting.FRAME_INTERPOLATION.getValueAsString(needsGlobal)
|
||||||
|
|
||||||
|
override fun reset() = BooleanSetting.FRAME_INTERPOLATION.reset()
|
||||||
|
}
|
||||||
|
|
||||||
private val useSyncCoreSpeedSetting = object : AbstractBooleanSetting {
|
private val useSyncCoreSpeedSetting = object : AbstractBooleanSetting {
|
||||||
override val key = BooleanSetting.SYNC_CORE_SPEED.key
|
override val key = BooleanSetting.SYNC_CORE_SPEED.key
|
||||||
|
|
||||||
@ -226,34 +246,26 @@ private fun addPhoenixHacksSubmenu(sl: ArrayList<SettingsItem>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val frameSkippingSetting = object : AbstractBooleanSetting {
|
private val frameSkippingSetting = object : AbstractBooleanSetting {
|
||||||
override val key = "frame_skipping"
|
override val key = BooleanSetting.FRAME_SKIPPING.key
|
||||||
|
|
||||||
override fun getBoolean(needsGlobal: Boolean): Boolean {
|
override fun getBoolean(needsGlobal: Boolean): Boolean {
|
||||||
return BooleanSetting.FRAME_SKIPPING.getBoolean(needsGlobal)
|
return BooleanSetting.FRAME_SKIPPING.getBoolean(needsGlobal)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setBoolean(value: Boolean) {
|
||||||
|
BooleanSetting.FRAME_SKIPPING.setBoolean(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val defaultValue = BooleanSetting.FRAME_SKIPPING.defaultValue
|
||||||
|
|
||||||
|
override fun getValueAsString(needsGlobal: Boolean): String =
|
||||||
|
BooleanSetting.FRAME_SKIPPING.getValueAsString(needsGlobal)
|
||||||
|
|
||||||
|
override fun reset() = BooleanSetting.FRAME_SKIPPING.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setBoolean(value: Boolean) {
|
private fun addPhoenixHacksSettings(sl: ArrayList<SettingsItem>) {
|
||||||
BooleanSetting.FRAME_SKIPPING.setBoolean(value)
|
|
||||||
NativeLibrary.setFrameSkipping(value) // Ensure this toggles the frame skipping in Vulkan renderer
|
|
||||||
}
|
|
||||||
|
|
||||||
override val defaultValue = BooleanSetting.FRAME_SKIPPING.defaultValue
|
|
||||||
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean): String = getBoolean(needsGlobal).toString()
|
|
||||||
|
|
||||||
override fun reset() = setBoolean(defaultValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addPhoenixHacksSettings(sl: ArrayList<SettingsItem>) {
|
|
||||||
sl.apply {
|
sl.apply {
|
||||||
// Add the multi-core setting to Phoenix Hacks submenu
|
|
||||||
add(
|
|
||||||
SwitchSetting(
|
|
||||||
frameSkippingSetting,
|
|
||||||
titleId = R.string.frame_skipping,
|
|
||||||
descriptionId = R.string.frame_skipping_description
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
add(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
useSyncCoreSpeedSetting,
|
useSyncCoreSpeedSetting,
|
||||||
@ -268,7 +280,20 @@ private fun addPhoenixHacksSettings(sl: ArrayList<SettingsItem>) {
|
|||||||
descriptionId = R.string.use_multi_core_description
|
descriptionId = R.string.use_multi_core_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// Add the new settings
|
add(
|
||||||
|
SwitchSetting(
|
||||||
|
frameSkippingSetting,
|
||||||
|
titleId = R.string.frame_skipping,
|
||||||
|
descriptionId = R.string.frame_skipping_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
add(
|
||||||
|
SwitchSetting(
|
||||||
|
useInterpolationSetting, // The interpolation setting object you've created
|
||||||
|
titleId = R.string.frame_interpolation, // Use appropriate string resources for the title
|
||||||
|
descriptionId = R.string.frame_interpolation_description // Description resource for the interpolation setting
|
||||||
|
)
|
||||||
|
)
|
||||||
add(
|
add(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_SHADER_BACKEND,
|
IntSetting.RENDERER_SHADER_BACKEND,
|
||||||
|
@ -390,9 +390,15 @@
|
|||||||
<string name="sync_core_speed">Sync Core Speed</string>
|
<string name="sync_core_speed">Sync Core Speed</string>
|
||||||
<string name="sync_core_speed_description">Synchronizes the CPU core speed with the game\'s maximum rendering speed.</string>
|
<string name="sync_core_speed_description">Synchronizes the CPU core speed with the game\'s maximum rendering speed.</string>
|
||||||
|
|
||||||
|
<string name="frame_interpolation">Frame Interpolation</string>
|
||||||
|
<string name="frame_interpolation_description">Enable or disable frame interpolation for smoother transitions between frames.</string>
|
||||||
|
|
||||||
|
<string name="frame_skipping">Frame Skipping</string>
|
||||||
|
<string name="frame_skipping_description">Enables frame skipping to improve performance by skipping certain frames during rendering.</string>
|
||||||
|
|
||||||
<!-- Miscellaneous -->
|
<!-- Miscellaneous -->
|
||||||
<string name="preferences_phoenix_hacks">🐦🔥 Phoenix Hacks</string>
|
<string name="preferences_phoenix_hacks">🐦🔥 Phoenix Tweaks</string>
|
||||||
<string name="preferences_phoenix_hacks_description">Enable or adjust Phoenix-specific hacks</string>
|
<string name="preferences_phoenix_hacks_description">Enable or adjust Phoenix-specific tweaks</string>
|
||||||
<string name="slider_default">Default</string>
|
<string name="slider_default">Default</string>
|
||||||
<string name="ini_saved">Saved settings</string>
|
<string name="ini_saved">Saved settings</string>
|
||||||
<string name="gameid_saved">Saved settings for %1$s</string>
|
<string name="gameid_saved">Saved settings for %1$s</string>
|
||||||
|
@ -210,10 +210,12 @@ struct Values {
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
&use_speed_limit};
|
&use_speed_limit};
|
||||||
SwitchableSetting<bool> sync_core_speed{linkage, false, "sync_core_speed", Category::Core, Specialization::Default};
|
SwitchableSetting<bool> sync_core_speed{linkage, true, "sync_core_speed", Category::Core, Specialization::Default};
|
||||||
|
|
||||||
// Frame-Skipping (Experimental)
|
// Frame-Skipping (Experimental)
|
||||||
SwitchableSetting<bool> frame_skipping{linkage, false, "frame_skipping", Category::Core, Specialization::Default};
|
SwitchableSetting<bool> frame_skipping{linkage, false, "frame_skipping", Category::Renderer, Specialization::Default};
|
||||||
|
// Frame Interpolation (Experimental)
|
||||||
|
SwitchableSetting<bool> frame_interpolation{linkage, false, "frame_interpolation", Category::Renderer, Specialization::Default};
|
||||||
|
|
||||||
// Cpu
|
// Cpu
|
||||||
SwitchableSetting<CpuBackend, true> cpu_backend{linkage,
|
SwitchableSetting<CpuBackend, true> cpu_backend{linkage,
|
||||||
|
@ -189,6 +189,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
|
|||||||
tr("Enables frame skipping to improve performance in cases where the emulator cannot maintain the target frame rate. "
|
tr("Enables frame skipping to improve performance in cases where the emulator cannot maintain the target frame rate. "
|
||||||
"Frame skipping will cause some frames to be skipped to maintain smoother gameplay at the cost of visual fluidity. "
|
"Frame skipping will cause some frames to be skipped to maintain smoother gameplay at the cost of visual fluidity. "
|
||||||
"This is especially useful on lower-end hardware."));
|
"This is especially useful on lower-end hardware."));
|
||||||
|
INSERT(Settings, frame_interpolation, tr("Enable Frame Interpolation"),
|
||||||
|
tr("Enables frame interpolation to smooth out transitions between frames for a more fluid visual experience. "
|
||||||
|
"This can be useful when the emulator's frame rate is lower than the target frame rate, creating smoother movement at the cost of input lag or visual accuracy. "
|
||||||
|
"Recommended for higher-end hardware or when fluidity is prioritized over precision."));
|
||||||
|
|
||||||
// Renderer (Advanced Graphics)
|
// Renderer (Advanced Graphics)
|
||||||
INSERT(Settings, async_presentation, tr("Enable asynchronous presentation (Vulkan only)"),
|
INSERT(Settings, async_presentation, tr("Enable asynchronous presentation (Vulkan only)"),
|
||||||
|
@ -124,6 +124,12 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
|
|||||||
rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker,
|
rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker,
|
||||||
scheduler),
|
scheduler),
|
||||||
applet_frame() {
|
applet_frame() {
|
||||||
|
// Move frame skipping initialization here
|
||||||
|
frame_skipping_enabled = Settings::values.frame_skipping.GetValue();
|
||||||
|
|
||||||
|
// Initialize frame interpolation here
|
||||||
|
frame_interpolation_enabled = Settings::values.frame_interpolation.GetValue();
|
||||||
|
|
||||||
if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) {
|
if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) {
|
||||||
turbo_mode.emplace(instance, dld);
|
turbo_mode.emplace(instance, dld);
|
||||||
scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
|
scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
|
||||||
@ -139,80 +145,83 @@ RendererVulkan::~RendererVulkan() {
|
|||||||
void(device.GetLogical().WaitIdle());
|
void(device.GetLogical().WaitIdle());
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebuffers) {
|
void RendererVulkan::InterpolateFrames(Frame* prev_frame, Frame* next_frame, Frame* interpolated_frame, float interpolation_factor) {
|
||||||
if (frame_skip_interval > 0 && (frame_counter % (frame_skip_interval + 1)) == 0) {
|
if (prev_frame && next_frame && interpolated_frame) {
|
||||||
frame_counter++;
|
// Implement further interpolation logic here, e.g., linear interpolation, spherical interpolation, etc.
|
||||||
is_frame_skipped = true; // Mark this frame as skipped
|
for (size_t i = 0; i < interpolated_frame->data.size(); ++i) {
|
||||||
return; // Skip this frame
|
interpolated_frame->data[i] = (1.0f - interpolation_factor) * prev_frame->data[i] +
|
||||||
|
interpolation_factor * next_frame->data[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
frame_counter++;
|
bool frame_skipping_enabled = false;
|
||||||
|
bool frame_interpolation_enabled = false;
|
||||||
|
|
||||||
|
void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebuffers) {
|
||||||
|
static int frame_counter = 0; // Track frame count
|
||||||
|
static Frame* prev_frame = nullptr; // Track the previous frame
|
||||||
|
Frame* interpolated_frame = nullptr;
|
||||||
|
|
||||||
|
// Frame skipping logic
|
||||||
|
if (frame_skipping_enabled) {
|
||||||
|
// Skip rendering every other frame
|
||||||
|
if (++frame_counter % 2 != 0) {
|
||||||
|
return; // Skip this frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (framebuffers.empty()) {
|
if (framebuffers.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SCOPE_EXIT {
|
SCOPE_EXIT {
|
||||||
render_window.OnFrameDisplayed();
|
render_window.OnFrameDisplayed();
|
||||||
};
|
};
|
||||||
|
|
||||||
RenderAppletCaptureLayer(framebuffers);
|
// Check if frame interpolation is enabled
|
||||||
|
if (frame_interpolation_enabled && prev_frame != nullptr) {
|
||||||
|
// Allocate or retrieve a new frame for interpolation
|
||||||
|
interpolated_frame = present_manager.GetRenderFrame();
|
||||||
|
|
||||||
if (!render_window.IsShown()) {
|
// Calculate interpolation factor (e.g., halfway between frames = 0.5)
|
||||||
return;
|
float interpolation_factor = 0.5f;
|
||||||
|
|
||||||
|
// Interpolate between prev_frame and the current frame
|
||||||
|
InterpolateFrames(prev_frame, present_manager.GetRenderFrame(), interpolated_frame, interpolation_factor);
|
||||||
|
|
||||||
|
// Render the interpolated frame instead of the current frame
|
||||||
|
blit_swapchain.DrawToFrame(rasterizer, interpolated_frame, framebuffers,
|
||||||
|
render_window.GetFramebufferLayout(), swapchain.GetImageCount(),
|
||||||
|
swapchain.GetImageViewFormat());
|
||||||
|
|
||||||
|
scheduler.Flush(*interpolated_frame->render_ready);
|
||||||
|
present_manager.Present(interpolated_frame);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Render the current frame as normal if interpolation is disabled
|
||||||
|
RenderAppletCaptureLayer(framebuffers);
|
||||||
|
|
||||||
|
if (!render_window.IsShown()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderScreenshot(framebuffers);
|
||||||
|
Frame* frame = present_manager.GetRenderFrame();
|
||||||
|
blit_swapchain.DrawToFrame(rasterizer, frame, framebuffers,
|
||||||
|
render_window.GetFramebufferLayout(), swapchain.GetImageCount(),
|
||||||
|
swapchain.GetImageViewFormat());
|
||||||
|
scheduler.Flush(*frame->render_ready);
|
||||||
|
present_manager.Present(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_frame_skipped && previous_frame_buffer.has_value()) {
|
// Save the current frame as the previous frame for the next call
|
||||||
// Interpolate between the previous frame and the current frame
|
prev_frame = present_manager.GetRenderFrame();
|
||||||
InterpolateFrame(previous_frame_buffer.value(), framebuffers);
|
|
||||||
is_frame_skipped = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capture current frame buffer for future interpolation
|
|
||||||
vk::Buffer current_frame_buffer = RenderToBuffer(framebuffers, render_window.GetFramebufferLayout(),
|
|
||||||
VK_FORMAT_B8G8R8A8_UNORM, layout.width * layout.height * 4);
|
|
||||||
previous_frame_buffer = current_frame_buffer;
|
|
||||||
|
|
||||||
RenderScreenshot(framebuffers);
|
|
||||||
Frame* frame = present_manager.GetRenderFrame();
|
|
||||||
blit_swapchain.DrawToFrame(rasterizer, frame, framebuffers,
|
|
||||||
render_window.GetFramebufferLayout(), swapchain.GetImageCount(),
|
|
||||||
swapchain.GetImageViewFormat());
|
|
||||||
scheduler.Flush(*frame->render_ready);
|
|
||||||
present_manager.Present(frame);
|
|
||||||
|
|
||||||
gpu.RendererFrameEndNotify();
|
gpu.RendererFrameEndNotify();
|
||||||
rasterizer.TickFrame();
|
rasterizer.TickFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererVulkan::InterpolateFrame(vk::Buffer& previous_frame, std::span<const Tegra::FramebufferConfig> framebuffers) {
|
|
||||||
// Allocate a buffer for the interpolated frame
|
|
||||||
vk::Buffer interpolated_frame_buffer = RenderToBuffer(framebuffers, render_window.GetFramebufferLayout(),
|
|
||||||
VK_FORMAT_B8G8R8A8_UNORM, layout.width * layout.height * 4);
|
|
||||||
|
|
||||||
// Perform the interpolation here (simplified linear blending)
|
|
||||||
// In reality, you'd need to read from both frame buffers and blend the pixel values
|
|
||||||
scheduler.Record([&](vk::CommandBuffer cmdbuf) {
|
|
||||||
// For simplicity, assume a 50-50 blend between previous and current frames
|
|
||||||
// More complex interpolation can be implemented by reading pixel data and manually blending
|
|
||||||
|
|
||||||
// vkCmdCopyBuffer and other Vulkan operations to blend the previous and current frames
|
|
||||||
// Here you would implement your actual blending logic
|
|
||||||
BlitBuffer(cmdbuf, previous_frame, interpolated_frame_buffer); // Pseudo-code function for blending
|
|
||||||
});
|
|
||||||
|
|
||||||
scheduler.Finish(); // Ensure the interpolation finishes before presenting
|
|
||||||
|
|
||||||
// Present the interpolated frame
|
|
||||||
present_manager.PresentInterpolatedFrame(interpolated_frame_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RendererVulkan::BlitBuffer(vk::CommandBuffer cmdbuf, vk::Buffer& src, vk::Buffer& dst) {
|
|
||||||
// This is a placeholder for Vulkan operations that blend two frame buffers.
|
|
||||||
// Vulkan commands would go here, such as vkCmdCopyBuffer, and shader operations
|
|
||||||
// that blend the contents of src and dst.
|
|
||||||
}
|
|
||||||
|
|
||||||
void RendererVulkan::Report() const {
|
void RendererVulkan::Report() const {
|
||||||
using namespace Common::Literals;
|
using namespace Common::Literals;
|
||||||
const std::string vendor_name{device.GetVendorName()};
|
const std::string vendor_name{device.GetVendorName()};
|
||||||
|
@ -58,6 +58,8 @@ public:
|
|||||||
return device.GetDriverName();
|
return device.GetDriverName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InterpolateFrames(Frame* prev_frame, Frame* next_frame, Frame* interpolated_frame, float interpolation_factor);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Report() const;
|
void Report() const;
|
||||||
|
|
||||||
@ -92,11 +94,8 @@ private:
|
|||||||
|
|
||||||
Frame applet_frame;
|
Frame applet_frame;
|
||||||
|
|
||||||
std::optional<vk::Buffer> previous_frame_buffer; // Store previous frame buffer for interpolation
|
bool frame_skipping_enabled;
|
||||||
bool is_frame_skipped = false; // Flag to check if the previous frame was skipped
|
bool frame_interpolation_enabled;
|
||||||
|
|
||||||
int frame_skip_interval = 0; // Number of frames to skip (0 means no skipping)
|
|
||||||
int frame_counter = 0; // Keeps track of the current frame number
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
Loading…
Reference in New Issue
Block a user