2 changed files with 0 additions and 244 deletions
-
168src/video_core/renderer_vulkan/vk_stream_buffer.cpp
-
76src/video_core/renderer_vulkan/vk_stream_buffer.h
@ -1,168 +0,0 @@ |
|||
// Copyright 2019 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <algorithm>
|
|||
#include <limits>
|
|||
#include <optional>
|
|||
#include <tuple>
|
|||
#include <vector>
|
|||
|
|||
#include "common/alignment.h"
|
|||
#include "common/assert.h"
|
|||
#include "common/literals.h"
|
|||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
|||
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
|
|||
#include "video_core/vulkan_common/vulkan_device.h"
|
|||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
|||
|
|||
namespace Vulkan { |
|||
|
|||
namespace { |
|||
|
|||
using namespace Common::Literals; |
|||
|
|||
constexpr VkBufferUsageFlags BUFFER_USAGE = |
|||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | |
|||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; |
|||
|
|||
constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000; |
|||
constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000; |
|||
|
|||
constexpr u64 PREFERRED_STREAM_BUFFER_SIZE = 256_MiB; |
|||
|
|||
/// Find a memory type with the passed requirements
|
|||
std::optional<u32> FindMemoryType(const VkPhysicalDeviceMemoryProperties& properties, |
|||
VkMemoryPropertyFlags wanted, |
|||
u32 filter = std::numeric_limits<u32>::max()) { |
|||
for (u32 i = 0; i < properties.memoryTypeCount; ++i) { |
|||
const auto flags = properties.memoryTypes[i].propertyFlags; |
|||
if ((flags & wanted) == wanted && (filter & (1U << i)) != 0) { |
|||
return i; |
|||
} |
|||
} |
|||
return std::nullopt; |
|||
} |
|||
|
|||
/// Get the preferred host visible memory type.
|
|||
u32 GetMemoryType(const VkPhysicalDeviceMemoryProperties& properties, |
|||
u32 filter = std::numeric_limits<u32>::max()) { |
|||
// Prefer device local host visible allocations. Both AMD and Nvidia now provide one.
|
|||
// Otherwise search for a host visible allocation.
|
|||
static constexpr auto HOST_MEMORY = |
|||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; |
|||
static constexpr auto DYNAMIC_MEMORY = HOST_MEMORY | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; |
|||
|
|||
std::optional preferred_type = FindMemoryType(properties, DYNAMIC_MEMORY); |
|||
if (!preferred_type) { |
|||
preferred_type = FindMemoryType(properties, HOST_MEMORY); |
|||
ASSERT_MSG(preferred_type, "No host visible and coherent memory type found"); |
|||
} |
|||
return preferred_type.value_or(0); |
|||
} |
|||
|
|||
} // Anonymous namespace
|
|||
|
|||
VKStreamBuffer::VKStreamBuffer(const Device& device_, VKScheduler& scheduler_) |
|||
: device{device_}, scheduler{scheduler_} { |
|||
CreateBuffers(); |
|||
ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE); |
|||
ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE); |
|||
} |
|||
|
|||
VKStreamBuffer::~VKStreamBuffer() = default; |
|||
|
|||
std::pair<u8*, u64> VKStreamBuffer::Map(u64 size, u64 alignment) { |
|||
ASSERT(size <= stream_buffer_size); |
|||
mapped_size = size; |
|||
|
|||
if (alignment > 0) { |
|||
offset = Common::AlignUp(offset, alignment); |
|||
} |
|||
|
|||
WaitPendingOperations(offset); |
|||
|
|||
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; |
|||
|
|||
// 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(); |
|||
} |
|||
|
|||
return std::make_pair(memory.Map(offset, size), offset); |
|||
} |
|||
|
|||
void VKStreamBuffer::Unmap(u64 size) { |
|||
ASSERT_MSG(size <= mapped_size, "Reserved size is too small"); |
|||
|
|||
memory.Unmap(); |
|||
|
|||
offset += size; |
|||
|
|||
if (current_watch_cursor + 1 >= current_watches.size()) { |
|||
// Ensure that there are enough watches.
|
|||
ReserveWatches(current_watches, WATCHES_RESERVE_CHUNK); |
|||
} |
|||
auto& watch = current_watches[current_watch_cursor++]; |
|||
watch.upper_bound = offset; |
|||
watch.tick = scheduler.CurrentTick(); |
|||
} |
|||
|
|||
void VKStreamBuffer::CreateBuffers() { |
|||
const auto memory_properties = device.GetPhysical().GetMemoryProperties(); |
|||
const u32 preferred_type = GetMemoryType(memory_properties); |
|||
const u32 preferred_heap = memory_properties.memoryTypes[preferred_type].heapIndex; |
|||
|
|||
// Substract from the preferred heap size some bytes to avoid getting out of memory.
|
|||
const VkDeviceSize heap_size = memory_properties.memoryHeaps[preferred_heap].size; |
|||
// As per DXVK's example, using `heap_size / 2`
|
|||
const VkDeviceSize allocable_size = heap_size / 2; |
|||
buffer = device.GetLogical().CreateBuffer({ |
|||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, |
|||
.pNext = nullptr, |
|||
.flags = 0, |
|||
.size = std::min(PREFERRED_STREAM_BUFFER_SIZE, allocable_size), |
|||
.usage = BUFFER_USAGE, |
|||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE, |
|||
.queueFamilyIndexCount = 0, |
|||
.pQueueFamilyIndices = nullptr, |
|||
}); |
|||
|
|||
const auto requirements = device.GetLogical().GetBufferMemoryRequirements(*buffer); |
|||
const u32 required_flags = requirements.memoryTypeBits; |
|||
stream_buffer_size = static_cast<u64>(requirements.size); |
|||
|
|||
memory = device.GetLogical().AllocateMemory({ |
|||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, |
|||
.pNext = nullptr, |
|||
.allocationSize = requirements.size, |
|||
.memoryTypeIndex = GetMemoryType(memory_properties, required_flags), |
|||
}); |
|||
buffer.BindMemory(*memory, 0); |
|||
} |
|||
|
|||
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; |
|||
scheduler.Wait(watch.tick); |
|||
++wait_cursor; |
|||
} |
|||
} |
|||
|
|||
} // namespace Vulkan
|
|||
@ -1,76 +0,0 @@ |
|||
// Copyright 2019 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <optional> |
|||
#include <utility> |
|||
#include <vector> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "video_core/vulkan_common/vulkan_wrapper.h" |
|||
|
|||
namespace Vulkan { |
|||
|
|||
class Device; |
|||
class VKFenceWatch; |
|||
class VKScheduler; |
|||
|
|||
class VKStreamBuffer final { |
|||
public: |
|||
explicit VKStreamBuffer(const Device& device, VKScheduler& scheduler); |
|||
~VKStreamBuffer(); |
|||
|
|||
/** |
|||
* Reserves a region of memory from the stream buffer. |
|||
* @param size Size to reserve. |
|||
* @returns A pair of a raw memory pointer (with offset added), and the buffer offset |
|||
*/ |
|||
std::pair<u8*, u64> Map(u64 size, u64 alignment); |
|||
|
|||
/// Ensures that "size" bytes of memory are available to the GPU, potentially recording a copy. |
|||
void Unmap(u64 size); |
|||
|
|||
VkBuffer Handle() const noexcept { |
|||
return *buffer; |
|||
} |
|||
|
|||
u64 Address() const noexcept { |
|||
return 0; |
|||
} |
|||
|
|||
private: |
|||
struct Watch { |
|||
u64 tick{}; |
|||
u64 upper_bound{}; |
|||
}; |
|||
|
|||
/// Creates Vulkan buffer handles committing the required the required memory. |
|||
void CreateBuffers(); |
|||
|
|||
/// Increases the amount of watches available. |
|||
void ReserveWatches(std::vector<Watch>& watches, std::size_t grow_size); |
|||
|
|||
void WaitPendingOperations(u64 requested_upper_bound); |
|||
|
|||
const Device& device; ///< Vulkan device manager. |
|||
VKScheduler& scheduler; ///< Command scheduler. |
|||
|
|||
vk::Buffer buffer; ///< Mapped buffer. |
|||
vk::DeviceMemory memory; ///< Memory allocation. |
|||
u64 stream_buffer_size{}; ///< Stream buffer size. |
|||
|
|||
u64 offset{}; ///< Buffer iterator. |
|||
u64 mapped_size{}; ///< Size reserved for the current copy. |
|||
|
|||
std::vector<Watch> current_watches; ///< Watches recorded in the current iteration. |
|||
std::size_t current_watch_cursor{}; ///< Count of watches, reset on invalidation. |
|||
std::optional<std::size_t> invalidation_mark; ///< Number of watches used in the previous cycle. |
|||
|
|||
std::vector<Watch> previous_watches; ///< Watches used in the previous iteration. |
|||
std::size_t wait_cursor{}; ///< Last watch being waited for completion. |
|||
u64 wait_bound{}; ///< Highest offset being watched for completion. |
|||
}; |
|||
|
|||
} // namespace Vulkan |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue