|
|
|
@ -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,27 @@ 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(); |
|
|
|
}; |
|
|
|
|
|
|
|
// 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); |
|
|
|
|
|
|
|
if (!render_window.IsShown()) { |
|
|
|
@ -346,6 +222,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(); |
|
|
|
} |
|
|
|
|