Revert some wip changes
This commit is contained in:
parent
808276b48a
commit
b695ca5a2a
17 changed files with 45 additions and 1135 deletions
|
@ -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"),
|
||||
|
|
|
@ -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<u64, std::vector<std::string>> disabled_addons;
|
||||
|
||||
// Renderer Advanced Settings
|
||||
SwitchableSetting<bool> use_enhanced_shader_building{linkage, false, "Enhanced Shader Building",
|
||||
Category::RendererAdvanced};
|
||||
|
||||
// Add a new setting for shader compilation priority
|
||||
SwitchableSetting<int> shader_compilation_priority{linkage, 0, "Shader Compilation Priority",
|
||||
Category::RendererAdvanced};
|
||||
};
|
||||
|
||||
extern Values values;
|
||||
|
||||
void UpdateGPUAccuracy();
|
||||
// boold isGPULevelNormal();
|
||||
// TODO: ZEP
|
||||
bool IsGPULevelExtreme();
|
||||
bool IsGPULevelHigh();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <algorithm>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
|
||||
#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<std::function<void()>> 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<std::chrono::milliseconds>(
|
||||
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<GraphicsPipeline*>(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<std::chrono::seconds>(
|
||||
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
|
||||
|
|
|
@ -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 <typename Spec>
|
||||
static auto MakeConfigureSpecFunc() {
|
||||
|
|
|
@ -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 <atomic>
|
||||
|
@ -609,33 +608,9 @@ std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
|
|||
}
|
||||
|
||||
std::unique_ptr<ShaderWorker> 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<ShaderWorker>(
|
||||
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<ShaderWorker>(std::max(std::thread::hardware_concurrency(), 2U) - 1,
|
||||
"GlShaderBuilder",
|
||||
[this] { return Context{emu_window}; });
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
|
|
|
@ -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 <algorithm>
|
||||
|
@ -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<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);
|
||||
|
@ -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<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())};
|
||||
|
@ -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
|
||||
|
|
|
@ -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 <memory>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <functional>
|
||||
|
||||
#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<const Tegra::FramebufferConfig> framebuffers);
|
||||
void RenderAppletCaptureLayer(std::span<const Tegra::FramebufferConfig> 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<HybridMemory> hybrid_memory;
|
||||
|
||||
// Enhanced texture and shader management
|
||||
TextureManager texture_manager;
|
||||
ShaderManager shader_manager;
|
||||
|
||||
Frame applet_frame;
|
||||
};
|
||||
|
||||
|
|
|
@ -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 <algorithm>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
|
@ -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<std::chrono::milliseconds>(
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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 <algorithm>
|
||||
|
@ -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<std::chrono::milliseconds>(
|
||||
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<u32>(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)};
|
||||
|
|
|
@ -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 <algorithm>
|
||||
|
@ -266,42 +265,18 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
|
|||
}
|
||||
|
||||
size_t GetTotalPipelineWorkers() {
|
||||
const size_t num_cores = std::max<size_t>(static_cast<size_t>(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<size_t>(static_cast<size_t>(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<std::chrono::seconds>(
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <cstring>
|
||||
#include <thread>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <queue>
|
||||
#include <condition_variable>
|
||||
#include <future>
|
||||
#include <chrono>
|
||||
#include <unordered_set>
|
||||
|
||||
#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<std::function<void()>> commandQueue;
|
||||
std::condition_variable commandQueueCondition;
|
||||
std::atomic<bool> 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<void()> command;
|
||||
{
|
||||
std::unique_lock<std::mutex> 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<void()> command) {
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<void(vk::CommandBuffer)> 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<std::mutex> 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<const u32> 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<const u32> 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<bool> compilingShader(false);
|
||||
|
||||
void AsyncCompileShader(const Device& device, const std::string& shader_path,
|
||||
std::function<void(VkShaderModule)> 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<u32> 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<size_t>(shader_file.tellg());
|
||||
shader_file.seekg(0, std::ios::beg);
|
||||
|
||||
spir_v.resize(file_size / sizeof(u32));
|
||||
if (shader_file.read(reinterpret_cast<char*>(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<const char*>(spir_v.data()),
|
||||
spir_v.size() * sizeof(u32));
|
||||
}
|
||||
|
||||
auto endTime = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<double> 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<std::mutex> 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<std::mutex> 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<size_t>(cache_file.tellg());
|
||||
|
||||
if (file_size > 0 && file_size % sizeof(u32) == 0) {
|
||||
cache_file.seekg(0, std::ios::beg);
|
||||
std::vector<u32> spir_v;
|
||||
spir_v.resize(file_size / sizeof(u32));
|
||||
|
||||
if (cache_file.read(reinterpret_cast<char*>(spir_v.data()), file_size)) {
|
||||
vk::ShaderModule shader = BuildShader(device, spir_v);
|
||||
if (IsShaderValid(*shader)) {
|
||||
// Store in memory cache
|
||||
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<u32> 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<size_t>(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<char*>(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<std::mutex> 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<const char*>(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<std::mutex> 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<std::string>& 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<std::string> shaders_to_load;
|
||||
|
||||
// First check which shaders are not already cached
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<unsigned>(4));
|
||||
std::vector<std::future<void>> 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
|
||||
|
|
|
@ -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 <span>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#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<void()> command);
|
||||
void CommandQueueWorker();
|
||||
|
||||
// Scheduler integration functions
|
||||
void SetGlobalScheduler(Scheduler* scheduler);
|
||||
void SubmitToScheduler(std::function<void(vk::CommandBuffer)> command);
|
||||
u64 FlushScheduler(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr);
|
||||
void ProcessAllCommands();
|
||||
|
||||
vk::ShaderModule BuildShader(const Device& device, std::span<const u32> code);
|
||||
|
||||
// Enhanced shader functionality
|
||||
bool IsShaderValid(VkShaderModule shader_module);
|
||||
|
||||
void AsyncCompileShader(const Device& device, const std::string& shader_path,
|
||||
std::function<void(VkShaderModule)> 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<std::string>& shader_paths);
|
||||
|
||||
// Integrate with Citron's scheduler
|
||||
void SetScheduler(Scheduler* scheduler);
|
||||
|
||||
private:
|
||||
const Device& device;
|
||||
std::mutex shader_mutex;
|
||||
std::unordered_map<std::string, vk::ShaderModule> shader_cache;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -5,10 +5,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#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<std::string, VkImage> 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<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
|
||||
|
||||
// Enhanced texture management
|
||||
TextureCacheManager texture_cache_manager;
|
||||
|
||||
static constexpr size_t indexing_slots = 8 * sizeof(size_t);
|
||||
std::array<vk::Buffer, indexing_slots> buffers{};
|
||||
};
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
|
||||
#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<void(VkImage)> 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<std::string, vk::Image> texture_cache;
|
||||
std::optional<vk::Image> default_texture;
|
||||
VkFormat texture_format = VK_FORMAT_B8G8R8A8_SRGB;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
|
@ -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<MemoryCommit> 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<MemoryAllocation>(this, std::move(memory), flags, aligned_size, *type_opt));
|
||||
std::make_unique<MemoryAllocation>(this, std::move(memory), flags, size, type));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue