diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 6abdc1e1bd..31f8a62397 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -18,7 +18,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"), RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"), RENDERER_DEBUG("debug"), - RENDERER_ENHANCED_SHADER_BUILDING("use_enhanced_shader_building"), PICTURE_IN_PICTURE("picture_in_picture"), USE_CUSTOM_RTC("custom_rtc_enabled"), BLACK_BACKGROUNDS("black_backgrounds"), diff --git a/src/common/settings.h b/src/common/settings.h index 0ddc5f85f8..b16106985b 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -1,5 +1,4 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -644,21 +643,11 @@ struct Values { // Add-Ons std::map> disabled_addons; - - // Renderer Advanced Settings - SwitchableSetting use_enhanced_shader_building{linkage, false, "Enhanced Shader Building", - Category::RendererAdvanced}; - - // Add a new setting for shader compilation priority - SwitchableSetting shader_compilation_priority{linkage, 0, "Shader Compilation Priority", - Category::RendererAdvanced}; }; extern Values values; void UpdateGPUAccuracy(); -// boold isGPULevelNormal(); -// TODO: ZEP bool IsGPULevelExtreme(); bool IsGPULevelHigh(); diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index ccbcc2341f..744b686fe6 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -1,5 +1,4 @@ # SPDX-FileCopyrightText: 2018 yuzu Emulator Project -# SPDX-FileCopyrightText: 2025 Citron Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later add_subdirectory(host_shaders) @@ -246,8 +245,6 @@ add_library(video_core STATIC renderer_vulkan/vk_turbo_mode.h renderer_vulkan/vk_update_descriptor.cpp renderer_vulkan/vk_update_descriptor.h - renderer_vulkan/vk_texture_manager.cpp - renderer_vulkan/vk_texture_manager.h shader_cache.cpp shader_cache.h shader_environment.cpp diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp index e25f731fea..af0a453ee7 100644 --- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp @@ -1,13 +1,10 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include -#include -#include #include "common/settings.h" // for enum class Settings::ShaderBackend #include "common/thread_worker.h" @@ -237,68 +234,26 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c auto func{[this, sources_ = std::move(sources), sources_spirv_ = std::move(sources_spirv), shader_notify, backend, in_parallel, force_context_flush](ShaderContext::Context*) mutable { - // Track time for shader compilation for possible performance tuning - const auto start_time = std::chrono::high_resolution_clock::now(); - - // Prepare compilation steps for all shader stages - std::vector> compilation_steps; - compilation_steps.reserve(5); // Maximum number of shader stages - - // Prepare all compilation steps first to better distribute work for (size_t stage = 0; stage < 5; ++stage) { switch (backend) { case Settings::ShaderBackend::Glsl: if (!sources_[stage].empty()) { - compilation_steps.emplace_back([this, stage, source = sources_[stage]]() { - source_programs[stage] = CreateProgram(source, Stage(stage)); - }); + source_programs[stage] = CreateProgram(sources_[stage], Stage(stage)); } break; case Settings::ShaderBackend::Glasm: if (!sources_[stage].empty()) { - compilation_steps.emplace_back([this, stage, source = sources_[stage]]() { - assembly_programs[stage] = CompileProgram(source, AssemblyStage(stage)); - }); + assembly_programs[stage] = + CompileProgram(sources_[stage], AssemblyStage(stage)); } break; case Settings::ShaderBackend::SpirV: if (!sources_spirv_[stage].empty()) { - compilation_steps.emplace_back([this, stage, source = sources_spirv_[stage]]() { - source_programs[stage] = CreateProgram(source, Stage(stage)); - }); + source_programs[stage] = CreateProgram(sources_spirv_[stage], Stage(stage)); } break; } } - - // If we're running in parallel, use high-priority execution for vertex and fragment shaders - // as these are typically needed first by the renderer - if (in_parallel && compilation_steps.size() > 1) { - // Execute vertex (0) and fragment (4) shaders first if they exist - for (size_t priority_stage : {0, 4}) { - for (size_t i = 0; i < compilation_steps.size(); ++i) { - if ((i == priority_stage || (priority_stage == 0 && i <= 1)) && i < compilation_steps.size()) { - compilation_steps[i](); - compilation_steps[i] = [](){}; // Mark as executed - } - } - } - } - - // Execute all remaining compilation steps - for (auto& step : compilation_steps) { - step(); // Will do nothing for already executed steps - } - - // Performance measurement for possible logging or optimization - const auto end_time = std::chrono::high_resolution_clock::now(); - const auto compilation_time = std::chrono::duration_cast( - end_time - start_time).count(); - - if (compilation_time > 50) { // Only log slow compilations - LOG_DEBUG(Render_OpenGL, "Shader compilation took {}ms", compilation_time); - } - if (force_context_flush || in_parallel) { std::scoped_lock lock{built_mutex}; built_fence.Create(); @@ -668,41 +623,15 @@ void GraphicsPipeline::WaitForBuild() { is_built = true; } -bool GraphicsPipeline::IsBuilt() const noexcept { +bool GraphicsPipeline::IsBuilt() noexcept { if (is_built) { return true; } - if (!built_fence.handle) { + if (built_fence.handle == 0) { return false; } - - // Check if the async build has finished by polling the fence - const GLsync sync = built_fence.handle; - const GLuint result = glClientWaitSync(sync, 0, 0); - if (result == GL_ALREADY_SIGNALED || result == GL_CONDITION_SATISFIED) { - // Mark this as mutable even though we're in a const method - this is - // essentially a cached value update which is acceptable - const_cast(this)->is_built = true; - return true; - } - - // For better performance tracking, capture time spent waiting for shaders - static thread_local std::chrono::high_resolution_clock::time_point last_shader_wait_log; - static thread_local u32 shader_wait_count = 0; - - auto now = std::chrono::high_resolution_clock::now(); - auto elapsed = std::chrono::duration_cast( - now - last_shader_wait_log).count(); - - // Log shader compilation status periodically to help diagnose performance issues - if (elapsed >= 5) { // Log every 5 seconds - shader_wait_count++; - LOG_DEBUG(Render_OpenGL, "Waiting for async shader compilation... (count={})", - shader_wait_count); - last_shader_wait_log = now; - } - - return false; + is_built = built_fence.IsSignaled(); + return is_built; } } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.h b/src/video_core/renderer_opengl/gl_graphics_pipeline.h index 5852c02893..2f70c1ae9c 100644 --- a/src/video_core/renderer_opengl/gl_graphics_pipeline.h +++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.h @@ -1,5 +1,4 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -103,7 +102,7 @@ public: return uses_local_memory; } - [[nodiscard]] bool IsBuilt() const noexcept; + [[nodiscard]] bool IsBuilt() noexcept; template static auto MakeConfigureSpecFunc() { diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index a99992a518..c4bad6fca5 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -1,5 +1,4 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -609,33 +608,9 @@ std::unique_ptr ShaderCache::CreateComputePipeline( } std::unique_ptr ShaderCache::CreateWorkers() const { - // Calculate optimal number of workers based on available CPU cores - // Leave at least 1 core for main thread and other operations - // Use more cores for more parallelism in shader compilation - const u32 num_worker_threads = std::max(std::thread::hardware_concurrency(), 2U); - const u32 optimal_workers = num_worker_threads <= 3 ? - num_worker_threads - 1 : // On dual/quad core, leave 1 core free - num_worker_threads - 2; // On 6+ core systems, leave 2 cores free for other tasks - - auto worker = std::make_unique( - optimal_workers, - "GlShaderBuilder", - [this] { - auto context = Context{emu_window}; - - // Apply thread priority based on settings - // This allows users to control how aggressive shader compilation is - const int priority = Settings::values.shader_compilation_priority.GetValue(); - if (priority != 0) { - Common::SetCurrentThreadPriority( - priority > 0 ? Common::ThreadPriority::High : Common::ThreadPriority::Low); - } - - return context; - } - ); - - return worker; + return std::make_unique(std::max(std::thread::hardware_concurrency(), 2U) - 1, + "GlShaderBuilder", + [this] { return Context{emu_window}; }); } } // namespace OpenGL diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 2ff38226cb..c4fe8235c7 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -1,5 +1,4 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -127,8 +126,6 @@ RendererVulkan::RendererVulkan(Core::Frontend::EmuWindow& emu_window, rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker, scheduler), hybrid_memory(std::make_unique(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); @@ -178,41 +175,7 @@ RendererVulkan::RendererVulkan(Core::Frontend::EmuWindow& emu_window, 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 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())}; @@ -517,154 +480,4 @@ 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 diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 57e2942873..0a606d6fed 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -1,5 +1,4 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -7,7 +6,6 @@ #include #include #include -#include #include "common/dynamic_library.h" #include "video_core/host1x/gpu_device_memory_manager.h" @@ -19,8 +17,6 @@ #include "video_core/renderer_vulkan/vk_state_tracker.h" #include "video_core/renderer_vulkan/vk_swapchain.h" #include "video_core/renderer_vulkan/vk_turbo_mode.h" -#include "video_core/renderer_vulkan/vk_texture_manager.h" -#include "video_core/renderer_vulkan/vk_shader_util.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_memory_allocator.h" #include "video_core/vulkan_common/hybrid_memory.h" @@ -58,9 +54,6 @@ public: return device.GetDriverName(); } - void FixMSAADepthStencil(VkCommandBuffer cmd_buffer, const Framebuffer& framebuffer); - void ResolveMSAA(VkCommandBuffer cmd_buffer, const Framebuffer& framebuffer); - // Enhanced platform-specific initialization void InitializePlatformSpecific(); @@ -77,10 +70,6 @@ private: void RenderScreenshot(std::span framebuffers); void RenderAppletCaptureLayer(std::span framebuffers); - // Enhanced error handling - bool HandleVulkanError(VkResult result, const std::string& operation); - void RecoverFromError(); - Tegra::MaxwellDeviceMemoryManager& device_memory; Tegra::GPU& gpu; @@ -106,10 +95,6 @@ private: // HybridMemory for advanced memory management std::unique_ptr hybrid_memory; - // Enhanced texture and shader management - TextureManager texture_manager; - ShaderManager shader_manager; - Frame applet_frame; }; diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index f154f3073b..73e585c2b7 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -1,10 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include -#include #include @@ -39,23 +37,10 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel if (shader_notify) { shader_notify->MarkShaderBuilding(); } - - // Track compilation start time for performance metrics - const auto start_time = std::chrono::high_resolution_clock::now(); - std::copy_n(info.constant_buffer_used_sizes.begin(), uniform_buffer_sizes.size(), - uniform_buffer_sizes.begin()); - - auto func{[this, &descriptor_pool, shader_notify, pipeline_statistics, start_time] { - // Simplify the high priority determination - we can't use workgroup_size - // because it doesn't exist, so use a simpler heuristic - const bool is_high_priority = false; // Default to false until we can find a better criterion - - if (is_high_priority) { - // Increase thread priority for small compute shaders that are likely part of critical path - Common::SetCurrentThreadPriority(Common::ThreadPriority::High); - } + uniform_buffer_sizes.begin()); + auto func{[this, &descriptor_pool, shader_notify, pipeline_statistics] { DescriptorLayoutBuilder builder{device}; builder.Add(info, VK_SHADER_STAGE_COMPUTE_BIT); @@ -64,11 +49,15 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel descriptor_update_template = builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, false); descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, info); + const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT, + .pNext = nullptr, + .requiredSubgroupSize = GuestWarpSize, + }; VkPipelineCreateFlags flags{}; if (device.IsKhrPipelineExecutablePropertiesEnabled()) { flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; } - pipeline = device.GetLogical().CreateComputePipeline( { .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, @@ -76,7 +65,8 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel .flags = flags, .stage{ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - .pNext = nullptr, + .pNext = + device.IsExtSubgroupSizeControlSupported() ? &subgroup_size_ci : nullptr, .flags = 0, .stage = VK_SHADER_STAGE_COMPUTE_BIT, .module = *spv_module, @@ -89,15 +79,6 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel }, *pipeline_cache); - // Performance measurement - const auto end_time = std::chrono::high_resolution_clock::now(); - const auto compilation_time = std::chrono::duration_cast( - end_time - start_time).count(); - - if (compilation_time > 50) { // Only log slow compilations - LOG_DEBUG(Render_Vulkan, "Compiled compute shader in {}ms", compilation_time); - } - if (pipeline_statistics) { pipeline_statistics->Collect(*pipeline); } diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index b73b7630f5..2765a44f3c 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -1,5 +1,4 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -260,16 +259,7 @@ GraphicsPipeline::GraphicsPipeline( std::ranges::copy(info->constant_buffer_used_sizes, uniform_buffer_sizes[stage].begin()); num_textures += Shader::NumDescriptors(info->texture_descriptors); } - - // Track compilation start time for performance metrics - const auto start_time = std::chrono::high_resolution_clock::now(); - - auto func{[this, shader_notify, &render_pass_cache, &descriptor_pool, pipeline_statistics, start_time] { - // Use enhanced shader compilation if enabled in settings - if (Settings::values.use_enhanced_shader_building.GetValue()) { - Common::SetCurrentThreadPriority(Common::ThreadPriority::High); - } - + auto func{[this, shader_notify, &render_pass_cache, &descriptor_pool, pipeline_statistics] { DescriptorLayoutBuilder builder{MakeBuilder(device, stage_infos)}; uses_push_descriptor = builder.CanUsePushDescriptor(); descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor); @@ -284,17 +274,6 @@ GraphicsPipeline::GraphicsPipeline( const VkRenderPass render_pass{render_pass_cache.Get(MakeRenderPassKey(key.state))}; Validate(); MakePipeline(render_pass); - - // Performance measurement - const auto end_time = std::chrono::high_resolution_clock::now(); - const auto compilation_time = std::chrono::duration_cast( - end_time - start_time).count(); - - // Log shader compilation time for slow shaders to help diagnose performance issues - if (compilation_time > 100) { // Only log very slow compilations - LOG_DEBUG(Render_Vulkan, "Compiled graphics pipeline in {}ms", compilation_time); - } - if (pipeline_statistics) { pipeline_statistics->Collect(*pipeline); } @@ -333,9 +312,6 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { const auto& regs{maxwell3d->regs}; const bool via_header_index{regs.sampler_binding == Maxwell::SamplerBinding::ViaHeaderBinding}; const auto config_stage{[&](size_t stage) LAMBDA_FORCEINLINE { - // Get the constant buffer information from Maxwell's state - const auto& cbufs = maxwell3d->state.shader_stages[stage].const_buffers; - const Shader::Info& info{stage_infos[stage]}; buffer_cache.UnbindGraphicsStorageBuffers(stage); if constexpr (Spec::has_storage_buffers) { @@ -347,7 +323,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { ++ssbo_index; } } - + const auto& cbufs{maxwell3d->state.shader_stages[stage].const_buffers}; const auto read_handle{[&](const auto& desc, u32 index) { ASSERT(cbufs[desc.cbuf_index].enabled); const u32 index_offset{index << desc.size_shift}; @@ -369,7 +345,6 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { } return TexturePair(gpu_memory->Read(addr), via_header_index); }}; - const auto add_image{[&](const auto& desc, bool blacklist) LAMBDA_FORCEINLINE { for (u32 index = 0; index < desc.count; ++index) { const auto handle{read_handle(desc, index)}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 104c1ed46a..a7bba4eaba 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -1,5 +1,4 @@ // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -266,42 +265,18 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program } size_t GetTotalPipelineWorkers() { - const size_t num_cores = std::max(static_cast(std::thread::hardware_concurrency()), 2ULL); - - // Calculate optimal number of workers based on available CPU cores - size_t optimal_workers; - + const size_t max_core_threads = + std::max(static_cast(std::thread::hardware_concurrency()), 2ULL) - 1ULL; #ifdef ANDROID - // Mobile devices need more conservative threading to avoid thermal issues - // Leave more cores free on Android for system processes and other apps - constexpr size_t min_free_cores = 3ULL; - if (num_cores <= min_free_cores + 1) { - return 1ULL; // At least one worker + // Leave at least a few cores free in android + constexpr size_t free_cores = 3ULL; + if (max_core_threads <= free_cores) { + return 1ULL; } - optimal_workers = num_cores - min_free_cores; + return max_core_threads - free_cores; #else - // Desktop systems can use more aggressive threading - if (num_cores <= 3) { - optimal_workers = num_cores - 1; // Dual/triple core: leave 1 core free - } else if (num_cores <= 6) { - optimal_workers = num_cores - 2; // Quad/hex core: leave 2 cores free - } else { - // For 8+ core systems, use more workers but still leave some cores for other tasks - optimal_workers = num_cores - (num_cores / 4); // Leave ~25% of cores free - } + return max_core_threads; #endif - - // Apply threading priority via shader_compilation_priority setting if enabled - const int priority = Settings::values.shader_compilation_priority.GetValue(); - if (priority > 0) { - // High priority - use more cores for shader compilation - optimal_workers = std::min(optimal_workers + 1, num_cores - 1); - } else if (priority < 0) { - // Low priority - use fewer cores for shader compilation - optimal_workers = (optimal_workers >= 2) ? optimal_workers - 1 : 1; - } - - return optimal_workers; } } // Anonymous namespace @@ -619,35 +594,14 @@ GraphicsPipeline* PipelineCache::BuiltPipeline(GraphicsPipeline* pipeline) const if (pipeline->IsBuilt()) { return pipeline; } - if (!use_asynchronous_shaders) { return pipeline; } - - // Advanced heuristics for smarter async shader compilation - - // Track stutter metrics for better debugging and performance tuning - static thread_local u32 async_shader_count = 0; - static thread_local std::chrono::high_resolution_clock::time_point last_async_shader_log; - auto now = std::chrono::high_resolution_clock::now(); - - // Simplify UI shader detection since we don't have access to clear_buffers - const bool is_ui_shader = !maxwell3d->regs.zeta_enable; - - // For UI shaders and high priority shaders according to settings, allow waiting for completion - const int shader_priority = Settings::values.shader_compilation_priority.GetValue(); - if ((is_ui_shader && shader_priority >= 0) || shader_priority > 1) { - // For UI/menu elements and critical visuals, let's wait for the shader to compile - // but only if high shader priority - return pipeline; - } - // If something is using depth, we can assume that games are not rendering anything which // will be used one time. if (maxwell3d->regs.zeta_enable) { return nullptr; } - // If games are using a small index count, we can assume these are full screen quads. // Usually these shaders are only used once for building textures so we can assume they // can't be built async @@ -655,23 +609,6 @@ GraphicsPipeline* PipelineCache::BuiltPipeline(GraphicsPipeline* pipeline) const if (draw_state.index_buffer.count <= 6 || draw_state.vertex_buffer.count <= 6) { return pipeline; } - - // Track and log async shader statistics periodically - auto elapsed = std::chrono::duration_cast( - now - last_async_shader_log).count(); - - if (elapsed >= 10) { // Log every 10 seconds - async_shader_count = 0; - last_async_shader_log = now; - } - async_shader_count++; - - // Log less frequently to avoid spamming log - if (async_shader_count % 100 == 1) { - LOG_DEBUG(Render_Vulkan, "Async shader compilation in progress (count={})", - async_shader_count); - } - return nullptr; } diff --git a/src/video_core/renderer_vulkan/vk_shader_util.cpp b/src/video_core/renderer_vulkan/vk_shader_util.cpp index c2d365411a..7a0a2b154a 100644 --- a/src/video_core/renderer_vulkan/vk_shader_util.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_util.cpp @@ -1,141 +1,15 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include "common/common_types.h" -#include "common/logging/log.h" #include "video_core/renderer_vulkan/vk_shader_util.h" -#include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" -#define SHADER_CACHE_DIR "./shader_cache" - namespace Vulkan { -// Global command submission queue for asynchronous operations -std::mutex commandQueueMutex; -std::queue> commandQueue; -std::condition_variable commandQueueCondition; -std::atomic isCommandQueueActive{true}; -std::thread commandQueueThread; - -// Pointer to Citron's scheduler for integration -Scheduler* globalScheduler = nullptr; - -// Command queue worker thread (multi-threaded command recording) -void CommandQueueWorker() { - while (isCommandQueueActive.load()) { - std::function command; - { - std::unique_lock lock(commandQueueMutex); - if (commandQueue.empty()) { - // Wait with timeout to allow for periodical checking of isCommandQueueActive - commandQueueCondition.wait_for(lock, std::chrono::milliseconds(100), - []{ return !commandQueue.empty() || !isCommandQueueActive.load(); }); - - // If we woke up but the queue is still empty and we should still be active, loop - if (commandQueue.empty()) { - continue; - } - } - - command = commandQueue.front(); - commandQueue.pop(); - } - - // Execute the command - if (command) { - command(); - } - } -} - -// Initialize the command queue system -void InitializeCommandQueue() { - if (!commandQueueThread.joinable()) { - isCommandQueueActive.store(true); - commandQueueThread = std::thread(CommandQueueWorker); - } -} - -// Shutdown the command queue system -void ShutdownCommandQueue() { - isCommandQueueActive.store(false); - commandQueueCondition.notify_all(); - - if (commandQueueThread.joinable()) { - commandQueueThread.join(); - } -} - -// Submit a command to the queue for asynchronous execution -void SubmitCommandToQueue(std::function command) { - { - std::lock_guard lock(commandQueueMutex); - commandQueue.push(command); - } - commandQueueCondition.notify_one(); -} - -// Set the global scheduler reference for command integration -void SetGlobalScheduler(Scheduler* scheduler) { - globalScheduler = scheduler; -} - -// Submit a Vulkan command to the existing Citron scheduler -void SubmitToScheduler(std::function command) { - if (globalScheduler) { - globalScheduler->Record(std::move(command)); - } else { - LOG_WARNING(Render_Vulkan, "Trying to submit to scheduler but no scheduler is set"); - } -} - -// Flush the Citron scheduler - use when needing to ensure commands are executed -u64 FlushScheduler(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) { - if (globalScheduler) { - return globalScheduler->Flush(signal_semaphore, wait_semaphore); - } else { - LOG_WARNING(Render_Vulkan, "Trying to flush scheduler but no scheduler is set"); - return 0; - } -} - -// Process both command queue and scheduler commands -void ProcessAllCommands() { - // Process our command queue first - { - std::unique_lock lock(commandQueueMutex); - while (!commandQueue.empty()) { - auto command = commandQueue.front(); - commandQueue.pop(); - lock.unlock(); - - command(); - - lock.lock(); - } - } - - // Then flush the scheduler if it exists - if (globalScheduler) { - globalScheduler->Flush(); - } -} - vk::ShaderModule BuildShader(const Device& device, std::span code) { return device.GetLogical().CreateShaderModule({ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, @@ -146,368 +20,4 @@ vk::ShaderModule BuildShader(const Device& device, std::span code) { }); } -bool IsShaderValid(VkShaderModule shader_module) { - // TODO: validate the shader by checking if it's null - // or by examining SPIR-V data for correctness [ZEP] - return shader_module != VK_NULL_HANDLE; -} - -// Atomic flag for tracking shader compilation status -std::atomic compilingShader(false); - -void AsyncCompileShader(const Device& device, const std::string& shader_path, - std::function callback) { - LOG_INFO(Render_Vulkan, "Asynchronously compiling shader: {}", shader_path); - - // Create shader cache directory if it doesn't exist - if (!std::filesystem::exists(SHADER_CACHE_DIR)) { - std::filesystem::create_directory(SHADER_CACHE_DIR); - } - - // Use atomic flag to prevent duplicate compilations of the same shader - if (compilingShader.exchange(true)) { - LOG_WARNING(Render_Vulkan, "Shader compilation already in progress, skipping: {}", shader_path); - return; - } - - // Use actual threading for async compilation - std::thread([device_ptr = &device, shader_path, callback = std::move(callback)]() mutable { - auto startTime = std::chrono::high_resolution_clock::now(); - - try { - std::vector spir_v; - bool success = false; - - // Check if the file exists and attempt to read it - if (std::filesystem::exists(shader_path)) { - std::ifstream shader_file(shader_path, std::ios::binary); - if (shader_file) { - shader_file.seekg(0, std::ios::end); - size_t file_size = static_cast(shader_file.tellg()); - shader_file.seekg(0, std::ios::beg); - - spir_v.resize(file_size / sizeof(u32)); - if (shader_file.read(reinterpret_cast(spir_v.data()), file_size)) { - success = true; - } - } - } - - if (success) { - vk::ShaderModule shader = BuildShader(*device_ptr, spir_v); - if (IsShaderValid(*shader)) { - // Cache the compiled shader to disk for faster loading next time - std::string cache_path = std::string(SHADER_CACHE_DIR) + "/" + - std::filesystem::path(shader_path).filename().string() + ".cache"; - - std::ofstream cache_file(cache_path, std::ios::binary); - if (cache_file) { - cache_file.write(reinterpret_cast(spir_v.data()), - spir_v.size() * sizeof(u32)); - } - - auto endTime = std::chrono::high_resolution_clock::now(); - std::chrono::duration duration = endTime - startTime; - LOG_INFO(Render_Vulkan, "Shader compiled in {:.2f} seconds: {}", - duration.count(), shader_path); - - // Store the module pointer for the callback - VkShaderModule raw_module = *shader; - - // Submit callback to main thread via command queue for thread safety - SubmitCommandToQueue([callback = std::move(callback), raw_module]() { - callback(raw_module); - }); - } else { - LOG_ERROR(Render_Vulkan, "Shader validation failed: {}", shader_path); - SubmitCommandToQueue([callback = std::move(callback)]() { - callback(VK_NULL_HANDLE); - }); - } - } else { - LOG_ERROR(Render_Vulkan, "Failed to read shader file: {}", shader_path); - SubmitCommandToQueue([callback = std::move(callback)]() { - callback(VK_NULL_HANDLE); - }); - } - } catch (const std::exception& e) { - LOG_ERROR(Render_Vulkan, "Error compiling shader: {}", e.what()); - SubmitCommandToQueue([callback = std::move(callback)]() { - callback(VK_NULL_HANDLE); - }); - } - - // Release the compilation flag - compilingShader.store(false); - }).detach(); -} - -ShaderManager::ShaderManager(const Device& device_) : device(device_) { - // Initialize command queue system - InitializeCommandQueue(); -} - -ShaderManager::~ShaderManager() { - // Wait for any pending compilations to finish - WaitForCompilation(); - - // Clean up shader modules - std::lock_guard lock(shader_mutex); - shader_cache.clear(); - - // Shutdown command queue - ShutdownCommandQueue(); -} - -VkShaderModule ShaderManager::GetShaderModule(const std::string& shader_path) { - // Check in-memory cache first - { - std::lock_guard lock(shader_mutex); - auto it = shader_cache.find(shader_path); - if (it != shader_cache.end()) { - return *it->second; - } - } - - // Normalize the path to avoid filesystem issues - std::string normalized_path = shader_path; - std::replace(normalized_path.begin(), normalized_path.end(), '\\', '/'); - - // Check if shader exists - if (!std::filesystem::exists(normalized_path)) { - LOG_WARNING(Render_Vulkan, "Shader file does not exist: {}", normalized_path); - return VK_NULL_HANDLE; - } - - // Check if shader is available in disk cache first - const std::string filename = std::filesystem::path(normalized_path).filename().string(); - std::string cache_path = std::string(SHADER_CACHE_DIR) + "/" + filename + ".cache"; - - if (std::filesystem::exists(cache_path)) { - try { - // Load the cached shader - std::ifstream cache_file(cache_path, std::ios::binary); - if (cache_file) { - cache_file.seekg(0, std::ios::end); - size_t file_size = static_cast(cache_file.tellg()); - - if (file_size > 0 && file_size % sizeof(u32) == 0) { - cache_file.seekg(0, std::ios::beg); - std::vector spir_v; - spir_v.resize(file_size / sizeof(u32)); - - if (cache_file.read(reinterpret_cast(spir_v.data()), file_size)) { - vk::ShaderModule shader = BuildShader(device, spir_v); - if (IsShaderValid(*shader)) { - // Store in memory cache - std::lock_guard lock(shader_mutex); - shader_cache[normalized_path] = std::move(shader); - LOG_INFO(Render_Vulkan, "Loaded shader from cache: {}", normalized_path); - return *shader_cache[normalized_path]; - } - } - } - } - } catch (const std::exception& e) { - LOG_WARNING(Render_Vulkan, "Failed to load shader from cache: {}", e.what()); - // Continue to load from original file - } - } - - // Try to load the shader directly if cache load failed - if (LoadShader(normalized_path)) { - std::lock_guard lock(shader_mutex); - return *shader_cache[normalized_path]; - } - - LOG_ERROR(Render_Vulkan, "Failed to load shader: {}", normalized_path); - return VK_NULL_HANDLE; -} - -void ShaderManager::ReloadShader(const std::string& shader_path) { - LOG_INFO(Render_Vulkan, "Reloading shader: {}", shader_path); - - // Remove the old shader from cache - { - std::lock_guard lock(shader_mutex); - shader_cache.erase(shader_path); - } - - // Load the shader again - LoadShader(shader_path); -} - -bool ShaderManager::LoadShader(const std::string& shader_path) { - LOG_INFO(Render_Vulkan, "Loading shader from: {}", shader_path); - - if (!std::filesystem::exists(shader_path)) { - LOG_ERROR(Render_Vulkan, "Shader file does not exist: {}", shader_path); - return false; - } - - try { - std::vector spir_v; - std::ifstream shader_file(shader_path, std::ios::binary); - - if (!shader_file.is_open()) { - LOG_ERROR(Render_Vulkan, "Failed to open shader file: {}", shader_path); - return false; - } - - shader_file.seekg(0, std::ios::end); - const size_t file_size = static_cast(shader_file.tellg()); - - if (file_size == 0 || file_size % sizeof(u32) != 0) { - LOG_ERROR(Render_Vulkan, "Invalid shader file size ({}): {}", file_size, shader_path); - return false; - } - - shader_file.seekg(0, std::ios::beg); - spir_v.resize(file_size / sizeof(u32)); - - if (!shader_file.read(reinterpret_cast(spir_v.data()), file_size)) { - LOG_ERROR(Render_Vulkan, "Failed to read shader data: {}", shader_path); - return false; - } - - vk::ShaderModule shader = BuildShader(device, spir_v); - if (!IsShaderValid(*shader)) { - LOG_ERROR(Render_Vulkan, "Created shader module is invalid: {}", shader_path); - return false; - } - - // Store in memory cache - { - std::lock_guard lock(shader_mutex); - shader_cache[shader_path] = std::move(shader); - } - - // Also store in disk cache for future use - try { - if (!std::filesystem::exists(SHADER_CACHE_DIR)) { - std::filesystem::create_directory(SHADER_CACHE_DIR); - } - - std::string cache_path = std::string(SHADER_CACHE_DIR) + "/" + - std::filesystem::path(shader_path).filename().string() + ".cache"; - - std::ofstream cache_file(cache_path, std::ios::binary); - if (cache_file.is_open()) { - cache_file.write(reinterpret_cast(spir_v.data()), - spir_v.size() * sizeof(u32)); - - if (!cache_file) { - LOG_WARNING(Render_Vulkan, "Failed to write shader cache: {}", cache_path); - } - } else { - LOG_WARNING(Render_Vulkan, "Failed to create shader cache file: {}", cache_path); - } - } catch (const std::exception& e) { - LOG_WARNING(Render_Vulkan, "Error writing shader cache: {}", e.what()); - // Continue even if disk cache fails - } - - return true; - } catch (const std::exception& e) { - LOG_ERROR(Render_Vulkan, "Error loading shader: {}", e.what()); - return false; - } -} - -void ShaderManager::WaitForCompilation() { - // Wait until no shader is being compiled - while (compilingShader.load()) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - - // Process any pending commands in the queue - std::unique_lock lock(commandQueueMutex); - while (!commandQueue.empty()) { - auto command = commandQueue.front(); - commandQueue.pop(); - lock.unlock(); - - command(); - - lock.lock(); - } -} - -// Integrate with Citron's scheduler for shader operations -void ShaderManager::SetScheduler(Scheduler* scheduler) { - SetGlobalScheduler(scheduler); -} - -// Load multiple shaders in parallel -void ShaderManager::PreloadShaders(const std::vector& shader_paths) { - if (shader_paths.empty()) { - return; - } - - LOG_INFO(Render_Vulkan, "Preloading {} shaders", shader_paths.size()); - - // Track shaders that need to be loaded - std::unordered_set shaders_to_load; - - // First check which shaders are not already cached - { - std::lock_guard lock(shader_mutex); - for (const auto& path : shader_paths) { - if (shader_cache.find(path) == shader_cache.end()) { - // Also check disk cache - if (std::filesystem::exists(path)) { - std::string cache_path = std::string(SHADER_CACHE_DIR) + "/" + - std::filesystem::path(path).filename().string() + ".cache"; - if (!std::filesystem::exists(cache_path)) { - shaders_to_load.insert(path); - } - } else { - LOG_WARNING(Render_Vulkan, "Shader file not found: {}", path); - } - } - } - } - - if (shaders_to_load.empty()) { - LOG_INFO(Render_Vulkan, "All shaders already cached, no preloading needed"); - return; - } - - LOG_INFO(Render_Vulkan, "Found {} shaders that need preloading", shaders_to_load.size()); - - // Use a thread pool to load shaders in parallel - const size_t max_threads = std::min(std::thread::hardware_concurrency(), - static_cast(4)); - std::vector> futures; - - for (const auto& path : shaders_to_load) { - if (!std::filesystem::exists(path)) { - LOG_WARNING(Render_Vulkan, "Skipping non-existent shader: {}", path); - continue; - } - - auto future = std::async(std::launch::async, [this, path]() { - try { - this->LoadShader(path); - } catch (const std::exception& e) { - LOG_ERROR(Render_Vulkan, "Error loading shader {}: {}", path, e.what()); - } - }); - futures.push_back(std::move(future)); - - // Limit max parallel threads - if (futures.size() >= max_threads) { - futures.front().wait(); - futures.erase(futures.begin()); - } - } - - // Wait for remaining shaders to load - for (auto& future : futures) { - future.wait(); - } - - LOG_INFO(Render_Vulkan, "Finished preloading shaders"); -} - } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_shader_util.h b/src/video_core/renderer_vulkan/vk_shader_util.h index 9a3b512c56..2f7c9f25c3 100644 --- a/src/video_core/renderer_vulkan/vk_shader_util.h +++ b/src/video_core/renderer_vulkan/vk_shader_util.h @@ -1,16 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include -#include -#include -#include -#include -#include -#include #include "common/common_types.h" #include "video_core/vulkan_common/vulkan_wrapper.h" @@ -18,48 +11,7 @@ namespace Vulkan { class Device; -class Scheduler; - -// Command queue system for asynchronous operations -void InitializeCommandQueue(); -void ShutdownCommandQueue(); -void SubmitCommandToQueue(std::function command); -void CommandQueueWorker(); - -// Scheduler integration functions -void SetGlobalScheduler(Scheduler* scheduler); -void SubmitToScheduler(std::function command); -u64 FlushScheduler(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr); -void ProcessAllCommands(); vk::ShaderModule BuildShader(const Device& device, std::span code); -// Enhanced shader functionality -bool IsShaderValid(VkShaderModule shader_module); - -void AsyncCompileShader(const Device& device, const std::string& shader_path, - std::function callback); - -class ShaderManager { -public: - explicit ShaderManager(const Device& device); - ~ShaderManager(); - - VkShaderModule GetShaderModule(const std::string& shader_path); - void ReloadShader(const std::string& shader_path); - bool LoadShader(const std::string& shader_path); - void WaitForCompilation(); - - // Batch process multiple shaders in parallel - void PreloadShaders(const std::vector& shader_paths); - - // Integrate with Citron's scheduler - void SetScheduler(Scheduler* scheduler); - -private: - const Device& device; - std::mutex shader_mutex; - std::unordered_map shader_cache; -}; - } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 18bee98e49..4e53dc6f55 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -30,10 +30,6 @@ namespace Vulkan { -// TextureCacheManager implementations to fix linker errors -TextureCacheManager::TextureCacheManager() = default; -TextureCacheManager::~TextureCacheManager() = default; - using Tegra::Engines::Fermi2D; using Tegra::Texture::SwizzleSource; using Tegra::Texture::TextureMipmapFilter; diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index b4d903eb52..fd540c849c 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -5,10 +5,6 @@ #pragma once #include -#include -#include -#include -#include #include "video_core/texture_cache/texture_cache_base.h" @@ -41,22 +37,6 @@ class RenderPassCache; class StagingBufferPool; class Scheduler; -// Enhanced texture management for better error handling and thread safety -class TextureCacheManager { -public: - explicit TextureCacheManager(); - ~TextureCacheManager(); - - VkImage GetTextureFromCache(const std::string& texture_path); - void ReloadTexture(const std::string& texture_path); - bool IsTextureLoadedCorrectly(VkImage texture); - void HandleTextureCache(); - -private: - std::mutex texture_mutex; - std::unordered_map texture_cache; -}; - class TextureCacheRuntime { public: explicit TextureCacheRuntime(const Device& device_, Scheduler& scheduler_, @@ -137,10 +117,6 @@ public: VkFormat GetSupportedFormat(VkFormat requested_format, VkFormatFeatureFlags required_features) const; - // Enhanced texture error handling - bool IsTextureLoadedCorrectly(VkImage texture); - void HandleTextureError(const std::string& texture_path); - const Device& device; Scheduler& scheduler; MemoryAllocator& memory_allocator; @@ -152,9 +128,6 @@ public: const Settings::ResolutionScalingInfo& resolution; std::array, VideoCore::Surface::MaxPixelFormat> view_formats; - // Enhanced texture management - TextureCacheManager texture_cache_manager; - static constexpr size_t indexing_slots = 8 * sizeof(size_t); std::array buffers{}; }; diff --git a/src/video_core/renderer_vulkan/vk_texture_manager.h b/src/video_core/renderer_vulkan/vk_texture_manager.h deleted file mode 100644 index 8cf116c884..0000000000 --- a/src/video_core/renderer_vulkan/vk_texture_manager.h +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "video_core/vulkan_common/vulkan_wrapper.h" - -namespace Vulkan { - -class Device; -class MemoryAllocator; - -// Enhanced texture manager for better error handling and thread safety -class TextureManager { -public: - explicit TextureManager(const Device& device, MemoryAllocator& memory_allocator); - ~TextureManager(); - - // Get a texture from the cache, loading it if necessary - VkImage GetTexture(const std::string& texture_path); - - // Force a texture to reload from disk - void ReloadTexture(const std::string& texture_path); - - // Check if a texture is loaded correctly - bool IsTextureLoadedCorrectly(VkImage texture); - - // Remove old textures from the cache - void CleanupTextureCache(); - - // Handle texture rendering, with automatic reload if needed - void HandleTextureRendering(const std::string& texture_path, - std::function render_callback); - -private: - // Load a texture from disk and create a Vulkan image - vk::Image LoadTexture(const std::string& texture_path); - - // Create a default texture to use in case of errors - vk::Image CreateDefaultTexture(); - - const Device& device; - MemoryAllocator& memory_allocator; - std::mutex texture_mutex; - std::unordered_map texture_cache; - std::optional default_texture; - VkFormat texture_format = VK_FORMAT_B8G8R8A8_SRGB; -}; - -} // namespace Vulkan \ No newline at end of file diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index c54ab3d09b..54331688e3 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp @@ -140,10 +140,6 @@ public: return (flags & property_flags) == flags && (type_mask & shifted_memory_type) != 0; } - [[nodiscard]] bool IsEmpty() const noexcept { - return commits.empty(); - } - private: [[nodiscard]] static constexpr u32 ShiftType(u32 type) { return 1U << type; @@ -288,78 +284,39 @@ MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements& requirements, M const u32 type_mask = requirements.memoryTypeBits; const VkMemoryPropertyFlags usage_flags = MemoryUsagePropertyFlags(usage); const VkMemoryPropertyFlags flags = MemoryPropertyFlags(type_mask, usage_flags); - - // First attempt if (std::optional commit = TryCommit(requirements, flags)) { return std::move(*commit); } - - // Commit has failed, allocate more memory + // Commit has failed, allocate more memory. const u64 chunk_size = AllocationChunkSize(requirements.size); - if (TryAllocMemory(flags, type_mask, chunk_size)) { - return TryCommit(requirements, flags).value(); + if (!TryAllocMemory(flags, type_mask, chunk_size)) { + // TODO(Rodrigo): Handle out of memory situations in some way like flushing to guest memory. + throw vk::Exception(VK_ERROR_OUT_OF_DEVICE_MEMORY); } - - // Memory allocation failed - try to recover by releasing empty allocations - for (auto it = allocations.begin(); it != allocations.end();) { - if ((*it)->IsEmpty()) { - it = allocations.erase(it); - } else { - ++it; - } - } - - // Try allocating again after cleanup - if (TryAllocMemory(flags, type_mask, chunk_size)) { - return TryCommit(requirements, flags).value(); - } - - // If still failing, try with non-device-local memory as a last resort - if (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) { - const VkMemoryPropertyFlags fallback_flags = flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; - if (TryAllocMemory(fallback_flags, type_mask, chunk_size)) { - if (auto commit = TryCommit(requirements, fallback_flags)) { - LOG_WARNING(Render_Vulkan, "Falling back to non-device-local memory due to OOM"); - return std::move(*commit); - } - } - } - - LOG_CRITICAL(Render_Vulkan, "Vulkan memory allocation failed - out of device memory"); - throw vk::Exception(VK_ERROR_OUT_OF_DEVICE_MEMORY); + // Commit again, this time it won't fail since there's a fresh allocation above. + // If it does, there's a bug. + return TryCommit(requirements, flags).value(); } bool MemoryAllocator::TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size) { - const auto type_opt = FindType(flags, type_mask); - if (!type_opt) { - if ((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0) { - // Try to allocate non device local memory - return TryAllocMemory(flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, type_mask, size); - } - return false; - } - - const u64 aligned_size = (device.GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) ? - Common::AlignUp(size, 4096) : // Adreno requires 4KB alignment - size; // Others (NVIDIA, AMD, Intel, etc) - + const u32 type = FindType(flags, type_mask).value(); vk::DeviceMemory memory = device.GetLogical().TryAllocateMemory({ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .pNext = nullptr, - .allocationSize = aligned_size, - .memoryTypeIndex = *type_opt, + .allocationSize = size, + .memoryTypeIndex = type, }); - if (!memory) { if ((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0) { // Try to allocate non device local memory return TryAllocMemory(flags & ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, type_mask, size); + } else { + // RIP + return false; } - return false; } - allocations.push_back( - std::make_unique(this, std::move(memory), flags, aligned_size, *type_opt)); + std::make_unique(this, std::move(memory), flags, size, type)); return true; }