buffer_base.h 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  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, true, 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, bool clear, Func&& func) {
  199. ForEachModifiedRange<Type::GPU>(query_cpu_range, size, clear, func);
  200. }
  201. template <typename Func>
  202. void ForEachDownloadRangeAndClear(VAddr query_cpu_range, u64 size, Func&& func) {
  203. ForEachModifiedRange<Type::GPU>(query_cpu_range, size, true, func);
  204. }
  205. /// Call 'func' for each GPU modified range and unmark those pages as GPU modified
  206. template <typename Func>
  207. void ForEachDownloadRange(Func&& func) {
  208. ForEachModifiedRange<Type::GPU>(cpu_addr, SizeBytes(), true, func);
  209. }
  210. /// Mark buffer as picked
  211. void Pick() noexcept {
  212. flags |= BufferFlagBits::Picked;
  213. }
  214. /// Unmark buffer as picked
  215. void Unpick() noexcept {
  216. flags &= ~BufferFlagBits::Picked;
  217. }
  218. /// Increases the likeliness of this being a stream buffer
  219. void IncreaseStreamScore(int score) noexcept {
  220. stream_score += score;
  221. }
  222. /// Returns the likeliness of this being a stream buffer
  223. [[nodiscard]] int StreamScore() const noexcept {
  224. return stream_score;
  225. }
  226. /// Returns true when vaddr -> vaddr+size is fully contained in the buffer
  227. [[nodiscard]] bool IsInBounds(VAddr addr, u64 size) const noexcept {
  228. return addr >= cpu_addr && addr + size <= cpu_addr + SizeBytes();
  229. }
  230. /// Returns true if the buffer has been marked as picked
  231. [[nodiscard]] bool IsPicked() const noexcept {
  232. return True(flags & BufferFlagBits::Picked);
  233. }
  234. /// Returns true when the buffer has pending cached writes
  235. [[nodiscard]] bool HasCachedWrites() const noexcept {
  236. return True(flags & BufferFlagBits::CachedWrites);
  237. }
  238. /// Returns the base CPU address of the buffer
  239. [[nodiscard]] VAddr CpuAddr() const noexcept {
  240. return cpu_addr;
  241. }
  242. /// Returns the offset relative to the given CPU address
  243. /// @pre IsInBounds returns true
  244. [[nodiscard]] u32 Offset(VAddr other_cpu_addr) const noexcept {
  245. return static_cast<u32>(other_cpu_addr - cpu_addr);
  246. }
  247. /// Returns the size in bytes of the buffer
  248. [[nodiscard]] u64 SizeBytes() const noexcept {
  249. return words.size_bytes;
  250. }
  251. size_t lru_id;
  252. private:
  253. template <Type type>
  254. u64* Array() noexcept {
  255. if constexpr (type == Type::CPU) {
  256. return words.cpu.Pointer(IsShort());
  257. } else if constexpr (type == Type::GPU) {
  258. return words.gpu.Pointer(IsShort());
  259. } else if constexpr (type == Type::CachedCPU) {
  260. return words.cached_cpu.Pointer(IsShort());
  261. } else if constexpr (type == Type::Untracked) {
  262. return words.untracked.Pointer(IsShort());
  263. }
  264. }
  265. template <Type type>
  266. const u64* Array() const noexcept {
  267. if constexpr (type == Type::CPU) {
  268. return words.cpu.Pointer(IsShort());
  269. } else if constexpr (type == Type::GPU) {
  270. return words.gpu.Pointer(IsShort());
  271. } else if constexpr (type == Type::CachedCPU) {
  272. return words.cached_cpu.Pointer(IsShort());
  273. } else if constexpr (type == Type::Untracked) {
  274. return words.untracked.Pointer(IsShort());
  275. }
  276. }
  277. /**
  278. * Change the state of a range of pages
  279. *
  280. * @param dirty_addr Base address to mark or unmark as modified
  281. * @param size Size in bytes to mark or unmark as modified
  282. */
  283. template <Type type, bool enable>
  284. void ChangeRegionState(u64 dirty_addr, s64 size) noexcept(type == Type::GPU) {
  285. const s64 difference = dirty_addr - cpu_addr;
  286. const u64 offset = std::max<s64>(difference, 0);
  287. size += std::min<s64>(difference, 0);
  288. if (offset >= SizeBytes() || size < 0) {
  289. return;
  290. }
  291. u64* const untracked_words = Array<Type::Untracked>();
  292. u64* const state_words = Array<type>();
  293. const u64 offset_end = std::min(offset + size, SizeBytes());
  294. const u64 begin_page_index = offset / BYTES_PER_PAGE;
  295. const u64 begin_word_index = begin_page_index / PAGES_PER_WORD;
  296. const u64 end_page_index = Common::DivCeil(offset_end, BYTES_PER_PAGE);
  297. const u64 end_word_index = Common::DivCeil(end_page_index, PAGES_PER_WORD);
  298. u64 page_index = begin_page_index % PAGES_PER_WORD;
  299. u64 word_index = begin_word_index;
  300. while (word_index < end_word_index) {
  301. const u64 next_word_first_page = (word_index + 1) * PAGES_PER_WORD;
  302. const u64 left_offset =
  303. std::min(next_word_first_page - end_page_index, PAGES_PER_WORD) % PAGES_PER_WORD;
  304. const u64 right_offset = page_index;
  305. u64 bits = ~u64{0};
  306. bits = (bits >> right_offset) << right_offset;
  307. bits = (bits << left_offset) >> left_offset;
  308. if constexpr (type == Type::CPU || type == Type::CachedCPU) {
  309. NotifyRasterizer<!enable>(word_index, untracked_words[word_index], bits);
  310. }
  311. if constexpr (enable) {
  312. state_words[word_index] |= bits;
  313. if constexpr (type == Type::CPU || type == Type::CachedCPU) {
  314. untracked_words[word_index] |= bits;
  315. }
  316. } else {
  317. state_words[word_index] &= ~bits;
  318. if constexpr (type == Type::CPU || type == Type::CachedCPU) {
  319. untracked_words[word_index] &= ~bits;
  320. }
  321. }
  322. page_index = 0;
  323. ++word_index;
  324. }
  325. }
  326. /**
  327. * Notify rasterizer about changes in the CPU tracking state of a word in the buffer
  328. *
  329. * @param word_index Index to the word to notify to the rasterizer
  330. * @param current_bits Current state of the word
  331. * @param new_bits New state of the word
  332. *
  333. * @tparam add_to_rasterizer True when the rasterizer should start tracking the new pages
  334. */
  335. template <bool add_to_rasterizer>
  336. void NotifyRasterizer(u64 word_index, u64 current_bits, u64 new_bits) const {
  337. u64 changed_bits = (add_to_rasterizer ? current_bits : ~current_bits) & new_bits;
  338. VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
  339. while (changed_bits != 0) {
  340. const int empty_bits = std::countr_zero(changed_bits);
  341. addr += empty_bits * BYTES_PER_PAGE;
  342. changed_bits >>= empty_bits;
  343. const u32 continuous_bits = std::countr_one(changed_bits);
  344. const u64 size = continuous_bits * BYTES_PER_PAGE;
  345. const VAddr begin_addr = addr;
  346. addr += size;
  347. changed_bits = continuous_bits < PAGES_PER_WORD ? (changed_bits >> continuous_bits) : 0;
  348. rasterizer->UpdatePagesCachedCount(begin_addr, size, add_to_rasterizer ? 1 : -1);
  349. }
  350. }
  351. /**
  352. * Loop over each page in the given range, turn off those bits and notify the rasterizer if
  353. * needed. Call the given function on each turned off range.
  354. *
  355. * @param query_cpu_range Base CPU address to loop over
  356. * @param size Size in bytes of the CPU range to loop over
  357. * @param func Function to call for each turned off region
  358. */
  359. template <Type type, typename Func>
  360. void ForEachModifiedRange(VAddr query_cpu_range, s64 size, bool clear, Func&& func) {
  361. static_assert(type != Type::Untracked);
  362. const s64 difference = query_cpu_range - cpu_addr;
  363. const u64 query_begin = std::max<s64>(difference, 0);
  364. size += std::min<s64>(difference, 0);
  365. if (query_begin >= SizeBytes() || size < 0) {
  366. return;
  367. }
  368. u64* const untracked_words = Array<Type::Untracked>();
  369. u64* const state_words = Array<type>();
  370. const u64 query_end = query_begin + std::min(static_cast<u64>(size), SizeBytes());
  371. u64* const words_begin = state_words + query_begin / BYTES_PER_WORD;
  372. u64* const words_end = state_words + Common::DivCeil(query_end, BYTES_PER_WORD);
  373. const auto modified = [](u64 word) { return word != 0; };
  374. const auto first_modified_word = std::find_if(words_begin, words_end, modified);
  375. if (first_modified_word == words_end) {
  376. // Exit early when the buffer is not modified
  377. return;
  378. }
  379. const auto last_modified_word = std::find_if_not(first_modified_word, words_end, modified);
  380. const u64 word_index_begin = std::distance(state_words, first_modified_word);
  381. const u64 word_index_end = std::distance(state_words, last_modified_word);
  382. const unsigned local_page_begin = std::countr_zero(*first_modified_word);
  383. const unsigned local_page_end =
  384. static_cast<unsigned>(PAGES_PER_WORD) - std::countl_zero(last_modified_word[-1]);
  385. const u64 word_page_begin = word_index_begin * PAGES_PER_WORD;
  386. const u64 word_page_end = (word_index_end - 1) * PAGES_PER_WORD;
  387. const u64 query_page_begin = query_begin / BYTES_PER_PAGE;
  388. const u64 query_page_end = Common::DivCeil(query_end, BYTES_PER_PAGE);
  389. const u64 page_index_begin = std::max(word_page_begin + local_page_begin, query_page_begin);
  390. const u64 page_index_end = std::min(word_page_end + local_page_end, query_page_end);
  391. const u64 first_word_page_begin = page_index_begin % PAGES_PER_WORD;
  392. const u64 last_word_page_end = (page_index_end - 1) % PAGES_PER_WORD + 1;
  393. u64 page_begin = first_word_page_begin;
  394. u64 current_base = 0;
  395. u64 current_size = 0;
  396. bool on_going = false;
  397. for (u64 word_index = word_index_begin; word_index < word_index_end; ++word_index) {
  398. const bool is_last_word = word_index + 1 == word_index_end;
  399. const u64 page_end = is_last_word ? last_word_page_end : PAGES_PER_WORD;
  400. const u64 right_offset = page_begin;
  401. const u64 left_offset = PAGES_PER_WORD - page_end;
  402. u64 bits = ~u64{0};
  403. bits = (bits >> right_offset) << right_offset;
  404. bits = (bits << left_offset) >> left_offset;
  405. const u64 current_word = state_words[word_index] & bits;
  406. if (clear) {
  407. state_words[word_index] &= ~bits;
  408. }
  409. if constexpr (type == Type::CPU) {
  410. const u64 current_bits = untracked_words[word_index] & bits;
  411. untracked_words[word_index] &= ~bits;
  412. NotifyRasterizer<true>(word_index, current_bits, ~u64{0});
  413. }
  414. // Exclude CPU modified pages when visiting GPU pages
  415. const u64 word = current_word & ~(type == Type::GPU ? untracked_words[word_index] : 0);
  416. u64 page = page_begin;
  417. page_begin = 0;
  418. while (page < page_end) {
  419. const int empty_bits = std::countr_zero(word >> page);
  420. if (on_going && empty_bits != 0) {
  421. InvokeModifiedRange(func, current_size, current_base);
  422. current_size = 0;
  423. on_going = false;
  424. }
  425. if (empty_bits == PAGES_PER_WORD) {
  426. break;
  427. }
  428. page += empty_bits;
  429. const int continuous_bits = std::countr_one(word >> page);
  430. if (!on_going && continuous_bits != 0) {
  431. current_base = word_index * PAGES_PER_WORD + page;
  432. on_going = true;
  433. }
  434. current_size += continuous_bits;
  435. page += continuous_bits;
  436. }
  437. }
  438. if (on_going && current_size > 0) {
  439. InvokeModifiedRange(func, current_size, current_base);
  440. }
  441. }
  442. template <typename Func>
  443. void InvokeModifiedRange(Func&& func, u64 current_size, u64 current_base) {
  444. const u64 current_size_bytes = current_size * BYTES_PER_PAGE;
  445. const u64 offset_begin = current_base * BYTES_PER_PAGE;
  446. const u64 offset_end = std::min(offset_begin + current_size_bytes, SizeBytes());
  447. func(offset_begin, offset_end - offset_begin);
  448. }
  449. /**
  450. * Returns true when a region has been modified
  451. *
  452. * @param offset Offset in bytes from the start of the buffer
  453. * @param size Size in bytes of the region to query for modifications
  454. */
  455. template <Type type>
  456. [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept {
  457. static_assert(type != Type::Untracked);
  458. const u64* const untracked_words = Array<Type::Untracked>();
  459. const u64* const state_words = Array<type>();
  460. const u64 num_query_words = size / BYTES_PER_WORD + 1;
  461. const u64 word_begin = offset / BYTES_PER_WORD;
  462. const u64 word_end = std::min(word_begin + num_query_words, NumWords());
  463. const u64 page_limit = Common::DivCeil(offset + size, BYTES_PER_PAGE);
  464. u64 page_index = (offset / BYTES_PER_PAGE) % PAGES_PER_WORD;
  465. for (u64 word_index = word_begin; word_index < word_end; ++word_index, page_index = 0) {
  466. const u64 off_word = type == Type::GPU ? untracked_words[word_index] : 0;
  467. const u64 word = state_words[word_index] & ~off_word;
  468. if (word == 0) {
  469. continue;
  470. }
  471. const u64 page_end = std::min((word_index + 1) * PAGES_PER_WORD, page_limit);
  472. const u64 local_page_end = page_end % PAGES_PER_WORD;
  473. const u64 page_end_shift = (PAGES_PER_WORD - local_page_end) % PAGES_PER_WORD;
  474. if (((word >> page_index) << page_index) << page_end_shift != 0) {
  475. return true;
  476. }
  477. }
  478. return false;
  479. }
  480. /**
  481. * Returns a begin end pair with the inclusive modified region
  482. *
  483. * @param offset Offset in bytes from the start of the buffer
  484. * @param size Size in bytes of the region to query for modifications
  485. */
  486. template <Type type>
  487. [[nodiscard]] std::pair<u64, u64> ModifiedRegion(u64 offset, u64 size) const noexcept {
  488. static_assert(type != Type::Untracked);
  489. const u64* const untracked_words = Array<Type::Untracked>();
  490. const u64* const state_words = Array<type>();
  491. const u64 num_query_words = size / BYTES_PER_WORD + 1;
  492. const u64 word_begin = offset / BYTES_PER_WORD;
  493. const u64 word_end = std::min(word_begin + num_query_words, NumWords());
  494. const u64 page_base = offset / BYTES_PER_PAGE;
  495. const u64 page_limit = Common::DivCeil(offset + size, BYTES_PER_PAGE);
  496. u64 begin = std::numeric_limits<u64>::max();
  497. u64 end = 0;
  498. for (u64 word_index = word_begin; word_index < word_end; ++word_index) {
  499. const u64 off_word = type == Type::GPU ? untracked_words[word_index] : 0;
  500. const u64 word = state_words[word_index] & ~off_word;
  501. if (word == 0) {
  502. continue;
  503. }
  504. const u64 local_page_begin = std::countr_zero(word);
  505. const u64 local_page_end = PAGES_PER_WORD - std::countl_zero(word);
  506. const u64 page_index = word_index * PAGES_PER_WORD;
  507. const u64 page_begin = std::max(page_index + local_page_begin, page_base);
  508. const u64 page_end = std::min(page_index + local_page_end, page_limit);
  509. begin = std::min(begin, page_begin);
  510. end = std::max(end, page_end);
  511. }
  512. static constexpr std::pair<u64, u64> EMPTY{0, 0};
  513. return begin < end ? std::make_pair(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE) : EMPTY;
  514. }
  515. /// Returns the number of words of the buffer
  516. [[nodiscard]] size_t NumWords() const noexcept {
  517. return words.NumWords();
  518. }
  519. /// Returns true when the buffer fits in the small vector optimization
  520. [[nodiscard]] bool IsShort() const noexcept {
  521. return words.IsShort();
  522. }
  523. RasterizerInterface* rasterizer = nullptr;
  524. VAddr cpu_addr = 0;
  525. Words words;
  526. BufferFlagBits flags{};
  527. int stream_score = 0;
  528. };
  529. } // namespace VideoCommon