mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-03 11:23:49 +00:00
359 lines
14 KiB
C++
359 lines
14 KiB
C++
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
|
|
|
|
#include "common/assert.h"
|
|
#include "common/profiler.h"
|
|
#include "graphics/host_gpu/graphicContext.h"
|
|
#include "graphics/host_gpu/renderer/commandScheduler.h"
|
|
#include "graphics/host_gpu/vma.h"
|
|
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <numeric>
|
|
#include <vk_mem_alloc.h>
|
|
|
|
namespace Libs::Graphics {
|
|
|
|
namespace {
|
|
|
|
constexpr size_t WATCHES_INITIAL_RESERVE = 0x4000;
|
|
constexpr size_t WATCHES_RESERVE_CHUNK = 0x1000;
|
|
|
|
[[nodiscard]] VmaAllocationCreateFlags AllocationFlags(MemoryUsage usage) {
|
|
switch (usage) {
|
|
case MemoryUsage::Upload:
|
|
case MemoryUsage::Stream:
|
|
return VMA_ALLOCATION_CREATE_MAPPED_BIT |
|
|
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
|
case MemoryUsage::Download:
|
|
return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
|
|
case MemoryUsage::DeviceLocal: return {};
|
|
}
|
|
return {};
|
|
}
|
|
|
|
[[nodiscard]] VmaMemoryUsage AllocationUsage(MemoryUsage usage) {
|
|
switch (usage) {
|
|
case MemoryUsage::DeviceLocal:
|
|
case MemoryUsage::Stream: return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
|
case MemoryUsage::Upload:
|
|
case MemoryUsage::Download: return VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
|
|
}
|
|
return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
|
}
|
|
|
|
[[nodiscard]] bool AlignUp(uint64_t value, uint64_t alignment, uint64_t& result) {
|
|
if (alignment == 0) {
|
|
result = value;
|
|
return true;
|
|
}
|
|
const auto remainder = value % alignment;
|
|
if (remainder == 0) {
|
|
result = value;
|
|
return true;
|
|
}
|
|
const auto increment = alignment - remainder;
|
|
if (value > std::numeric_limits<uint64_t>::max() - increment) {
|
|
return false;
|
|
}
|
|
result = value + increment;
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Buffer::Buffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
|
|
uint64_t cpu_address, vk::BufferUsageFlags flags, uint64_t size)
|
|
: m_graphics(&graphics), m_scheduler(&scheduler), m_usage(usage), m_cpu_address(cpu_address),
|
|
m_size(size), m_buffer(std::make_unique<VulkanBuffer>()) {
|
|
KYTY_PROFILER_FUNCTION();
|
|
EXIT_IF(graphics.allocator == nullptr || size == 0);
|
|
|
|
vk::BufferCreateInfo buffer_info {};
|
|
buffer_info.size = size;
|
|
buffer_info.usage = flags;
|
|
buffer_info.sharingMode = vk::SharingMode::eExclusive;
|
|
|
|
VmaAllocationCreateInfo allocation_info {};
|
|
allocation_info.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | AllocationFlags(usage);
|
|
allocation_info.usage = AllocationUsage(usage);
|
|
allocation_info.preferredFlags = usage == MemoryUsage::DeviceLocal
|
|
? VkMemoryPropertyFlags {}
|
|
: VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
|
|
VmaAllocationInfo allocation_result {};
|
|
VkBuffer native_buffer = VK_NULL_HANDLE;
|
|
const auto result = static_cast<vk::Result>(vmaCreateBuffer(
|
|
graphics.allocator, static_cast<const VkBufferCreateInfo*>(buffer_info), &allocation_info,
|
|
&native_buffer, &m_buffer->memory.allocation, &allocation_result));
|
|
if (result != vk::Result::eSuccess) {
|
|
graphics.LogMemoryBudget();
|
|
}
|
|
EXIT_NOT_IMPLEMENTED(result != vk::Result::eSuccess);
|
|
|
|
m_buffer->buffer = native_buffer;
|
|
m_buffer->usage = flags;
|
|
m_buffer->buffer_size = size;
|
|
m_buffer->memory.allocation_info = allocation_result;
|
|
m_buffer->memory.memory = allocation_result.deviceMemory;
|
|
m_buffer->memory.offset = allocation_result.offset;
|
|
m_buffer->memory.type = allocation_result.memoryType;
|
|
m_buffer->memory.unique_id = VulkanNextMemoryUniqueId();
|
|
graphics.device.getBufferMemoryRequirements(m_buffer->buffer, &m_buffer->memory.requirements);
|
|
|
|
VkMemoryPropertyFlags properties = 0;
|
|
vmaGetAllocationMemoryProperties(graphics.allocator, m_buffer->memory.allocation, &properties);
|
|
m_buffer->memory.property = vk::MemoryPropertyFlags(properties);
|
|
m_is_coherent = (properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
|
|
if (allocation_result.pMappedData != nullptr) {
|
|
m_mapped = {static_cast<uint8_t*>(allocation_result.pMappedData),
|
|
static_cast<size_t>(size)};
|
|
}
|
|
VulkanTrackAllocation(m_buffer->memory);
|
|
}
|
|
|
|
Buffer::~Buffer() {
|
|
if (m_buffer->buffer != nullptr) {
|
|
VulkanUntrackAllocation(m_buffer->memory);
|
|
vmaDestroyBuffer(m_graphics->allocator, m_buffer->buffer, m_buffer->memory.allocation);
|
|
}
|
|
}
|
|
|
|
vk::Buffer Buffer::Handle() const noexcept {
|
|
return m_buffer->buffer;
|
|
}
|
|
|
|
bool Buffer::IsInBounds(uint64_t address, uint64_t size) const noexcept {
|
|
return address >= m_cpu_address && size <= m_size && address - m_cpu_address <= m_size - size;
|
|
}
|
|
|
|
void Buffer::Write(uint64_t offset, const void* source, uint64_t size) {
|
|
EXIT_IF(source == nullptr || m_mapped.empty() || offset > m_size || size > m_size - offset);
|
|
std::memcpy(m_mapped.data() + offset, source, static_cast<size_t>(size));
|
|
Flush(offset, size);
|
|
}
|
|
|
|
void Buffer::Flush(uint64_t offset, uint64_t size) {
|
|
EXIT_IF(m_mapped.empty() || offset > m_size || size > m_size - offset);
|
|
if (!m_is_coherent && size != 0) {
|
|
const auto result =
|
|
vmaFlushAllocation(m_graphics->allocator, m_buffer->memory.allocation, offset, size);
|
|
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
|
|
}
|
|
}
|
|
|
|
vk::BufferMemoryBarrier Buffer::Barrier(uint64_t offset, uint64_t size, vk::AccessFlags source,
|
|
vk::AccessFlags destination) const {
|
|
if (Handle() == nullptr || size == 0 || offset > m_size || size > m_size - offset) {
|
|
EXIT("Buffer: invalid DMA barrier, handle=%p offset=0x%016" PRIx64 " size=0x%016" PRIx64
|
|
" capacity=0x%016" PRIx64 "\n",
|
|
static_cast<const void*>(Handle()), offset, size, m_size);
|
|
}
|
|
vk::BufferMemoryBarrier barrier {};
|
|
barrier.sType = vk::StructureType::eBufferMemoryBarrier;
|
|
barrier.srcAccessMask = source;
|
|
barrier.dstAccessMask = destination;
|
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.buffer = Handle();
|
|
barrier.offset = offset;
|
|
barrier.size = size;
|
|
return barrier;
|
|
}
|
|
|
|
void Buffer::CopyFrom(CommandBuffer& command, const Buffer& source, uint64_t source_offset,
|
|
uint64_t destination_offset, uint64_t size, vk::AccessFlags source_before,
|
|
vk::AccessFlags destination_before, vk::AccessFlags source_after,
|
|
vk::AccessFlags destination_after) {
|
|
if (size == 0 || source_offset > source.m_size || size > source.m_size - source_offset ||
|
|
destination_offset > m_size || size > m_size - destination_offset) {
|
|
EXIT("Buffer: invalid copy range\n");
|
|
}
|
|
if (source.Handle() == Handle() && source_offset < destination_offset + size &&
|
|
destination_offset < source_offset + size) {
|
|
EXIT("Buffer: overlapping self-copy\n");
|
|
}
|
|
command.EndRendering();
|
|
const vk::BufferMemoryBarrier before[] = {
|
|
source.Barrier(source_offset, size, source_before, vk::AccessFlagBits::eTransferRead),
|
|
Barrier(destination_offset, size, destination_before, vk::AccessFlagBits::eTransferWrite),
|
|
};
|
|
const auto host_access = vk::AccessFlagBits::eHostRead | vk::AccessFlagBits::eHostWrite;
|
|
auto before_stage = vk::PipelineStageFlags {vk::PipelineStageFlagBits::eAllCommands};
|
|
if (static_cast<bool>((source_before | destination_before) & host_access)) {
|
|
before_stage |= vk::PipelineStageFlagBits::eHost;
|
|
}
|
|
const auto native = command.Handle();
|
|
native.pipelineBarrier(before_stage, vk::PipelineStageFlagBits::eTransfer,
|
|
vk::DependencyFlagBits::eByRegion, 0, nullptr, 2, before, 0, nullptr);
|
|
const vk::BufferCopy copy {source_offset, destination_offset, size};
|
|
native.copyBuffer(source.Handle(), Handle(), 1, ©);
|
|
const vk::BufferMemoryBarrier after[] = {
|
|
source.Barrier(source_offset, size, vk::AccessFlagBits::eTransferRead, source_after),
|
|
Barrier(destination_offset, size, vk::AccessFlagBits::eTransferWrite, destination_after),
|
|
};
|
|
auto after_stage = vk::PipelineStageFlags {vk::PipelineStageFlagBits::eAllCommands};
|
|
if (static_cast<bool>((source_after | destination_after) & host_access)) {
|
|
after_stage |= vk::PipelineStageFlagBits::eHost;
|
|
}
|
|
native.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, after_stage,
|
|
vk::DependencyFlagBits::eByRegion, 0, nullptr, 2, after, 0, nullptr);
|
|
}
|
|
|
|
void Buffer::Fill(uint64_t offset, uint64_t size, uint32_t value) {
|
|
if (((offset | size) & 3u) != 0) {
|
|
EXIT("Buffer: fill range must be dword aligned\n");
|
|
}
|
|
auto& command = Scheduler().Current();
|
|
command.EndRendering();
|
|
const auto before =
|
|
Barrier(offset, size, vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
|
|
vk::AccessFlagBits::eTransferWrite);
|
|
const auto native = command.Handle();
|
|
native.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
|
|
vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion,
|
|
0, nullptr, 1, &before, 0, nullptr);
|
|
native.fillBuffer(Handle(), offset, size, value);
|
|
const auto after = Barrier(offset, size, vk::AccessFlagBits::eTransferWrite,
|
|
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite);
|
|
native.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
|
vk::PipelineStageFlagBits::eAllCommands,
|
|
vk::DependencyFlagBits::eByRegion, 0, nullptr, 1, &after, 0, nullptr);
|
|
}
|
|
|
|
StreamBuffer::StreamBuffer(GraphicContext& graphics, CommandScheduler& scheduler, MemoryUsage usage,
|
|
uint64_t size)
|
|
: Buffer(graphics, scheduler, usage, 0, AllFlags, size) {
|
|
ReserveWatches(m_current_watches, WATCHES_INITIAL_RESERVE);
|
|
ReserveWatches(m_previous_watches, WATCHES_INITIAL_RESERVE);
|
|
}
|
|
|
|
bool StreamBuffer::NormalizeReservation(bool coherent, uint64_t atom, uint64_t& size,
|
|
uint64_t& alignment) {
|
|
if (coherent) {
|
|
return true;
|
|
}
|
|
if (!AlignUp(size, atom, size)) {
|
|
return false;
|
|
}
|
|
const auto divisor = std::gcd(alignment, atom);
|
|
if (alignment != 0 && alignment / divisor > UINT64_MAX / atom) {
|
|
return false;
|
|
}
|
|
alignment = alignment == 0 ? atom : alignment / divisor * atom;
|
|
return true;
|
|
}
|
|
|
|
std::pair<uint8_t*, uint64_t> StreamBuffer::Map(uint64_t size, uint64_t alignment,
|
|
bool allow_wait) {
|
|
if (Mapped().empty()) {
|
|
return {nullptr, 0};
|
|
}
|
|
uint64_t mapped_size = size;
|
|
const auto atom = Graphics().physical_device_properties.limits.nonCoherentAtomSize;
|
|
if (!NormalizeReservation(IsCoherent(), atom, mapped_size, alignment)) {
|
|
return {nullptr, 0};
|
|
}
|
|
if (mapped_size > Size()) {
|
|
return {nullptr, 0};
|
|
}
|
|
|
|
uint64_t aligned_offset = 0;
|
|
if (!AlignUp(m_offset, alignment, aligned_offset)) {
|
|
return {nullptr, 0};
|
|
}
|
|
|
|
const bool wrap = aligned_offset > Size() - mapped_size;
|
|
if (wrap) {
|
|
aligned_offset = 0;
|
|
}
|
|
|
|
auto wait_cursor = wrap ? size_t {0} : m_wait_cursor;
|
|
auto wait_bound = wrap ? uint64_t {0} : m_wait_bound;
|
|
auto invalidation_mark =
|
|
wrap ? std::optional<size_t> {m_current_watch_cursor} : m_invalidation_mark;
|
|
auto& pending_watches = wrap ? m_current_watches : m_previous_watches;
|
|
if (!WaitPendingOperations(pending_watches, invalidation_mark, aligned_offset + mapped_size,
|
|
allow_wait, wait_cursor, wait_bound)) {
|
|
return {nullptr, 0};
|
|
}
|
|
|
|
if (wrap) {
|
|
m_invalidation_mark = invalidation_mark;
|
|
m_current_watch_cursor = 0;
|
|
std::swap(m_previous_watches, m_current_watches);
|
|
}
|
|
m_wait_cursor = wait_cursor;
|
|
m_wait_bound = wait_bound;
|
|
m_offset = aligned_offset;
|
|
m_mapped_size = mapped_size;
|
|
return {Mapped().data() + m_offset, m_offset};
|
|
}
|
|
|
|
void StreamBuffer::Commit() {
|
|
if (!IsCoherent() && Usage() != MemoryUsage::Download && m_mapped_size != 0) {
|
|
const auto result = vmaFlushAllocation(
|
|
Graphics().allocator, NativeBuffer().memory.allocation, m_offset, m_mapped_size);
|
|
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
|
|
}
|
|
|
|
m_offset += m_mapped_size;
|
|
const auto tick = Scheduler().CurrentTick();
|
|
if (m_current_watch_cursor != 0 && m_current_watches[m_current_watch_cursor - 1].tick == tick) {
|
|
m_current_watches[m_current_watch_cursor - 1].upper_bound = m_offset;
|
|
return;
|
|
}
|
|
if (m_current_watch_cursor + 1 >= m_current_watches.size()) {
|
|
ReserveWatches(m_current_watches, WATCHES_RESERVE_CHUNK);
|
|
}
|
|
auto& watch = m_current_watches[m_current_watch_cursor++];
|
|
watch.upper_bound = m_offset;
|
|
watch.tick = tick;
|
|
}
|
|
|
|
void StreamBuffer::Invalidate(uint64_t offset, uint64_t size) {
|
|
EXIT_IF(Usage() != MemoryUsage::Download || offset > Size() || size > Size() - offset);
|
|
if (IsCoherent() || size == 0) {
|
|
return;
|
|
}
|
|
const auto result = vmaInvalidateAllocation(Graphics().allocator,
|
|
NativeBuffer().memory.allocation, offset, size);
|
|
EXIT_NOT_IMPLEMENTED(static_cast<vk::Result>(result) != vk::Result::eSuccess);
|
|
}
|
|
|
|
uint64_t StreamBuffer::Copy(const void* source, uint64_t size, uint64_t alignment) {
|
|
EXIT_IF(source == nullptr);
|
|
const auto [data, offset] = Map(size, alignment);
|
|
EXIT_IF(data == nullptr);
|
|
std::memcpy(data, source, static_cast<size_t>(size));
|
|
Commit();
|
|
return offset;
|
|
}
|
|
|
|
void StreamBuffer::ReserveWatches(std::vector<Watch>& watches, size_t grow_size) {
|
|
watches.resize(watches.size() + grow_size);
|
|
}
|
|
|
|
bool StreamBuffer::WaitPendingOperations(const std::vector<Watch>& watches,
|
|
std::optional<size_t> invalidation_mark,
|
|
uint64_t requested_upper_bound, bool allow_wait,
|
|
size_t& wait_cursor, uint64_t& wait_bound) {
|
|
if (!invalidation_mark.has_value()) {
|
|
return true;
|
|
}
|
|
while (requested_upper_bound > wait_bound && wait_cursor < *invalidation_mark) {
|
|
const auto& watch = watches[wait_cursor];
|
|
if (!Scheduler().IsFree(watch.tick) && !allow_wait) {
|
|
return false;
|
|
}
|
|
Scheduler().Wait(watch.tick);
|
|
if (Usage() == MemoryUsage::Download) {
|
|
Scheduler().WaitPriorityOperations(watch.tick);
|
|
}
|
|
wait_bound = watch.upper_bound;
|
|
++wait_cursor;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace Libs::Graphics
|