buffer_cache.h 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. // Copyright 2019 yuzu Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #pragma once
  5. #include <list>
  6. #include <memory>
  7. #include <mutex>
  8. #include <unordered_map>
  9. #include <unordered_set>
  10. #include <utility>
  11. #include <vector>
  12. #include <boost/container/small_vector.hpp>
  13. #include <boost/icl/interval_set.hpp>
  14. #include <boost/intrusive/set.hpp>
  15. #include "common/alignment.h"
  16. #include "common/assert.h"
  17. #include "common/common_types.h"
  18. #include "common/logging/log.h"
  19. #include "core/core.h"
  20. #include "core/memory.h"
  21. #include "core/settings.h"
  22. #include "video_core/buffer_cache/buffer_block.h"
  23. #include "video_core/buffer_cache/map_interval.h"
  24. #include "video_core/memory_manager.h"
  25. #include "video_core/rasterizer_interface.h"
  26. namespace VideoCommon {
  27. template <typename Buffer, typename BufferType, typename StreamBuffer>
  28. class BufferCache {
  29. using IntervalSet = boost::icl::interval_set<VAddr>;
  30. using IntervalType = typename IntervalSet::interval_type;
  31. using VectorMapInterval = boost::container::small_vector<MapInterval*, 1>;
  32. static constexpr u64 WRITE_PAGE_BIT = 11;
  33. static constexpr u64 BLOCK_PAGE_BITS = 21;
  34. static constexpr u64 BLOCK_PAGE_SIZE = 1ULL << BLOCK_PAGE_BITS;
  35. public:
  36. struct BufferInfo {
  37. BufferType handle;
  38. u64 offset;
  39. u64 address;
  40. };
  41. BufferInfo UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4,
  42. bool is_written = false, bool use_fast_cbuf = false) {
  43. std::lock_guard lock{mutex};
  44. const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
  45. if (!cpu_addr) {
  46. return GetEmptyBuffer(size);
  47. }
  48. // Cache management is a big overhead, so only cache entries with a given size.
  49. // TODO: Figure out which size is the best for given games.
  50. constexpr std::size_t max_stream_size = 0x800;
  51. if (use_fast_cbuf || size < max_stream_size) {
  52. if (!is_written && !IsRegionWritten(*cpu_addr, *cpu_addr + size - 1)) {
  53. const bool is_granular = gpu_memory.IsGranularRange(gpu_addr, size);
  54. if (use_fast_cbuf) {
  55. u8* dest;
  56. if (is_granular) {
  57. dest = gpu_memory.GetPointer(gpu_addr);
  58. } else {
  59. staging_buffer.resize(size);
  60. dest = staging_buffer.data();
  61. gpu_memory.ReadBlockUnsafe(gpu_addr, dest, size);
  62. }
  63. return ConstBufferUpload(dest, size);
  64. }
  65. if (is_granular) {
  66. u8* const host_ptr = gpu_memory.GetPointer(gpu_addr);
  67. return StreamBufferUpload(size, alignment, [host_ptr, size](u8* dest) {
  68. std::memcpy(dest, host_ptr, size);
  69. });
  70. } else {
  71. return StreamBufferUpload(size, alignment, [this, gpu_addr, size](u8* dest) {
  72. gpu_memory.ReadBlockUnsafe(gpu_addr, dest, size);
  73. });
  74. }
  75. }
  76. }
  77. Buffer* const block = GetBlock(*cpu_addr, size);
  78. MapInterval* const map = MapAddress(block, gpu_addr, *cpu_addr, size);
  79. if (!map) {
  80. return GetEmptyBuffer(size);
  81. }
  82. if (is_written) {
  83. map->MarkAsModified(true, GetModifiedTicks());
  84. if (Settings::IsGPULevelHigh() &&
  85. Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
  86. MarkForAsyncFlush(map);
  87. }
  88. if (!map->is_written) {
  89. map->is_written = true;
  90. MarkRegionAsWritten(map->start, map->end - 1);
  91. }
  92. }
  93. return BufferInfo{block->Handle(), block->Offset(*cpu_addr), block->Address()};
  94. }
  95. /// Uploads from a host memory. Returns the OpenGL buffer where it's located and its offset.
  96. BufferInfo UploadHostMemory(const void* raw_pointer, std::size_t size,
  97. std::size_t alignment = 4) {
  98. std::lock_guard lock{mutex};
  99. return StreamBufferUpload(size, alignment, [raw_pointer, size](u8* dest) {
  100. std::memcpy(dest, raw_pointer, size);
  101. });
  102. }
  103. /// Prepares the buffer cache for data uploading
  104. /// @param max_size Maximum number of bytes that will be uploaded
  105. /// @return True when a stream buffer invalidation was required, false otherwise
  106. bool Map(std::size_t max_size) {
  107. std::lock_guard lock{mutex};
  108. bool invalidated;
  109. std::tie(buffer_ptr, buffer_offset_base, invalidated) = stream_buffer->Map(max_size, 4);
  110. buffer_offset = buffer_offset_base;
  111. return invalidated;
  112. }
  113. /// Finishes the upload stream
  114. void Unmap() {
  115. std::lock_guard lock{mutex};
  116. stream_buffer->Unmap(buffer_offset - buffer_offset_base);
  117. }
  118. /// Function called at the end of each frame, inteded for deferred operations
  119. void TickFrame() {
  120. ++epoch;
  121. while (!pending_destruction.empty()) {
  122. // Delay at least 4 frames before destruction.
  123. // This is due to triple buffering happening on some drivers.
  124. static constexpr u64 epochs_to_destroy = 5;
  125. if (pending_destruction.front()->Epoch() + epochs_to_destroy > epoch) {
  126. break;
  127. }
  128. pending_destruction.pop();
  129. }
  130. }
  131. /// Write any cached resources overlapping the specified region back to memory
  132. void FlushRegion(VAddr addr, std::size_t size) {
  133. std::lock_guard lock{mutex};
  134. VectorMapInterval objects = GetMapsInRange(addr, size);
  135. std::sort(objects.begin(), objects.end(),
  136. [](MapInterval* lhs, MapInterval* rhs) { return lhs->ticks < rhs->ticks; });
  137. for (MapInterval* object : objects) {
  138. if (object->is_modified && object->is_registered) {
  139. mutex.unlock();
  140. FlushMap(object);
  141. mutex.lock();
  142. }
  143. }
  144. }
  145. bool MustFlushRegion(VAddr addr, std::size_t size) {
  146. std::lock_guard lock{mutex};
  147. const VectorMapInterval objects = GetMapsInRange(addr, size);
  148. return std::any_of(objects.cbegin(), objects.cend(), [](const MapInterval* map) {
  149. return map->is_modified && map->is_registered;
  150. });
  151. }
  152. /// Mark the specified region as being invalidated
  153. void InvalidateRegion(VAddr addr, u64 size) {
  154. std::lock_guard lock{mutex};
  155. for (auto& object : GetMapsInRange(addr, size)) {
  156. if (object->is_registered) {
  157. Unregister(object);
  158. }
  159. }
  160. }
  161. void OnCPUWrite(VAddr addr, std::size_t size) {
  162. std::lock_guard lock{mutex};
  163. for (MapInterval* object : GetMapsInRange(addr, size)) {
  164. if (object->is_memory_marked && object->is_registered) {
  165. UnmarkMemory(object);
  166. object->is_sync_pending = true;
  167. marked_for_unregister.emplace_back(object);
  168. }
  169. }
  170. }
  171. void SyncGuestHost() {
  172. std::lock_guard lock{mutex};
  173. for (auto& object : marked_for_unregister) {
  174. if (object->is_registered) {
  175. object->is_sync_pending = false;
  176. Unregister(object);
  177. }
  178. }
  179. marked_for_unregister.clear();
  180. }
  181. void CommitAsyncFlushes() {
  182. if (uncommitted_flushes) {
  183. auto commit_list = std::make_shared<std::list<MapInterval*>>();
  184. for (MapInterval* map : *uncommitted_flushes) {
  185. if (map->is_registered && map->is_modified) {
  186. // TODO(Blinkhawk): Implement backend asynchronous flushing
  187. // AsyncFlushMap(map)
  188. commit_list->push_back(map);
  189. }
  190. }
  191. if (!commit_list->empty()) {
  192. committed_flushes.push_back(commit_list);
  193. } else {
  194. committed_flushes.emplace_back();
  195. }
  196. } else {
  197. committed_flushes.emplace_back();
  198. }
  199. uncommitted_flushes.reset();
  200. }
  201. bool ShouldWaitAsyncFlushes() const {
  202. return !committed_flushes.empty() && committed_flushes.front() != nullptr;
  203. }
  204. bool HasUncommittedFlushes() const {
  205. return uncommitted_flushes != nullptr;
  206. }
  207. void PopAsyncFlushes() {
  208. if (committed_flushes.empty()) {
  209. return;
  210. }
  211. auto& flush_list = committed_flushes.front();
  212. if (!flush_list) {
  213. committed_flushes.pop_front();
  214. return;
  215. }
  216. for (MapInterval* map : *flush_list) {
  217. if (map->is_registered) {
  218. // TODO(Blinkhawk): Replace this for reading the asynchronous flush
  219. FlushMap(map);
  220. }
  221. }
  222. committed_flushes.pop_front();
  223. }
  224. virtual BufferInfo GetEmptyBuffer(std::size_t size) = 0;
  225. protected:
  226. explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_,
  227. Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_,
  228. std::unique_ptr<StreamBuffer> stream_buffer_)
  229. : rasterizer{rasterizer_}, gpu_memory{gpu_memory_}, cpu_memory{cpu_memory_},
  230. stream_buffer{std::move(stream_buffer_)}, stream_buffer_handle{stream_buffer->Handle()} {}
  231. ~BufferCache() = default;
  232. virtual std::shared_ptr<Buffer> CreateBlock(VAddr cpu_addr, std::size_t size) = 0;
  233. virtual BufferInfo ConstBufferUpload(const void* raw_pointer, std::size_t size) {
  234. return {};
  235. }
  236. /// Register an object into the cache
  237. MapInterval* Register(MapInterval new_map, bool inherit_written = false) {
  238. const VAddr cpu_addr = new_map.start;
  239. if (!cpu_addr) {
  240. LOG_CRITICAL(HW_GPU, "Failed to register buffer with unmapped gpu_address 0x{:016x}",
  241. new_map.gpu_addr);
  242. return nullptr;
  243. }
  244. const std::size_t size = new_map.end - new_map.start;
  245. new_map.is_registered = true;
  246. rasterizer.UpdatePagesCachedCount(cpu_addr, size, 1);
  247. new_map.is_memory_marked = true;
  248. if (inherit_written) {
  249. MarkRegionAsWritten(new_map.start, new_map.end - 1);
  250. new_map.is_written = true;
  251. }
  252. MapInterval* const storage = mapped_addresses_allocator.Allocate();
  253. *storage = new_map;
  254. mapped_addresses.insert(*storage);
  255. return storage;
  256. }
  257. void UnmarkMemory(MapInterval* map) {
  258. if (!map->is_memory_marked) {
  259. return;
  260. }
  261. const std::size_t size = map->end - map->start;
  262. rasterizer.UpdatePagesCachedCount(map->start, size, -1);
  263. map->is_memory_marked = false;
  264. }
  265. /// Unregisters an object from the cache
  266. void Unregister(MapInterval* map) {
  267. UnmarkMemory(map);
  268. map->is_registered = false;
  269. if (map->is_sync_pending) {
  270. map->is_sync_pending = false;
  271. marked_for_unregister.remove(map);
  272. }
  273. if (map->is_written) {
  274. UnmarkRegionAsWritten(map->start, map->end - 1);
  275. }
  276. const auto it = mapped_addresses.find(*map);
  277. ASSERT(it != mapped_addresses.end());
  278. mapped_addresses.erase(it);
  279. mapped_addresses_allocator.Release(map);
  280. }
  281. private:
  282. MapInterval* MapAddress(Buffer* block, GPUVAddr gpu_addr, VAddr cpu_addr, std::size_t size) {
  283. const VectorMapInterval overlaps = GetMapsInRange(cpu_addr, size);
  284. if (overlaps.empty()) {
  285. const VAddr cpu_addr_end = cpu_addr + size;
  286. if (gpu_memory.IsGranularRange(gpu_addr, size)) {
  287. u8* const host_ptr = gpu_memory.GetPointer(gpu_addr);
  288. block->Upload(block->Offset(cpu_addr), size, host_ptr);
  289. } else {
  290. staging_buffer.resize(size);
  291. gpu_memory.ReadBlockUnsafe(gpu_addr, staging_buffer.data(), size);
  292. block->Upload(block->Offset(cpu_addr), size, staging_buffer.data());
  293. }
  294. return Register(MapInterval(cpu_addr, cpu_addr_end, gpu_addr));
  295. }
  296. const VAddr cpu_addr_end = cpu_addr + size;
  297. if (overlaps.size() == 1) {
  298. MapInterval* const current_map = overlaps[0];
  299. if (current_map->IsInside(cpu_addr, cpu_addr_end)) {
  300. return current_map;
  301. }
  302. }
  303. VAddr new_start = cpu_addr;
  304. VAddr new_end = cpu_addr_end;
  305. bool write_inheritance = false;
  306. bool modified_inheritance = false;
  307. // Calculate new buffer parameters
  308. for (MapInterval* overlap : overlaps) {
  309. new_start = std::min(overlap->start, new_start);
  310. new_end = std::max(overlap->end, new_end);
  311. write_inheritance |= overlap->is_written;
  312. modified_inheritance |= overlap->is_modified;
  313. }
  314. GPUVAddr new_gpu_addr = gpu_addr + new_start - cpu_addr;
  315. for (auto& overlap : overlaps) {
  316. Unregister(overlap);
  317. }
  318. UpdateBlock(block, new_start, new_end, overlaps);
  319. const MapInterval new_map{new_start, new_end, new_gpu_addr};
  320. MapInterval* const map = Register(new_map, write_inheritance);
  321. if (!map) {
  322. return nullptr;
  323. }
  324. if (modified_inheritance) {
  325. map->MarkAsModified(true, GetModifiedTicks());
  326. if (Settings::IsGPULevelHigh() &&
  327. Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
  328. MarkForAsyncFlush(map);
  329. }
  330. }
  331. return map;
  332. }
  333. void UpdateBlock(Buffer* block, VAddr start, VAddr end, const VectorMapInterval& overlaps) {
  334. const IntervalType base_interval{start, end};
  335. IntervalSet interval_set{};
  336. interval_set.add(base_interval);
  337. for (auto& overlap : overlaps) {
  338. const IntervalType subtract{overlap->start, overlap->end};
  339. interval_set.subtract(subtract);
  340. }
  341. for (auto& interval : interval_set) {
  342. const std::size_t size = interval.upper() - interval.lower();
  343. if (size == 0) {
  344. continue;
  345. }
  346. staging_buffer.resize(size);
  347. cpu_memory.ReadBlockUnsafe(interval.lower(), staging_buffer.data(), size);
  348. block->Upload(block->Offset(interval.lower()), size, staging_buffer.data());
  349. }
  350. }
  351. VectorMapInterval GetMapsInRange(VAddr addr, std::size_t size) {
  352. VectorMapInterval result;
  353. if (size == 0) {
  354. return result;
  355. }
  356. const VAddr addr_end = addr + size;
  357. auto it = mapped_addresses.lower_bound(addr);
  358. if (it != mapped_addresses.begin()) {
  359. --it;
  360. }
  361. while (it != mapped_addresses.end() && it->start < addr_end) {
  362. if (it->Overlaps(addr, addr_end)) {
  363. result.push_back(&*it);
  364. }
  365. ++it;
  366. }
  367. return result;
  368. }
  369. /// Returns a ticks counter used for tracking when cached objects were last modified
  370. u64 GetModifiedTicks() {
  371. return ++modified_ticks;
  372. }
  373. void FlushMap(MapInterval* map) {
  374. const auto it = blocks.find(map->start >> BLOCK_PAGE_BITS);
  375. ASSERT_OR_EXECUTE(it != blocks.end(), return;);
  376. std::shared_ptr<Buffer> block = it->second;
  377. const std::size_t size = map->end - map->start;
  378. staging_buffer.resize(size);
  379. block->Download(block->Offset(map->start), size, staging_buffer.data());
  380. cpu_memory.WriteBlockUnsafe(map->start, staging_buffer.data(), size);
  381. map->MarkAsModified(false, 0);
  382. }
  383. template <typename Callable>
  384. BufferInfo StreamBufferUpload(std::size_t size, std::size_t alignment, Callable&& callable) {
  385. AlignBuffer(alignment);
  386. const std::size_t uploaded_offset = buffer_offset;
  387. callable(buffer_ptr);
  388. buffer_ptr += size;
  389. buffer_offset += size;
  390. return BufferInfo{stream_buffer->Handle(), uploaded_offset, stream_buffer->Address()};
  391. }
  392. void AlignBuffer(std::size_t alignment) {
  393. // Align the offset, not the mapped pointer
  394. const std::size_t offset_aligned = Common::AlignUp(buffer_offset, alignment);
  395. buffer_ptr += offset_aligned - buffer_offset;
  396. buffer_offset = offset_aligned;
  397. }
  398. std::shared_ptr<Buffer> EnlargeBlock(std::shared_ptr<Buffer> buffer) {
  399. const std::size_t old_size = buffer->Size();
  400. const std::size_t new_size = old_size + BLOCK_PAGE_SIZE;
  401. const VAddr cpu_addr = buffer->CpuAddr();
  402. std::shared_ptr<Buffer> new_buffer = CreateBlock(cpu_addr, new_size);
  403. new_buffer->CopyFrom(*buffer, 0, 0, old_size);
  404. QueueDestruction(std::move(buffer));
  405. const VAddr cpu_addr_end = cpu_addr + new_size - 1;
  406. const u64 page_end = cpu_addr_end >> BLOCK_PAGE_BITS;
  407. for (u64 page_start = cpu_addr >> BLOCK_PAGE_BITS; page_start <= page_end; ++page_start) {
  408. blocks.insert_or_assign(page_start, new_buffer);
  409. }
  410. return new_buffer;
  411. }
  412. std::shared_ptr<Buffer> MergeBlocks(std::shared_ptr<Buffer> first,
  413. std::shared_ptr<Buffer> second) {
  414. const std::size_t size_1 = first->Size();
  415. const std::size_t size_2 = second->Size();
  416. const VAddr first_addr = first->CpuAddr();
  417. const VAddr second_addr = second->CpuAddr();
  418. const VAddr new_addr = std::min(first_addr, second_addr);
  419. const std::size_t new_size = size_1 + size_2;
  420. std::shared_ptr<Buffer> new_buffer = CreateBlock(new_addr, new_size);
  421. new_buffer->CopyFrom(*first, 0, new_buffer->Offset(first_addr), size_1);
  422. new_buffer->CopyFrom(*second, 0, new_buffer->Offset(second_addr), size_2);
  423. QueueDestruction(std::move(first));
  424. QueueDestruction(std::move(second));
  425. const VAddr cpu_addr_end = new_addr + new_size - 1;
  426. const u64 page_end = cpu_addr_end >> BLOCK_PAGE_BITS;
  427. for (u64 page_start = new_addr >> BLOCK_PAGE_BITS; page_start <= page_end; ++page_start) {
  428. blocks.insert_or_assign(page_start, new_buffer);
  429. }
  430. return new_buffer;
  431. }
  432. Buffer* GetBlock(VAddr cpu_addr, std::size_t size) {
  433. std::shared_ptr<Buffer> found;
  434. const VAddr cpu_addr_end = cpu_addr + size - 1;
  435. const u64 page_end = cpu_addr_end >> BLOCK_PAGE_BITS;
  436. for (u64 page_start = cpu_addr >> BLOCK_PAGE_BITS; page_start <= page_end; ++page_start) {
  437. auto it = blocks.find(page_start);
  438. if (it == blocks.end()) {
  439. if (found) {
  440. found = EnlargeBlock(found);
  441. continue;
  442. }
  443. const VAddr start_addr = page_start << BLOCK_PAGE_BITS;
  444. found = CreateBlock(start_addr, BLOCK_PAGE_SIZE);
  445. blocks.insert_or_assign(page_start, found);
  446. continue;
  447. }
  448. if (!found) {
  449. found = it->second;
  450. continue;
  451. }
  452. if (found != it->second) {
  453. found = MergeBlocks(std::move(found), it->second);
  454. }
  455. }
  456. return found.get();
  457. }
  458. void MarkRegionAsWritten(VAddr start, VAddr end) {
  459. const u64 page_end = end >> WRITE_PAGE_BIT;
  460. for (u64 page_start = start >> WRITE_PAGE_BIT; page_start <= page_end; ++page_start) {
  461. if (const auto [it, inserted] = written_pages.emplace(page_start, 1); !inserted) {
  462. ++it->second;
  463. }
  464. }
  465. }
  466. void UnmarkRegionAsWritten(VAddr start, VAddr end) {
  467. const u64 page_end = end >> WRITE_PAGE_BIT;
  468. for (u64 page_start = start >> WRITE_PAGE_BIT; page_start <= page_end; ++page_start) {
  469. auto it = written_pages.find(page_start);
  470. if (it != written_pages.end()) {
  471. if (it->second > 1) {
  472. --it->second;
  473. } else {
  474. written_pages.erase(it);
  475. }
  476. }
  477. }
  478. }
  479. bool IsRegionWritten(VAddr start, VAddr end) const {
  480. const u64 page_end = end >> WRITE_PAGE_BIT;
  481. for (u64 page_start = start >> WRITE_PAGE_BIT; page_start <= page_end; ++page_start) {
  482. if (written_pages.contains(page_start)) {
  483. return true;
  484. }
  485. }
  486. return false;
  487. }
  488. void QueueDestruction(std::shared_ptr<Buffer> buffer) {
  489. buffer->SetEpoch(epoch);
  490. pending_destruction.push(std::move(buffer));
  491. }
  492. void MarkForAsyncFlush(MapInterval* map) {
  493. if (!uncommitted_flushes) {
  494. uncommitted_flushes = std::make_shared<std::unordered_set<MapInterval*>>();
  495. }
  496. uncommitted_flushes->insert(map);
  497. }
  498. VideoCore::RasterizerInterface& rasterizer;
  499. Tegra::MemoryManager& gpu_memory;
  500. Core::Memory::Memory& cpu_memory;
  501. std::unique_ptr<StreamBuffer> stream_buffer;
  502. BufferType stream_buffer_handle;
  503. u8* buffer_ptr = nullptr;
  504. u64 buffer_offset = 0;
  505. u64 buffer_offset_base = 0;
  506. MapIntervalAllocator mapped_addresses_allocator;
  507. boost::intrusive::set<MapInterval, boost::intrusive::compare<MapIntervalCompare>>
  508. mapped_addresses;
  509. std::unordered_map<u64, u32> written_pages;
  510. std::unordered_map<u64, std::shared_ptr<Buffer>> blocks;
  511. std::queue<std::shared_ptr<Buffer>> pending_destruction;
  512. u64 epoch = 0;
  513. u64 modified_ticks = 0;
  514. std::vector<u8> staging_buffer;
  515. std::list<MapInterval*> marked_for_unregister;
  516. std::shared_ptr<std::unordered_set<MapInterval*>> uncommitted_flushes;
  517. std::list<std::shared_ptr<std::list<MapInterval*>>> committed_flushes;
  518. std::recursive_mutex mutex;
  519. };
  520. } // namespace VideoCommon