Compare commits

...

4 Commits

Author SHA1 Message Date
CamilleLaVey e46dd1bd5f [TEST] Split downloads 2026-08-08 19:03:46 -04:00
CamilleLaVey e26153b3d3 [TEST] Adjustment on gpu modified ranges 2026-08-08 18:42:20 -04:00
CamilleLaVey 487ff4ed4d [TEST] Adjust introduction on shader write/read 2026-08-08 18:16:17 -04:00
CamilleLaVey ee5c838463 [TEST] Trasnfer flags + binding descriptors 2026-08-08 17:06:40 -04:00
7 changed files with 384 additions and 121 deletions
+186 -16
View File
@@ -26,6 +26,7 @@ BufferCache<P>::BufferCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, R
// Ensure the first slot is used for the null buffer
void(slot_buffers.insert(runtime, NullBufferParams{}));
gpu_modified_ranges.Clear();
in_place_gpu_written_ranges.Clear();
inline_buffer_id = NULL_BUFFER_ID;
#ifdef YUZU_LEGACY
immediately_free = (Settings::values.vram_usage_mode.GetValue() == Settings::VramUsageMode::Aggressive);
@@ -116,7 +117,7 @@ template <class P>
void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) {
if (memory_tracker.IsRegionGpuModified(device_addr, size)) {
ClearDownload(device_addr, size);
gpu_modified_ranges.Subtract(device_addr, size);
ForgetGpuModifiedRange(device_addr, size);
}
memory_tracker.MarkRegionAsCpuModified(device_addr, size);
}
@@ -231,7 +232,7 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
};
gpu_modified_ranges.ForEachInRange(*cpu_src_address, amount, mirror);
// This subtraction in this order is important for overlapping copies.
gpu_modified_ranges.Subtract(*cpu_dest_address, amount);
ForgetGpuModifiedRange(*cpu_dest_address, amount);
const bool has_new_downloads = tmp_intervals.size() != 0;
for (const auto& pair : tmp_intervals) {
gpu_modified_ranges.Add(pair.first, pair.second);
@@ -263,7 +264,7 @@ bool BufferCache<P>::DMAClear(GPUVAddr dst_address, u64 amount, u32 value) {
const size_t size = amount * sizeof(u32);
ClearDownload(*cpu_dst_address, size);
gpu_modified_ranges.Subtract(*cpu_dst_address, size);
ForgetGpuModifiedRange(*cpu_dst_address, size);
const BufferId buffer = FindBuffer(*cpu_dst_address, static_cast<u32>(size));
Buffer& dest_buffer = slot_buffers[buffer];
@@ -308,7 +309,7 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainCPUBuffer(
const DAddr device_addr_end = Common::AlignUp(device_addr + size, 64);
const size_t new_size = device_addr_end - device_addr_start;
ClearDownload(device_addr_start, new_size);
gpu_modified_ranges.Subtract(device_addr_start, new_size);
ForgetGpuModifiedRange(device_addr_start, new_size);
break;
}
default:
@@ -601,7 +602,13 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
it++;
}
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> downloads;
struct QueuedDownload {
BufferCopy copy;
BufferId buffer_id;
bool written_in_place;
};
boost::container::small_vector<QueuedDownload, 16> downloads;
u64 total_size_bytes = 0;
u64 largest_copy = 0;
for (const Common::RangeSet<DAddr>& range_set : committed_gpu_modified_ranges) {
@@ -617,7 +624,8 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
new_start, new_end - new_start, false,
[&](u64 device_addr_out, u64 range_size) {
const DAddr buffer_addr = buffer.CpuAddr();
const auto add_download = [&](DAddr start, DAddr end) {
const auto add_download = [&](DAddr start, DAddr end,
bool written_in_place) {
const u64 new_offset = start - buffer_addr;
const u64 new_size = end - start;
downloads.push_back({
@@ -627,6 +635,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
.size = new_size,
},
buffer_id,
written_in_place,
});
// Align up to avoid cache conflicts
constexpr u64 align = 64ULL;
@@ -634,9 +643,23 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
total_size_bytes += (new_size + align - 1) & mask;
largest_copy = (std::max)(largest_copy, new_size);
};
const auto split_by_in_place = [&](DAddr start, DAddr end) {
DAddr cursor = start;
in_place_gpu_written_ranges.ForEachInRange(
start, end - start, [&](DAddr in_start, DAddr in_end) {
if (cursor < in_start) {
add_download(cursor, in_start, false);
}
add_download(in_start, in_end, true);
cursor = in_end;
});
if (cursor < end) {
add_download(cursor, end, false);
}
};
gpu_modified_ranges.ForEachInRange(device_addr_out, range_size,
add_download);
split_by_in_place);
});
});
});
@@ -660,9 +683,18 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
boost::container::small_vector<u64, 4> window_ids;
UnifiedWindowGroups groups;
u64 staging_size_bytes = 0;
for (auto& [copy, buffer_id] : downloads) {
bool has_in_place_writes = false;
for (auto& [copy, buffer_id, written_in_place] : downloads) {
Buffer& buffer = slot_buffers[buffer_id];
const DAddr orig_device_addr = buffer.CpuAddr() + copy.src_offset;
if (written_in_place) {
BufferCopy record{copy};
record.src_offset = static_cast<size_t>(orig_device_addr);
async_downloads.Add(orig_device_addr, copy.size);
batch.unified_copies.push_back(record);
has_in_place_writes = true;
continue;
}
bool unified = false;
if constexpr (USE_UNIFIED_MEMORY) {
if (runtime.HasUnifiedMemory()) {
@@ -717,6 +749,11 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
runtime.UnifiedMemoryHostBarrier();
}
}
if constexpr (USE_UNIFIED_DIRECT_BINDING) {
if (has_in_place_writes) {
runtime.UnifiedMemoryShaderWriteBarrier();
}
}
runtime.PostCopyBarrier();
pending_downloads.emplace_back(std::move(batch));
async_buffers.emplace_back(std::move(download_staging));
@@ -760,7 +797,7 @@ void BufferCache<P>::PopAsyncBuffers() {
{start, &read_mapped_memory[start - device_addr], end - start});
});
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
gpu_modified_ranges.Subtract(start, end - start);
ForgetGpuModifiedRange(start, end - start);
});
}
async_buffers_death_ring.emplace_back(*async_buffer);
@@ -768,7 +805,7 @@ void BufferCache<P>::PopAsyncBuffers() {
for (const auto& copy : batch.unified_copies) {
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
gpu_modified_ranges.Subtract(start, end - start);
ForgetGpuModifiedRange(start, end - start);
});
}
async_buffers.pop_front();
@@ -820,6 +857,22 @@ void BufferCache<P>::BindHostIndexBuffer() {
const u32 size = channel_state->index_buffer.size;
const auto& draw_state = maxwell3d->draw_manager.draw_state;
if (draw_state.inline_index_draw_indexes.empty()) {
if constexpr (USE_UNIFIED_DIRECT_BINDING && !HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
u64 window_index = 0;
u64 window_offset = 0;
if (runtime.CanBindIndexBufferDirectly(draw_state.topology,
draw_state.index_buffer.format) &&
ResolveUnifiedDirectBinding(channel_state->index_buffer.device_addr, size,
draw_state.index_buffer.FormatSizeInBytes(),
window_index, window_offset)) {
runtime.BindIndexBuffer(draw_state.topology, draw_state.index_buffer.format,
draw_state.index_buffer.first,
draw_state.index_buffer.count,
runtime.UnifiedWindowBuffer(window_index),
static_cast<u32>(window_offset), size);
return;
}
}
SynchronizeBuffer(buffer, channel_state->index_buffer.device_addr, size);
} else {
if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) {
@@ -905,13 +958,30 @@ void BufferCache<P>::BindHostVertexBuffers() {
const Binding& binding = VertexBufferSlot(index);
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
SynchronizeBuffer(buffer, binding.device_addr, binding.size);
u64 window_index = 0;
u64 window_offset = 0;
const bool bind_from_window =
ResolveUnifiedDirectBinding(binding.device_addr, binding.size,
UNIFIED_VERTEX_BINDING_ALIGNMENT, window_index,
window_offset);
if (!bind_from_window) {
SynchronizeBuffer(buffer, binding.device_addr, binding.size);
}
if (!flags[Dirty::VertexBuffer0 + index]) {
flush_bindings();
continue;
}
flags[Dirty::VertexBuffer0 + index] = false;
const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
if constexpr (USE_UNIFIED_DIRECT_BINDING) {
if (bind_from_window) {
flush_bindings();
runtime.BindVertexBuffer(index, runtime.UnifiedWindowBuffer(window_index),
static_cast<u32>(window_offset), binding.size,
stride);
continue;
}
}
const u32 offset = buffer.Offset(binding.device_addr);
buffer.MarkUsage(offset, binding.size);
if (!bindings.buffers.empty() && index != last_index + 1) {
@@ -1084,11 +1154,26 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
const u32 size = binding.size;
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
if constexpr (USE_UNIFIED_DIRECT_BINDING && !NEEDS_BIND_STORAGE_INDEX) {
u64 window_index = 0;
u64 window_offset = 0;
if (!is_written && runtime.SupportsUnifiedDescriptorBinding() &&
size <= runtime.UnifiedMaxStorageBufferRange() &&
ResolveUnifiedDirectBinding(binding.device_addr, size,
runtime.UnifiedStorageBufferAlignment(), window_index,
window_offset)) {
runtime.BindStorageBufferFromWindow(window_index, static_cast<u32>(window_offset),
size);
return;
}
}
SynchronizeBuffer(buffer, binding.device_addr, size);
const u32 offset = buffer.Offset(binding.device_addr);
buffer.MarkUsage(offset, size);
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
if (is_written) {
MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size);
@@ -1220,12 +1305,27 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
const u32 size = binding.size;
const bool is_written =
((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
if constexpr (USE_UNIFIED_DIRECT_BINDING && !NEEDS_BIND_STORAGE_INDEX) {
u64 window_index = 0;
u64 window_offset = 0;
if (!is_written && runtime.SupportsUnifiedDescriptorBinding() &&
size <= runtime.UnifiedMaxStorageBufferRange() &&
ResolveUnifiedDirectBinding(binding.device_addr, size,
runtime.UnifiedStorageBufferAlignment(), window_index,
window_offset)) {
runtime.BindStorageBufferFromWindow(window_index, static_cast<u32>(window_offset),
size);
return;
}
}
SynchronizeBuffer(buffer, binding.device_addr, size);
const u32 offset = buffer.Offset(binding.device_addr);
buffer.MarkUsage(offset, size);
const bool is_written =
((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
if (is_written) {
MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size);
@@ -1517,6 +1617,20 @@ void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u3
uncommitted_gpu_modified_ranges.Add(device_addr, size);
}
template <class P>
void BufferCache<P>::MarkWrittenBufferInPlace(DAddr device_addr, u32 size) {
memory_tracker.MarkRegionAsGpuModified(device_addr, size);
gpu_modified_ranges.Add(device_addr, size);
uncommitted_gpu_modified_ranges.Add(device_addr, size);
in_place_gpu_written_ranges.Add(device_addr, size);
}
template <class P>
void BufferCache<P>::ForgetGpuModifiedRange(DAddr device_addr, u64 size) {
gpu_modified_ranges.Subtract(device_addr, size);
in_place_gpu_written_ranges.Subtract(device_addr, size);
}
template <class P>
BufferId BufferCache<P>::FindBuffer(DAddr device_addr, u32 size) {
if (device_addr == 0) {
@@ -1780,6 +1894,62 @@ void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer,
}
}
template <class P>
bool BufferCache<P>::ResolveUnifiedDirectBinding([[maybe_unused]] DAddr device_addr,
[[maybe_unused]] u64 size,
[[maybe_unused]] u64 alignment,
[[maybe_unused]] u64& window_index,
[[maybe_unused]] u64& window_offset) {
if constexpr (USE_UNIFIED_DIRECT_BINDING) {
if (size == 0 || !runtime.SupportsUnifiedDirectBinding()) {
return false;
}
const u64 window_size = runtime.UnifiedMemoryWindowSize();
if (window_size == 0) {
return false;
}
const u8* const physical_base = device_memory.GetPhysicalBase();
const u8* const host_ptr = device_memory.GetPointer<u8>(device_addr);
if (host_ptr == nullptr) {
return false;
}
const u64 phys_offset = static_cast<u64>(host_ptr - physical_base);
const u64 unified_base = runtime.UnifiedMemoryBase();
if (phys_offset < unified_base) {
return false;
}
const u64 relative = phys_offset - unified_base;
if (relative + size > runtime.UnifiedMemorySize()) {
return false;
}
const u64 local_offset = relative % window_size;
if (local_offset + size > window_size) {
return false;
}
if (alignment != 0 && (local_offset & (alignment - 1)) != 0) {
return false;
}
u64 contiguous = (std::min)(size, static_cast<u64>(Core::DEVICE_PAGESIZE) -
(device_addr & Core::DEVICE_PAGEMASK));
while (contiguous < size) {
const u8* const next = device_memory.GetPointer<u8>(device_addr + contiguous);
if (next == nullptr ||
static_cast<u64>(next - physical_base) != phys_offset + contiguous) {
return false;
}
contiguous += Core::DEVICE_PAGESIZE;
}
if (IsRegionGpuModified(device_addr, size)) {
return false;
}
window_index = relative / window_size;
window_offset = local_offset;
return true;
} else {
return false;
}
}
template <class P>
bool BufferCache<P>::ResolveUnifiedWindows(
[[maybe_unused]] DAddr device_addr, [[maybe_unused]] u64 buffer_offset,
@@ -1944,7 +2114,7 @@ template <class P>
void BufferCache<P>::InlineMemoryImplementation(DAddr dest_address, size_t copy_size,
std::span<const u8> inlined_buffer) {
ClearDownload(dest_address, copy_size);
gpu_modified_ranges.Subtract(dest_address, copy_size);
ForgetGpuModifiedRange(dest_address, copy_size);
BufferId buffer_id = FindBuffer(dest_address, static_cast<u32>(copy_size));
auto& buffer = slot_buffers[buffer_id];
@@ -1996,7 +2166,7 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64
gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, add_download);
ClearDownload(device_addr_out, range_size);
gpu_modified_ranges.Subtract(device_addr_out, range_size);
ForgetGpuModifiedRange(device_addr_out, range_size);
});
if (total_size_bytes == 0) {
return;
@@ -182,6 +182,8 @@ class BufferCache : public VideoCommon::ChannelSetupCaches<BufferCacheChannelInf
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = P::USE_MEMORY_MAPS_FOR_UPLOADS;
static constexpr bool USE_UNIFIED_MEMORY = P::USE_UNIFIED_MEMORY;
static constexpr bool USE_UNIFIED_UPLOADS = P::USE_UNIFIED_UPLOADS;
static constexpr bool USE_UNIFIED_DIRECT_BINDING = P::USE_UNIFIED_DIRECT_BINDING;
static constexpr u64 UNIFIED_VERTEX_BINDING_ALIGNMENT = 4;
#ifdef YUZU_LEGACY
static constexpr s64 TARGET_THRESHOLD = 3_GiB;
@@ -416,6 +418,10 @@ private:
void MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u32 size);
void MarkWrittenBufferInPlace(DAddr device_addr, u32 size);
void ForgetGpuModifiedRange(DAddr device_addr, u64 size);
[[nodiscard]] BufferId FindBuffer(DAddr device_addr, u32 size);
void WaitForGpuFenceIfNeeded(Buffer& buffer);
@@ -456,6 +462,9 @@ private:
boost::container::small_vector<u64, 4>& window_ids,
UnifiedWindowGroups& groups);
bool ResolveUnifiedDirectBinding(DAddr device_addr, u64 size, u64 alignment,
u64& window_index, u64& window_offset);
void DownloadBufferMemory(Buffer& buffer_id);
void DownloadBufferMemory(Buffer& buffer_id, DAddr device_addr, u64 size);
@@ -508,6 +517,7 @@ private:
MemoryTracker memory_tracker;
Common::RangeSet<DAddr> uncommitted_gpu_modified_ranges;
Common::RangeSet<DAddr> gpu_modified_ranges;
Common::RangeSet<DAddr> in_place_gpu_written_ranges;
std::deque<Common::RangeSet<DAddr>> committed_gpu_modified_ranges;
// Async Buffers
@@ -263,6 +263,7 @@ struct BufferCacheParams {
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = false;
static constexpr bool USE_UNIFIED_MEMORY = false;
static constexpr bool USE_UNIFIED_UPLOADS = false;
static constexpr bool USE_UNIFIED_DIRECT_BINDING = false;
};
using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
@@ -381,7 +381,46 @@ void BufferCacheRuntime::TryEnableUnifiedMemory(void* base, size_t size,
device, base, size, hardware_buffers, hardware_buffer_window, hardware_buffer_base);
if (!unified_memory->IsValid()) {
unified_memory.reset();
return;
}
AcquireUnifiedWindowsFromForeign();
}
void BufferCacheRuntime::AcquireUnifiedWindowsFromForeign() {
if (!unified_memory->NeedsForeignOwnershipTransfer()) {
return;
}
boost::container::small_vector<VkBuffer, 16> window_buffers;
const size_t window_count = unified_memory->GetWindowCount();
for (size_t i = 0; i < window_count; ++i) {
const VkBuffer buffer = unified_memory->GetWindowBuffer(i);
if (buffer != VK_NULL_HANDLE) {
window_buffers.push_back(buffer);
}
}
if (window_buffers.empty()) {
return;
}
const u32 queue_family = device.GetGraphicsFamily();
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([window_buffers = std::move(window_buffers),
queue_family](vk::CommandBuffer cmdbuf) {
for (const VkBuffer buffer : window_buffers) {
const VkBufferMemoryBarrier acquire{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
.dstQueueFamilyIndex = queue_family,
.buffer = buffer,
.offset = 0,
.size = VK_WHOLE_SIZE,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, acquire);
}
});
}
void BufferCacheRuntime::CopyToUnifiedMemory(
@@ -395,54 +434,12 @@ void BufferCacheRuntime::CopyToUnifiedMemory(
if (dst_buffer == VK_NULL_HANDLE) {
return;
}
VkDeviceSize covered_begin = std::numeric_limits<VkDeviceSize>::max();
VkDeviceSize covered_end = 0;
for (const VideoCommon::BufferCopy& copy : copies) {
covered_begin = (std::min)(covered_begin, static_cast<VkDeviceSize>(copy.dst_offset));
covered_end = (std::max)(covered_end,
static_cast<VkDeviceSize>(copy.dst_offset + copy.size));
}
boost::container::small_vector<VkBufferCopy, 8> vk_copies(copies.size());
std::ranges::transform(copies, vk_copies.begin(), MakeBufferCopy);
const bool foreign = unified_memory->NeedsForeignOwnershipTransfer();
const u32 queue_family = device.GetGraphicsFamily();
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([src_buffer, dst_buffer, vk_copies, foreign, queue_family, covered_begin,
covered_end](vk::CommandBuffer cmdbuf) {
if (foreign) {
const VkBufferMemoryBarrier acquire{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
.dstQueueFamilyIndex = queue_family,
.buffer = dst_buffer,
.offset = covered_begin,
.size = covered_end - covered_begin,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, acquire);
}
scheduler.Record([src_buffer, dst_buffer, vk_copies](vk::CommandBuffer cmdbuf) {
cmdbuf.CopyBuffer(src_buffer, dst_buffer, VideoCommon::FixSmallVectorADL(vk_copies));
if (foreign) {
const VkBufferMemoryBarrier release{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = 0,
.srcQueueFamilyIndex = queue_family,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
.buffer = dst_buffer,
.offset = covered_begin,
.size = covered_end - covered_begin,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, release);
}
});
}
@@ -457,54 +454,12 @@ void BufferCacheRuntime::CopyFromUnifiedMemory(
if (src_buffer == VK_NULL_HANDLE) {
return;
}
VkDeviceSize covered_begin = std::numeric_limits<VkDeviceSize>::max();
VkDeviceSize covered_end = 0;
for (const VideoCommon::BufferCopy& copy : copies) {
covered_begin = (std::min)(covered_begin, static_cast<VkDeviceSize>(copy.dst_offset));
covered_end = (std::max)(covered_end,
static_cast<VkDeviceSize>(copy.dst_offset + copy.size));
}
boost::container::small_vector<VkBufferCopy, 8> vk_copies(copies.size());
std::ranges::transform(copies, vk_copies.begin(), MakeUnifiedUploadCopy);
const bool foreign = unified_memory->NeedsForeignOwnershipTransfer();
const u32 queue_family = device.GetGraphicsFamily();
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([src_buffer, dst_buffer, vk_copies, foreign, queue_family, covered_begin,
covered_end](vk::CommandBuffer cmdbuf) {
if (foreign) {
const VkBufferMemoryBarrier acquire{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
.dstQueueFamilyIndex = queue_family,
.buffer = src_buffer,
.offset = covered_begin,
.size = covered_end - covered_begin,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, acquire);
}
scheduler.Record([src_buffer, dst_buffer, vk_copies](vk::CommandBuffer cmdbuf) {
cmdbuf.CopyBuffer(src_buffer, dst_buffer, VideoCommon::FixSmallVectorADL(vk_copies));
if (foreign) {
const VkBufferMemoryBarrier release{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.dstAccessMask = 0,
.srcQueueFamilyIndex = queue_family,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
.buffer = src_buffer,
.offset = covered_begin,
.size = covered_end - covered_begin,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, release);
}
});
}
@@ -536,6 +491,20 @@ void BufferCacheRuntime::UnifiedMemoryHostBarrier() {
});
}
void BufferCacheRuntime::UnifiedMemoryShaderWriteBarrier() {
static constexpr VkMemoryBarrier SHADER_WRITE_BARRIER{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_HOST_READ_BIT,
};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([](vk::CommandBuffer cmdbuf) {
cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, VK_PIPELINE_STAGE_HOST_BIT, 0,
SHADER_WRITE_BARRIER);
});
}
StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) {
return staging_pool.Request(size, MemoryUsage::Upload);
}
@@ -707,6 +676,15 @@ void BufferCacheRuntime::ClearBuffer(VkBuffer dest_buffer, u32 offset, size_t si
});
}
bool BufferCacheRuntime::CanBindIndexBufferDirectly(PrimitiveTopology topology,
IndexFormat index_format) const {
if (topology == PrimitiveTopology::Quads || topology == PrimitiveTopology::QuadStrip) {
return false;
}
return MaxwellToVK::IndexFormat(index_format) != VK_INDEX_TYPE_UINT8_EXT ||
device.IsExtIndexTypeUint8Supported();
}
void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format,
u32 base_vertex, u32 num_indices, VkBuffer buffer,
u32 offset, [[maybe_unused]] u32 size) {
@@ -119,6 +119,36 @@ public:
return unified_memory ? unified_memory->GetWindowSize() : 0;
}
[[nodiscard]] bool SupportsUnifiedDirectBinding() const noexcept {
return unified_memory != nullptr && unified_memory->SupportsDirectDescriptors();
}
[[nodiscard]] VkBuffer UnifiedWindowBuffer(size_t window_index) const noexcept {
if (!unified_memory || window_index >= unified_memory->GetWindowCount()) {
return VK_NULL_HANDLE;
}
return unified_memory->GetWindowBuffer(window_index);
}
[[nodiscard]] bool CanBindIndexBufferDirectly(PrimitiveTopology topology,
IndexFormat index_format) const;
[[nodiscard]] bool SupportsUnifiedDescriptorBinding() const noexcept {
return SupportsUnifiedDirectBinding() && !guest_descriptor_queue.UsesDescriptorBuffer();
}
[[nodiscard]] u64 UnifiedStorageBufferAlignment() const noexcept {
return device.GetStorageBufferAlignment();
}
[[nodiscard]] u64 UnifiedMaxStorageBufferRange() const noexcept {
return device.GetMaxStorageBufferRange();
}
void BindStorageBufferFromWindow(size_t window_index, u32 offset, u32 size) {
guest_descriptor_queue.AddBuffer(UnifiedWindowBuffer(window_index), offset, size);
}
void CopyToUnifiedMemory(size_t window_index, VkBuffer src_buffer,
std::span<const VideoCommon::BufferCopy> copies);
@@ -129,6 +159,8 @@ public:
void UnifiedMemoryUploadBarrier();
void UnifiedMemoryShaderWriteBarrier();
u64 CurrentTick();
u64 KnownGpuTick();
@@ -223,6 +255,8 @@ private:
VkFormat TexelBufferFormat(VideoCore::Surface::PixelFormat format) const;
void AcquireUnifiedWindowsFromForeign();
void ReserveNullBuffer();
vk::Buffer CreateNullBuffer();
@@ -261,6 +295,7 @@ struct BufferCacheParams {
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = true;
static constexpr bool USE_UNIFIED_MEMORY = true;
static constexpr bool USE_UNIFIED_UPLOADS = true;
static constexpr bool USE_UNIFIED_DIRECT_BINDING = true;
};
using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
@@ -53,6 +53,25 @@ namespace Vulkan {
return type_index;
}
constexpr VkBufferUsageFlags ImportedTransferUsage =
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
[[nodiscard]] VkBufferUsageFlags ImportedDescriptorUsage(const Device &device) {
VkBufferUsageFlags flags =
ImportedTransferUsage | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT |
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
if (device.IsExtTransformFeedbackSupported()) {
flags |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
}
if (device.IsExtConditionalRendering()) {
flags |= VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT;
}
return flags;
}
// Helpers translating MemoryUsage to flags/usage
[[maybe_unused]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) {
@@ -245,6 +264,50 @@ namespace Vulkan {
LOG_INFO(Render_Vulkan, "Unified memory disabled, no host memory import path");
}
bool HostMemoryImport::TryCreateWindowBuffer(const void *external_info, VkDeviceSize length,
u32 external_type_bits, VkDeviceSize capacity,
VkBufferUsageFlags usage, VkBuffer *out_buffer,
u32 *out_type_mask) const {
const VkBufferCreateInfo buffer_ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = external_info,
.flags = 0,
.size = length,
.usage = usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
const auto &logical = device.GetLogical();
VkBuffer new_buffer{};
if (logical.CreateBufferRaw(buffer_ci, &new_buffer) != VK_SUCCESS) {
return false;
}
const VkMemoryRequirements requirements = logical.GetBufferMemoryRequirements(new_buffer);
const u32 type_mask = requirements.memoryTypeBits & external_type_bits;
if (type_mask == 0 || requirements.size > capacity) {
logical.DestroyBufferRaw(new_buffer);
return false;
}
*out_buffer = new_buffer;
*out_type_mask = type_mask;
return true;
}
bool HostMemoryImport::CreateDescriptorCapableWindowBuffer(
const void *external_info, VkDeviceSize length, u32 external_type_bits,
VkDeviceSize capacity, VkBuffer *out_buffer, u32 *out_type_mask,
bool *out_descriptor_capable) const {
if (TryCreateWindowBuffer(external_info, length, external_type_bits, capacity,
ImportedDescriptorUsage(device), out_buffer, out_type_mask)) {
*out_descriptor_capable = true;
return true;
}
*out_descriptor_capable = false;
return TryCreateWindowBuffer(external_info, length, external_type_bits, capacity,
ImportedTransferUsage, out_buffer, out_type_mask);
}
bool HostMemoryImport::ImportHostPointer(void *base, size_t size) {
if (!device.IsExtExternalMemoryHostSupported()) {
return false;
@@ -299,7 +362,7 @@ namespace Vulkan {
.pNext = &external_info,
.flags = 0,
.size = window_len,
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.usage = ImportedTransferUsage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
@@ -379,6 +442,7 @@ namespace Vulkan {
const auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
window_size = hardware_buffer_window;
base_offset = hardware_buffer_base;
bool every_window_descriptor_capable = true;
for (size_t i = 0; i < hardware_buffers.size(); ++i) {
const size_t offset = hardware_buffer_base + i * hardware_buffer_window;
if (offset >= size) {
@@ -404,25 +468,13 @@ namespace Vulkan {
.handleTypes =
VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
};
const VkBufferCreateInfo buffer_ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = &external_info,
.flags = 0,
.size = window_len,
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
VkBuffer new_buffer{};
if (logical.CreateBufferRaw(buffer_ci, &new_buffer) != VK_SUCCESS) {
break;
}
const VkMemoryRequirements requirements =
logical.GetBufferMemoryRequirements(new_buffer);
const u32 type_mask = requirements.memoryTypeBits & ahb_props.memoryTypeBits;
if (type_mask == 0 || requirements.size > ahb_props.allocationSize) {
logical.DestroyBufferRaw(new_buffer);
u32 type_mask = 0;
bool descriptor_capable = false;
if (!CreateDescriptorCapableWindowBuffer(&external_info, window_len,
ahb_props.memoryTypeBits,
ahb_props.allocationSize, &new_buffer,
&type_mask, &descriptor_capable)) {
break;
}
const auto type_index = FindImportMemoryType(memory_props, type_mask);
@@ -460,8 +512,10 @@ namespace Vulkan {
.memory = std::move(memory),
.buffer = new_buffer,
});
every_window_descriptor_capable &= descriptor_capable;
imported_size += static_cast<size_t>(window_len);
}
direct_descriptors = !windows.empty() && every_window_descriptor_capable;
if (windows.empty()) {
window_size = 0;
base_offset = 0;
@@ -126,6 +126,10 @@ namespace Vulkan {
return windows.size();
}
[[nodiscard]] bool SupportsDirectDescriptors() const noexcept {
return direct_descriptors;
}
private:
struct Window {
vk::DeviceMemory memory;
@@ -138,12 +142,23 @@ namespace Vulkan {
size_t hardware_buffer_window, size_t hardware_buffer_base,
size_t size);
bool TryCreateWindowBuffer(const void *external_info, VkDeviceSize length,
u32 external_type_bits, VkDeviceSize capacity,
VkBufferUsageFlags usage, VkBuffer *out_buffer,
u32 *out_type_mask) const;
bool CreateDescriptorCapableWindowBuffer(const void *external_info, VkDeviceSize length,
u32 external_type_bits, VkDeviceSize capacity,
VkBuffer *out_buffer, u32 *out_type_mask,
bool *out_descriptor_capable) const;
const Device &device;
std::vector<Window> windows;
VkDeviceSize window_size{};
size_t imported_size{};
size_t base_offset{};
bool foreign_ownership{};
bool direct_descriptors{};
};
/// Memory allocator container.