Frame interpolation rewrite
All checks were successful
eden-license / license-header (pull_request) Successful in 20s
All checks were successful
eden-license / license-header (pull_request) Successful in 20s
This commit is contained in:
parent
eae48c2658
commit
a9d8ce9543
4 changed files with 21 additions and 147 deletions
|
@ -68,8 +68,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||||
USE_LRU_CACHE("use_lru_cache");
|
USE_LRU_CACHE("use_lru_cache");
|
||||||
|
|
||||||
external fun isRaiiEnabled(): Boolean
|
external fun isRaiiEnabled(): Boolean
|
||||||
// external fun isFrameSkippingEnabled(): Boolean
|
|
||||||
external fun isFrameInterpolationEnabled(): Boolean
|
|
||||||
|
|
||||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
||||||
NativeConfig.getBoolean(key, needsGlobal)
|
NativeConfig.getBoolean(key, needsGlobal)
|
||||||
|
|
|
@ -322,12 +322,8 @@ struct Values {
|
||||||
SwitchableSetting<int> vulkan_device{linkage, 0, "vulkan_device", Category::Renderer,
|
SwitchableSetting<int> vulkan_device{linkage, 0, "vulkan_device", Category::Renderer,
|
||||||
Specialization::RuntimeList};
|
Specialization::RuntimeList};
|
||||||
SwitchableSetting<bool> enable_raii{linkage, false, "enable_raii", Category::Renderer};
|
SwitchableSetting<bool> enable_raii{linkage, false, "enable_raii", Category::Renderer};
|
||||||
#ifdef __ANDROID__
|
|
||||||
SwitchableSetting<bool> frame_interpolation{linkage, true, "frame_interpolation", Category::Renderer,
|
SwitchableSetting<bool> frame_interpolation{linkage, true, "frame_interpolation", Category::Renderer,
|
||||||
Specialization::RuntimeList};
|
Specialization::RuntimeList};
|
||||||
SwitchableSetting<bool> frame_skipping{linkage, false, "frame_skipping", Category::Renderer,
|
|
||||||
Specialization::RuntimeList};
|
|
||||||
#endif
|
|
||||||
SwitchableSetting<bool> use_disk_shader_cache{linkage, true, "use_disk_shader_cache",
|
SwitchableSetting<bool> use_disk_shader_cache{linkage, true, "use_disk_shader_cache",
|
||||||
Category::Renderer};
|
Category::Renderer};
|
||||||
SwitchableSetting<SpirvOptimizeMode, true> optimize_spirv_output{linkage,
|
SwitchableSetting<SpirvOptimizeMode, true> optimize_spirv_output{linkage,
|
||||||
|
|
|
@ -38,9 +38,7 @@
|
||||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||||
#include "video_core/vulkan_common/vulkan_surface.h"
|
#include "video_core/vulkan_common/vulkan_surface.h"
|
||||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||||
#ifdef __ANDROID__
|
|
||||||
#include <jni.h>
|
|
||||||
#endif
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -189,149 +187,27 @@ RendererVulkan::~RendererVulkan() {
|
||||||
void(device.GetLogical().WaitIdle());
|
void(device.GetLogical().WaitIdle());
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
class BooleanSetting {
|
|
||||||
public:
|
|
||||||
// static BooleanSetting FRAME_SKIPPING;
|
|
||||||
static BooleanSetting FRAME_INTERPOLATION;
|
|
||||||
explicit BooleanSetting(bool initial_value = false) : value(initial_value) {}
|
|
||||||
|
|
||||||
[[nodiscard]] bool getBoolean() const {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setBoolean(bool new_value) {
|
|
||||||
value = new_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool value;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize static members
|
|
||||||
// BooleanSetting BooleanSetting::FRAME_SKIPPING(false);
|
|
||||||
BooleanSetting BooleanSetting::FRAME_INTERPOLATION(false);
|
|
||||||
|
|
||||||
// extern "C" JNIEXPORT jboolean JNICALL
|
|
||||||
// Java_org_yuzu_yuzu_1emu_features_settings_model_BooleanSetting_isFrameSkippingEnabled(JNIEnv* env, jobject /* this */) {
|
|
||||||
// return static_cast<jboolean>(BooleanSetting::FRAME_SKIPPING.getBoolean());
|
|
||||||
// }
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT jboolean JNICALL
|
|
||||||
Java_org_yuzu_yuzu_1emu_features_settings_model_BooleanSetting_isFrameInterpolationEnabled(JNIEnv* env, jobject /* this */) {
|
|
||||||
return static_cast<jboolean>(BooleanSetting::FRAME_INTERPOLATION.getBoolean());
|
|
||||||
}
|
|
||||||
|
|
||||||
void RendererVulkan::InterpolateFrames(Frame* prev_frame, Frame* interpolated_frame) {
|
|
||||||
if (!prev_frame || !interpolated_frame || !prev_frame->image || !interpolated_frame->image) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& framebuffer_layout = render_window.GetFramebufferLayout();
|
|
||||||
// Fixed aggressive downscale (50%)
|
|
||||||
VkExtent2D dst_extent{
|
|
||||||
.width = framebuffer_layout.width / 2,
|
|
||||||
.height = framebuffer_layout.height / 2
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if we need to recreate the destination frame
|
|
||||||
bool needs_recreation = false; // Only recreate when necessary
|
|
||||||
if (!interpolated_frame->image_view) {
|
|
||||||
needs_recreation = true; // Need to create initially
|
|
||||||
} else {
|
|
||||||
// Check if dimensions have changed
|
|
||||||
if (interpolated_frame->framebuffer) {
|
|
||||||
needs_recreation = (framebuffer_layout.width / 2 != dst_extent.width) ||
|
|
||||||
(framebuffer_layout.height / 2 != dst_extent.height);
|
|
||||||
} else {
|
|
||||||
needs_recreation = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needs_recreation) {
|
|
||||||
interpolated_frame->image = CreateWrappedImage(memory_allocator, dst_extent, swapchain.GetImageViewFormat());
|
|
||||||
interpolated_frame->image_view = CreateWrappedImageView(device, interpolated_frame->image, swapchain.GetImageViewFormat());
|
|
||||||
interpolated_frame->framebuffer = blit_swapchain.CreateFramebuffer(
|
|
||||||
Layout::FramebufferLayout{dst_extent.width, dst_extent.height},
|
|
||||||
*interpolated_frame->image_view,
|
|
||||||
swapchain.GetImageViewFormat());
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduler.RequestOutsideRenderPassOperationContext();
|
|
||||||
scheduler.Record([&](vk::CommandBuffer cmdbuf) {
|
|
||||||
// Transition images to transfer layouts
|
|
||||||
TransitionImageLayout(cmdbuf, *prev_frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
|
||||||
TransitionImageLayout(cmdbuf, *interpolated_frame->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
|
||||||
|
|
||||||
// Perform the downscale blit
|
|
||||||
VkImageBlit blit_region{};
|
|
||||||
blit_region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
|
||||||
blit_region.srcOffsets[0] = {0, 0, 0};
|
|
||||||
blit_region.srcOffsets[1] = {
|
|
||||||
static_cast<int32_t>(framebuffer_layout.width),
|
|
||||||
static_cast<int32_t>(framebuffer_layout.height),
|
|
||||||
1
|
|
||||||
};
|
|
||||||
blit_region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
|
||||||
blit_region.dstOffsets[0] = {0, 0, 0};
|
|
||||||
blit_region.dstOffsets[1] = {
|
|
||||||
static_cast<int32_t>(dst_extent.width),
|
|
||||||
static_cast<int32_t>(dst_extent.height),
|
|
||||||
1
|
|
||||||
};
|
|
||||||
|
|
||||||
// Using the wrapper's BlitImage with proper parameters
|
|
||||||
cmdbuf.BlitImage(
|
|
||||||
*prev_frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
||||||
*interpolated_frame->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
||||||
blit_region, VK_FILTER_NEAREST
|
|
||||||
);
|
|
||||||
|
|
||||||
// Transition back to general layout
|
|
||||||
TransitionImageLayout(cmdbuf, *prev_frame->image, VK_IMAGE_LAYOUT_GENERAL);
|
|
||||||
TransitionImageLayout(cmdbuf, *interpolated_frame->image, VK_IMAGE_LAYOUT_GENERAL);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebuffers) {
|
void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebuffers) {
|
||||||
#ifdef __ANDROID__
|
|
||||||
static int frame_counter = 0;
|
|
||||||
static int target_fps = 60; // Target FPS (30 or 60)
|
|
||||||
int frame_skip_threshold = 1;
|
|
||||||
|
|
||||||
bool frame_skipping = false; //BooleanSetting::FRAME_SKIPPING.getBoolean();
|
|
||||||
bool frame_interpolation = BooleanSetting::FRAME_INTERPOLATION.getBoolean();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (framebuffers.empty()) {
|
if (framebuffers.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
|
||||||
if (frame_skipping) {
|
|
||||||
frame_skip_threshold = (target_fps == 30) ? 2 : 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
frame_counter++;
|
|
||||||
if (frame_counter % frame_skip_threshold != 0) {
|
|
||||||
if (frame_interpolation && previous_frame) {
|
|
||||||
Frame* interpolated_frame = present_manager.GetRenderFrame();
|
|
||||||
InterpolateFrames(previous_frame, interpolated_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);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SCOPE_EXIT {
|
SCOPE_EXIT {
|
||||||
render_window.OnFrameDisplayed();
|
render_window.OnFrameDisplayed();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Frame interpolation logic
|
||||||
|
if (Settings::values.frame_interpolation.GetValue()) {
|
||||||
|
skip_next_frame = !skip_next_frame;
|
||||||
|
if (skip_next_frame && last_presented_frame) {
|
||||||
|
// Present the last frame again, skip rendering
|
||||||
|
present_manager.Present(last_presented_frame);
|
||||||
|
gpu.RendererFrameEndNotify();
|
||||||
|
rasterizer.TickFrame();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RenderAppletCaptureLayer(framebuffers);
|
RenderAppletCaptureLayer(framebuffers);
|
||||||
|
|
||||||
if (!render_window.IsShown()) {
|
if (!render_window.IsShown()) {
|
||||||
|
@ -346,6 +222,11 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
|
||||||
scheduler.Flush(*frame->render_ready);
|
scheduler.Flush(*frame->render_ready);
|
||||||
present_manager.Present(frame);
|
present_manager.Present(frame);
|
||||||
|
|
||||||
|
// Store the last presented frame for interpolation
|
||||||
|
if (Settings::values.frame_interpolation.GetValue()) {
|
||||||
|
last_presented_frame = frame;
|
||||||
|
}
|
||||||
|
|
||||||
gpu.RendererFrameEndNotify();
|
gpu.RendererFrameEndNotify();
|
||||||
rasterizer.TickFrame();
|
rasterizer.TickFrame();
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,10 +58,6 @@ public:
|
||||||
void InitializePlatformSpecific();
|
void InitializePlatformSpecific();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void InterpolateFrames(Frame* prev_frame, Frame* curr_frame);
|
|
||||||
Frame* previous_frame = nullptr; // Store the previous frame for interpolation
|
|
||||||
VkCommandBuffer BeginSingleTimeCommands();
|
|
||||||
void EndSingleTimeCommands(VkCommandBuffer command_buffer);
|
|
||||||
void Report() const;
|
void Report() const;
|
||||||
|
|
||||||
vk::Buffer RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
|
vk::Buffer RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
|
||||||
|
@ -102,6 +98,9 @@ private:
|
||||||
std::optional<TurboMode> turbo_mode;
|
std::optional<TurboMode> turbo_mode;
|
||||||
|
|
||||||
Frame applet_frame;
|
Frame applet_frame;
|
||||||
|
|
||||||
|
bool skip_next_frame = false;
|
||||||
|
Frame* last_presented_frame = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue