|
@@ -3,86 +3,144 @@
|
|
|
// Refer to the license.txt file included.
|
|
// Refer to the license.txt file included.
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
#include <algorithm>
|
|
|
-#include <memory>
|
|
|
|
|
#include <optional>
|
|
#include <optional>
|
|
|
|
|
+#include <tuple>
|
|
|
#include <vector>
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
+#include "common/alignment.h"
|
|
|
#include "common/assert.h"
|
|
#include "common/assert.h"
|
|
|
#include "video_core/renderer_vulkan/declarations.h"
|
|
#include "video_core/renderer_vulkan/declarations.h"
|
|
|
#include "video_core/renderer_vulkan/vk_device.h"
|
|
#include "video_core/renderer_vulkan/vk_device.h"
|
|
|
-#include "video_core/renderer_vulkan/vk_memory_manager.h"
|
|
|
|
|
#include "video_core/renderer_vulkan/vk_resource_manager.h"
|
|
#include "video_core/renderer_vulkan/vk_resource_manager.h"
|
|
|
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
|
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
|
|
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
|
|
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
|
|
|
|
|
|
|
|
namespace Vulkan {
|
|
namespace Vulkan {
|
|
|
|
|
|
|
|
|
|
+namespace {
|
|
|
|
|
+
|
|
|
constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000;
|
|
constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000;
|
|
|
constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000;
|
|
constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000;
|
|
|
|
|
|
|
|
-VKStreamBuffer::VKStreamBuffer(const VKDevice& device, VKMemoryManager& memory_manager,
|
|
|
|
|
- VKScheduler& scheduler, u64 size, vk::BufferUsageFlags usage,
|
|
|
|
|
- vk::AccessFlags access, vk::PipelineStageFlags pipeline_stage)
|
|
|
|
|
- : device{device}, scheduler{scheduler}, buffer_size{size}, access{access}, pipeline_stage{
|
|
|
|
|
- pipeline_stage} {
|
|
|
|
|
- CreateBuffers(memory_manager, usage);
|
|
|
|
|
- ReserveWatches(WATCHES_INITIAL_RESERVE);
|
|
|
|
|
|
|
+constexpr u64 STREAM_BUFFER_SIZE = 256 * 1024 * 1024;
|
|
|
|
|
+
|
|
|
|
|
+std::optional<u32> FindMemoryType(const VKDevice& device, u32 filter,
|
|
|
|
|
+ vk::MemoryPropertyFlags wanted) {
|
|
|
|
|
+ const auto properties = device.GetPhysical().getMemoryProperties(device.GetDispatchLoader());
|
|
|
|
|
+ for (u32 i = 0; i < properties.memoryTypeCount; i++) {
|
|
|
|
|
+ if (!(filter & (1 << i))) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if ((properties.memoryTypes[i].propertyFlags & wanted) == wanted) {
|
|
|
|
|
+ return i;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return {};
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+} // Anonymous namespace
|
|
|
|
|
+
|
|
|
|
|
+VKStreamBuffer::VKStreamBuffer(const VKDevice& device, VKScheduler& scheduler,
|
|
|
|
|
+ vk::BufferUsageFlags usage)
|
|
|
|
|
+ : device{device}, scheduler{scheduler} {
|
|
|
|
|
+ CreateBuffers(usage);
|
|
|
|
|
+ ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE);
|
|
|
|
|
+ ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
VKStreamBuffer::~VKStreamBuffer() = default;
|
|
VKStreamBuffer::~VKStreamBuffer() = default;
|
|
|
|
|
|
|
|
-std::tuple<u8*, u64, bool> VKStreamBuffer::Reserve(u64 size) {
|
|
|
|
|
- ASSERT(size <= buffer_size);
|
|
|
|
|
|
|
+std::tuple<u8*, u64, bool> VKStreamBuffer::Map(u64 size, u64 alignment) {
|
|
|
|
|
+ ASSERT(size <= STREAM_BUFFER_SIZE);
|
|
|
mapped_size = size;
|
|
mapped_size = size;
|
|
|
|
|
|
|
|
- if (offset + size > buffer_size) {
|
|
|
|
|
- // The buffer would overflow, save the amount of used buffers, signal an invalidation and
|
|
|
|
|
- // reset the state.
|
|
|
|
|
- invalidation_mark = used_watches;
|
|
|
|
|
- used_watches = 0;
|
|
|
|
|
|
|
+ if (alignment > 0) {
|
|
|
|
|
+ offset = Common::AlignUp(offset, alignment);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ WaitPendingOperations(offset);
|
|
|
|
|
+
|
|
|
|
|
+ bool invalidated = false;
|
|
|
|
|
+ if (offset + size > STREAM_BUFFER_SIZE) {
|
|
|
|
|
+ // The buffer would overflow, save the amount of used watches and reset the state.
|
|
|
|
|
+ invalidation_mark = current_watch_cursor;
|
|
|
|
|
+ current_watch_cursor = 0;
|
|
|
offset = 0;
|
|
offset = 0;
|
|
|
|
|
+
|
|
|
|
|
+ // Swap watches and reset waiting cursors.
|
|
|
|
|
+ std::swap(previous_watches, current_watches);
|
|
|
|
|
+ wait_cursor = 0;
|
|
|
|
|
+ wait_bound = 0;
|
|
|
|
|
+
|
|
|
|
|
+ // Ensure that we don't wait for uncommitted fences.
|
|
|
|
|
+ scheduler.Flush();
|
|
|
|
|
+
|
|
|
|
|
+ invalidated = true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return {mapped_pointer + offset, offset, invalidation_mark.has_value()};
|
|
|
|
|
|
|
+ const auto dev = device.GetLogical();
|
|
|
|
|
+ const auto& dld = device.GetDispatchLoader();
|
|
|
|
|
+ const auto pointer = reinterpret_cast<u8*>(dev.mapMemory(*memory, offset, size, {}, dld));
|
|
|
|
|
+ return {pointer, offset, invalidated};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-void VKStreamBuffer::Send(u64 size) {
|
|
|
|
|
|
|
+void VKStreamBuffer::Unmap(u64 size) {
|
|
|
ASSERT_MSG(size <= mapped_size, "Reserved size is too small");
|
|
ASSERT_MSG(size <= mapped_size, "Reserved size is too small");
|
|
|
|
|
|
|
|
- if (invalidation_mark) {
|
|
|
|
|
- // TODO(Rodrigo): Find a better way to invalidate than waiting for all watches to finish.
|
|
|
|
|
- scheduler.Flush();
|
|
|
|
|
- std::for_each(watches.begin(), watches.begin() + *invalidation_mark,
|
|
|
|
|
- [&](auto& resource) { resource->Wait(); });
|
|
|
|
|
- invalidation_mark = std::nullopt;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const auto dev = device.GetLogical();
|
|
|
|
|
+ dev.unmapMemory(*memory, device.GetDispatchLoader());
|
|
|
|
|
+
|
|
|
|
|
+ offset += size;
|
|
|
|
|
|
|
|
- if (used_watches + 1 >= watches.size()) {
|
|
|
|
|
|
|
+ if (current_watch_cursor + 1 >= current_watches.size()) {
|
|
|
// Ensure that there are enough watches.
|
|
// Ensure that there are enough watches.
|
|
|
- ReserveWatches(WATCHES_RESERVE_CHUNK);
|
|
|
|
|
|
|
+ ReserveWatches(current_watches, WATCHES_RESERVE_CHUNK);
|
|
|
}
|
|
}
|
|
|
- // Add a watch for this allocation.
|
|
|
|
|
- watches[used_watches++]->Watch(scheduler.GetFence());
|
|
|
|
|
-
|
|
|
|
|
- offset += size;
|
|
|
|
|
|
|
+ auto& watch = current_watches[current_watch_cursor++];
|
|
|
|
|
+ watch.upper_bound = offset;
|
|
|
|
|
+ watch.fence.Watch(scheduler.GetFence());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-void VKStreamBuffer::CreateBuffers(VKMemoryManager& memory_manager, vk::BufferUsageFlags usage) {
|
|
|
|
|
- const vk::BufferCreateInfo buffer_ci({}, buffer_size, usage, vk::SharingMode::eExclusive, 0,
|
|
|
|
|
- nullptr);
|
|
|
|
|
-
|
|
|
|
|
|
|
+void VKStreamBuffer::CreateBuffers(vk::BufferUsageFlags usage) {
|
|
|
|
|
+ const vk::BufferCreateInfo buffer_ci({}, STREAM_BUFFER_SIZE, usage, vk::SharingMode::eExclusive,
|
|
|
|
|
+ 0, nullptr);
|
|
|
const auto dev = device.GetLogical();
|
|
const auto dev = device.GetLogical();
|
|
|
const auto& dld = device.GetDispatchLoader();
|
|
const auto& dld = device.GetDispatchLoader();
|
|
|
buffer = dev.createBufferUnique(buffer_ci, nullptr, dld);
|
|
buffer = dev.createBufferUnique(buffer_ci, nullptr, dld);
|
|
|
- commit = memory_manager.Commit(*buffer, true);
|
|
|
|
|
- mapped_pointer = commit->GetData();
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const auto requirements = dev.getBufferMemoryRequirements(*buffer, dld);
|
|
|
|
|
+ // Prefer device local host visible allocations (this should hit AMD's pinned memory).
|
|
|
|
|
+ auto type = FindMemoryType(device, requirements.memoryTypeBits,
|
|
|
|
|
+ vk::MemoryPropertyFlagBits::eHostVisible |
|
|
|
|
|
+ vk::MemoryPropertyFlagBits::eHostCoherent |
|
|
|
|
|
+ vk::MemoryPropertyFlagBits::eDeviceLocal);
|
|
|
|
|
+ if (!type) {
|
|
|
|
|
+ // Otherwise search for a host visible allocation.
|
|
|
|
|
+ type = FindMemoryType(device, requirements.memoryTypeBits,
|
|
|
|
|
+ vk::MemoryPropertyFlagBits::eHostVisible |
|
|
|
|
|
+ vk::MemoryPropertyFlagBits::eHostCoherent);
|
|
|
|
|
+ ASSERT_MSG(type, "No host visible and coherent memory type found");
|
|
|
|
|
+ }
|
|
|
|
|
+ const vk::MemoryAllocateInfo alloc_ci(requirements.size, *type);
|
|
|
|
|
+ memory = dev.allocateMemoryUnique(alloc_ci, nullptr, dld);
|
|
|
|
|
+
|
|
|
|
|
+ dev.bindBufferMemory(*buffer, *memory, 0, dld);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-void VKStreamBuffer::ReserveWatches(std::size_t grow_size) {
|
|
|
|
|
- const std::size_t previous_size = watches.size();
|
|
|
|
|
- watches.resize(previous_size + grow_size);
|
|
|
|
|
- std::generate(watches.begin() + previous_size, watches.end(),
|
|
|
|
|
- []() { return std::make_unique<VKFenceWatch>(); });
|
|
|
|
|
|
|
+void VKStreamBuffer::ReserveWatches(std::vector<Watch>& watches, std::size_t grow_size) {
|
|
|
|
|
+ watches.resize(watches.size() + grow_size);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void VKStreamBuffer::WaitPendingOperations(u64 requested_upper_bound) {
|
|
|
|
|
+ if (!invalidation_mark) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ while (requested_upper_bound < wait_bound && wait_cursor < *invalidation_mark) {
|
|
|
|
|
+ auto& watch = previous_watches[wait_cursor];
|
|
|
|
|
+ wait_bound = watch.upper_bound;
|
|
|
|
|
+ watch.fence.Wait();
|
|
|
|
|
+ ++wait_cursor;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
} // namespace Vulkan
|
|
} // namespace Vulkan
|