aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/renderer_vulkan/renderer_vulkan.cpp
diff options
context:
space:
mode:
authorCamilleLaVey <camillelavey@noreply.localhost>2025-04-19 22:33:59 +0000
committerCamilleLaVey <camillelavey@noreply.localhost>2025-04-19 22:33:59 +0000
commit922d678abd185eab71545f6fe33788635eb67285 (patch)
tree49edfc47713a05100f31b702ef066b3847bf3093 /src/video_core/renderer_vulkan/renderer_vulkan.cpp
parent6acf63839bfeb873e87cef75080b2ef6d3462a90 (diff)
parente77ef26293d36259b4cd5e7853e1e18319da8680 (diff)
Merge pull request 'Add changes of my fork into newer repo' (#32) from MrPurple666/eden:purple into master
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/32 Reviewed NCE modifications and implementations, those makes a good addition; merging also Briar features.
Diffstat (limited to 'src/video_core/renderer_vulkan/renderer_vulkan.cpp')
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp402
1 files changed, 401 insertions, 1 deletions
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 4a2d4b23ef..2ff38226cb 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
@@ -8,6 +9,8 @@
#include <optional>
#include <string>
#include <vector>
+#include <fstream>
+#include <filesystem>
#include <fmt/ranges.h>
@@ -33,9 +36,12 @@
#include "video_core/vulkan_common/vulkan_instance.h"
#include "video_core/vulkan_common/vulkan_library.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
+#include "video_core/vulkan_common/hybrid_memory.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 {
@@ -120,12 +126,93 @@ RendererVulkan::RendererVulkan(Core::Frontend::EmuWindow& emu_window,
PresentFiltersForAppletCapture),
rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker,
scheduler),
+ hybrid_memory(std::make_unique<HybridMemory>(device, memory_allocator)),
+ texture_manager(device, memory_allocator),
+ shader_manager(device),
applet_frame() {
if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) {
turbo_mode.emplace(instance, dld);
scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); });
}
+
+ // Initialize HybridMemory system
+ if (Settings::values.use_gpu_memory_manager.GetValue()) {
+#if defined(__linux__) || defined(__ANDROID__) || defined(_WIN32)
+ try {
+ // Define memory size with explicit types to avoid conversion warnings
+ constexpr size_t memory_size_mb = 64;
+ constexpr size_t memory_size_bytes = memory_size_mb * 1024 * 1024;
+
+ void* guest_memory_base = nullptr;
+#if defined(_WIN32)
+ // On Windows, use VirtualAlloc to reserve (but not commit) memory
+ const SIZE_T win_size = static_cast<SIZE_T>(memory_size_bytes);
+ LPVOID result = VirtualAlloc(nullptr, win_size, MEM_RESERVE, PAGE_NOACCESS);
+ if (result != nullptr) {
+ guest_memory_base = result;
+ }
+#else
+ // On Linux/Android, use aligned_alloc
+ guest_memory_base = std::aligned_alloc(4096, memory_size_bytes);
+#endif
+ if (guest_memory_base != nullptr) {
+ try {
+ hybrid_memory->InitializeGuestMemory(guest_memory_base, memory_size_bytes);
+ LOG_INFO(Render_Vulkan, "HybridMemory initialized with {} MB of fault-managed memory", memory_size_mb);
+ } catch (const std::exception&) {
+#if defined(_WIN32)
+ if (guest_memory_base != nullptr) {
+ const LPVOID win_ptr = static_cast<LPVOID>(guest_memory_base);
+ VirtualFree(win_ptr, 0, MEM_RELEASE);
+ }
+#else
+ std::free(guest_memory_base);
+#endif
+ throw;
+ }
+ }
+ } catch (const std::exception& e) {
+ LOG_ERROR(Render_Vulkan, "Failed to initialize HybridMemory: {}", e.what());
+ }
+#else
+ LOG_INFO(Render_Vulkan, "Fault-managed memory not supported on this platform");
+#endif
+ }
+
+ // Initialize enhanced shader compilation system
+ shader_manager.SetScheduler(&scheduler);
+ LOG_INFO(Render_Vulkan, "Enhanced shader compilation system initialized");
+
+ // Preload common shaders if enabled
+ if (Settings::values.use_asynchronous_shaders.GetValue()) {
+ // Use a simple shader directory path - can be updated to match Citron's actual path structure
+ const std::string shader_dir = "./shaders";
+ std::vector<std::string> common_shaders;
+
+ // Add paths to common shaders that should be preloaded
+ // These will be compiled in parallel for faster startup
+ try {
+ if (std::filesystem::exists(shader_dir)) {
+ for (const auto& entry : std::filesystem::directory_iterator(shader_dir)) {
+ if (entry.is_regular_file() && entry.path().extension() == ".spv") {
+ common_shaders.push_back(entry.path().string());
+ }
+ }
+
+ if (!common_shaders.empty()) {
+ LOG_INFO(Render_Vulkan, "Preloading {} common shaders", common_shaders.size());
+ shader_manager.PreloadShaders(common_shaders);
+ }
+ } else {
+ LOG_INFO(Render_Vulkan, "Shader directory not found at {}", shader_dir);
+ }
+ } catch (const std::exception& e) {
+ LOG_ERROR(Render_Vulkan, "Error during shader preloading: {}", e.what());
+ }
+ }
+
Report();
+ InitializePlatformSpecific();
} catch (const vk::Exception& exception) {
LOG_ERROR(Render_Vulkan, "Vulkan initialization failed with error: {}", exception.what());
throw std::runtime_error{fmt::format("Vulkan initialization error {}", exception.what())};
@@ -136,11 +223,145 @@ 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_uzuy_uzuy_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_uzuy_uzuy_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 = 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();
};
@@ -216,6 +437,35 @@ void RendererVulkan::RenderScreenshot(std::span<const Tegra::FramebufferConfig>
return;
}
+ // If memory snapshots are enabled, take a snapshot with the screenshot
+ if (Settings::values.enable_memory_snapshots.GetValue() && hybrid_memory) {
+ try {
+ const auto now = std::chrono::system_clock::now();
+ const auto now_time_t = std::chrono::system_clock::to_time_t(now);
+ std::tm local_tm;
+#ifdef _WIN32
+ localtime_s(&local_tm, &now_time_t);
+#else
+ localtime_r(&now_time_t, &local_tm);
+#endif
+ char time_str[128];
+ std::strftime(time_str, sizeof(time_str), "%Y%m%d_%H%M%S", &local_tm);
+
+ std::string snapshot_path = fmt::format("snapshots/memory_snapshot_{}.bin", time_str);
+ hybrid_memory->SaveSnapshot(snapshot_path);
+
+ // Also save a differential snapshot if there's been a previous snapshot
+ if (Settings::values.use_gpu_memory_manager.GetValue()) {
+ std::string diff_path = fmt::format("snapshots/diff_snapshot_{}.bin", time_str);
+ hybrid_memory->SaveDifferentialSnapshot(diff_path);
+ hybrid_memory->ResetDirtyTracking();
+ LOG_INFO(Render_Vulkan, "Memory snapshots saved with screenshot");
+ }
+ } catch (const std::exception& e) {
+ LOG_ERROR(Render_Vulkan, "Failed to save memory snapshot: {}", e.what());
+ }
+ }
+
const auto& layout{renderer_settings.screenshot_framebuffer_layout};
const auto dst_buffer = RenderToBuffer(framebuffers, layout, VK_FORMAT_B8G8R8A8_UNORM,
layout.width * layout.height * 4);
@@ -267,4 +517,154 @@ void RendererVulkan::RenderAppletCaptureLayer(
CaptureFormat);
}
+void RendererVulkan::FixMSAADepthStencil(VkCommandBuffer cmd_buffer, const Framebuffer& framebuffer) {
+ if (framebuffer.Samples() == VK_SAMPLE_COUNT_1_BIT) {
+ return;
+ }
+
+ // Use the scheduler's command buffer wrapper
+ scheduler.Record([&](vk::CommandBuffer cmdbuf) {
+ // Find the depth/stencil image in the framebuffer's attachments
+ for (u32 i = 0; i < framebuffer.NumImages(); ++i) {
+ if (framebuffer.HasAspectDepthBit() && (framebuffer.ImageRanges()[i].aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT)) {
+ VkImageMemoryBarrier barrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = framebuffer.Images()[i],
+ .subresourceRange = framebuffer.ImageRanges()[i]
+ };
+
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0, nullptr, nullptr, barrier);
+ break;
+ }
+ }
+ });
+}
+
+void RendererVulkan::ResolveMSAA(VkCommandBuffer cmd_buffer, const Framebuffer& framebuffer) {
+ if (framebuffer.Samples() == VK_SAMPLE_COUNT_1_BIT) {
+ return;
+ }
+
+ // Use the scheduler's command buffer wrapper
+ scheduler.Record([&](vk::CommandBuffer cmdbuf) {
+ // Find color attachments
+ for (u32 i = 0; i < framebuffer.NumColorBuffers(); ++i) {
+ if (framebuffer.HasAspectColorBit(i)) {
+ VkImageResolve resolve_region{
+ .srcSubresource{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .srcOffset = {0, 0, 0},
+ .dstSubresource{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .dstOffset = {0, 0, 0},
+ .extent{
+ .width = framebuffer.RenderArea().width,
+ .height = framebuffer.RenderArea().height,
+ .depth = 1
+ }
+ };
+
+ cmdbuf.ResolveImage(
+ framebuffer.Images()[i], VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ framebuffer.Images()[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ resolve_region
+ );
+ }
+ }
+ });
+}
+
+bool RendererVulkan::HandleVulkanError(VkResult result, const std::string& operation) {
+ if (result == VK_SUCCESS) {
+ return true;
+ }
+
+ if (result == VK_ERROR_DEVICE_LOST) {
+ LOG_CRITICAL(Render_Vulkan, "Vulkan device lost during {}", operation);
+ RecoverFromError();
+ return false;
+ }
+
+ if (result == VK_ERROR_OUT_OF_DEVICE_MEMORY || result == VK_ERROR_OUT_OF_HOST_MEMORY) {
+ LOG_CRITICAL(Render_Vulkan, "Vulkan out of memory during {}", operation);
+ // Potential recovery: clear caches, reduce workload
+ texture_manager.CleanupTextureCache();
+ return false;
+ }
+
+ LOG_ERROR(Render_Vulkan, "Vulkan error during {}: {}", operation, result);
+ return false;
+}
+
+void RendererVulkan::RecoverFromError() {
+ LOG_INFO(Render_Vulkan, "Attempting to recover from Vulkan error");
+
+ // Wait for device to finish operations
+ void(device.GetLogical().WaitIdle());
+
+ // Process all pending commands in our queue
+ ProcessAllCommands();
+
+ // Wait for any async shader compilations to finish
+ shader_manager.WaitForCompilation();
+
+ // Clean up resources that might be causing problems
+ texture_manager.CleanupTextureCache();
+
+ // Reset command buffers and pipelines
+ scheduler.Flush();
+
+ LOG_INFO(Render_Vulkan, "Recovery attempt completed");
+}
+
+void RendererVulkan::InitializePlatformSpecific() {
+ LOG_INFO(Render_Vulkan, "Initializing platform-specific Vulkan components");
+
+#if defined(_WIN32) || defined(_WIN64)
+ LOG_INFO(Render_Vulkan, "Initializing Vulkan for Windows");
+ // Windows-specific initialization
+#elif defined(__linux__)
+ LOG_INFO(Render_Vulkan, "Initializing Vulkan for Linux");
+ // Linux-specific initialization
+#elif defined(__ANDROID__)
+ LOG_INFO(Render_Vulkan, "Initializing Vulkan for Android");
+ // Android-specific initialization
+#else
+ LOG_INFO(Render_Vulkan, "Platform-specific Vulkan initialization not implemented for this platform");
+#endif
+
+ // Create a compute buffer using the HybridMemory system if enabled
+ if (Settings::values.use_gpu_memory_manager.GetValue()) {
+ try {
+ // Create a small compute buffer for testing
+ const VkDeviceSize buffer_size = 1 * 1024 * 1024; // 1 MB
+ ComputeBuffer compute_buffer = hybrid_memory->CreateComputeBuffer(
+ buffer_size,
+ VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ MemoryUsage::DeviceLocal);
+
+ LOG_INFO(Render_Vulkan, "Successfully created compute buffer using HybridMemory");
+ } catch (const std::exception& e) {
+ LOG_ERROR(Render_Vulkan, "Failed to create compute buffer: {}", e.what());
+ }
+ }
+}
+
} // namespace Vulkan