buffer_base.h 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  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 <bit>
  7. #include <limits>
  8. #include <utility>
  9. #include "common/alignment.h"
  10. #include "common/common_funcs.h"
  11. #include "common/common_types.h"
  12. #include "common/div_ceil.h"
  13. #include "core/memory.h"
  14. namespace VideoCommon {
  15. enum class BufferFlagBits {
  16. Picked = 1 << 0,
  17. CachedWrites = 1 << 1,
  18. };
  19. DECLARE_ENUM_FLAG_OPERATORS(BufferFlagBits)
  20. /// Tag for creating null buffers with no storage or size
  21. struct NullBufferParams {};
  22. /**
  23. * Range tracking buffer container.
  24. *
  25. * It keeps track of the modified CPU and GPU ranges on a CPU page granularity, notifying the given
  26. * rasterizer about state changes in the tracking behavior of the buffer.
  27. *
  28. * The buffer size and address is forcefully aligned to CPU page boundaries.
  29. */
  30. template <class RasterizerInterface>
  31. class BufferBase {
  32. static constexpr u64 PAGES_PER_WORD = 64;
  33. static constexpr u64 BYTES_PER_PAGE = Core::Memory::PAGE_SIZE;
  34. static constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE;
  35. /// Vector tracking modified pages tightly packed with small vector optimization
  36. union WordsArray {
  37. /// Returns the pointer to the words state
  38. [[nodiscard]] const u64* Pointer(bool is_short) const noexcept {
  39. return is_short ? &stack : heap;
  40. }
  41. /// Returns the pointer to the words state
  42. [[nodiscard]] u64* Pointer(bool is_short) noexcept {
  43. return is_short ? &stack : heap;
  44. }
  45. u64 stack = 0; ///< Small buffers storage
  46. u64* heap; ///< Not-small buffers pointer to the storage
  47. };
  48. struct Words {
  49. explicit Words() = default;
  50. explicit Words(u64 size_bytes_) : size_bytes{size_bytes_} {
  51. if (IsShort()) {
  52. cpu.stack = ~u64{0};
  53. gpu.stack = 0;
  54. cached_cpu.stack = 0;
  55. untracked.stack = ~u64{0};
  56. } else {
  57. // Share allocation between CPU and GPU pages and set their default values
  58. const size_t num_words = NumWords();
  59. u64* const alloc = new u64[num_words * 4];
  60. cpu.heap = alloc;
  61. gpu.heap = alloc + num_words;
  62. cached_cpu.heap = alloc + num_words * 2;
  63. untracked.heap = alloc + num_words * 3;
  64. std::fill_n(cpu.heap, num_words, ~u64{0});
  65. std::fill_n(gpu.heap, num_words, 0);
  66. std::fill_n(cached_cpu.heap, num_words, 0);
  67. std::fill_n(untracked.heap, num_words, ~u64{0});
  68. }
  69. // Clean up tailing bits
  70. const u64 last_word_size = size_bytes % BYTES_PER_WORD;
  71. const u64 last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE);
  72. const u64 shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD;
  73. const u64 last_word = (~u64{0} << shift) >> shift;
  74. cpu.Pointer(IsShort())[NumWords() - 1] = last_word;
  75. untracked.Pointer(IsShort())[NumWords() - 1] = last_word;
  76. }
  77. ~Words() {
  78. Release();
  79. }
  80. Words& operator=(Words&& rhs) noexcept {
  81. Release();
  82. size_bytes = rhs.size_bytes;
  83. cpu = rhs.cpu;
  84. gpu = rhs.gpu;
  85. cached_cpu = rhs.cached_cpu;
  86. untracked = rhs.untracked;
  87. rhs.cpu.heap = nullptr;
  88. return *this;
  89. }
  90. Words(Words&& rhs) noexcept
  91. : size_bytes{rhs.size_bytes}, cpu{rhs.cpu}, gpu{rhs.gpu},
  92. cached_cpu{rhs.cached_cpu}, untracked{rhs.untracked} {
  93. rhs.cpu.heap = nullptr;
  94. }
  95. Words& operator=(const Words&) = delete;
  96. Words(const Words&) = delete;
  97. /// Returns true when the buffer fits in the small vector optimization
  98. [[nodiscard]] bool IsShort() const noexcept {
  99. return size_bytes <= BYTES_PER_WORD;
  100. }
  101. /// Returns the number of words of the buffer
  102. [[nodiscard]] size_t NumWords() const noexcept {
  103. return Common::DivCeil(size_bytes, BYTES_PER_WORD);
  104. }
  105. /// Release buffer resources
  106. void Release() {
  107. if (!IsShort()) {
  108. // CPU written words is the base for the heap allocation
  109. delete[] cpu.heap;
  110. }
  111. }
  112. u64 size_bytes = 0;
  113. WordsArray cpu;
  114. WordsArray gpu;
  115. WordsArray cached_cpu;
  116. WordsArray untracked;
  117. };
  118. enum class Type {
  119. CPU,
  120. GPU,
  121. CachedCPU,
  122. Untracked,
  123. };
  124. public:
  125. explicit BufferBase(RasterizerInterface& rasterizer_, VAddr cpu_addr_, u64 size_bytes)
  126. : rasterizer{&rasterizer_}, cpu_addr{Common::AlignDown(cpu_addr_, BYTES_PER_PAGE)},
  127. words(Common::AlignUp(size_bytes + (cpu_addr_ - cpu_addr), BYTES_PER_PAGE)) {}
  128. explicit BufferBase(NullBufferParams) {}
  129. BufferBase& operator=(const BufferBase&) = delete;
  130. BufferBase(const BufferBase&) = delete;
  131. BufferBase& operator=(BufferBase&&) = default;
  132. BufferBase(BufferBase&&) = default;
  133. /// Returns the inclusive CPU modified range in a begin end pair
  134. [[nodiscard]] std::pair<u64, u64> ModifiedCpuRegion(VAddr query_cpu_addr,
  135. u64 query_size) const noexcept {
  136. const u64 offset = query_cpu_addr - cpu_addr;
  137. return ModifiedRegion<Type::CPU>(offset, query_size);
  138. }
  139. /// Returns the inclusive GPU modified range in a begin end pair
  140. [[nodiscard]] std::pair<u64, u64> ModifiedGpuRegion(VAddr query_cpu_addr,
  141. u64 query_size) const noexcept {
  142. const u64 offset = query_cpu_addr - cpu_addr;
  143. return ModifiedRegion<Type::GPU>(offset, query_size);
  144. }
  145. /// Returns true if a region has been modified from the CPU
  146. [[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) const noexcept {
  147. const u64 offset = query_cpu_addr - cpu_addr;
  148. return IsRegionModified<Type::CPU>(offset, query_size);
  149. }
  150. /// Returns true if a region has been modified from the GPU
  151. [[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) const noexcept {
  152. const u64 offset = query_cpu_addr - cpu_addr;
  153. return IsRegionModified<Type::GPU>(offset, query_size);
  154. }
  155. /// Mark region as CPU modified, notifying the rasterizer about this change
  156. void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 size) {
  157. ChangeRegionState<Type::CPU, true>(dirty_cpu_addr, size);
  158. }
  159. /// Unmark region as CPU modified, notifying the rasterizer about this change
  160. void UnmarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 size) {
  161. ChangeRegionState<Type::CPU, false>(dirty_cpu_addr, size);
  162. }
  163. /// Mark region as modified from the host GPU
  164. void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 size) noexcept {
  165. ChangeRegionState<Type::GPU, true>(dirty_cpu_addr, size);
  166. }
  167. /// Unmark region as modified from the host GPU
  168. void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 size) noexcept {
  169. ChangeRegionState<Type::GPU, false>(dirty_cpu_addr, size);
  170. }
  171. /// Mark region as modified from the CPU
  172. /// but don't mark it as modified until FlusHCachedWrites is called.
  173. void CachedCpuWrite(VAddr dirty_cpu_addr, u64 size) {
  174. flags |= BufferFlagBits::CachedWrites;
  175. ChangeRegionState<Type::CachedCPU, true>(dirty_cpu_addr, size);
  176. }
  177. /// Flushes cached CPU writes, and notify the rasterizer about the deltas
  178. void FlushCachedWrites() noexcept {
  179. flags &= ~BufferFlagBits::CachedWrites;
  180. const u64 num_words = NumWords();
  181. const u64* const cached_words = Array<Type::CachedCPU>();
  182. u64* const untracked_words = Array<Type::Untracked>();
  183. u64* const cpu_words = Array<Type::CPU>();
  184. for (u64 word_index = 0; word_index < num_words; ++word_index) {
  185. const u64 cached_bits = cached_words[word_index];
  186. NotifyRasterizer<false>(word_index, untracked_words[word_index], cached_bits);
  187. untracked_words[word_index] |= cached_bits;
  188. cpu_words[word_index] |= cached_bits;
  189. }
  190. }
  191. /// Call 'func' for each CPU modified range and unmark those pages as CPU modified
  192. template <typename Func>
  193. void ForEachUploadRange(VAddr query_cpu_range, u64 size, Func&& func) {
  194. ForEachModifiedRange<Type::CPU>(query_cpu_range, size, func);
  195. }
  196. /// Call 'func' for each GPU modified range and unmark those pages as GPU modified
  197. template <typename Func>
  198. void ForEachDownloadRange(VAddr query_cpu_range, u64 size, Func&& func) {
  199. ForEachModifiedRange<Type::GPU>(query_cpu_range, size, func);
  200. }
  201. /// Call 'func' for each GPU modified range and unmark those pages as GPU modified
  202. template <typename Func>
  203. void ForEachDownloadRange(Func&& func) {
  204. ForEachModifiedRange<Type::GPU>(cpu_addr, SizeBytes(), func);
  205. }
  206. /// Mark buffer as picked
  207. void Pick() noexcept {
  208. flags |= BufferFlagBits::Picked;
  209. }
  210. /// Unmark buffer as picked
  211. void Unpick() noexcept {
  212. flags &= ~BufferFlagBits::Picked;
  213. }
  214. /// Returns true when vaddr -> vaddr+size is fully contained in the buffer
  215. [[nodiscard]] bool IsInBounds(VAddr addr, u64 size) const noexcept {
  216. return addr >= cpu_addr && addr + size <= cpu_addr + SizeBytes();
  217. }
  218. /// Returns true if the buffer has been marked as picked
  219. [[nodiscard]] bool IsPicked() const noexcept {
  220. return True(flags & BufferFlagBits::Picked);
  221. }
  222. /// Returns true when the buffer has pending cached writes
  223. [[nodiscard]] bool HasCachedWrites() const noexcept {
  224. return True(flags & BufferFlagBits::CachedWrites);
  225. }
  226. /// Returns the base CPU address of the buffer
  227. [[nodiscard]] VAddr CpuAddr() const noexcept {
  228. return cpu_addr;
  229. }
  230. /// Returns the offset relative to the given CPU address
  231. /// @pre IsInBounds returns true
  232. [[nodiscard]] u32 Offset(VAddr other_cpu_addr) const noexcept {
  233. return static_cast<u32>(other_cpu_addr - cpu_addr);
  234. }
  235. /// Returns the size in bytes of the buffer
  236. [[nodiscard]] u64 SizeBytes() const noexcept {
  237. return words.size_bytes;
  238. }
  239. private:
  240. template <Type type>
  241. u64* Array() noexcept {
  242. if constexpr (type == Type::CPU) {
  243. return words.cpu.Pointer(IsShort());
  244. } else if constexpr (type == Type::GPU) {
  245. return words.gpu.Pointer(IsShort());
  246. } else if constexpr (type == Type::CachedCPU) {
  247. return words.cached_cpu.Pointer(IsShort());
  248. } else if constexpr (type == Type::Untracked) {
  249. return words.untracked.Pointer(IsShort());
  250. }
  251. }
  252. template <Type type>
  253. const u64* Array() const noexcept {
  254. if constexpr (type == Type::CPU) {
  255. return words.cpu.Pointer(IsShort());
  256. } else if constexpr (type == Type::GPU) {
  257. return words.gpu.Pointer(IsShort());
  258. } else if constexpr (type == Type::CachedCPU) {
  259. return words.cached_cpu.Pointer(IsShort());
  260. } else if constexpr (type == Type::Untracked) {
  261. return words.untracked.Pointer(IsShort());
  262. }
  263. }
  264. /**
  265. * Change the state of a range of pages
  266. *
  267. * @param dirty_addr Base address to mark or unmark as modified
  268. * @param size Size in bytes to mark or unmark as modified
  269. */
  270. template <Type type, bool enable>
  271. void ChangeRegionState(u64 dirty_addr, s64 size) noexcept(type == Type::GPU) {
  272. const s64 difference = dirty_addr - cpu_addr;
  273. const u64 offset = std::max<s64>(difference, 0);
  274. size += std::min<s64>(difference, 0);
  275. if (offset >= SizeBytes() || size < 0) {
  276. return;
  277. }
  278. u64* const untracked_words = Array<Type::Untracked>();
  279. u64* const state_words = Array<type>();
  280. const u64 offset_end = std::min(offset + size, SizeBytes());
  281. const u64 begin_page_index = offset / BYTES_PER_PAGE;
  282. const u64 begin_word_index = begin_page_index / PAGES_PER_WORD;
  283. const u64 end_page_index = Common::DivCeil(offset_end, BYTES_PER_PAGE);
  284. const u64 end_word_index = Common::DivCeil(end_page_index, PAGES_PER_WORD);
  285. u64 page_index = begin_page_index % PAGES_PER_WORD;
  286. u64 word_index = begin_word_index;
  287. while (word_index < end_word_index) {
  288. const u64 next_word_first_page = (word_index + 1) * PAGES_PER_WORD;
  289. const u64 left_offset =
  290. std::min(next_word_first_page - end_page_index, PAGES_PER_WORD) % PAGES_PER_WORD;
  291. const u64 right_offset = page_index;
  292. u64 bits = ~u64{0};
  293. bits = (bits >> right_offset) << right_offset;
  294. bits = (bits << left_offset) >> left_offset;
  295. if constexpr (type == Type::CPU || type == Type::CachedCPU) {
  296. NotifyRasterizer<!enable>(word_index, untracked_words[word_index], bits);
  297. }
  298. if constexpr (enable) {
  299. state_words[word_index] |= bits;
  300. if constexpr (type == Type::CPU || type == Type::CachedCPU) {
  301. untracked_words[word_index] |= bits;
  302. }
  303. } else {
  304. state_words[word_index] &= ~bits;
  305. if constexpr (type == Type::CPU || type == Type::CachedCPU) {
  306. untracked_words[word_index] &= ~bits;
  307. }
  308. }
  309. page_index = 0;
  310. ++word_index;
  311. }
  312. }
  313. /**
  314. * Notify rasterizer about changes in the CPU tracking state of a word in the buffer
  315. *
  316. * @param word_index Index to the word to notify to the rasterizer
  317. * @param current_bits Current state of the word
  318. * @param new_bits New state of the word
  319. *
  320. * @tparam add_to_rasterizer True when the rasterizer should start tracking the new pages
  321. */
  322. template <bool add_to_rasterizer>
  323. void NotifyRasterizer(u64 word_index, u64 current_bits, u64 new_bits) const {
  324. u64 changed_bits = (add_to_rasterizer ? current_bits : ~current_bits) & new_bits;
  325. VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
  326. while (changed_bits != 0) {
  327. const int empty_bits = std::countr_zero(changed_bits);
  328. addr += empty_bits * BYTES_PER_PAGE;
  329. changed_bits >>= empty_bits;
  330. const u32 continuous_bits = std::countr_one(changed_bits);
  331. const u64 size = continuous_bits * BYTES_PER_PAGE;
  332. const VAddr begin_addr = addr;
  333. addr += size;
  334. changed_bits = continuous_bits < PAGES_PER_WORD ? (changed_bits >> continuous_bits) : 0;
  335. rasterizer->UpdatePagesCachedCount(begin_addr, size, add_to_rasterizer ? 1 : -1);
  336. }
  337. }
  338. /**
  339. * Loop over each page in the given range, turn off those bits and notify the rasterizer if
  340. * needed. Call the given function on each turned off range.
  341. *
  342. * @param query_cpu_range Base CPU address to loop over
  343. * @param size Size in bytes of the CPU range to loop over
  344. * @param func Function to call for each turned off region
  345. */
  346. template <Type type, typename Func>
  347. void ForEachModifiedRange(VAddr query_cpu_range, s64 size, Func&& func) {
  348. static_assert(type != Type::Untracked);
  349. const s64 difference = query_cpu_range - cpu_addr;
  350. const u64 query_begin = std::max<s64>(difference, 0);
  351. size += std::min<s64>(difference, 0);
  352. if (query_begin >= SizeBytes() || size < 0) {
  353. return;
  354. }
  355. u64* const untracked_words = Array<Type::Untracked>();
  356. u64* const state_words = Array<type>();
  357. const u64 query_end = query_begin + std::min(static_cast<u64>(size), SizeBytes());
  358. u64* const words_begin = state_words + query_begin / BYTES_PER_WORD;
  359. u64* const words_end = state_words + Common::DivCeil(query_end, BYTES_PER_WORD);
  360. const auto modified = [](u64 word) { return word != 0; };
  361. const auto first_modified_word = std::find_if(words_begin, words_end, modified);
  362. if (first_modified_word == words_end) {
  363. // Exit early when the buffer is not modified
  364. return;
  365. }
  366. const auto last_modified_word = std::find_if_not(first_modified_word, words_end, modified);
  367. const u64 word_index_begin = std::distance(state_words, first_modified_word);
  368. const u64 word_index_end = std::distance(state_words, last_modified_word);
  369. const unsigned local_page_begin = std::countr_zero(*first_modified_word);
  370. const unsigned local_page_end =
  371. static_cast<unsigned>(PAGES_PER_WORD) - std::countl_zero(last_modified_word[-1]);
  372. const u64 word_page_begin = word_index_begin * PAGES_PER_WORD;
  373. const u64 word_page_end = (word_index_end - 1) * PAGES_PER_WORD;
  374. const u64 query_page_begin = query_begin / BYTES_PER_PAGE;
  375. const u64 query_page_end = Common::DivCeil(query_end, BYTES_PER_PAGE);
  376. const u64 page_index_begin = std::max(word_page_begin + local_page_begin, query_page_begin);
  377. const u64 page_index_end = std::min(word_page_end + local_page_end, query_page_end);
  378. const u64 first_word_page_begin = page_index_begin % PAGES_PER_WORD;
  379. const u64 last_word_page_end = (page_index_end - 1) % PAGES_PER_WORD + 1;
  380. u64 page_begin = first_word_page_begin;
  381. u64 current_base = 0;
  382. u64 current_size = 0;
  383. bool on_going = false;
  384. for (u64 word_index = word_index_begin; word_index < word_index_end; ++word_index) {
  385. const bool is_last_word = word_index + 1 == word_index_end;
  386. const u64 page_end = is_last_word ? last_word_page_end : PAGES_PER_WORD;
  387. const u64 right_offset = page_begin;
  388. const u64 left_offset = PAGES_PER_WORD - page_end;
  389. u64 bits = ~u64{0};
  390. bits = (bits >> right_offset) << right_offset;
  391. bits = (bits << left_offset) >> left_offset;
  392. const u64 current_word = state_words[word_index] & bits;
  393. state_words[word_index] &= ~bits;
  394. if constexpr (type == Type::CPU) {
  395. const u64 current_bits = untracked_words[word_index] & bits;
  396. untracked_words[word_index] &= ~bits;
  397. NotifyRasterizer<true>(word_index, current_bits, ~u64{0});
  398. }
  399. // Exclude CPU modified pages when visiting GPU pages
  400. const u64 word = current_word & ~(type == Type::GPU ? untracked_words[word_index] : 0);
  401. u64 page = page_begin;
  402. page_begin = 0;
  403. while (page < page_end) {
  404. const int empty_bits = std::countr_zero(word >> page);
  405. if (on_going && empty_bits != 0) {
  406. InvokeModifiedRange(func, current_size, current_base);
  407. current_size = 0;
  408. on_going = false;
  409. }
  410. page += empty_bits;
  411. const int continuous_bits = std::countr_one(word >> page);
  412. if (!on_going && continuous_bits != 0) {
  413. current_base = word_index * PAGES_PER_WORD + page;
  414. on_going = true;
  415. }
  416. current_size += continuous_bits;
  417. page += continuous_bits;
  418. }
  419. }
  420. if (on_going && current_size > 0) {
  421. InvokeModifiedRange(func, current_size, current_base);
  422. }
  423. }
  424. template <typename Func>
  425. void InvokeModifiedRange(Func&& func, u64 current_size, u64 current_base) {
  426. const u64 current_size_bytes = current_size * BYTES_PER_PAGE;
  427. const u64 offset_begin = current_base * BYTES_PER_PAGE;
  428. const u64 offset_end = std::min(offset_begin + current_size_bytes, SizeBytes());
  429. func(offset_begin, offset_end - offset_begin);
  430. }
  431. /**
  432. * Returns true when a region has been modified
  433. *
  434. * @param offset Offset in bytes from the start of the buffer
  435. * @param size Size in bytes of the region to query for modifications
  436. */
  437. template <Type type>
  438. [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept {
  439. static_assert(type != Type::Untracked);
  440. const u64* const untracked_words = Array<Type::Untracked>();
  441. const u64* const state_words = Array<type>();
  442. const u64 num_query_words = size / BYTES_PER_WORD + 1;
  443. const u64 word_begin = offset / BYTES_PER_WORD;
  444. const u64 word_end = std::min(word_begin + num_query_words, NumWords());
  445. const u64 page_limit = Common::DivCeil(offset + size, BYTES_PER_PAGE);
  446. u64 page_index = (offset / BYTES_PER_PAGE) % PAGES_PER_WORD;
  447. for (u64 word_index = word_begin; word_index < word_end; ++word_index, page_index = 0) {
  448. const u64 off_word = type == Type::GPU ? untracked_words[word_index] : 0;
  449. const u64 word = state_words[word_index] & ~off_word;
  450. if (word == 0) {
  451. continue;
  452. }
  453. const u64 page_end = std::min((word_index + 1) * PAGES_PER_WORD, page_limit);
  454. const u64 local_page_end = page_end % PAGES_PER_WORD;
  455. const u64 page_end_shift = (PAGES_PER_WORD - local_page_end) % PAGES_PER_WORD;
  456. if (((word >> page_index) << page_index) << page_end_shift != 0) {
  457. return true;
  458. }
  459. }
  460. return false;
  461. }
  462. /**
  463. * Returns a begin end pair with the inclusive modified region
  464. *
  465. * @param offset Offset in bytes from the start of the buffer
  466. * @param size Size in bytes of the region to query for modifications
  467. */
  468. template <Type type>
  469. [[nodiscard]] std::pair<u64, u64> ModifiedRegion(u64 offset, u64 size) const noexcept {
  470. static_assert(type != Type::Untracked);
  471. const u64* const untracked_words = Array<Type::Untracked>();
  472. const u64* const state_words = Array<type>();
  473. const u64 num_query_words = size / BYTES_PER_WORD + 1;
  474. const u64 word_begin = offset / BYTES_PER_WORD;
  475. const u64 word_end = std::min(word_begin + num_query_words, NumWords());
  476. const u64 page_base = offset / BYTES_PER_PAGE;
  477. const u64 page_limit = Common::DivCeil(offset + size, BYTES_PER_PAGE);
  478. u64 begin = std::numeric_limits<u64>::max();
  479. u64 end = 0;
  480. for (u64 word_index = word_begin; word_index < word_end; ++word_index) {
  481. const u64 off_word = type == Type::GPU ? untracked_words[word_index] : 0;
  482. const u64 word = state_words[word_index] & ~off_word;
  483. if (word == 0) {
  484. continue;
  485. }
  486. const u64 local_page_begin = std::countr_zero(word);
  487. const u64 local_page_end = PAGES_PER_WORD - std::countl_zero(word);
  488. const u64 page_index = word_index * PAGES_PER_WORD;
  489. const u64 page_begin = std::max(page_index + local_page_begin, page_base);
  490. const u64 page_end = std::min(page_index + local_page_end, page_limit);
  491. begin = std::min(begin, page_begin);
  492. end = std::max(end, page_end);
  493. }
  494. static constexpr std::pair<u64, u64> EMPTY{0, 0};
  495. return begin < end ? std::make_pair(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE) : EMPTY;
  496. }
  497. /// Returns the number of words of the buffer
  498. [[nodiscard]] size_t NumWords() const noexcept {
  499. return words.NumWords();
  500. }
  501. /// Returns true when the buffer fits in the small vector optimization
  502. [[nodiscard]] bool IsShort() const noexcept {
  503. return words.IsShort();
  504. }
  505. RasterizerInterface* rasterizer = nullptr;
  506. VAddr cpu_addr = 0;
  507. Words words;
  508. BufferFlagBits flags{};
  509. };
  510. } // namespace VideoCommon