diff options
26 files changed, 275 insertions, 132 deletions
diff --git a/src/common/time_zone.cpp b/src/common/time_zone.cpp index ce239eb63..7aa1b59ea 100644 --- a/src/common/time_zone.cpp +++ b/src/common/time_zone.cpp | |||
| @@ -3,8 +3,9 @@ | |||
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <chrono> | 5 | #include <chrono> |
| 6 | #include <iomanip> | 6 | #include <ctime> |
| 7 | #include <sstream> | 7 | |
| 8 | #include <fmt/chrono.h> | ||
| 8 | 9 | ||
| 9 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 10 | #include "common/time_zone.h" | 11 | #include "common/time_zone.h" |
| @@ -16,13 +17,8 @@ std::string GetDefaultTimeZone() { | |||
| 16 | } | 17 | } |
| 17 | 18 | ||
| 18 | static std::string GetOsTimeZoneOffset() { | 19 | static std::string GetOsTimeZoneOffset() { |
| 19 | const std::time_t t{std::time(nullptr)}; | 20 | // Get the current timezone offset, e.g. "-400", as a string |
| 20 | const std::tm tm{*std::localtime(&t)}; | 21 | return fmt::format("%z", fmt::localtime(std::time(nullptr))); |
| 21 | |||
| 22 | std::stringstream ss; | ||
| 23 | ss << std::put_time(&tm, "%z"); // Get the current timezone offset, e.g. "-400", as a string | ||
| 24 | |||
| 25 | return ss.str(); | ||
| 26 | } | 22 | } |
| 27 | 23 | ||
| 28 | static int ConvertOsTimeZoneOffsetToInt(const std::string& timezone) { | 24 | static int ConvertOsTimeZoneOffsetToInt(const std::string& timezone) { |
diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp index 330996b24..6a9734812 100644 --- a/src/core/crypto/aes_util.cpp +++ b/src/core/crypto/aes_util.cpp | |||
| @@ -116,7 +116,7 @@ void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, std::size_t size, u8* | |||
| 116 | 116 | ||
| 117 | for (std::size_t i = 0; i < size; i += sector_size) { | 117 | for (std::size_t i = 0; i < size; i += sector_size) { |
| 118 | SetIV(CalculateNintendoTweak(sector_id++)); | 118 | SetIV(CalculateNintendoTweak(sector_id++)); |
| 119 | Transcode<u8, u8>(src + i, sector_size, dest + i, op); | 119 | Transcode(src + i, sector_size, dest + i, op); |
| 120 | } | 120 | } |
| 121 | } | 121 | } |
| 122 | 122 | ||
diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp index 3e96f7516..46136d04a 100644 --- a/src/core/crypto/partition_data_manager.cpp +++ b/src/core/crypto/partition_data_manager.cpp | |||
| @@ -367,8 +367,8 @@ static bool AttemptDecrypt(const std::array<u8, 16>& key, Package2Header& header | |||
| 367 | Package2Header temp = header; | 367 | Package2Header temp = header; |
| 368 | AESCipher<Key128> cipher(key, Mode::CTR); | 368 | AESCipher<Key128> cipher(key, Mode::CTR); |
| 369 | cipher.SetIV(header.header_ctr); | 369 | cipher.SetIV(header.header_ctr); |
| 370 | cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - 0x100, &temp.header_ctr, | 370 | cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - sizeof(Package2Header::signature), |
| 371 | Op::Decrypt); | 371 | &temp.header_ctr, Op::Decrypt); |
| 372 | if (temp.magic == Common::MakeMagic('P', 'K', '2', '1')) { | 372 | if (temp.magic == Common::MakeMagic('P', 'K', '2', '1')) { |
| 373 | header = temp; | 373 | header = temp; |
| 374 | return true; | 374 | return true; |
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index cb284db77..4af5824cd 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp | |||
| @@ -177,15 +177,7 @@ RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWind | |||
| 177 | } | 177 | } |
| 178 | 178 | ||
| 179 | if (device.UseAsynchronousShaders()) { | 179 | if (device.UseAsynchronousShaders()) { |
| 180 | // Max worker threads we should allow | 180 | async_shaders.AllocateWorkers(); |
| 181 | constexpr u32 MAX_THREADS = 4; | ||
| 182 | // Deduce how many threads we can use | ||
| 183 | const u32 threads_used = std::thread::hardware_concurrency() / 4; | ||
| 184 | // Always allow at least 1 thread regardless of our settings | ||
| 185 | const auto max_worker_count = std::max(1U, threads_used); | ||
| 186 | // Don't use more than MAX_THREADS | ||
| 187 | const auto worker_count = std::min(max_worker_count, MAX_THREADS); | ||
| 188 | async_shaders.AllocateWorkers(worker_count); | ||
| 189 | } | 181 | } |
| 190 | } | 182 | } |
| 191 | 183 | ||
diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp index 0c03e4d83..ebcfaa0e3 100644 --- a/src/video_core/renderer_vulkan/vk_device.cpp +++ b/src/video_core/renderer_vulkan/vk_device.cpp | |||
| @@ -382,6 +382,8 @@ bool VKDevice::Create() { | |||
| 382 | 382 | ||
| 383 | graphics_queue = logical.GetQueue(graphics_family); | 383 | graphics_queue = logical.GetQueue(graphics_family); |
| 384 | present_queue = logical.GetQueue(present_family); | 384 | present_queue = logical.GetQueue(present_family); |
| 385 | |||
| 386 | use_asynchronous_shaders = Settings::values.use_asynchronous_shaders.GetValue(); | ||
| 385 | return true; | 387 | return true; |
| 386 | } | 388 | } |
| 387 | 389 | ||
diff --git a/src/video_core/renderer_vulkan/vk_device.h b/src/video_core/renderer_vulkan/vk_device.h index 529744f2d..26a233db1 100644 --- a/src/video_core/renderer_vulkan/vk_device.h +++ b/src/video_core/renderer_vulkan/vk_device.h | |||
| @@ -202,6 +202,11 @@ public: | |||
| 202 | return reported_extensions; | 202 | return reported_extensions; |
| 203 | } | 203 | } |
| 204 | 204 | ||
| 205 | /// Returns true if the setting for async shader compilation is enabled. | ||
| 206 | bool UseAsynchronousShaders() const { | ||
| 207 | return use_asynchronous_shaders; | ||
| 208 | } | ||
| 209 | |||
| 205 | /// Checks if the physical device is suitable. | 210 | /// Checks if the physical device is suitable. |
| 206 | static bool IsSuitable(vk::PhysicalDevice physical, VkSurfaceKHR surface); | 211 | static bool IsSuitable(vk::PhysicalDevice physical, VkSurfaceKHR surface); |
| 207 | 212 | ||
| @@ -252,6 +257,9 @@ private: | |||
| 252 | bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state. | 257 | bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state. |
| 253 | bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config. | 258 | bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config. |
| 254 | 259 | ||
| 260 | // Asynchronous Graphics Pipeline setting | ||
| 261 | bool use_asynchronous_shaders{}; ///< Setting to use asynchronous shaders/graphics pipeline | ||
| 262 | |||
| 255 | // Telemetry parameters | 263 | // Telemetry parameters |
| 256 | std::string vendor_name; ///< Device's driver name. | 264 | std::string vendor_name; ///< Device's driver name. |
| 257 | std::vector<std::string> reported_extensions; ///< Reported Vulkan extensions. | 265 | std::vector<std::string> reported_extensions; ///< Reported Vulkan extensions. |
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.cpp b/src/video_core/renderer_vulkan/vk_fence_manager.cpp index a02be5487..d7f65d435 100644 --- a/src/video_core/renderer_vulkan/vk_fence_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_fence_manager.cpp | |||
| @@ -29,7 +29,7 @@ void InnerFence::Queue() { | |||
| 29 | } | 29 | } |
| 30 | ASSERT(!event); | 30 | ASSERT(!event); |
| 31 | 31 | ||
| 32 | event = device.GetLogical().CreateEvent(); | 32 | event = device.GetLogical().CreateNewEvent(); |
| 33 | ticks = scheduler.Ticks(); | 33 | ticks = scheduler.Ticks(); |
| 34 | 34 | ||
| 35 | scheduler.RequestOutsideRenderPassOperationContext(); | 35 | scheduler.RequestOutsideRenderPassOperationContext(); |
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index aaf930b90..2e46c6278 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp | |||
| @@ -78,15 +78,14 @@ VKGraphicsPipeline::VKGraphicsPipeline(const VKDevice& device, VKScheduler& sche | |||
| 78 | const GraphicsPipelineCacheKey& key, | 78 | const GraphicsPipelineCacheKey& key, |
| 79 | vk::Span<VkDescriptorSetLayoutBinding> bindings, | 79 | vk::Span<VkDescriptorSetLayoutBinding> bindings, |
| 80 | const SPIRVProgram& program) | 80 | const SPIRVProgram& program) |
| 81 | : device{device}, scheduler{scheduler}, fixed_state{key.fixed_state}, hash{key.Hash()}, | 81 | : device{device}, scheduler{scheduler}, cache_key{key}, hash{cache_key.Hash()}, |
| 82 | descriptor_set_layout{CreateDescriptorSetLayout(bindings)}, | 82 | descriptor_set_layout{CreateDescriptorSetLayout(bindings)}, |
| 83 | descriptor_allocator{descriptor_pool, *descriptor_set_layout}, | 83 | descriptor_allocator{descriptor_pool, *descriptor_set_layout}, |
| 84 | update_descriptor_queue{update_descriptor_queue}, layout{CreatePipelineLayout()}, | 84 | update_descriptor_queue{update_descriptor_queue}, layout{CreatePipelineLayout()}, |
| 85 | descriptor_template{CreateDescriptorUpdateTemplate(program)}, modules{CreateShaderModules( | 85 | descriptor_template{CreateDescriptorUpdateTemplate(program)}, modules{CreateShaderModules( |
| 86 | program)}, | 86 | program)}, |
| 87 | renderpass{renderpass_cache.GetRenderPass(key.renderpass_params)}, pipeline{CreatePipeline( | 87 | renderpass{renderpass_cache.GetRenderPass(cache_key.renderpass_params)}, |
| 88 | key.renderpass_params, | 88 | pipeline{CreatePipeline(cache_key.renderpass_params, program)} {} |
| 89 | program)} {} | ||
| 90 | 89 | ||
| 91 | VKGraphicsPipeline::~VKGraphicsPipeline() = default; | 90 | VKGraphicsPipeline::~VKGraphicsPipeline() = default; |
| 92 | 91 | ||
| @@ -181,7 +180,7 @@ std::vector<vk::ShaderModule> VKGraphicsPipeline::CreateShaderModules( | |||
| 181 | 180 | ||
| 182 | vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpass_params, | 181 | vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpass_params, |
| 183 | const SPIRVProgram& program) const { | 182 | const SPIRVProgram& program) const { |
| 184 | const auto& state = fixed_state; | 183 | const auto& state = cache_key.fixed_state; |
| 185 | const auto& viewport_swizzles = state.viewport_swizzles; | 184 | const auto& viewport_swizzles = state.viewport_swizzles; |
| 186 | 185 | ||
| 187 | FixedPipelineState::DynamicState dynamic; | 186 | FixedPipelineState::DynamicState dynamic; |
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index a1d699a6c..58aa35efd 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h | |||
| @@ -19,7 +19,27 @@ namespace Vulkan { | |||
| 19 | 19 | ||
| 20 | using Maxwell = Tegra::Engines::Maxwell3D::Regs; | 20 | using Maxwell = Tegra::Engines::Maxwell3D::Regs; |
| 21 | 21 | ||
| 22 | struct GraphicsPipelineCacheKey; | 22 | struct GraphicsPipelineCacheKey { |
| 23 | RenderPassParams renderpass_params; | ||
| 24 | u32 padding; | ||
| 25 | std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders; | ||
| 26 | FixedPipelineState fixed_state; | ||
| 27 | |||
| 28 | std::size_t Hash() const noexcept; | ||
| 29 | |||
| 30 | bool operator==(const GraphicsPipelineCacheKey& rhs) const noexcept; | ||
| 31 | |||
| 32 | bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept { | ||
| 33 | return !operator==(rhs); | ||
| 34 | } | ||
| 35 | |||
| 36 | std::size_t Size() const noexcept { | ||
| 37 | return sizeof(renderpass_params) + sizeof(padding) + sizeof(shaders) + fixed_state.Size(); | ||
| 38 | } | ||
| 39 | }; | ||
| 40 | static_assert(std::has_unique_object_representations_v<GraphicsPipelineCacheKey>); | ||
| 41 | static_assert(std::is_trivially_copyable_v<GraphicsPipelineCacheKey>); | ||
| 42 | static_assert(std::is_trivially_constructible_v<GraphicsPipelineCacheKey>); | ||
| 23 | 43 | ||
| 24 | class VKDescriptorPool; | 44 | class VKDescriptorPool; |
| 25 | class VKDevice; | 45 | class VKDevice; |
| @@ -54,6 +74,10 @@ public: | |||
| 54 | return renderpass; | 74 | return renderpass; |
| 55 | } | 75 | } |
| 56 | 76 | ||
| 77 | GraphicsPipelineCacheKey GetCacheKey() const { | ||
| 78 | return cache_key; | ||
| 79 | } | ||
| 80 | |||
| 57 | private: | 81 | private: |
| 58 | vk::DescriptorSetLayout CreateDescriptorSetLayout( | 82 | vk::DescriptorSetLayout CreateDescriptorSetLayout( |
| 59 | vk::Span<VkDescriptorSetLayoutBinding> bindings) const; | 83 | vk::Span<VkDescriptorSetLayoutBinding> bindings) const; |
| @@ -70,7 +94,7 @@ private: | |||
| 70 | 94 | ||
| 71 | const VKDevice& device; | 95 | const VKDevice& device; |
| 72 | VKScheduler& scheduler; | 96 | VKScheduler& scheduler; |
| 73 | const FixedPipelineState fixed_state; | 97 | const GraphicsPipelineCacheKey cache_key; |
| 74 | const u64 hash; | 98 | const u64 hash; |
| 75 | 99 | ||
| 76 | vk::DescriptorSetLayout descriptor_set_layout; | 100 | vk::DescriptorSetLayout descriptor_set_layout; |
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 418c62bc4..cfdcdd6ab 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | |||
| @@ -28,6 +28,7 @@ | |||
| 28 | #include "video_core/shader/compiler_settings.h" | 28 | #include "video_core/shader/compiler_settings.h" |
| 29 | #include "video_core/shader/memory_util.h" | 29 | #include "video_core/shader/memory_util.h" |
| 30 | #include "video_core/shader_cache.h" | 30 | #include "video_core/shader_cache.h" |
| 31 | #include "video_core/shader_notify.h" | ||
| 31 | 32 | ||
| 32 | namespace Vulkan { | 33 | namespace Vulkan { |
| 33 | 34 | ||
| @@ -205,24 +206,43 @@ std::array<Shader*, Maxwell::MaxShaderProgram> VKPipelineCache::GetShaders() { | |||
| 205 | return last_shaders = shaders; | 206 | return last_shaders = shaders; |
| 206 | } | 207 | } |
| 207 | 208 | ||
| 208 | VKGraphicsPipeline& VKPipelineCache::GetGraphicsPipeline(const GraphicsPipelineCacheKey& key) { | 209 | VKGraphicsPipeline* VKPipelineCache::GetGraphicsPipeline( |
| 210 | const GraphicsPipelineCacheKey& key, VideoCommon::Shader::AsyncShaders& async_shaders) { | ||
| 209 | MICROPROFILE_SCOPE(Vulkan_PipelineCache); | 211 | MICROPROFILE_SCOPE(Vulkan_PipelineCache); |
| 210 | 212 | ||
| 211 | if (last_graphics_pipeline && last_graphics_key == key) { | 213 | if (last_graphics_pipeline && last_graphics_key == key) { |
| 212 | return *last_graphics_pipeline; | 214 | return last_graphics_pipeline; |
| 213 | } | 215 | } |
| 214 | last_graphics_key = key; | 216 | last_graphics_key = key; |
| 215 | 217 | ||
| 218 | if (device.UseAsynchronousShaders() && async_shaders.IsShaderAsync(system.GPU())) { | ||
| 219 | std::unique_lock lock{pipeline_cache}; | ||
| 220 | const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key); | ||
| 221 | if (is_cache_miss) { | ||
| 222 | system.GPU().ShaderNotify().MarkSharderBuilding(); | ||
| 223 | LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash()); | ||
| 224 | const auto [program, bindings] = DecompileShaders(key.fixed_state); | ||
| 225 | async_shaders.QueueVulkanShader(this, device, scheduler, descriptor_pool, | ||
| 226 | update_descriptor_queue, renderpass_cache, bindings, | ||
| 227 | program, key); | ||
| 228 | } | ||
| 229 | last_graphics_pipeline = pair->second.get(); | ||
| 230 | return last_graphics_pipeline; | ||
| 231 | } | ||
| 232 | |||
| 216 | const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key); | 233 | const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key); |
| 217 | auto& entry = pair->second; | 234 | auto& entry = pair->second; |
| 218 | if (is_cache_miss) { | 235 | if (is_cache_miss) { |
| 236 | system.GPU().ShaderNotify().MarkSharderBuilding(); | ||
| 219 | LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash()); | 237 | LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash()); |
| 220 | const auto [program, bindings] = DecompileShaders(key); | 238 | const auto [program, bindings] = DecompileShaders(key.fixed_state); |
| 221 | entry = std::make_unique<VKGraphicsPipeline>(device, scheduler, descriptor_pool, | 239 | entry = std::make_unique<VKGraphicsPipeline>(device, scheduler, descriptor_pool, |
| 222 | update_descriptor_queue, renderpass_cache, key, | 240 | update_descriptor_queue, renderpass_cache, key, |
| 223 | bindings, program); | 241 | bindings, program); |
| 242 | system.GPU().ShaderNotify().MarkShaderComplete(); | ||
| 224 | } | 243 | } |
| 225 | return *(last_graphics_pipeline = entry.get()); | 244 | last_graphics_pipeline = entry.get(); |
| 245 | return last_graphics_pipeline; | ||
| 226 | } | 246 | } |
| 227 | 247 | ||
| 228 | VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCacheKey& key) { | 248 | VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCacheKey& key) { |
| @@ -277,6 +297,12 @@ VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCach | |||
| 277 | return *entry; | 297 | return *entry; |
| 278 | } | 298 | } |
| 279 | 299 | ||
| 300 | void VKPipelineCache::EmplacePipeline(std::unique_ptr<VKGraphicsPipeline> pipeline) { | ||
| 301 | system.GPU().ShaderNotify().MarkShaderComplete(); | ||
| 302 | std::unique_lock lock{pipeline_cache}; | ||
| 303 | graphics_cache.at(pipeline->GetCacheKey()) = std::move(pipeline); | ||
| 304 | } | ||
| 305 | |||
| 280 | void VKPipelineCache::OnShaderRemoval(Shader* shader) { | 306 | void VKPipelineCache::OnShaderRemoval(Shader* shader) { |
| 281 | bool finished = false; | 307 | bool finished = false; |
| 282 | const auto Finish = [&] { | 308 | const auto Finish = [&] { |
| @@ -312,8 +338,7 @@ void VKPipelineCache::OnShaderRemoval(Shader* shader) { | |||
| 312 | } | 338 | } |
| 313 | 339 | ||
| 314 | std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>> | 340 | std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>> |
| 315 | VKPipelineCache::DecompileShaders(const GraphicsPipelineCacheKey& key) { | 341 | VKPipelineCache::DecompileShaders(const FixedPipelineState& fixed_state) { |
| 316 | const auto& fixed_state = key.fixed_state; | ||
| 317 | auto& memory_manager = system.GPU().MemoryManager(); | 342 | auto& memory_manager = system.GPU().MemoryManager(); |
| 318 | const auto& gpu = system.GPU().Maxwell3D(); | 343 | const auto& gpu = system.GPU().Maxwell3D(); |
| 319 | 344 | ||
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 0a3fe65fb..c04829e77 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h | |||
| @@ -22,6 +22,7 @@ | |||
| 22 | #include "video_core/renderer_vulkan/vk_renderpass_cache.h" | 22 | #include "video_core/renderer_vulkan/vk_renderpass_cache.h" |
| 23 | #include "video_core/renderer_vulkan/vk_shader_decompiler.h" | 23 | #include "video_core/renderer_vulkan/vk_shader_decompiler.h" |
| 24 | #include "video_core/renderer_vulkan/wrapper.h" | 24 | #include "video_core/renderer_vulkan/wrapper.h" |
| 25 | #include "video_core/shader/async_shaders.h" | ||
| 25 | #include "video_core/shader/memory_util.h" | 26 | #include "video_core/shader/memory_util.h" |
| 26 | #include "video_core/shader/registry.h" | 27 | #include "video_core/shader/registry.h" |
| 27 | #include "video_core/shader/shader_ir.h" | 28 | #include "video_core/shader/shader_ir.h" |
| @@ -43,28 +44,6 @@ class VKUpdateDescriptorQueue; | |||
| 43 | 44 | ||
| 44 | using Maxwell = Tegra::Engines::Maxwell3D::Regs; | 45 | using Maxwell = Tegra::Engines::Maxwell3D::Regs; |
| 45 | 46 | ||
| 46 | struct GraphicsPipelineCacheKey { | ||
| 47 | RenderPassParams renderpass_params; | ||
| 48 | u32 padding; | ||
| 49 | std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders; | ||
| 50 | FixedPipelineState fixed_state; | ||
| 51 | |||
| 52 | std::size_t Hash() const noexcept; | ||
| 53 | |||
| 54 | bool operator==(const GraphicsPipelineCacheKey& rhs) const noexcept; | ||
| 55 | |||
| 56 | bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept { | ||
| 57 | return !operator==(rhs); | ||
| 58 | } | ||
| 59 | |||
| 60 | std::size_t Size() const noexcept { | ||
| 61 | return sizeof(renderpass_params) + sizeof(padding) + sizeof(shaders) + fixed_state.Size(); | ||
| 62 | } | ||
| 63 | }; | ||
| 64 | static_assert(std::has_unique_object_representations_v<GraphicsPipelineCacheKey>); | ||
| 65 | static_assert(std::is_trivially_copyable_v<GraphicsPipelineCacheKey>); | ||
| 66 | static_assert(std::is_trivially_constructible_v<GraphicsPipelineCacheKey>); | ||
| 67 | |||
| 68 | struct ComputePipelineCacheKey { | 47 | struct ComputePipelineCacheKey { |
| 69 | GPUVAddr shader; | 48 | GPUVAddr shader; |
| 70 | u32 shared_memory_size; | 49 | u32 shared_memory_size; |
| @@ -152,16 +131,19 @@ public: | |||
| 152 | 131 | ||
| 153 | std::array<Shader*, Maxwell::MaxShaderProgram> GetShaders(); | 132 | std::array<Shader*, Maxwell::MaxShaderProgram> GetShaders(); |
| 154 | 133 | ||
| 155 | VKGraphicsPipeline& GetGraphicsPipeline(const GraphicsPipelineCacheKey& key); | 134 | VKGraphicsPipeline* GetGraphicsPipeline(const GraphicsPipelineCacheKey& key, |
| 135 | VideoCommon::Shader::AsyncShaders& async_shaders); | ||
| 156 | 136 | ||
| 157 | VKComputePipeline& GetComputePipeline(const ComputePipelineCacheKey& key); | 137 | VKComputePipeline& GetComputePipeline(const ComputePipelineCacheKey& key); |
| 158 | 138 | ||
| 139 | void EmplacePipeline(std::unique_ptr<VKGraphicsPipeline> pipeline); | ||
| 140 | |||
| 159 | protected: | 141 | protected: |
| 160 | void OnShaderRemoval(Shader* shader) final; | 142 | void OnShaderRemoval(Shader* shader) final; |
| 161 | 143 | ||
| 162 | private: | 144 | private: |
| 163 | std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>> DecompileShaders( | 145 | std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>> DecompileShaders( |
| 164 | const GraphicsPipelineCacheKey& key); | 146 | const FixedPipelineState& fixed_state); |
| 165 | 147 | ||
| 166 | Core::System& system; | 148 | Core::System& system; |
| 167 | const VKDevice& device; | 149 | const VKDevice& device; |
| @@ -178,6 +160,7 @@ private: | |||
| 178 | GraphicsPipelineCacheKey last_graphics_key; | 160 | GraphicsPipelineCacheKey last_graphics_key; |
| 179 | VKGraphicsPipeline* last_graphics_pipeline = nullptr; | 161 | VKGraphicsPipeline* last_graphics_pipeline = nullptr; |
| 180 | 162 | ||
| 163 | std::mutex pipeline_cache; | ||
| 181 | std::unordered_map<GraphicsPipelineCacheKey, std::unique_ptr<VKGraphicsPipeline>> | 164 | std::unordered_map<GraphicsPipelineCacheKey, std::unique_ptr<VKGraphicsPipeline>> |
| 182 | graphics_cache; | 165 | graphics_cache; |
| 183 | std::unordered_map<ComputePipelineCacheKey, std::unique_ptr<VKComputePipeline>> compute_cache; | 166 | std::unordered_map<ComputePipelineCacheKey, std::unique_ptr<VKComputePipeline>> compute_cache; |
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 7500e8244..936f76195 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp | |||
| @@ -14,6 +14,7 @@ | |||
| 14 | #include "common/assert.h" | 14 | #include "common/assert.h" |
| 15 | #include "common/logging/log.h" | 15 | #include "common/logging/log.h" |
| 16 | #include "common/microprofile.h" | 16 | #include "common/microprofile.h" |
| 17 | #include "common/scope_exit.h" | ||
| 17 | #include "core/core.h" | 18 | #include "core/core.h" |
| 18 | #include "core/settings.h" | 19 | #include "core/settings.h" |
| 19 | #include "video_core/engines/kepler_compute.h" | 20 | #include "video_core/engines/kepler_compute.h" |
| @@ -400,8 +401,12 @@ RasterizerVulkan::RasterizerVulkan(Core::System& system, Core::Frontend::EmuWind | |||
| 400 | buffer_cache(*this, system, device, memory_manager, scheduler, staging_pool), | 401 | buffer_cache(*this, system, device, memory_manager, scheduler, staging_pool), |
| 401 | sampler_cache(device), | 402 | sampler_cache(device), |
| 402 | fence_manager(system, *this, device, scheduler, texture_cache, buffer_cache, query_cache), | 403 | fence_manager(system, *this, device, scheduler, texture_cache, buffer_cache, query_cache), |
| 403 | query_cache(system, *this, device, scheduler), wfi_event{device.GetLogical().CreateEvent()} { | 404 | query_cache(system, *this, device, scheduler), |
| 405 | wfi_event{device.GetLogical().CreateNewEvent()}, async_shaders{renderer} { | ||
| 404 | scheduler.SetQueryCache(query_cache); | 406 | scheduler.SetQueryCache(query_cache); |
| 407 | if (device.UseAsynchronousShaders()) { | ||
| 408 | async_shaders.AllocateWorkers(); | ||
| 409 | } | ||
| 405 | } | 410 | } |
| 406 | 411 | ||
| 407 | RasterizerVulkan::~RasterizerVulkan() = default; | 412 | RasterizerVulkan::~RasterizerVulkan() = default; |
| @@ -413,6 +418,8 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { | |||
| 413 | 418 | ||
| 414 | query_cache.UpdateCounters(); | 419 | query_cache.UpdateCounters(); |
| 415 | 420 | ||
| 421 | SCOPE_EXIT({ system.GPU().TickWork(); }); | ||
| 422 | |||
| 416 | const auto& gpu = system.GPU().Maxwell3D(); | 423 | const auto& gpu = system.GPU().Maxwell3D(); |
| 417 | GraphicsPipelineCacheKey key; | 424 | GraphicsPipelineCacheKey key; |
| 418 | key.fixed_state.Fill(gpu.regs, device.IsExtExtendedDynamicStateSupported()); | 425 | key.fixed_state.Fill(gpu.regs, device.IsExtExtendedDynamicStateSupported()); |
| @@ -439,10 +446,15 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { | |||
| 439 | key.renderpass_params = GetRenderPassParams(texceptions); | 446 | key.renderpass_params = GetRenderPassParams(texceptions); |
| 440 | key.padding = 0; | 447 | key.padding = 0; |
| 441 | 448 | ||
| 442 | auto& pipeline = pipeline_cache.GetGraphicsPipeline(key); | 449 | auto* pipeline = pipeline_cache.GetGraphicsPipeline(key, async_shaders); |
| 443 | scheduler.BindGraphicsPipeline(pipeline.GetHandle()); | 450 | if (pipeline == nullptr || pipeline->GetHandle() == VK_NULL_HANDLE) { |
| 451 | // Async graphics pipeline was not ready. | ||
| 452 | return; | ||
| 453 | } | ||
| 454 | |||
| 455 | scheduler.BindGraphicsPipeline(pipeline->GetHandle()); | ||
| 444 | 456 | ||
| 445 | const auto renderpass = pipeline.GetRenderPass(); | 457 | const auto renderpass = pipeline->GetRenderPass(); |
| 446 | const auto [framebuffer, render_area] = ConfigureFramebuffers(renderpass); | 458 | const auto [framebuffer, render_area] = ConfigureFramebuffers(renderpass); |
| 447 | scheduler.RequestRenderpass(renderpass, framebuffer, render_area); | 459 | scheduler.RequestRenderpass(renderpass, framebuffer, render_area); |
| 448 | 460 | ||
| @@ -452,8 +464,8 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { | |||
| 452 | 464 | ||
| 453 | BeginTransformFeedback(); | 465 | BeginTransformFeedback(); |
| 454 | 466 | ||
| 455 | const auto pipeline_layout = pipeline.GetLayout(); | 467 | const auto pipeline_layout = pipeline->GetLayout(); |
| 456 | const auto descriptor_set = pipeline.CommitDescriptorSet(); | 468 | const auto descriptor_set = pipeline->CommitDescriptorSet(); |
| 457 | scheduler.Record([pipeline_layout, descriptor_set, draw_params](vk::CommandBuffer cmdbuf) { | 469 | scheduler.Record([pipeline_layout, descriptor_set, draw_params](vk::CommandBuffer cmdbuf) { |
| 458 | if (descriptor_set) { | 470 | if (descriptor_set) { |
| 459 | cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, | 471 | cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout, |
| @@ -463,8 +475,6 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) { | |||
| 463 | }); | 475 | }); |
| 464 | 476 | ||
| 465 | EndTransformFeedback(); | 477 | EndTransformFeedback(); |
| 466 | |||
| 467 | system.GPU().TickWork(); | ||
| 468 | } | 478 | } |
| 469 | 479 | ||
| 470 | void RasterizerVulkan::Clear() { | 480 | void RasterizerVulkan::Clear() { |
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 923178b0b..f640ba649 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h | |||
| @@ -32,6 +32,7 @@ | |||
| 32 | #include "video_core/renderer_vulkan/vk_texture_cache.h" | 32 | #include "video_core/renderer_vulkan/vk_texture_cache.h" |
| 33 | #include "video_core/renderer_vulkan/vk_update_descriptor.h" | 33 | #include "video_core/renderer_vulkan/vk_update_descriptor.h" |
| 34 | #include "video_core/renderer_vulkan/wrapper.h" | 34 | #include "video_core/renderer_vulkan/wrapper.h" |
| 35 | #include "video_core/shader/async_shaders.h" | ||
| 35 | 36 | ||
| 36 | namespace Core { | 37 | namespace Core { |
| 37 | class System; | 38 | class System; |
| @@ -136,6 +137,14 @@ public: | |||
| 136 | u32 pixel_stride) override; | 137 | u32 pixel_stride) override; |
| 137 | void SetupDirtyFlags() override; | 138 | void SetupDirtyFlags() override; |
| 138 | 139 | ||
| 140 | VideoCommon::Shader::AsyncShaders& GetAsyncShaders() { | ||
| 141 | return async_shaders; | ||
| 142 | } | ||
| 143 | |||
| 144 | const VideoCommon::Shader::AsyncShaders& GetAsyncShaders() const { | ||
| 145 | return async_shaders; | ||
| 146 | } | ||
| 147 | |||
| 139 | /// Maximum supported size that a constbuffer can have in bytes. | 148 | /// Maximum supported size that a constbuffer can have in bytes. |
| 140 | static constexpr std::size_t MaxConstbufferSize = 0x10000; | 149 | static constexpr std::size_t MaxConstbufferSize = 0x10000; |
| 141 | static_assert(MaxConstbufferSize % (4 * sizeof(float)) == 0, | 150 | static_assert(MaxConstbufferSize % (4 * sizeof(float)) == 0, |
| @@ -297,6 +306,7 @@ private: | |||
| 297 | vk::Buffer default_buffer; | 306 | vk::Buffer default_buffer; |
| 298 | VKMemoryCommit default_buffer_commit; | 307 | VKMemoryCommit default_buffer_commit; |
| 299 | vk::Event wfi_event; | 308 | vk::Event wfi_event; |
| 309 | VideoCommon::Shader::AsyncShaders async_shaders; | ||
| 300 | 310 | ||
| 301 | std::array<View, Maxwell::NumRenderTargets> color_attachments; | 311 | std::array<View, Maxwell::NumRenderTargets> color_attachments; |
| 302 | View zeta_attachment; | 312 | View zeta_attachment; |
diff --git a/src/video_core/renderer_vulkan/wrapper.cpp b/src/video_core/renderer_vulkan/wrapper.cpp index 14cac38ea..c43d60adf 100644 --- a/src/video_core/renderer_vulkan/wrapper.cpp +++ b/src/video_core/renderer_vulkan/wrapper.cpp | |||
| @@ -644,7 +644,7 @@ ShaderModule Device::CreateShaderModule(const VkShaderModuleCreateInfo& ci) cons | |||
| 644 | return ShaderModule(object, handle, *dld); | 644 | return ShaderModule(object, handle, *dld); |
| 645 | } | 645 | } |
| 646 | 646 | ||
| 647 | Event Device::CreateEvent() const { | 647 | Event Device::CreateNewEvent() const { |
| 648 | static constexpr VkEventCreateInfo ci{ | 648 | static constexpr VkEventCreateInfo ci{ |
| 649 | .sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO, | 649 | .sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO, |
| 650 | .pNext = nullptr, | 650 | .pNext = nullptr, |
diff --git a/src/video_core/renderer_vulkan/wrapper.h b/src/video_core/renderer_vulkan/wrapper.h index 31885ef42..b9d3fedc1 100644 --- a/src/video_core/renderer_vulkan/wrapper.h +++ b/src/video_core/renderer_vulkan/wrapper.h | |||
| @@ -721,7 +721,7 @@ public: | |||
| 721 | 721 | ||
| 722 | ShaderModule CreateShaderModule(const VkShaderModuleCreateInfo& ci) const; | 722 | ShaderModule CreateShaderModule(const VkShaderModuleCreateInfo& ci) const; |
| 723 | 723 | ||
| 724 | Event CreateEvent() const; | 724 | Event CreateNewEvent() const; |
| 725 | 725 | ||
| 726 | SwapchainKHR CreateSwapchainKHR(const VkSwapchainCreateInfoKHR& ci) const; | 726 | SwapchainKHR CreateSwapchainKHR(const VkSwapchainCreateInfoKHR& ci) const; |
| 727 | 727 | ||
diff --git a/src/video_core/shader/async_shaders.cpp b/src/video_core/shader/async_shaders.cpp index b7f66d7ee..f815584f7 100644 --- a/src/video_core/shader/async_shaders.cpp +++ b/src/video_core/shader/async_shaders.cpp | |||
| @@ -2,7 +2,6 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <chrono> | ||
| 6 | #include <condition_variable> | 5 | #include <condition_variable> |
| 7 | #include <mutex> | 6 | #include <mutex> |
| 8 | #include <thread> | 7 | #include <thread> |
| @@ -20,9 +19,18 @@ AsyncShaders::~AsyncShaders() { | |||
| 20 | KillWorkers(); | 19 | KillWorkers(); |
| 21 | } | 20 | } |
| 22 | 21 | ||
| 23 | void AsyncShaders::AllocateWorkers(std::size_t num_workers) { | 22 | void AsyncShaders::AllocateWorkers() { |
| 24 | // If we're already have workers queued or don't want to queue workers, ignore | 23 | // Max worker threads we should allow |
| 25 | if (num_workers == worker_threads.size() || num_workers == 0) { | 24 | constexpr u32 MAX_THREADS = 4; |
| 25 | // Deduce how many threads we can use | ||
| 26 | const u32 threads_used = std::thread::hardware_concurrency() / 4; | ||
| 27 | // Always allow at least 1 thread regardless of our settings | ||
| 28 | const auto max_worker_count = std::max(1U, threads_used); | ||
| 29 | // Don't use more than MAX_THREADS | ||
| 30 | const auto num_workers = std::min(max_worker_count, MAX_THREADS); | ||
| 31 | |||
| 32 | // If we already have workers queued, ignore | ||
| 33 | if (num_workers == worker_threads.size()) { | ||
| 26 | return; | 34 | return; |
| 27 | } | 35 | } |
| 28 | 36 | ||
| @@ -34,8 +42,8 @@ void AsyncShaders::AllocateWorkers(std::size_t num_workers) { | |||
| 34 | // Create workers | 42 | // Create workers |
| 35 | for (std::size_t i = 0; i < num_workers; i++) { | 43 | for (std::size_t i = 0; i < num_workers; i++) { |
| 36 | context_list.push_back(emu_window.CreateSharedContext()); | 44 | context_list.push_back(emu_window.CreateSharedContext()); |
| 37 | worker_threads.push_back(std::move( | 45 | worker_threads.push_back( |
| 38 | std::thread(&AsyncShaders::ShaderCompilerThread, this, context_list[i].get()))); | 46 | std::thread(&AsyncShaders::ShaderCompilerThread, this, context_list[i].get())); |
| 39 | } | 47 | } |
| 40 | } | 48 | } |
| 41 | 49 | ||
| @@ -111,24 +119,50 @@ void AsyncShaders::QueueOpenGLShader(const OpenGL::Device& device, | |||
| 111 | VideoCommon::Shader::CompilerSettings compiler_settings, | 119 | VideoCommon::Shader::CompilerSettings compiler_settings, |
| 112 | const VideoCommon::Shader::Registry& registry, | 120 | const VideoCommon::Shader::Registry& registry, |
| 113 | VAddr cpu_addr) { | 121 | VAddr cpu_addr) { |
| 114 | WorkerParams params{device.UseAssemblyShaders() ? AsyncShaders::Backend::GLASM | 122 | WorkerParams params{ |
| 115 | : AsyncShaders::Backend::OpenGL, | 123 | .backend = device.UseAssemblyShaders() ? Backend::GLASM : Backend::OpenGL, |
| 116 | device, | 124 | .device = &device, |
| 117 | shader_type, | 125 | .shader_type = shader_type, |
| 118 | uid, | 126 | .uid = uid, |
| 119 | std::move(code), | 127 | .code = std::move(code), |
| 120 | std::move(code_b), | 128 | .code_b = std::move(code_b), |
| 121 | main_offset, | 129 | .main_offset = main_offset, |
| 122 | compiler_settings, | 130 | .compiler_settings = compiler_settings, |
| 123 | registry, | 131 | .registry = registry, |
| 124 | cpu_addr}; | 132 | .cpu_address = cpu_addr, |
| 133 | }; | ||
| 125 | std::unique_lock lock(queue_mutex); | 134 | std::unique_lock lock(queue_mutex); |
| 126 | pending_queue.push_back(std::move(params)); | 135 | pending_queue.push(std::move(params)); |
| 136 | cv.notify_one(); | ||
| 137 | } | ||
| 138 | |||
| 139 | void AsyncShaders::QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache, | ||
| 140 | const Vulkan::VKDevice& device, Vulkan::VKScheduler& scheduler, | ||
| 141 | Vulkan::VKDescriptorPool& descriptor_pool, | ||
| 142 | Vulkan::VKUpdateDescriptorQueue& update_descriptor_queue, | ||
| 143 | Vulkan::VKRenderPassCache& renderpass_cache, | ||
| 144 | std::vector<VkDescriptorSetLayoutBinding> bindings, | ||
| 145 | Vulkan::SPIRVProgram program, | ||
| 146 | Vulkan::GraphicsPipelineCacheKey key) { | ||
| 147 | WorkerParams params{ | ||
| 148 | .backend = Backend::Vulkan, | ||
| 149 | .pp_cache = pp_cache, | ||
| 150 | .vk_device = &device, | ||
| 151 | .scheduler = &scheduler, | ||
| 152 | .descriptor_pool = &descriptor_pool, | ||
| 153 | .update_descriptor_queue = &update_descriptor_queue, | ||
| 154 | .renderpass_cache = &renderpass_cache, | ||
| 155 | .bindings = bindings, | ||
| 156 | .program = program, | ||
| 157 | .key = key, | ||
| 158 | }; | ||
| 159 | |||
| 160 | std::unique_lock lock(queue_mutex); | ||
| 161 | pending_queue.push(std::move(params)); | ||
| 127 | cv.notify_one(); | 162 | cv.notify_one(); |
| 128 | } | 163 | } |
| 129 | 164 | ||
| 130 | void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context) { | 165 | void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context) { |
| 131 | using namespace std::chrono_literals; | ||
| 132 | while (!is_thread_exiting.load(std::memory_order_relaxed)) { | 166 | while (!is_thread_exiting.load(std::memory_order_relaxed)) { |
| 133 | std::unique_lock lock{queue_mutex}; | 167 | std::unique_lock lock{queue_mutex}; |
| 134 | cv.wait(lock, [this] { return HasWorkQueued() || is_thread_exiting; }); | 168 | cv.wait(lock, [this] { return HasWorkQueued() || is_thread_exiting; }); |
| @@ -144,18 +178,17 @@ void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context | |||
| 144 | if (pending_queue.empty()) { | 178 | if (pending_queue.empty()) { |
| 145 | continue; | 179 | continue; |
| 146 | } | 180 | } |
| 181 | |||
| 147 | // Pull work from queue | 182 | // Pull work from queue |
| 148 | WorkerParams work = std::move(pending_queue.front()); | 183 | WorkerParams work = std::move(pending_queue.front()); |
| 149 | pending_queue.pop_front(); | 184 | pending_queue.pop(); |
| 150 | |||
| 151 | lock.unlock(); | 185 | lock.unlock(); |
| 152 | 186 | ||
| 153 | if (work.backend == AsyncShaders::Backend::OpenGL || | 187 | if (work.backend == Backend::OpenGL || work.backend == Backend::GLASM) { |
| 154 | work.backend == AsyncShaders::Backend::GLASM) { | 188 | const ShaderIR ir(work.code, work.main_offset, work.compiler_settings, *work.registry); |
| 155 | const ShaderIR ir(work.code, work.main_offset, work.compiler_settings, work.registry); | ||
| 156 | const auto scope = context->Acquire(); | 189 | const auto scope = context->Acquire(); |
| 157 | auto program = | 190 | auto program = |
| 158 | OpenGL::BuildShader(work.device, work.shader_type, work.uid, ir, work.registry); | 191 | OpenGL::BuildShader(*work.device, work.shader_type, work.uid, ir, *work.registry); |
| 159 | Result result{}; | 192 | Result result{}; |
| 160 | result.backend = work.backend; | 193 | result.backend = work.backend; |
| 161 | result.cpu_address = work.cpu_address; | 194 | result.cpu_address = work.cpu_address; |
| @@ -164,9 +197,9 @@ void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context | |||
| 164 | result.code_b = std::move(work.code_b); | 197 | result.code_b = std::move(work.code_b); |
| 165 | result.shader_type = work.shader_type; | 198 | result.shader_type = work.shader_type; |
| 166 | 199 | ||
| 167 | if (work.backend == AsyncShaders::Backend::OpenGL) { | 200 | if (work.backend == Backend::OpenGL) { |
| 168 | result.program.opengl = std::move(program->source_program); | 201 | result.program.opengl = std::move(program->source_program); |
| 169 | } else if (work.backend == AsyncShaders::Backend::GLASM) { | 202 | } else if (work.backend == Backend::GLASM) { |
| 170 | result.program.glasm = std::move(program->assembly_program); | 203 | result.program.glasm = std::move(program->assembly_program); |
| 171 | } | 204 | } |
| 172 | 205 | ||
| @@ -174,6 +207,13 @@ void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context | |||
| 174 | std::unique_lock complete_lock(completed_mutex); | 207 | std::unique_lock complete_lock(completed_mutex); |
| 175 | finished_work.push_back(std::move(result)); | 208 | finished_work.push_back(std::move(result)); |
| 176 | } | 209 | } |
| 210 | } else if (work.backend == Backend::Vulkan) { | ||
| 211 | auto pipeline = std::make_unique<Vulkan::VKGraphicsPipeline>( | ||
| 212 | *work.vk_device, *work.scheduler, *work.descriptor_pool, | ||
| 213 | *work.update_descriptor_queue, *work.renderpass_cache, work.key, work.bindings, | ||
| 214 | work.program); | ||
| 215 | |||
| 216 | work.pp_cache->EmplacePipeline(std::move(pipeline)); | ||
| 177 | } | 217 | } |
| 178 | } | 218 | } |
| 179 | } | 219 | } |
diff --git a/src/video_core/shader/async_shaders.h b/src/video_core/shader/async_shaders.h index 2f5ee94ad..d5ae814d5 100644 --- a/src/video_core/shader/async_shaders.h +++ b/src/video_core/shader/async_shaders.h | |||
| @@ -14,6 +14,10 @@ | |||
| 14 | #include "video_core/renderer_opengl/gl_device.h" | 14 | #include "video_core/renderer_opengl/gl_device.h" |
| 15 | #include "video_core/renderer_opengl/gl_resource_manager.h" | 15 | #include "video_core/renderer_opengl/gl_resource_manager.h" |
| 16 | #include "video_core/renderer_opengl/gl_shader_decompiler.h" | 16 | #include "video_core/renderer_opengl/gl_shader_decompiler.h" |
| 17 | #include "video_core/renderer_vulkan/vk_device.h" | ||
| 18 | #include "video_core/renderer_vulkan/vk_pipeline_cache.h" | ||
| 19 | #include "video_core/renderer_vulkan/vk_scheduler.h" | ||
| 20 | #include "video_core/renderer_vulkan/vk_update_descriptor.h" | ||
| 17 | 21 | ||
| 18 | namespace Core::Frontend { | 22 | namespace Core::Frontend { |
| 19 | class EmuWindow; | 23 | class EmuWindow; |
| @@ -24,6 +28,10 @@ namespace Tegra { | |||
| 24 | class GPU; | 28 | class GPU; |
| 25 | } | 29 | } |
| 26 | 30 | ||
| 31 | namespace Vulkan { | ||
| 32 | class VKPipelineCache; | ||
| 33 | } | ||
| 34 | |||
| 27 | namespace VideoCommon::Shader { | 35 | namespace VideoCommon::Shader { |
| 28 | 36 | ||
| 29 | class AsyncShaders { | 37 | class AsyncShaders { |
| @@ -31,6 +39,7 @@ public: | |||
| 31 | enum class Backend { | 39 | enum class Backend { |
| 32 | OpenGL, | 40 | OpenGL, |
| 33 | GLASM, | 41 | GLASM, |
| 42 | Vulkan, | ||
| 34 | }; | 43 | }; |
| 35 | 44 | ||
| 36 | struct ResultPrograms { | 45 | struct ResultPrograms { |
| @@ -52,7 +61,7 @@ public: | |||
| 52 | ~AsyncShaders(); | 61 | ~AsyncShaders(); |
| 53 | 62 | ||
| 54 | /// Start up shader worker threads | 63 | /// Start up shader worker threads |
| 55 | void AllocateWorkers(std::size_t num_workers); | 64 | void AllocateWorkers(); |
| 56 | 65 | ||
| 57 | /// Clear the shader queue and kill all worker threads | 66 | /// Clear the shader queue and kill all worker threads |
| 58 | void FreeWorkers(); | 67 | void FreeWorkers(); |
| @@ -76,6 +85,14 @@ public: | |||
| 76 | VideoCommon::Shader::CompilerSettings compiler_settings, | 85 | VideoCommon::Shader::CompilerSettings compiler_settings, |
| 77 | const VideoCommon::Shader::Registry& registry, VAddr cpu_addr); | 86 | const VideoCommon::Shader::Registry& registry, VAddr cpu_addr); |
| 78 | 87 | ||
| 88 | void QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache, const Vulkan::VKDevice& device, | ||
| 89 | Vulkan::VKScheduler& scheduler, | ||
| 90 | Vulkan::VKDescriptorPool& descriptor_pool, | ||
| 91 | Vulkan::VKUpdateDescriptorQueue& update_descriptor_queue, | ||
| 92 | Vulkan::VKRenderPassCache& renderpass_cache, | ||
| 93 | std::vector<VkDescriptorSetLayoutBinding> bindings, | ||
| 94 | Vulkan::SPIRVProgram program, Vulkan::GraphicsPipelineCacheKey key); | ||
| 95 | |||
| 79 | private: | 96 | private: |
| 80 | void ShaderCompilerThread(Core::Frontend::GraphicsContext* context); | 97 | void ShaderCompilerThread(Core::Frontend::GraphicsContext* context); |
| 81 | 98 | ||
| @@ -83,16 +100,28 @@ private: | |||
| 83 | bool HasWorkQueued(); | 100 | bool HasWorkQueued(); |
| 84 | 101 | ||
| 85 | struct WorkerParams { | 102 | struct WorkerParams { |
| 86 | AsyncShaders::Backend backend; | 103 | Backend backend; |
| 87 | OpenGL::Device device; | 104 | // For OGL |
| 105 | const OpenGL::Device* device; | ||
| 88 | Tegra::Engines::ShaderType shader_type; | 106 | Tegra::Engines::ShaderType shader_type; |
| 89 | u64 uid; | 107 | u64 uid; |
| 90 | std::vector<u64> code; | 108 | std::vector<u64> code; |
| 91 | std::vector<u64> code_b; | 109 | std::vector<u64> code_b; |
| 92 | u32 main_offset; | 110 | u32 main_offset; |
| 93 | VideoCommon::Shader::CompilerSettings compiler_settings; | 111 | VideoCommon::Shader::CompilerSettings compiler_settings; |
| 94 | VideoCommon::Shader::Registry registry; | 112 | std::optional<VideoCommon::Shader::Registry> registry; |
| 95 | VAddr cpu_address; | 113 | VAddr cpu_address; |
| 114 | |||
| 115 | // For Vulkan | ||
| 116 | Vulkan::VKPipelineCache* pp_cache; | ||
| 117 | const Vulkan::VKDevice* vk_device; | ||
| 118 | Vulkan::VKScheduler* scheduler; | ||
| 119 | Vulkan::VKDescriptorPool* descriptor_pool; | ||
| 120 | Vulkan::VKUpdateDescriptorQueue* update_descriptor_queue; | ||
| 121 | Vulkan::VKRenderPassCache* renderpass_cache; | ||
| 122 | std::vector<VkDescriptorSetLayoutBinding> bindings; | ||
| 123 | Vulkan::SPIRVProgram program; | ||
| 124 | Vulkan::GraphicsPipelineCacheKey key; | ||
| 96 | }; | 125 | }; |
| 97 | 126 | ||
| 98 | std::condition_variable cv; | 127 | std::condition_variable cv; |
| @@ -101,7 +130,7 @@ private: | |||
| 101 | std::atomic<bool> is_thread_exiting{}; | 130 | std::atomic<bool> is_thread_exiting{}; |
| 102 | std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> context_list; | 131 | std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> context_list; |
| 103 | std::vector<std::thread> worker_threads; | 132 | std::vector<std::thread> worker_threads; |
| 104 | std::deque<WorkerParams> pending_queue; | 133 | std::queue<WorkerParams> pending_queue; |
| 105 | std::vector<AsyncShaders::Result> finished_work; | 134 | std::vector<AsyncShaders::Result> finished_work; |
| 106 | Core::Frontend::EmuWindow& emu_window; | 135 | Core::Frontend::EmuWindow& emu_window; |
| 107 | }; | 136 | }; |
diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index a793c803d..846a30586 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.ui +++ b/src/yuzu/configuration/configure_graphics_advanced.ui | |||
| @@ -92,7 +92,7 @@ | |||
| 92 | <string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string> | 92 | <string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string> |
| 93 | </property> | 93 | </property> |
| 94 | <property name="text"> | 94 | <property name="text"> |
| 95 | <string>Use asynchronous shader building (experimental, OpenGL or Assembly shaders only)</string> | 95 | <string>Use asynchronous shader building (experimental)</string> |
| 96 | </property> | 96 | </property> |
| 97 | </widget> | 97 | </widget> |
| 98 | </item> | 98 | </item> |
diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp index 6f7fd4414..cbee51a5e 100644 --- a/src/yuzu/configuration/configure_hotkeys.cpp +++ b/src/yuzu/configuration/configure_hotkeys.cpp | |||
| @@ -154,7 +154,7 @@ void ConfigureHotkeys::ClearAll() { | |||
| 154 | const QStandardItem* parent = model->item(r, 0); | 154 | const QStandardItem* parent = model->item(r, 0); |
| 155 | 155 | ||
| 156 | for (int r2 = 0; r2 < parent->rowCount(); ++r2) { | 156 | for (int r2 = 0; r2 < parent->rowCount(); ++r2) { |
| 157 | model->item(r, 0)->child(r2, 1)->setText(tr("")); | 157 | model->item(r, 0)->child(r2, 1)->setText(QString{}); |
| 158 | } | 158 | } |
| 159 | } | 159 | } |
| 160 | } | 160 | } |
| @@ -186,7 +186,7 @@ void ConfigureHotkeys::PopupContextMenu(const QPoint& menu_location) { | |||
| 186 | model->setData(selected, default_key_sequence.toString(QKeySequence::NativeText)); | 186 | model->setData(selected, default_key_sequence.toString(QKeySequence::NativeText)); |
| 187 | } | 187 | } |
| 188 | }); | 188 | }); |
| 189 | connect(clear, &QAction::triggered, [this, selected] { model->setData(selected, tr("")); }); | 189 | connect(clear, &QAction::triggered, [this, selected] { model->setData(selected, QString{}); }); |
| 190 | 190 | ||
| 191 | context_menu.exec(ui->hotkey_list->viewport()->mapToGlobal(menu_location)); | 191 | context_menu.exec(ui->hotkey_list->viewport()->mapToGlobal(menu_location)); |
| 192 | } | 192 | } |
diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp index 53049ffd6..0e26f765b 100644 --- a/src/yuzu/debugger/profiler.cpp +++ b/src/yuzu/debugger/profiler.cpp | |||
| @@ -109,8 +109,7 @@ MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) { | |||
| 109 | MicroProfileSetDisplayMode(1); // Timers screen | 109 | MicroProfileSetDisplayMode(1); // Timers screen |
| 110 | MicroProfileInitUI(); | 110 | MicroProfileInitUI(); |
| 111 | 111 | ||
| 112 | connect(&update_timer, &QTimer::timeout, this, | 112 | connect(&update_timer, &QTimer::timeout, this, qOverload<>(&MicroProfileWidget::update)); |
| 113 | static_cast<void (MicroProfileWidget::*)()>(&MicroProfileWidget::update)); | ||
| 114 | } | 113 | } |
| 115 | 114 | ||
| 116 | void MicroProfileWidget::paintEvent(QPaintEvent* ev) { | 115 | void MicroProfileWidget::paintEvent(QPaintEvent* ev) { |
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 62acc3720..967ef4a21 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -406,7 +406,7 @@ bool GameList::isEmpty() const { | |||
| 406 | type == GameListItemType::SysNandDir)) { | 406 | type == GameListItemType::SysNandDir)) { |
| 407 | item_model->invisibleRootItem()->removeRow(child->row()); | 407 | item_model->invisibleRootItem()->removeRow(child->row()); |
| 408 | i--; | 408 | i--; |
| 409 | }; | 409 | } |
| 410 | } | 410 | } |
| 411 | return !item_model->invisibleRootItem()->hasChildren(); | 411 | return !item_model->invisibleRootItem()->hasChildren(); |
| 412 | } | 412 | } |
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index de343be7f..e0ce45fd9 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp | |||
| @@ -374,7 +374,7 @@ void GameListWorker::run() { | |||
| 374 | ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(), | 374 | ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(), |
| 375 | game_dir.deep_scan ? 256 : 0, game_list_dir); | 375 | game_dir.deep_scan ? 256 : 0, game_list_dir); |
| 376 | } | 376 | } |
| 377 | }; | 377 | } |
| 378 | 378 | ||
| 379 | emit Finished(watch_list); | 379 | emit Finished(watch_list); |
| 380 | } | 380 | } |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 871dd2d5a..9d1e67a3a 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -894,6 +894,8 @@ void GMainWindow::ConnectMenuEvents() { | |||
| 894 | connect(ui.action_Open_FAQ, &QAction::triggered, this, &GMainWindow::OnOpenFAQ); | 894 | connect(ui.action_Open_FAQ, &QAction::triggered, this, &GMainWindow::OnOpenFAQ); |
| 895 | connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); }); | 895 | connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); }); |
| 896 | connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); | 896 | connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); |
| 897 | connect(ui.action_Configure_Current_Game, &QAction::triggered, this, | ||
| 898 | &GMainWindow::OnConfigurePerGame); | ||
| 897 | 899 | ||
| 898 | // View | 900 | // View |
| 899 | connect(ui.action_Single_Window_Mode, &QAction::triggered, this, | 901 | connect(ui.action_Single_Window_Mode, &QAction::triggered, this, |
| @@ -1167,6 +1169,7 @@ void GMainWindow::ShutdownGame() { | |||
| 1167 | ui.action_Pause->setEnabled(false); | 1169 | ui.action_Pause->setEnabled(false); |
| 1168 | ui.action_Stop->setEnabled(false); | 1170 | ui.action_Stop->setEnabled(false); |
| 1169 | ui.action_Restart->setEnabled(false); | 1171 | ui.action_Restart->setEnabled(false); |
| 1172 | ui.action_Configure_Current_Game->setEnabled(false); | ||
| 1170 | ui.action_Report_Compatibility->setEnabled(false); | 1173 | ui.action_Report_Compatibility->setEnabled(false); |
| 1171 | ui.action_Load_Amiibo->setEnabled(false); | 1174 | ui.action_Load_Amiibo->setEnabled(false); |
| 1172 | ui.action_Capture_Screenshot->setEnabled(false); | 1175 | ui.action_Capture_Screenshot->setEnabled(false); |
| @@ -1720,26 +1723,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { | |||
| 1720 | return; | 1723 | return; |
| 1721 | } | 1724 | } |
| 1722 | 1725 | ||
| 1723 | ConfigurePerGame dialog(this, title_id); | 1726 | OpenPerGameConfiguration(title_id, file); |
| 1724 | dialog.LoadFromFile(v_file); | ||
| 1725 | auto result = dialog.exec(); | ||
| 1726 | if (result == QDialog::Accepted) { | ||
| 1727 | dialog.ApplyConfiguration(); | ||
| 1728 | |||
| 1729 | const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); | ||
| 1730 | if (reload) { | ||
| 1731 | game_list->PopulateAsync(UISettings::values.game_dirs); | ||
| 1732 | } | ||
| 1733 | |||
| 1734 | // Do not cause the global config to write local settings into the config file | ||
| 1735 | Settings::RestoreGlobalState(); | ||
| 1736 | |||
| 1737 | if (!Core::System::GetInstance().IsPoweredOn()) { | ||
| 1738 | config->Save(); | ||
| 1739 | } | ||
| 1740 | } else { | ||
| 1741 | Settings::RestoreGlobalState(); | ||
| 1742 | } | ||
| 1743 | } | 1727 | } |
| 1744 | 1728 | ||
| 1745 | void GMainWindow::OnMenuLoadFile() { | 1729 | void GMainWindow::OnMenuLoadFile() { |
| @@ -2068,6 +2052,7 @@ void GMainWindow::OnStartGame() { | |||
| 2068 | ui.action_Pause->setEnabled(true); | 2052 | ui.action_Pause->setEnabled(true); |
| 2069 | ui.action_Stop->setEnabled(true); | 2053 | ui.action_Stop->setEnabled(true); |
| 2070 | ui.action_Restart->setEnabled(true); | 2054 | ui.action_Restart->setEnabled(true); |
| 2055 | ui.action_Configure_Current_Game->setEnabled(true); | ||
| 2071 | ui.action_Report_Compatibility->setEnabled(true); | 2056 | ui.action_Report_Compatibility->setEnabled(true); |
| 2072 | 2057 | ||
| 2073 | discord_rpc->Update(); | 2058 | discord_rpc->Update(); |
| @@ -2257,6 +2242,36 @@ void GMainWindow::OnConfigure() { | |||
| 2257 | UpdateStatusButtons(); | 2242 | UpdateStatusButtons(); |
| 2258 | } | 2243 | } |
| 2259 | 2244 | ||
| 2245 | void GMainWindow::OnConfigurePerGame() { | ||
| 2246 | const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); | ||
| 2247 | OpenPerGameConfiguration(title_id, game_path.toStdString()); | ||
| 2248 | } | ||
| 2249 | |||
| 2250 | void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file_name) { | ||
| 2251 | const auto v_file = Core::GetGameFileFromPath(vfs, file_name); | ||
| 2252 | |||
| 2253 | ConfigurePerGame dialog(this, title_id); | ||
| 2254 | dialog.LoadFromFile(v_file); | ||
| 2255 | auto result = dialog.exec(); | ||
| 2256 | if (result == QDialog::Accepted) { | ||
| 2257 | dialog.ApplyConfiguration(); | ||
| 2258 | |||
| 2259 | const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); | ||
| 2260 | if (reload) { | ||
| 2261 | game_list->PopulateAsync(UISettings::values.game_dirs); | ||
| 2262 | } | ||
| 2263 | |||
| 2264 | // Do not cause the global config to write local settings into the config file | ||
| 2265 | Settings::RestoreGlobalState(); | ||
| 2266 | |||
| 2267 | if (!Core::System::GetInstance().IsPoweredOn()) { | ||
| 2268 | config->Save(); | ||
| 2269 | } | ||
| 2270 | } else { | ||
| 2271 | Settings::RestoreGlobalState(); | ||
| 2272 | } | ||
| 2273 | } | ||
| 2274 | |||
| 2260 | void GMainWindow::OnLoadAmiibo() { | 2275 | void GMainWindow::OnLoadAmiibo() { |
| 2261 | const QString extensions{QStringLiteral("*.bin")}; | 2276 | const QString extensions{QStringLiteral("*.bin")}; |
| 2262 | const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions); | 2277 | const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions); |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 73a44a3bf..64c33830d 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -216,6 +216,7 @@ private slots: | |||
| 216 | void OnMenuInstallToNAND(); | 216 | void OnMenuInstallToNAND(); |
| 217 | void OnMenuRecentFile(); | 217 | void OnMenuRecentFile(); |
| 218 | void OnConfigure(); | 218 | void OnConfigure(); |
| 219 | void OnConfigurePerGame(); | ||
| 219 | void OnLoadAmiibo(); | 220 | void OnLoadAmiibo(); |
| 220 | void OnOpenYuzuFolder(); | 221 | void OnOpenYuzuFolder(); |
| 221 | void OnAbout(); | 222 | void OnAbout(); |
| @@ -249,6 +250,7 @@ private: | |||
| 249 | void ShowMouseCursor(); | 250 | void ShowMouseCursor(); |
| 250 | void OpenURL(const QUrl& url); | 251 | void OpenURL(const QUrl& url); |
| 251 | void LoadTranslation(); | 252 | void LoadTranslation(); |
| 253 | void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); | ||
| 252 | 254 | ||
| 253 | Ui::MainWindow ui; | 255 | Ui::MainWindow ui; |
| 254 | 256 | ||
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index c3a1d715e..87ea985d8 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui | |||
| @@ -81,6 +81,7 @@ | |||
| 81 | <addaction name="action_Restart"/> | 81 | <addaction name="action_Restart"/> |
| 82 | <addaction name="separator"/> | 82 | <addaction name="separator"/> |
| 83 | <addaction name="action_Configure"/> | 83 | <addaction name="action_Configure"/> |
| 84 | <addaction name="action_Configure_Current_Game"/> | ||
| 84 | </widget> | 85 | </widget> |
| 85 | <widget class="QMenu" name="menu_View"> | 86 | <widget class="QMenu" name="menu_View"> |
| 86 | <property name="title"> | 87 | <property name="title"> |
| @@ -287,6 +288,14 @@ | |||
| 287 | <string>Capture Screenshot</string> | 288 | <string>Capture Screenshot</string> |
| 288 | </property> | 289 | </property> |
| 289 | </action> | 290 | </action> |
| 291 | <action name="action_Configure_Current_Game"> | ||
| 292 | <property name="enabled"> | ||
| 293 | <bool>false</bool> | ||
| 294 | </property> | ||
| 295 | <property name="text"> | ||
| 296 | <string>Configure Current Game..</string> | ||
| 297 | </property> | ||
| 298 | </action> | ||
| 290 | </widget> | 299 | </widget> |
| 291 | <resources/> | 300 | <resources/> |
| 292 | <connections/> | 301 | <connections/> |
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index bbfeafc55..2d2e82f15 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h | |||
| @@ -29,14 +29,14 @@ extern const Themes themes; | |||
| 29 | 29 | ||
| 30 | struct GameDir { | 30 | struct GameDir { |
| 31 | QString path; | 31 | QString path; |
| 32 | bool deep_scan; | 32 | bool deep_scan = false; |
| 33 | bool expanded; | 33 | bool expanded = false; |
| 34 | bool operator==(const GameDir& rhs) const { | 34 | bool operator==(const GameDir& rhs) const { |
| 35 | return path == rhs.path; | 35 | return path == rhs.path; |
| 36 | }; | 36 | } |
| 37 | bool operator!=(const GameDir& rhs) const { | 37 | bool operator!=(const GameDir& rhs) const { |
| 38 | return !operator==(rhs); | 38 | return !operator==(rhs); |
| 39 | }; | 39 | } |
| 40 | }; | 40 | }; |
| 41 | 41 | ||
| 42 | struct Values { | 42 | struct Values { |