async_shaders.cpp 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. // Copyright 2020 yuzu Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <condition_variable>
  5. #include <mutex>
  6. #include <thread>
  7. #include <vector>
  8. #include "video_core/engines/maxwell_3d.h"
  9. #include "video_core/renderer_base.h"
  10. #include "video_core/renderer_opengl/gl_shader_cache.h"
  11. #include "video_core/shader/async_shaders.h"
  12. namespace VideoCommon::Shader {
  13. AsyncShaders::AsyncShaders(Core::Frontend::EmuWindow& emu_window_) : emu_window(emu_window_) {}
  14. AsyncShaders::~AsyncShaders() {
  15. KillWorkers();
  16. }
  17. void AsyncShaders::AllocateWorkers() {
  18. // Use at least one thread
  19. u32 num_workers = 1;
  20. // Deduce how many more threads we can use
  21. const u32 thread_count = std::thread::hardware_concurrency();
  22. if (thread_count >= 8) {
  23. // Increase async workers by 1 for every 2 threads >= 8
  24. num_workers += 1 + (thread_count - 8) / 2;
  25. }
  26. // If we already have workers queued, ignore
  27. if (num_workers == worker_threads.size()) {
  28. return;
  29. }
  30. // If workers already exist, clear them
  31. if (!worker_threads.empty()) {
  32. FreeWorkers();
  33. }
  34. // Create workers
  35. for (std::size_t i = 0; i < num_workers; i++) {
  36. context_list.push_back(emu_window.CreateSharedContext());
  37. worker_threads.emplace_back(&AsyncShaders::ShaderCompilerThread, this,
  38. context_list[i].get());
  39. }
  40. }
  41. void AsyncShaders::FreeWorkers() {
  42. // Mark all threads to quit
  43. is_thread_exiting.store(true);
  44. cv.notify_all();
  45. for (auto& thread : worker_threads) {
  46. thread.join();
  47. }
  48. // Clear our shared contexts
  49. context_list.clear();
  50. // Clear our worker threads
  51. worker_threads.clear();
  52. }
  53. void AsyncShaders::KillWorkers() {
  54. is_thread_exiting.store(true);
  55. for (auto& thread : worker_threads) {
  56. thread.detach();
  57. }
  58. // Clear our shared contexts
  59. context_list.clear();
  60. // Clear our worker threads
  61. worker_threads.clear();
  62. }
  63. bool AsyncShaders::HasWorkQueued() const {
  64. return !pending_queue.empty();
  65. }
  66. bool AsyncShaders::HasCompletedWork() const {
  67. std::shared_lock lock{completed_mutex};
  68. return !finished_work.empty();
  69. }
  70. bool AsyncShaders::IsShaderAsync(const Tegra::GPU& gpu) const {
  71. const auto& regs = gpu.Maxwell3D().regs;
  72. // If something is using depth, we can assume that games are not rendering anything which will
  73. // be used one time.
  74. if (regs.zeta_enable) {
  75. return true;
  76. }
  77. // If games are using a small index count, we can assume these are full screen quads. Usually
  78. // these shaders are only used once for building textures so we can assume they can't be built
  79. // async
  80. if (regs.index_array.count <= 6 || regs.vertex_buffer.count <= 6) {
  81. return false;
  82. }
  83. return true;
  84. }
  85. std::vector<AsyncShaders::Result> AsyncShaders::GetCompletedWork() {
  86. std::vector<Result> results;
  87. {
  88. std::unique_lock lock{completed_mutex};
  89. results = std::move(finished_work);
  90. finished_work.clear();
  91. }
  92. return results;
  93. }
  94. void AsyncShaders::QueueOpenGLShader(const OpenGL::Device& device,
  95. Tegra::Engines::ShaderType shader_type, u64 uid,
  96. std::vector<u64> code, std::vector<u64> code_b,
  97. u32 main_offset, CompilerSettings compiler_settings,
  98. const Registry& registry, VAddr cpu_addr) {
  99. std::unique_lock lock(queue_mutex);
  100. pending_queue.push({
  101. .backend = device.UseAssemblyShaders() ? Backend::GLASM : Backend::OpenGL,
  102. .device = &device,
  103. .shader_type = shader_type,
  104. .uid = uid,
  105. .code = std::move(code),
  106. .code_b = std::move(code_b),
  107. .main_offset = main_offset,
  108. .compiler_settings = compiler_settings,
  109. .registry = registry,
  110. .cpu_address = cpu_addr,
  111. });
  112. cv.notify_one();
  113. }
  114. void AsyncShaders::QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache,
  115. const Vulkan::VKDevice& device, Vulkan::VKScheduler& scheduler,
  116. Vulkan::VKDescriptorPool& descriptor_pool,
  117. Vulkan::VKUpdateDescriptorQueue& update_descriptor_queue,
  118. Vulkan::VKRenderPassCache& renderpass_cache,
  119. std::vector<VkDescriptorSetLayoutBinding> bindings,
  120. Vulkan::SPIRVProgram program,
  121. Vulkan::GraphicsPipelineCacheKey key) {
  122. std::unique_lock lock(queue_mutex);
  123. pending_queue.push({
  124. .backend = Backend::Vulkan,
  125. .pp_cache = pp_cache,
  126. .vk_device = &device,
  127. .scheduler = &scheduler,
  128. .descriptor_pool = &descriptor_pool,
  129. .update_descriptor_queue = &update_descriptor_queue,
  130. .renderpass_cache = &renderpass_cache,
  131. .bindings = std::move(bindings),
  132. .program = std::move(program),
  133. .key = key,
  134. });
  135. cv.notify_one();
  136. }
  137. void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context) {
  138. while (!is_thread_exiting.load(std::memory_order_relaxed)) {
  139. std::unique_lock lock{queue_mutex};
  140. cv.wait(lock, [this] { return HasWorkQueued() || is_thread_exiting; });
  141. if (is_thread_exiting) {
  142. return;
  143. }
  144. // Partial lock to allow all threads to read at the same time
  145. if (!HasWorkQueued()) {
  146. continue;
  147. }
  148. // Another thread beat us, just unlock and wait for the next load
  149. if (pending_queue.empty()) {
  150. continue;
  151. }
  152. // Pull work from queue
  153. WorkerParams work = std::move(pending_queue.front());
  154. pending_queue.pop();
  155. lock.unlock();
  156. if (work.backend == Backend::OpenGL || work.backend == Backend::GLASM) {
  157. const ShaderIR ir(work.code, work.main_offset, work.compiler_settings, *work.registry);
  158. const auto scope = context->Acquire();
  159. auto program =
  160. OpenGL::BuildShader(*work.device, work.shader_type, work.uid, ir, *work.registry);
  161. Result result{};
  162. result.backend = work.backend;
  163. result.cpu_address = work.cpu_address;
  164. result.uid = work.uid;
  165. result.code = std::move(work.code);
  166. result.code_b = std::move(work.code_b);
  167. result.shader_type = work.shader_type;
  168. if (work.backend == Backend::OpenGL) {
  169. result.program.opengl = std::move(program->source_program);
  170. } else if (work.backend == Backend::GLASM) {
  171. result.program.glasm = std::move(program->assembly_program);
  172. }
  173. {
  174. std::unique_lock complete_lock(completed_mutex);
  175. finished_work.push_back(std::move(result));
  176. }
  177. } else if (work.backend == Backend::Vulkan) {
  178. auto pipeline = std::make_unique<Vulkan::VKGraphicsPipeline>(
  179. *work.vk_device, *work.scheduler, *work.descriptor_pool,
  180. *work.update_descriptor_queue, *work.renderpass_cache, work.key, work.bindings,
  181. work.program);
  182. work.pp_cache->EmplacePipeline(std::move(pipeline));
  183. }
  184. }
  185. }
  186. } // namespace VideoCommon::Shader