buffer_base.h 25 KB

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