async_shaders.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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. .pp_cache = nullptr,
  112. .vk_device = nullptr,
  113. .scheduler = nullptr,
  114. .descriptor_pool = nullptr,
  115. .update_descriptor_queue = nullptr,
  116. .bindings{},
  117. .program{},
  118. .key{},
  119. .num_color_buffers = 0,
  120. });
  121. cv.notify_one();
  122. }
  123. void AsyncShaders::QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache,
  124. const Vulkan::Device& device, Vulkan::VKScheduler& scheduler,
  125. Vulkan::VKDescriptorPool& descriptor_pool,
  126. Vulkan::VKUpdateDescriptorQueue& update_descriptor_queue,
  127. std::vector<VkDescriptorSetLayoutBinding> bindings,
  128. Vulkan::SPIRVProgram program,
  129. Vulkan::GraphicsPipelineCacheKey key, u32 num_color_buffers) {
  130. std::unique_lock lock(queue_mutex);
  131. pending_queue.push({
  132. .backend = Backend::Vulkan,
  133. .device = nullptr,
  134. .shader_type{},
  135. .uid = 0,
  136. .code{},
  137. .code_b{},
  138. .main_offset = 0,
  139. .compiler_settings{},
  140. .registry{},
  141. .cpu_address = 0,
  142. .pp_cache = pp_cache,
  143. .vk_device = &device,
  144. .scheduler = &scheduler,
  145. .descriptor_pool = &descriptor_pool,
  146. .update_descriptor_queue = &update_descriptor_queue,
  147. .bindings = std::move(bindings),
  148. .program = std::move(program),
  149. .key = key,
  150. .num_color_buffers = num_color_buffers,
  151. });
  152. cv.notify_one();
  153. }
  154. void AsyncShaders::ShaderCompilerThread(Core::Frontend::GraphicsContext* context) {
  155. while (!is_thread_exiting.load(std::memory_order_relaxed)) {
  156. std::unique_lock lock{queue_mutex};
  157. cv.wait(lock, [this] { return HasWorkQueued() || is_thread_exiting; });
  158. if (is_thread_exiting) {
  159. return;
  160. }
  161. // Partial lock to allow all threads to read at the same time
  162. if (!HasWorkQueued()) {
  163. continue;
  164. }
  165. // Another thread beat us, just unlock and wait for the next load
  166. if (pending_queue.empty()) {
  167. continue;
  168. }
  169. // Pull work from queue
  170. WorkerParams work = std::move(pending_queue.front());
  171. pending_queue.pop();
  172. lock.unlock();
  173. if (work.backend == Backend::OpenGL || work.backend == Backend::GLASM) {
  174. const ShaderIR ir(work.code, work.main_offset, work.compiler_settings, *work.registry);
  175. const auto scope = context->Acquire();
  176. auto program =
  177. OpenGL::BuildShader(*work.device, work.shader_type, work.uid, ir, *work.registry);
  178. Result result{};
  179. result.backend = work.backend;
  180. result.cpu_address = work.cpu_address;
  181. result.uid = work.uid;
  182. result.code = std::move(work.code);
  183. result.code_b = std::move(work.code_b);
  184. result.shader_type = work.shader_type;
  185. if (work.backend == Backend::OpenGL) {
  186. result.program.opengl = std::move(program->source_program);
  187. } else if (work.backend == Backend::GLASM) {
  188. result.program.glasm = std::move(program->assembly_program);
  189. }
  190. {
  191. std::unique_lock complete_lock(completed_mutex);
  192. finished_work.push_back(std::move(result));
  193. }
  194. } else if (work.backend == Backend::Vulkan) {
  195. auto pipeline = std::make_unique<Vulkan::VKGraphicsPipeline>(
  196. *work.vk_device, *work.scheduler, *work.descriptor_pool,
  197. *work.update_descriptor_queue, work.key, work.bindings, work.program,
  198. work.num_color_buffers);
  199. work.pp_cache->EmplacePipeline(std::move(pipeline));
  200. }
  201. }
  202. }
  203. } // namespace VideoCommon::Shader