diff options
| author | CamilleLaVey <camillelavey@noreply.localhost> | 2025-04-19 22:33:59 +0000 |
|---|---|---|
| committer | CamilleLaVey <camillelavey@noreply.localhost> | 2025-04-19 22:33:59 +0000 |
| commit | 922d678abd185eab71545f6fe33788635eb67285 (patch) | |
| tree | 49edfc47713a05100f31b702ef066b3847bf3093 /src/video_core/renderer_vulkan/renderer_vulkan.cpp | |
| parent | 6acf63839bfeb873e87cef75080b2ef6d3462a90 (diff) | |
| parent | e77ef26293d36259b4cd5e7853e1e18319da8680 (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.cpp | 402 |
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 |
