// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/alignment.h" #include "common/assert.h" #include "video_core/buffer_cache/buffer.h" #include "video_core/renderer_vulkan/liverpool_to_vk.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_platform.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include namespace VideoCore { constexpr vk::BufferUsageFlags AllFlags = vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eUniformTexelBuffer | vk::BufferUsageFlagBits::eStorageTexelBuffer | vk::BufferUsageFlagBits::eUniformBuffer | vk::BufferUsageFlagBits::eStorageBuffer | vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eVertexBuffer; std::string_view BufferTypeName(MemoryUsage type) { switch (type) { case MemoryUsage::Upload: return "Upload"; case MemoryUsage::Download: return "Download"; case MemoryUsage::Stream: return "Stream"; case MemoryUsage::DeviceLocal: return "DeviceLocal"; default: return "Invalid"; } } [[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferredVmaFlags(MemoryUsage usage) { return usage != MemoryUsage::DeviceLocal ? VK_MEMORY_PROPERTY_HOST_COHERENT_BIT : VkMemoryPropertyFlagBits{}; } [[nodiscard]] VmaAllocationCreateFlags MemoryUsageVmaFlags(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 MemoryUsageVma(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; } UniqueBuffer::UniqueBuffer(vk::Device device_, VmaAllocator allocator_) : device{device_}, allocator{allocator_} {} UniqueBuffer::~UniqueBuffer() { if (buffer) { vmaDestroyBuffer(allocator, buffer, allocation); } } void UniqueBuffer::Create(const vk::BufferCreateInfo& buffer_ci, MemoryUsage usage, VmaAllocationInfo* out_alloc_info) { const VmaAllocationCreateInfo alloc_ci = { .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage), .usage = MemoryUsageVma(usage), .requiredFlags = 0, .preferredFlags = MemoryUsagePreferredVmaFlags(usage), .pool = VK_NULL_HANDLE, .pUserData = nullptr, }; const VkBufferCreateInfo buffer_ci_unsafe = static_cast(buffer_ci); VkBuffer unsafe_buffer{}; VkResult result = vmaCreateBuffer(allocator, &buffer_ci_unsafe, &alloc_ci, &unsafe_buffer, &allocation, out_alloc_info); ASSERT_MSG(result == VK_SUCCESS, "Failed allocating buffer with error {}", vk::to_string(vk::Result{result})); buffer = vk::Buffer{unsafe_buffer}; } Buffer::Buffer(const Vulkan::Instance& instance_, MemoryUsage usage_, VAddr cpu_addr_, u64 size_bytes_) : cpu_addr{cpu_addr_}, size_bytes{size_bytes_}, instance{&instance_}, usage{usage_}, buffer{instance->GetDevice(), instance->GetAllocator()} { // Create buffer object. const vk::BufferCreateInfo buffer_ci = { .size = size_bytes, .usage = AllFlags, }; VmaAllocationInfo alloc_info{}; buffer.Create(buffer_ci, usage, &alloc_info); if (instance->HasDebuggingToolAttached()) { const auto device = instance->GetDevice(); Vulkan::SetObjectName(device, Handle(), "Buffer {:#x} {} KiB", cpu_addr, size_bytes / 1024); } // Map it if it is host visible. VkMemoryPropertyFlags property_flags{}; vmaGetAllocationMemoryProperties(instance->GetAllocator(), buffer.allocation, &property_flags); if (alloc_info.pMappedData) { mapped_data = std::span{std::bit_cast(alloc_info.pMappedData), size_bytes}; } is_coherent = property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; } vk::BufferView Buffer::View(u32 offset, u32 size, AmdGpu::DataFormat dfmt, AmdGpu::NumberFormat nfmt) { const auto it{std::ranges::find_if(views, [offset, size, dfmt, nfmt](const BufferView& view) { return offset == view.offset && size == view.size && dfmt == view.dfmt && nfmt == view.nfmt; })}; if (it != views.end()) { return it->handle; } views.push_back({ .offset = offset, .size = size, .dfmt = dfmt, .nfmt = nfmt, .handle = instance->GetDevice().createBufferView({ .buffer = buffer.buffer, .format = Vulkan::LiverpoolToVK::SurfaceFormat(dfmt, nfmt), .offset = offset, .range = size, }), }); return views.back().handle; } constexpr u64 WATCHES_INITIAL_RESERVE = 0x4000; constexpr u64 WATCHES_RESERVE_CHUNK = 0x1000; StreamBuffer::StreamBuffer(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler_, MemoryUsage usage, u64 size_bytes) : Buffer{instance, usage, 0, size_bytes}, scheduler{scheduler_} { ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE); ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE); const auto device = instance.GetDevice(); if (instance.HasDebuggingToolAttached()) { Vulkan::SetObjectName(device, Handle(), "StreamBuffer({}): {} KiB", BufferTypeName(usage), size_bytes / 1024); } } std::pair StreamBuffer::Map(u64 size, u64 alignment) { if (!is_coherent && usage == MemoryUsage::Stream) { size = Common::AlignUp(size, instance->NonCoherentAtomSize()); } ASSERT(size <= this->size_bytes); mapped_size = size; if (alignment > 0) { offset = Common::AlignUp(offset, alignment); } if (offset + size > this->size_bytes) { // 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; } const u64 mapped_upper_bound = offset + size; WaitPendingOperations(mapped_upper_bound); return std::make_pair(mapped_data.data() + offset, offset); } void StreamBuffer::Commit() { if (!is_coherent) { if (usage == MemoryUsage::Download) { vmaInvalidateAllocation(instance->GetAllocator(), buffer.allocation, offset, mapped_size); } else { vmaFlushAllocation(instance->GetAllocator(), buffer.allocation, offset, mapped_size); } } offset += mapped_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 StreamBuffer::ReserveWatches(std::vector& watches, std::size_t grow_size) { watches.resize(watches.size() + grow_size); } void StreamBuffer::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 VideoCore