@ -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,144 +187,97 @@ 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 ;
}
void RendererVulkan : : InterpolateFrames ( Frame * prev_frame , Frame * interpolated_frame ) {
if ( ! prev_frame | | ! interpolated_frame | | ! prev_frame - > image | | ! interpolated_frame - > image ) {
return ;
}
private :
bool value ;
const auto & framebuffer_layout = render_window . GetFramebufferLayout ( ) ;
// Fixed aggressive downscale (50%)
VkExtent2D dst_extent {
. width = framebuffer_layout . width / 2 ,
. height = framebuffer_layout . height / 2
} ;
// 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 ( ) ) ;
// 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 ;
}
}
void RendererVulkan : : InterpolateFrames ( Frame * prev_frame , Frame * interpolated_frame ) {
if ( ! prev_frame | | ! interpolated_frame | | ! prev_frame - > image | | ! interpolated_frame - > image ) {
return ;
}
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 ( ) ) ;
}
const auto & framebuffer_layout = render_window . GetFramebufferLayout ( ) ;
// Fixed aggressive downscale (50%)
VkExtent2D dst_extent {
. width = framebuffer_layout . width / 2 ,
. height = framebuffer_layout . height / 2
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
} ;
// 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 ( ) ) ;
}
// 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
) ;
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
// Transition back to general layout
TransitionImageLayout ( cmdbuf , * prev_frame - > image , VK_IMAGE_LAYOUT_GENERAL ) ;
TransitionImageLayout ( cmdbuf , * interpolated_frame - > image , VK_IMAGE_LAYOUT_GENERAL ) ;
} ) ;
}
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 ;
}
bool frame_interpolation_enabled = Settings : : values . frame_interpolation . GetValue ( ) ;
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 ) ;
}
if ( frame_interpolation_enabled & & 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 ) ;
// Optionally, update previous_frame here if you want to chain interpolations
previous_frame = interpolated_frame ;
return ;
}
# endif
SCOPE_EXIT {
render_window . OnFrameDisplayed ( ) ;
@ -346,6 +297,11 @@ void RendererVulkan::Composite(std::span<const Tegra::FramebufferConfig> framebu
scheduler . Flush ( * frame - > render_ready ) ;
present_manager . Present ( frame ) ;
// Store the current frame for interpolation on the next call
if ( frame_interpolation_enabled ) {
previous_frame = frame ;
}
gpu . RendererFrameEndNotify ( ) ;
rasterizer . TickFrame ( ) ;
}