WIP: [EXPERIMENTAL] [gpu_thread]: Frame Interpolation Rewrite #212
4 changed files with 25 additions and 147 deletions
|
@ -68,8 +68,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||
USE_LRU_CACHE("use_lru_cache");
|
||||
|
||||
external fun isRaiiEnabled(): Boolean
|
||||
// external fun isFrameSkippingEnabled(): Boolean
|
||||
external fun isFrameInterpolationEnabled(): Boolean
|
||||
|
||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
||||
NativeConfig.getBoolean(key, needsGlobal)
|
||||
|
|
|
@ -322,12 +322,8 @@ struct Values {
|
|||
SwitchableSetting<int> vulkan_device{linkage, 0, "vulkan_device", Category::Renderer,
|
||||
Specialization::RuntimeList};
|
||||
SwitchableSetting<bool> enable_raii{linkage, false, "enable_raii", Category::Renderer};
|
||||
#ifdef __ANDROID__
|
||||
SwitchableSetting<bool> frame_interpolation{linkage, true, "frame_interpolation", Category::Renderer,
|
||||
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",
|
||||
Category::Renderer};
|
||||
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_surface.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#ifdef __ANDROID__
|
||||
#include <jni.h>
|
||||
#endif
|
||||
|
||||
namespace Vulkan {
|
||||
namespace {
|
||||
|
||||
|
@ -189,149 +187,31 @@ RendererVulkan::~RendererVulkan() {
|
|||
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) {
|
||||
#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()) {
|
||||
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 {
|
||||
render_window.OnFrameDisplayed();
|
||||
};
|
||||
|
||||
// Frameskip logic
|
||||
if (Settings::values.frame_interpolation.GetValue()) {
|
||||
skip_next_frame = !skip_next_frame;
|
||||
// Only skip if we have a valid last frame
|
||||
if (skip_next_frame && last_presented_frame != nullptr) {
|
||||
present_manager.Present(last_presented_frame);
|
||||
gpu.RendererFrameEndNotify();
|
||||
rasterizer.TickFrame();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// If frameskip is off, always reset skip state
|
||||
skip_next_frame = false;
|
||||
last_presented_frame = nullptr;
|
||||
}
|
||||
|
||||
RenderAppletCaptureLayer(framebuffers);
|
||||
|
||||
if (!render_window.IsShown()) {
|
||||
|
@ -346,6 +226,11 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
|
|||
scheduler.Flush(*frame->render_ready);
|
||||
present_manager.Present(frame);
|
||||
|
||||
// Store the last presented frame for interpolation
|
||||
if (Settings::values.frame_interpolation.GetValue()) {
|
||||
last_presented_frame = frame;
|
||||
}
|
||||
|
||||
gpu.RendererFrameEndNotify();
|
||||
rasterizer.TickFrame();
|
||||
}
|
||||
|
|
|
@ -58,10 +58,6 @@ public:
|
|||
void InitializePlatformSpecific();
|
||||
|
||||
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;
|
||||
|
||||
vk::Buffer RenderToBuffer(std::span<const Tegra::FramebufferConfig> framebuffers,
|
||||
|
@ -102,6 +98,9 @@ private:
|
|||
std::optional<TurboMode> turbo_mode;
|
||||
|
||||
Frame applet_frame;
|
||||
|
||||
bool skip_next_frame = false;
|
||||
Frame* last_presented_frame = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue