diff options
| -rw-r--r-- | src/video_core/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_stream_buffer.cpp | 124 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_stream_buffer.h | 73 |
3 files changed, 200 insertions, 1 deletions
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 6036d6ed3..60529323e 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt | |||
| @@ -111,7 +111,9 @@ if (ENABLE_VULKAN) | |||
| 111 | renderer_vulkan/vk_resource_manager.cpp | 111 | renderer_vulkan/vk_resource_manager.cpp |
| 112 | renderer_vulkan/vk_resource_manager.h | 112 | renderer_vulkan/vk_resource_manager.h |
| 113 | renderer_vulkan/vk_scheduler.cpp | 113 | renderer_vulkan/vk_scheduler.cpp |
| 114 | renderer_vulkan/vk_scheduler.h) | 114 | renderer_vulkan/vk_scheduler.h |
| 115 | renderer_vulkan/vk_stream_buffer.cpp | ||
| 116 | renderer_vulkan/vk_stream_buffer.h) | ||
| 115 | 117 | ||
| 116 | target_include_directories(video_core PRIVATE ../../externals/Vulkan-Headers/include) | 118 | target_include_directories(video_core PRIVATE ../../externals/Vulkan-Headers/include) |
| 117 | target_compile_definitions(video_core PRIVATE HAS_VULKAN) | 119 | target_compile_definitions(video_core PRIVATE HAS_VULKAN) |
diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp new file mode 100644 index 000000000..1c5aefaec --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp | |||
| @@ -0,0 +1,124 @@ | |||
| 1 | // Copyright 2019 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include <memory> | ||
| 7 | #include <optional> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include "common/assert.h" | ||
| 11 | #include "video_core/renderer_vulkan/declarations.h" | ||
| 12 | #include "video_core/renderer_vulkan/vk_device.h" | ||
| 13 | #include "video_core/renderer_vulkan/vk_memory_manager.h" | ||
| 14 | #include "video_core/renderer_vulkan/vk_resource_manager.h" | ||
| 15 | #include "video_core/renderer_vulkan/vk_scheduler.h" | ||
| 16 | #include "video_core/renderer_vulkan/vk_stream_buffer.h" | ||
| 17 | |||
| 18 | namespace Vulkan { | ||
| 19 | |||
| 20 | constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000; | ||
| 21 | constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000; | ||
| 22 | |||
| 23 | VKStreamBuffer::VKStreamBuffer(const VKDevice& device, VKMemoryManager& memory_manager, | ||
| 24 | VKScheduler& scheduler, u64 size, vk::BufferUsageFlags usage, | ||
| 25 | vk::AccessFlags access, vk::PipelineStageFlags pipeline_stage) | ||
| 26 | : device{device}, scheduler{scheduler}, | ||
| 27 | has_device_exclusive_memory{!memory_manager.IsMemoryUnified()}, | ||
| 28 | buffer_size{size}, access{access}, pipeline_stage{pipeline_stage} { | ||
| 29 | CreateBuffers(memory_manager, usage); | ||
| 30 | ReserveWatches(WATCHES_INITIAL_RESERVE); | ||
| 31 | } | ||
| 32 | |||
| 33 | VKStreamBuffer::~VKStreamBuffer() = default; | ||
| 34 | |||
| 35 | std::tuple<u8*, u64, vk::Buffer, bool> VKStreamBuffer::Reserve(u64 size, bool keep_in_host) { | ||
| 36 | ASSERT(size <= buffer_size); | ||
| 37 | mapped_size = size; | ||
| 38 | |||
| 39 | if (offset + size > buffer_size) { | ||
| 40 | // The buffer would overflow, save the amount of used buffers, signal an invalidation and | ||
| 41 | // reset the state. | ||
| 42 | invalidation_mark = used_watches; | ||
| 43 | used_watches = 0; | ||
| 44 | offset = 0; | ||
| 45 | } | ||
| 46 | |||
| 47 | use_device = has_device_exclusive_memory && !keep_in_host; | ||
| 48 | |||
| 49 | const vk::Buffer buffer = use_device ? *device_buffer : *mappable_buffer; | ||
| 50 | return {mapped_pointer + offset, offset, buffer, invalidation_mark.has_value()}; | ||
| 51 | } | ||
| 52 | |||
| 53 | VKExecutionContext VKStreamBuffer::Send(VKExecutionContext exctx, u64 size) { | ||
| 54 | ASSERT_MSG(size <= mapped_size, "Reserved size is too small"); | ||
| 55 | |||
| 56 | if (invalidation_mark) { | ||
| 57 | // TODO(Rodrigo): Find a better way to invalidate than waiting for all watches to finish. | ||
| 58 | exctx = scheduler.Flush(); | ||
| 59 | std::for_each(watches.begin(), watches.begin() + *invalidation_mark, | ||
| 60 | [&](auto& resource) { resource->Wait(); }); | ||
| 61 | invalidation_mark = std::nullopt; | ||
| 62 | } | ||
| 63 | |||
| 64 | // Only copy to VRAM when requested. | ||
| 65 | if (use_device) { | ||
| 66 | const auto& dld = device.GetDispatchLoader(); | ||
| 67 | const u32 graphics_family = device.GetGraphicsFamily(); | ||
| 68 | const auto cmdbuf = exctx.GetCommandBuffer(); | ||
| 69 | |||
| 70 | // Buffers are mirrored, that's why the copy is done with the same offset on both buffers. | ||
| 71 | const vk::BufferCopy copy_region(offset, offset, size); | ||
| 72 | cmdbuf.copyBuffer(*mappable_buffer, *device_buffer, {copy_region}, dld); | ||
| 73 | |||
| 74 | // Protect the buffer from GPU usage until the copy has finished. | ||
| 75 | const vk::BufferMemoryBarrier barrier(vk::AccessFlagBits::eTransferWrite, access, | ||
| 76 | graphics_family, graphics_family, *device_buffer, | ||
| 77 | offset, size); | ||
| 78 | cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, pipeline_stage, {}, {}, | ||
| 79 | {barrier}, {}, dld); | ||
| 80 | } | ||
| 81 | |||
| 82 | if (used_watches + 1 >= watches.size()) { | ||
| 83 | // Ensure that there are enough watches. | ||
| 84 | ReserveWatches(WATCHES_RESERVE_CHUNK); | ||
| 85 | } | ||
| 86 | // Add a watch for this allocation. | ||
| 87 | watches[used_watches++]->Watch(exctx.GetFence()); | ||
| 88 | |||
| 89 | offset += size; | ||
| 90 | |||
| 91 | return exctx; | ||
| 92 | } | ||
| 93 | |||
| 94 | void VKStreamBuffer::CreateBuffers(VKMemoryManager& memory_manager, vk::BufferUsageFlags usage) { | ||
| 95 | vk::BufferUsageFlags mappable_usage = usage; | ||
| 96 | if (has_device_exclusive_memory) { | ||
| 97 | mappable_usage |= vk::BufferUsageFlagBits::eTransferSrc; | ||
| 98 | } | ||
| 99 | const vk::BufferCreateInfo buffer_ci({}, buffer_size, mappable_usage, | ||
| 100 | vk::SharingMode::eExclusive, 0, nullptr); | ||
| 101 | |||
| 102 | const auto dev = device.GetLogical(); | ||
| 103 | const auto& dld = device.GetDispatchLoader(); | ||
| 104 | mappable_buffer = dev.createBufferUnique(buffer_ci, nullptr, dld); | ||
| 105 | mappable_commit = memory_manager.Commit(*mappable_buffer, true); | ||
| 106 | mapped_pointer = mappable_commit->GetData(); | ||
| 107 | |||
| 108 | if (has_device_exclusive_memory) { | ||
| 109 | const vk::BufferCreateInfo buffer_ci({}, buffer_size, | ||
| 110 | usage | vk::BufferUsageFlagBits::eTransferDst, | ||
| 111 | vk::SharingMode::eExclusive, 0, nullptr); | ||
| 112 | device_buffer = dev.createBufferUnique(buffer_ci, nullptr, dld); | ||
| 113 | device_commit = memory_manager.Commit(*device_buffer, false); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | void VKStreamBuffer::ReserveWatches(std::size_t grow_size) { | ||
| 118 | const std::size_t previous_size = watches.size(); | ||
| 119 | watches.resize(previous_size + grow_size); | ||
| 120 | std::generate(watches.begin() + previous_size, watches.end(), | ||
| 121 | []() { return std::make_unique<VKFenceWatch>(); }); | ||
| 122 | } | ||
| 123 | |||
| 124 | } // namespace Vulkan | ||
diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.h b/src/video_core/renderer_vulkan/vk_stream_buffer.h new file mode 100644 index 000000000..8c00d383a --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_stream_buffer.h | |||
| @@ -0,0 +1,73 @@ | |||
| 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 <memory> | ||
| 8 | #include <optional> | ||
| 9 | #include <tuple> | ||
| 10 | #include <vector> | ||
| 11 | |||
| 12 | #include "common/common_types.h" | ||
| 13 | #include "video_core/renderer_vulkan/declarations.h" | ||
| 14 | #include "video_core/renderer_vulkan/vk_memory_manager.h" | ||
| 15 | |||
| 16 | namespace Vulkan { | ||
| 17 | |||
| 18 | class VKDevice; | ||
| 19 | class VKFence; | ||
| 20 | class VKFenceWatch; | ||
| 21 | class VKResourceManager; | ||
| 22 | class VKScheduler; | ||
| 23 | |||
| 24 | class VKStreamBuffer { | ||
| 25 | public: | ||
| 26 | explicit VKStreamBuffer(const VKDevice& device, VKMemoryManager& memory_manager, | ||
| 27 | VKScheduler& scheduler, u64 size, vk::BufferUsageFlags usage, | ||
| 28 | vk::AccessFlags access, vk::PipelineStageFlags pipeline_stage); | ||
| 29 | ~VKStreamBuffer(); | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Reserves a region of memory from the stream buffer. | ||
| 33 | * @param size Size to reserve. | ||
| 34 | * @param keep_in_host Mapped buffer will be in host memory, skipping the copy to device local. | ||
| 35 | * @returns A tuple in the following order: Raw memory pointer (with offset added), buffer | ||
| 36 | * offset, Vulkan buffer handle, buffer has been invalited. | ||
| 37 | */ | ||
| 38 | std::tuple<u8*, u64, vk::Buffer, bool> Reserve(u64 size, bool keep_in_host); | ||
| 39 | |||
| 40 | /// Ensures that "size" bytes of memory are available to the GPU, potentially recording a copy. | ||
| 41 | [[nodiscard]] VKExecutionContext Send(VKExecutionContext exctx, u64 size); | ||
| 42 | |||
| 43 | private: | ||
| 44 | /// Creates Vulkan buffer handles committing the required the required memory. | ||
| 45 | void CreateBuffers(VKMemoryManager& memory_manager, vk::BufferUsageFlags usage); | ||
| 46 | |||
| 47 | /// Increases the amount of watches available. | ||
| 48 | void ReserveWatches(std::size_t grow_size); | ||
| 49 | |||
| 50 | const VKDevice& device; ///< Vulkan device manager. | ||
| 51 | VKScheduler& scheduler; ///< Command scheduler. | ||
| 52 | const u64 buffer_size; ///< Total size of the stream buffer. | ||
| 53 | const bool has_device_exclusive_memory; ///< True if the streaming buffer will use VRAM. | ||
| 54 | const vk::AccessFlags access; ///< Access usage of this stream buffer. | ||
| 55 | const vk::PipelineStageFlags pipeline_stage; ///< Pipeline usage of this stream buffer. | ||
| 56 | |||
| 57 | UniqueBuffer mappable_buffer; ///< Mapped buffer. | ||
| 58 | UniqueBuffer device_buffer; ///< Buffer exclusive to the GPU. | ||
| 59 | VKMemoryCommit mappable_commit; ///< Commit visible from the CPU. | ||
| 60 | VKMemoryCommit device_commit; ///< Commit stored in VRAM. | ||
| 61 | u8* mapped_pointer{}; ///< Pointer to the host visible commit | ||
| 62 | |||
| 63 | u64 offset{}; ///< Buffer iterator. | ||
| 64 | u64 mapped_size{}; ///< Size reserved for the current copy. | ||
| 65 | bool use_device{}; ///< True if the current uses VRAM. | ||
| 66 | |||
| 67 | std::vector<std::unique_ptr<VKFenceWatch>> watches; ///< Total watches | ||
| 68 | std::size_t used_watches{}; ///< Count of watches, reset on invalidation. | ||
| 69 | std::optional<std::size_t> | ||
| 70 | invalidation_mark{}; ///< Number of watches used in the current invalidation. | ||
| 71 | }; | ||
| 72 | |||
| 73 | } // namespace Vulkan | ||