shader_cache.h 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. // Copyright 2020 yuzu Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #pragma once
  5. #include <algorithm>
  6. #include <memory>
  7. #include <mutex>
  8. #include <unordered_map>
  9. #include <utility>
  10. #include <vector>
  11. #include "common/assert.h"
  12. #include "common/common_types.h"
  13. #include "video_core/rasterizer_interface.h"
  14. namespace VideoCommon {
  15. template <class T>
  16. class ShaderCache {
  17. static constexpr u64 PAGE_BITS = 14;
  18. static constexpr u64 PAGE_SIZE = u64(1) << PAGE_BITS;
  19. struct Entry {
  20. VAddr addr_start;
  21. VAddr addr_end;
  22. T* data;
  23. bool is_memory_marked = true;
  24. constexpr bool Overlaps(VAddr start, VAddr end) const noexcept {
  25. return start < addr_end && addr_start < end;
  26. }
  27. };
  28. public:
  29. virtual ~ShaderCache() = default;
  30. /// @brief Removes shaders inside a given region
  31. /// @note Checks for ranges
  32. /// @param addr Start address of the invalidation
  33. /// @param size Number of bytes of the invalidation
  34. void InvalidateRegion(VAddr addr, std::size_t size) {
  35. std::scoped_lock lock{invalidation_mutex};
  36. InvalidatePagesInRegion(addr, size);
  37. RemovePendingShaders();
  38. }
  39. /// @brief Unmarks a memory region as cached and marks it for removal
  40. /// @param addr Start address of the CPU write operation
  41. /// @param size Number of bytes of the CPU write operation
  42. void OnCPUWrite(VAddr addr, std::size_t size) {
  43. std::lock_guard lock{invalidation_mutex};
  44. InvalidatePagesInRegion(addr, size);
  45. }
  46. /// @brief Flushes delayed removal operations
  47. void SyncGuestHost() {
  48. std::scoped_lock lock{invalidation_mutex};
  49. RemovePendingShaders();
  50. }
  51. /// @brief Tries to obtain a cached shader starting in a given address
  52. /// @note Doesn't check for ranges, the given address has to be the start of the shader
  53. /// @param addr Start address of the shader, this doesn't cache for region
  54. /// @return Pointer to a valid shader, nullptr when nothing is found
  55. T* TryGet(VAddr addr) const {
  56. std::scoped_lock lock{lookup_mutex};
  57. const auto it = lookup_cache.find(addr);
  58. if (it == lookup_cache.end()) {
  59. return nullptr;
  60. }
  61. return it->second->data;
  62. }
  63. protected:
  64. explicit ShaderCache(VideoCore::RasterizerInterface& rasterizer_) : rasterizer{rasterizer_} {}
  65. /// @brief Register in the cache a given entry
  66. /// @param data Shader to store in the cache
  67. /// @param addr Start address of the shader that will be registered
  68. /// @param size Size in bytes of the shader
  69. void Register(std::unique_ptr<T> data, VAddr addr, std::size_t size) {
  70. std::scoped_lock lock{invalidation_mutex, lookup_mutex};
  71. const VAddr addr_end = addr + size;
  72. Entry* const entry = NewEntry(addr, addr_end, data.get());
  73. const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
  74. for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) {
  75. invalidation_cache[page].push_back(entry);
  76. }
  77. storage.push_back(std::move(data));
  78. rasterizer.UpdatePagesCachedCount(addr, size, 1);
  79. }
  80. /// @brief Called when a shader is going to be removed
  81. /// @param shader Shader that will be removed
  82. /// @pre invalidation_cache is locked
  83. /// @pre lookup_mutex is locked
  84. virtual void OnShaderRemoval([[maybe_unused]] T* shader) {}
  85. private:
  86. /// @brief Invalidate pages in a given region
  87. /// @pre invalidation_mutex is locked
  88. void InvalidatePagesInRegion(VAddr addr, std::size_t size) {
  89. const VAddr addr_end = addr + size;
  90. const u64 page_end = (addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
  91. for (u64 page = addr >> PAGE_BITS; page < page_end; ++page) {
  92. auto it = invalidation_cache.find(page);
  93. if (it == invalidation_cache.end()) {
  94. continue;
  95. }
  96. InvalidatePageEntries(it->second, addr, addr_end);
  97. }
  98. }
  99. /// @brief Remove shaders marked for deletion
  100. /// @pre invalidation_mutex is locked
  101. void RemovePendingShaders() {
  102. if (marked_for_removal.empty()) {
  103. return;
  104. }
  105. // Remove duplicates
  106. std::sort(marked_for_removal.begin(), marked_for_removal.end());
  107. marked_for_removal.erase(std::unique(marked_for_removal.begin(), marked_for_removal.end()),
  108. marked_for_removal.end());
  109. std::vector<T*> removed_shaders;
  110. removed_shaders.reserve(marked_for_removal.size());
  111. std::scoped_lock lock{lookup_mutex};
  112. for (Entry* const entry : marked_for_removal) {
  113. removed_shaders.push_back(entry->data);
  114. const auto it = lookup_cache.find(entry->addr_start);
  115. ASSERT(it != lookup_cache.end());
  116. lookup_cache.erase(it);
  117. }
  118. marked_for_removal.clear();
  119. if (!removed_shaders.empty()) {
  120. RemoveShadersFromStorage(std::move(removed_shaders));
  121. }
  122. }
  123. /// @brief Invalidates entries in a given range for the passed page
  124. /// @param entries Vector of entries in the page, it will be modified on overlaps
  125. /// @param addr Start address of the invalidation
  126. /// @param addr_end Non-inclusive end address of the invalidation
  127. /// @pre invalidation_mutex is locked
  128. void InvalidatePageEntries(std::vector<Entry*>& entries, VAddr addr, VAddr addr_end) {
  129. std::size_t index = 0;
  130. while (index < entries.size()) {
  131. Entry* const entry = entries[index];
  132. if (!entry->Overlaps(addr, addr_end)) {
  133. ++index;
  134. continue;
  135. }
  136. UnmarkMemory(entry);
  137. RemoveEntryFromInvalidationCache(entry);
  138. marked_for_removal.push_back(entry);
  139. }
  140. }
  141. /// @brief Removes all references to an entry in the invalidation cache
  142. /// @param entry Entry to remove from the invalidation cache
  143. /// @pre invalidation_mutex is locked
  144. void RemoveEntryFromInvalidationCache(const Entry* entry) {
  145. const u64 page_end = (entry->addr_end + PAGE_SIZE - 1) >> PAGE_BITS;
  146. for (u64 page = entry->addr_start >> PAGE_BITS; page < page_end; ++page) {
  147. const auto entries_it = invalidation_cache.find(page);
  148. ASSERT(entries_it != invalidation_cache.end());
  149. std::vector<Entry*>& entries = entries_it->second;
  150. const auto entry_it = std::find(entries.begin(), entries.end(), entry);
  151. ASSERT(entry_it != entries.end());
  152. entries.erase(entry_it);
  153. }
  154. }
  155. /// @brief Unmarks an entry from the rasterizer cache
  156. /// @param entry Entry to unmark from memory
  157. void UnmarkMemory(Entry* entry) {
  158. if (!entry->is_memory_marked) {
  159. return;
  160. }
  161. entry->is_memory_marked = false;
  162. const VAddr addr = entry->addr_start;
  163. const std::size_t size = entry->addr_end - addr;
  164. rasterizer.UpdatePagesCachedCount(addr, size, -1);
  165. }
  166. /// @brief Removes a vector of shaders from a list
  167. /// @param removed_shaders Shaders to be removed from the storage
  168. /// @pre invalidation_mutex is locked
  169. /// @pre lookup_mutex is locked
  170. void RemoveShadersFromStorage(std::vector<T*> removed_shaders) {
  171. // Notify removals
  172. for (T* const shader : removed_shaders) {
  173. OnShaderRemoval(shader);
  174. }
  175. // Remove them from the cache
  176. const auto is_removed = [&removed_shaders](const std::unique_ptr<T>& shader) {
  177. return std::find(removed_shaders.begin(), removed_shaders.end(), shader.get()) !=
  178. removed_shaders.end();
  179. };
  180. std::erase_if(storage, is_removed);
  181. }
  182. /// @brief Creates a new entry in the lookup cache and returns its pointer
  183. /// @pre lookup_mutex is locked
  184. Entry* NewEntry(VAddr addr, VAddr addr_end, T* data) {
  185. auto entry = std::make_unique<Entry>(Entry{addr, addr_end, data});
  186. Entry* const entry_pointer = entry.get();
  187. lookup_cache.emplace(addr, std::move(entry));
  188. return entry_pointer;
  189. }
  190. VideoCore::RasterizerInterface& rasterizer;
  191. mutable std::mutex lookup_mutex;
  192. std::mutex invalidation_mutex;
  193. std::unordered_map<u64, std::unique_ptr<Entry>> lookup_cache;
  194. std::unordered_map<u64, std::vector<Entry*>> invalidation_cache;
  195. std::vector<std::unique_ptr<T>> storage;
  196. std::vector<Entry*> marked_for_removal;
  197. };
  198. } // namespace VideoCommon