summaryrefslogtreecommitdiff
path: root/src/video_core/buffer_cache
diff options
context:
space:
mode:
Diffstat (limited to 'src/video_core/buffer_cache')
-rw-r--r--src/video_core/buffer_cache/buffer_block.h78
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h372
-rw-r--r--src/video_core/buffer_cache/map_interval.h48
3 files changed, 498 insertions, 0 deletions
diff --git a/src/video_core/buffer_cache/buffer_block.h b/src/video_core/buffer_cache/buffer_block.h
new file mode 100644
index 000000000..2c739a586
--- /dev/null
+++ b/src/video_core/buffer_cache/buffer_block.h
@@ -0,0 +1,78 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <unordered_set>
8#include <utility>
9
10#include "common/alignment.h"
11#include "common/common_types.h"
12#include "video_core/gpu.h"
13
14namespace VideoCommon {
15
16class BufferBlock {
17public:
18 bool Overlaps(const CacheAddr start, const CacheAddr end) const {
19 return (cache_addr < end) && (cache_addr_end > start);
20 }
21
22 bool IsInside(const CacheAddr other_start, const CacheAddr other_end) {
23 return (cache_addr <= other_start && other_end <= cache_addr_end);
24 }
25
26 u8* GetWritableHostPtr() const {
27 return FromCacheAddr(cache_addr);
28 }
29
30 u8* GetWritableHostPtr(std::size_t offset) const {
31 return FromCacheAddr(cache_addr + offset);
32 }
33
34 std::size_t GetOffset(const CacheAddr in_addr) {
35 return static_cast<std::size_t>(in_addr - cache_addr);
36 }
37
38 CacheAddr GetCacheAddr() const {
39 return cache_addr;
40 }
41
42 CacheAddr GetCacheAddrEnd() const {
43 return cache_addr_end;
44 }
45
46 void SetCacheAddr(const CacheAddr new_addr) {
47 cache_addr = new_addr;
48 cache_addr_end = new_addr + size;
49 }
50
51 std::size_t GetSize() const {
52 return size;
53 }
54
55 void SetEpoch(u64 new_epoch) {
56 epoch = new_epoch;
57 }
58
59 u64 GetEpoch() {
60 return epoch;
61 }
62
63protected:
64 explicit BufferBlock(CacheAddr cache_addr,const std::size_t size)
65 : size{size} {
66 SetCacheAddr(cache_addr);
67 }
68 ~BufferBlock() = default;
69
70private:
71 CacheAddr cache_addr{};
72 CacheAddr cache_addr_end{};
73 u64 pages{};
74 std::size_t size{};
75 u64 epoch{};
76};
77
78} // namespace VideoCommon
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
new file mode 100644
index 000000000..6c467eb80
--- /dev/null
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -0,0 +1,372 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <memory>
9#include <mutex>
10#include <unordered_map>
11#include <unordered_set>
12#include <utility>
13#include <vector>
14
15#include "common/alignment.h"
16#include "common/common_types.h"
17#include "core/core.h"
18#include "video_core/buffer_cache/map_interval.h"
19#include "video_core/buffer_cache/buffer_block.h"
20#include "video_core/memory_manager.h"
21
22namespace VideoCore {
23class RasterizerInterface;
24}
25
26namespace VideoCommon {
27
28template <typename TBuffer, typename TBufferType, typename StreamBuffer>
29class BufferCache {
30public:
31 using BufferInfo = std::pair<const TBufferType*, u64>;
32
33 BufferInfo UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4,
34 bool is_written = false) {
35 std::lock_guard lock{mutex};
36
37 auto& memory_manager = system.GPU().MemoryManager();
38 const auto host_ptr = memory_manager.GetPointer(gpu_addr);
39 if (!host_ptr) {
40 return {GetEmptyBuffer(size), 0};
41 }
42 const auto cache_addr = ToCacheAddr(host_ptr);
43
44 auto block = GetBlock(cache_addr, size);
45 MapAddress(block, gpu_addr, cache_addr, size, is_written);
46
47 const u64 offset = static_cast<u64>(block->GetOffset(cache_addr));
48
49 return {ToHandle(block), offset};
50 }
51
52 /// Uploads from a host memory. Returns the OpenGL buffer where it's located and its offset.
53 BufferInfo UploadHostMemory(const void* raw_pointer, std::size_t size,
54 std::size_t alignment = 4) {
55 std::lock_guard lock{mutex};
56 return StreamBufferUpload(raw_pointer, size, alignment);
57 }
58
59 void Map(std::size_t max_size) {
60 std::tie(buffer_ptr, buffer_offset_base, invalidated) = stream_buffer->Map(max_size, 4);
61 buffer_offset = buffer_offset_base;
62 }
63
64 /// Finishes the upload stream, returns true on bindings invalidation.
65 bool Unmap() {
66 stream_buffer->Unmap(buffer_offset - buffer_offset_base);
67 return std::exchange(invalidated, false);
68 }
69
70 void TickFrame() {
71 ++epoch;
72 while (!pending_destruction.empty()) {
73 if (pending_destruction.front()->GetEpoch() + 1 > epoch) {
74 break;
75 }
76 pending_destruction.pop_front();
77 }
78 }
79
80 /// Write any cached resources overlapping the specified region back to memory
81 void FlushRegion(CacheAddr addr, std::size_t size) {
82 std::lock_guard lock{mutex};
83
84 // TODO
85 }
86
87 /// Mark the specified region as being invalidated
88 void InvalidateRegion(CacheAddr addr, u64 size) {
89 std::lock_guard lock{mutex};
90
91 std::vector<MapInterval> objects = GetMapsInRange(addr, size);
92 for (auto& object : objects) {
93 Unregister(object);
94 }
95 }
96
97 virtual const TBufferType* GetEmptyBuffer(std::size_t size) = 0;
98
99protected:
100 explicit BufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system,
101 std::unique_ptr<StreamBuffer> stream_buffer)
102 : rasterizer{rasterizer}, system{system}, stream_buffer{std::move(stream_buffer)},
103 stream_buffer_handle{this->stream_buffer->GetHandle()} {}
104
105 ~BufferCache() = default;
106
107 virtual const TBufferType* ToHandle(const TBuffer& storage) = 0;
108
109 virtual void WriteBarrier() = 0;
110
111 virtual TBuffer CreateBlock(CacheAddr cache_addr, std::size_t size) = 0;
112
113 virtual void UploadBlockData(const TBuffer& buffer, std::size_t offset, std::size_t size,
114 const u8* data) = 0;
115
116 virtual void DownloadBlockData(const TBuffer& buffer, std::size_t offset, std::size_t size,
117 u8* data) = 0;
118
119 virtual void CopyBlock(const TBuffer& src, const TBuffer& dst, std::size_t src_offset,
120 std::size_t dst_offset, std::size_t size) = 0;
121
122 /// Register an object into the cache
123 void Register(const MapInterval& new_interval, const GPUVAddr gpu_addr) {
124 const CacheAddr cache_ptr = new_interval.start;
125 const std::size_t size = new_interval.end - new_interval.start;
126 const std::optional<VAddr> cpu_addr =
127 system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
128 if (!cache_ptr || !cpu_addr) {
129 LOG_CRITICAL(HW_GPU, "Failed to register buffer with unmapped gpu_address 0x{:016x}",
130 gpu_addr);
131 return;
132 }
133 const IntervalType interval{new_interval.start, new_interval.end};
134 mapped_addresses.insert(interval);
135 map_storage[new_interval] = MapInfo{gpu_addr, *cpu_addr};
136
137 rasterizer.UpdatePagesCachedCount(*cpu_addr, size, 1);
138 }
139
140 /// Unregisters an object from the cache
141 void Unregister(const MapInterval& interval) {
142 const MapInfo info = map_storage[interval];
143 const std::size_t size = interval.end - interval.start;
144 rasterizer.UpdatePagesCachedCount(info.cpu_addr, size, -1);
145 const IntervalType delete_interval{interval.start, interval.end};
146 mapped_addresses.erase(delete_interval);
147 map_storage.erase(interval);
148 }
149
150private:
151 void MapAddress(const TBuffer& block, const GPUVAddr gpu_addr, const CacheAddr cache_addr,
152 const std::size_t size, bool is_written) {
153
154 std::vector<MapInterval> overlaps = GetMapsInRange(cache_addr, size);
155 if (overlaps.empty()) {
156 const CacheAddr cache_addr_end = cache_addr + size;
157 MapInterval new_interval{cache_addr, cache_addr_end};
158 if (!is_written) {
159 u8* host_ptr = FromCacheAddr(cache_addr);
160 UploadBlockData(block, block->GetOffset(cache_addr), size, host_ptr);
161 }
162 Register(new_interval, gpu_addr);
163 return;
164 }
165
166 if (overlaps.size() == 1) {
167 MapInterval current_map = overlaps[0];
168 const CacheAddr cache_addr_end = cache_addr + size;
169 if (current_map.IsInside(cache_addr, cache_addr_end)) {
170 return;
171 }
172 const CacheAddr new_start = std::min(cache_addr, current_map.start);
173 const CacheAddr new_end = std::max(cache_addr_end, current_map.end);
174 const GPUVAddr new_gpu_addr = gpu_addr + new_start - cache_addr;
175 const std::size_t new_size = static_cast<std::size_t>(new_end - new_start);
176 MapInterval new_interval{new_start, new_end};
177 const std::size_t offset = current_map.start - new_start;
178 const std::size_t size = current_map.end - current_map.start;
179 // Upload the remaining data
180 if (!is_written) {
181 u8* host_ptr = FromCacheAddr(new_start);
182 if (new_start == cache_addr && new_end == cache_addr_end) {
183 std::size_t first_size = current_map.start - new_start;
184 if (first_size > 0) {
185 UploadBlockData(block, block->GetOffset(new_start), first_size, host_ptr);
186 }
187
188 std::size_t second_size = new_end - current_map.end;
189 if (second_size > 0) {
190 u8* host_ptr2 = FromCacheAddr(current_map.end);
191 UploadBlockData(block, block->GetOffset(current_map.end), second_size,
192 host_ptr2);
193 }
194 } else {
195 if (new_start == cache_addr) {
196 std::size_t second_size = new_end - current_map.end;
197 if (second_size > 0) {
198 u8* host_ptr2 = FromCacheAddr(current_map.end);
199 UploadBlockData(block, block->GetOffset(current_map.end), second_size,
200 host_ptr2);
201 }
202 } else {
203 std::size_t first_size = current_map.start - new_start;
204 if (first_size > 0) {
205 UploadBlockData(block, block->GetOffset(new_start), first_size, host_ptr);
206 }
207 }
208 }
209 }
210 Unregister(current_map);
211 Register(new_interval, new_gpu_addr);
212 } else {
213 // Calculate new buffer parameters
214 GPUVAddr new_gpu_addr = gpu_addr;
215 CacheAddr start = cache_addr;
216 CacheAddr end = cache_addr + size;
217 for (auto& overlap : overlaps) {
218 start = std::min(overlap.start, start);
219 end = std::max(overlap.end, end);
220 }
221 new_gpu_addr = gpu_addr + start - cache_addr;
222 MapInterval new_interval{start, end};
223 for (auto& overlap : overlaps) {
224 Unregister(overlap);
225 }
226 std::size_t new_size = end - start;
227 if (!is_written) {
228 u8* host_ptr = FromCacheAddr(start);
229 UploadBlockData(block, block->GetOffset(start), new_size, host_ptr);
230 }
231 Register(new_interval, new_gpu_addr);
232 }
233 }
234
235 std::vector<MapInterval> GetMapsInRange(CacheAddr addr, std::size_t size) {
236 if (size == 0) {
237 return {};
238 }
239
240 std::vector<MapInterval> objects{};
241 const IntervalType interval{addr, addr + size};
242 for (auto& pair : boost::make_iterator_range(mapped_addresses.equal_range(interval))) {
243 objects.emplace_back(pair.lower(), pair.upper());
244 }
245
246 return objects;
247 }
248
249 /// Returns a ticks counter used for tracking when cached objects were last modified
250 u64 GetModifiedTicks() {
251 return ++modified_ticks;
252 }
253
254 BufferInfo StreamBufferUpload(const void* raw_pointer, std::size_t size,
255 std::size_t alignment) {
256 AlignBuffer(alignment);
257 const std::size_t uploaded_offset = buffer_offset;
258 std::memcpy(buffer_ptr, raw_pointer, size);
259
260 buffer_ptr += size;
261 buffer_offset += size;
262 return {&stream_buffer_handle, uploaded_offset};
263 }
264
265 void AlignBuffer(std::size_t alignment) {
266 // Align the offset, not the mapped pointer
267 const std::size_t offset_aligned = Common::AlignUp(buffer_offset, alignment);
268 buffer_ptr += offset_aligned - buffer_offset;
269 buffer_offset = offset_aligned;
270 }
271
272 TBuffer EnlargeBlock(TBuffer buffer) {
273 const std::size_t old_size = buffer->GetSize();
274 const std::size_t new_size = old_size + block_page_size;
275 const CacheAddr cache_addr = buffer->GetCacheAddr();
276 TBuffer new_buffer = CreateBlock(cache_addr, new_size);
277 CopyBlock(buffer, new_buffer, 0, 0, old_size);
278 buffer->SetEpoch(epoch);
279 pending_destruction.push_back(buffer);
280 const CacheAddr cache_addr_end = cache_addr + new_size - 1;
281 u64 page_start = cache_addr >> block_page_bits;
282 const u64 page_end = cache_addr_end >> block_page_bits;
283 while (page_start <= page_end) {
284 blocks[page_start] = new_buffer;
285 ++page_start;
286 }
287 return new_buffer;
288 }
289
290 TBuffer MergeBlocks(TBuffer first, TBuffer second) {
291 const std::size_t size_1 = first->GetSize();
292 const std::size_t size_2 = second->GetSize();
293 const CacheAddr first_addr = first->GetCacheAddr();
294 const CacheAddr second_addr = second->GetCacheAddr();
295 const CacheAddr new_addr = std::min(first_addr, second_addr);
296 const std::size_t new_size = size_1 + size_2;
297 TBuffer new_buffer = CreateBlock(new_addr, new_size);
298 CopyBlock(first, new_buffer, 0, new_buffer->GetOffset(first_addr), size_1);
299 CopyBlock(second, new_buffer, 0, new_buffer->GetOffset(second_addr), size_2);
300 first->SetEpoch(epoch);
301 second->SetEpoch(epoch);
302 pending_destruction.push_back(first);
303 pending_destruction.push_back(second);
304 const CacheAddr cache_addr_end = new_addr + new_size - 1;
305 u64 page_start = new_addr >> block_page_bits;
306 const u64 page_end = cache_addr_end >> block_page_bits;
307 while (page_start <= page_end) {
308 blocks[page_start] = new_buffer;
309 ++page_start;
310 }
311 return new_buffer;
312 }
313
314 TBuffer GetBlock(const CacheAddr cache_addr, const std::size_t size) {
315 TBuffer found{};
316 const CacheAddr cache_addr_end = cache_addr + size - 1;
317 u64 page_start = cache_addr >> block_page_bits;
318 const u64 page_end = cache_addr_end >> block_page_bits;
319 const u64 num_pages = page_end - page_start + 1;
320 while (page_start <= page_end) {
321 auto it = blocks.find(page_start);
322 if (it == blocks.end()) {
323 if (found) {
324 found = EnlargeBlock(found);
325 } else {
326 const CacheAddr start_addr = (page_start << block_page_bits);
327 found = CreateBlock(start_addr, block_page_size);
328 blocks[page_start] = found;
329 }
330 } else {
331 if (found) {
332 if (found == it->second) {
333 ++page_start;
334 continue;
335 }
336 found = MergeBlocks(found, it->second);
337 } else {
338 found = it->second;
339 }
340 }
341 ++page_start;
342 }
343 return found;
344 }
345
346 std::unique_ptr<StreamBuffer> stream_buffer;
347 TBufferType stream_buffer_handle{};
348
349 bool invalidated = false;
350
351 u8* buffer_ptr = nullptr;
352 u64 buffer_offset = 0;
353 u64 buffer_offset_base = 0;
354
355 using IntervalCache = boost::icl::interval_set<CacheAddr>;
356 using IntervalType = typename IntervalCache::interval_type;
357 IntervalCache mapped_addresses{};
358 std::unordered_map<MapInterval, MapInfo> map_storage;
359
360 static constexpr u64 block_page_bits{24};
361 static constexpr u64 block_page_size{1 << block_page_bits};
362 std::unordered_map<u64, TBuffer> blocks;
363
364 std::list<TBuffer> pending_destruction;
365 u64 epoch{};
366 u64 modified_ticks{};
367 VideoCore::RasterizerInterface& rasterizer;
368 Core::System& system;
369 std::recursive_mutex mutex;
370};
371
372} // namespace VideoCommon
diff --git a/src/video_core/buffer_cache/map_interval.h b/src/video_core/buffer_cache/map_interval.h
new file mode 100644
index 000000000..652a35dcd
--- /dev/null
+++ b/src/video_core/buffer_cache/map_interval.h
@@ -0,0 +1,48 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <boost/functional/hash.hpp>
8#include "common/common_types.h"
9#include "video_core/gpu.h"
10
11namespace VideoCommon {
12
13struct MapInterval {
14 MapInterval(const CacheAddr start, const CacheAddr end) : start{start}, end{end} {}
15 CacheAddr start;
16 CacheAddr end;
17 bool IsInside(const CacheAddr other_start, const CacheAddr other_end) {
18 return (start <= other_start && other_end <= end);
19 }
20
21 bool operator==(const MapInterval& rhs) const {
22 return std::tie(start, end) == std::tie(rhs.start, rhs.end);
23 }
24
25 bool operator!=(const MapInterval& rhs) const {
26 return !operator==(rhs);
27 }
28};
29
30struct MapInfo {
31 GPUVAddr gpu_addr;
32 VAddr cpu_addr;
33};
34
35} // namespace VideoCommon
36
37namespace std {
38
39template <>
40struct hash<VideoCommon::MapInterval> {
41 std::size_t operator()(const VideoCommon::MapInterval& k) const noexcept {
42 std::size_t a = std::hash<CacheAddr>()(k.start);
43 boost::hash_combine(a, std::hash<CacheAddr>()(k.end));
44 return a;
45 }
46};
47
48} // namespace std