mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-10-24 14:00:52 +02:00
[memory, vulkan] Initial implementation for Unified Memory
This commit is contained in:
parent
1575f55abe
commit
42636ba127
28 changed files with 1427 additions and 47 deletions
|
|
@ -578,7 +578,11 @@ void BufferCache<P>::AccumulateFlushes() {
|
|||
|
||||
template <class P>
|
||||
bool BufferCache<P>::ShouldWaitAsyncFlushes() const noexcept {
|
||||
return (!async_buffers.empty() && async_buffers.front().has_value());
|
||||
if (async_buffers.empty()) {
|
||||
return false;
|
||||
}
|
||||
return async_buffers.front().has_value() ||
|
||||
!pending_downloads.front().unified_copies.empty();
|
||||
}
|
||||
|
||||
template <class P>
|
||||
|
|
@ -586,6 +590,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
|
|||
AccumulateFlushes();
|
||||
|
||||
if (committed_gpu_modified_ranges.empty()) {
|
||||
pending_downloads.emplace_back();
|
||||
async_buffers.emplace_back(std::optional<Async_Buffer>{});
|
||||
return;
|
||||
}
|
||||
|
|
@ -645,27 +650,83 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
|
|||
}
|
||||
committed_gpu_modified_ranges.clear();
|
||||
if (downloads.empty()) {
|
||||
pending_downloads.emplace_back();
|
||||
async_buffers.emplace_back(std::optional<Async_Buffer>{});
|
||||
return;
|
||||
}
|
||||
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes, true);
|
||||
boost::container::small_vector<BufferCopy, 4> normalized_copies;
|
||||
runtime.PreCopyBarrier();
|
||||
|
||||
struct QueuedUnifiedCopy {
|
||||
u64 window;
|
||||
BufferId buffer_id;
|
||||
boost::container::small_vector<BufferCopy, 16> copies;
|
||||
};
|
||||
|
||||
AsyncDownloadBatch batch;
|
||||
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> staging_downloads;
|
||||
boost::container::small_vector<QueuedUnifiedCopy, 4> unified_copy_queue;
|
||||
boost::container::small_vector<u64, 4> window_ids;
|
||||
UnifiedWindowGroups groups;
|
||||
u64 staging_size_bytes = 0;
|
||||
for (auto& [copy, buffer_id] : downloads) {
|
||||
copy.dst_offset += download_staging.offset;
|
||||
const std::array copies{copy};
|
||||
BufferCopy second_copy{copy};
|
||||
Buffer& buffer = slot_buffers[buffer_id];
|
||||
second_copy.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
|
||||
const DAddr orig_device_addr = static_cast<DAddr>(second_copy.src_offset);
|
||||
const DAddr orig_device_addr = buffer.CpuAddr() + copy.src_offset;
|
||||
bool unified = false;
|
||||
if constexpr (USE_UNIFIED_MEMORY) {
|
||||
if (runtime.HasUnifiedMemory()) {
|
||||
window_ids.clear();
|
||||
groups.clear();
|
||||
unified = ResolveUnifiedWindows(orig_device_addr, copy.src_offset, copy.size,
|
||||
window_ids, groups);
|
||||
}
|
||||
}
|
||||
BufferCopy record{copy};
|
||||
record.src_offset = static_cast<size_t>(orig_device_addr);
|
||||
if (unified) {
|
||||
async_downloads.Add(orig_device_addr, copy.size);
|
||||
buffer.MarkUsage(copy.src_offset, copy.size);
|
||||
for (size_t i = 0; i < window_ids.size(); ++i) {
|
||||
unified_copy_queue.push_back(
|
||||
QueuedUnifiedCopy{window_ids[i], buffer_id, std::move(groups[i])});
|
||||
}
|
||||
batch.unified_copies.push_back(record);
|
||||
continue;
|
||||
}
|
||||
copy.dst_offset = staging_size_bytes;
|
||||
constexpr u64 align = 64ULL;
|
||||
staging_size_bytes += (copy.size + align - 1) & ~(align - 1ULL);
|
||||
staging_downloads.push_back({copy, buffer_id});
|
||||
}
|
||||
|
||||
std::optional<Async_Buffer> download_staging;
|
||||
if (!staging_downloads.empty()) {
|
||||
download_staging = runtime.DownloadStagingBuffer(staging_size_bytes, true);
|
||||
}
|
||||
runtime.PreCopyBarrier();
|
||||
for (auto& [copy, buffer_id] : staging_downloads) {
|
||||
copy.dst_offset += download_staging->offset;
|
||||
const std::array copies{copy};
|
||||
Buffer& buffer = slot_buffers[buffer_id];
|
||||
BufferCopy record{copy};
|
||||
record.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
|
||||
const DAddr orig_device_addr = static_cast<DAddr>(record.src_offset);
|
||||
async_downloads.Add(orig_device_addr, copy.size);
|
||||
buffer.MarkUsage(copy.src_offset, copy.size);
|
||||
runtime.CopyBuffer(download_staging.buffer, buffer, copies, false);
|
||||
normalized_copies.push_back(second_copy);
|
||||
runtime.CopyBuffer(download_staging->buffer, buffer, copies, false);
|
||||
batch.staging_copies.push_back(record);
|
||||
}
|
||||
if constexpr (USE_UNIFIED_MEMORY) {
|
||||
for (const auto& queued : unified_copy_queue) {
|
||||
const std::span<const BufferCopy> group_span(queued.copies.data(),
|
||||
queued.copies.size());
|
||||
runtime.CopyToUnifiedMemory(queued.window, slot_buffers[queued.buffer_id], group_span);
|
||||
}
|
||||
if (!unified_copy_queue.empty()) {
|
||||
runtime.UnifiedMemoryHostBarrier();
|
||||
}
|
||||
}
|
||||
runtime.PostCopyBarrier();
|
||||
pending_downloads.emplace_back(std::move(normalized_copies));
|
||||
async_buffers.emplace_back(download_staging);
|
||||
pending_downloads.emplace_back(std::move(batch));
|
||||
async_buffers.emplace_back(std::move(download_staging));
|
||||
}
|
||||
|
||||
template <class P>
|
||||
|
|
@ -680,32 +741,49 @@ void BufferCache<P>::PopAsyncFlushes() {
|
|||
|
||||
template <class P>
|
||||
void BufferCache<P>::PopAsyncBuffers() {
|
||||
struct Writeback {
|
||||
DAddr addr;
|
||||
const u8* src;
|
||||
u64 size;
|
||||
};
|
||||
boost::container::small_vector<Writeback, 8> writebacks;
|
||||
{
|
||||
std::scoped_lock lock{mutex};
|
||||
if (async_buffers.empty()) {
|
||||
return;
|
||||
}
|
||||
if (!async_buffers.front().has_value()) {
|
||||
async_buffers.pop_front();
|
||||
return;
|
||||
}
|
||||
auto& downloads = pending_downloads.front();
|
||||
auto& batch = pending_downloads.front();
|
||||
auto& async_buffer = async_buffers.front();
|
||||
u8* base = async_buffer->mapped_span.data();
|
||||
if (async_buffer.has_value()) {
|
||||
const u8* base = async_buffer->mapped_span.data();
|
||||
const size_t base_offset = async_buffer->offset;
|
||||
for (const auto& copy : downloads) {
|
||||
for (const auto& copy : batch.staging_copies) {
|
||||
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
|
||||
const u64 dst_offset = copy.dst_offset - base_offset;
|
||||
const u8* read_mapped_memory = base + dst_offset;
|
||||
async_downloads.ForEachInRange(device_addr, copy.size, [&](DAddr start, DAddr end, s32) {
|
||||
device_memory.WriteBlockUnsafe(start, &read_mapped_memory[start - device_addr],
|
||||
end - start);
|
||||
async_downloads.ForEachInRange(
|
||||
device_addr, copy.size, [&](DAddr start, DAddr end, s32) {
|
||||
writebacks.push_back(
|
||||
{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);
|
||||
});
|
||||
}
|
||||
async_buffers_death_ring.emplace_back(*async_buffer);
|
||||
}
|
||||
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);
|
||||
});
|
||||
}
|
||||
async_buffers.pop_front();
|
||||
pending_downloads.pop_front();
|
||||
}
|
||||
for (const auto& wb : writebacks) {
|
||||
device_memory.WriteBlockUnsafe(wb.addr, wb.src, wb.size);
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
|
|
@ -1799,6 +1877,98 @@ void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer,
|
|||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
bool BufferCache<P>::ResolveUnifiedWindows(
|
||||
[[maybe_unused]] DAddr device_addr, [[maybe_unused]] u64 buffer_offset,
|
||||
[[maybe_unused]] u64 size, [[maybe_unused]] boost::container::small_vector<u64, 4>& window_ids,
|
||||
[[maybe_unused]] UnifiedWindowGroups& groups) {
|
||||
if constexpr (USE_UNIFIED_MEMORY) {
|
||||
const u8* const physical_base = device_memory.GetPhysicalBase();
|
||||
const u64 unified_base = runtime.UnifiedMemoryBase();
|
||||
const u64 unified_size = runtime.UnifiedMemorySize();
|
||||
const u64 window_size = runtime.UnifiedMemoryWindowSize();
|
||||
if (window_size == 0) {
|
||||
return false;
|
||||
}
|
||||
const auto group_for = [&](u64 window) -> boost::container::small_vector<BufferCopy, 16>& {
|
||||
for (size_t i = 0; i < window_ids.size(); ++i) {
|
||||
if (window_ids[i] == window) {
|
||||
return groups[i];
|
||||
}
|
||||
}
|
||||
window_ids.push_back(window);
|
||||
groups.emplace_back();
|
||||
return groups.back();
|
||||
};
|
||||
u64 downloaded = 0;
|
||||
while (downloaded < size) {
|
||||
const DAddr page_addr = device_addr + downloaded;
|
||||
const u8* const ptr = device_memory.GetPointer<u8>(page_addr);
|
||||
if (ptr == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const u64 page_offset = page_addr & Core::DEVICE_PAGEMASK;
|
||||
u64 chunk = (std::min)(size - downloaded,
|
||||
static_cast<u64>(Core::DEVICE_PAGESIZE) - page_offset);
|
||||
const u64 phys_offset = static_cast<u64>(ptr - physical_base);
|
||||
if (phys_offset < unified_base || phys_offset - unified_base + chunk > unified_size) {
|
||||
return false;
|
||||
}
|
||||
const u64 relative = phys_offset - unified_base;
|
||||
const u64 window = relative / window_size;
|
||||
const u64 local_offset = relative % window_size;
|
||||
chunk = (std::min)(chunk, window_size - local_offset);
|
||||
auto& group = group_for(window);
|
||||
if (!group.empty()) {
|
||||
BufferCopy& last = group.back();
|
||||
if (last.src_offset + last.size == buffer_offset + downloaded &&
|
||||
last.dst_offset + last.size == local_offset) {
|
||||
last.size += chunk;
|
||||
downloaded += chunk;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
group.push_back(BufferCopy{
|
||||
.src_offset = buffer_offset + downloaded,
|
||||
.dst_offset = local_offset,
|
||||
.size = chunk,
|
||||
});
|
||||
downloaded += chunk;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
bool BufferCache<P>::TryUnifiedDownloadMemory([[maybe_unused]] Buffer& buffer,
|
||||
[[maybe_unused]] std::span<BufferCopy> copies) {
|
||||
if constexpr (USE_UNIFIED_MEMORY) {
|
||||
boost::container::small_vector<u64, 4> window_ids;
|
||||
UnifiedWindowGroups groups;
|
||||
for (const BufferCopy& copy : copies) {
|
||||
if (!ResolveUnifiedWindows(buffer.CpuAddr() + copy.src_offset, copy.src_offset,
|
||||
copy.size, window_ids, groups)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const BufferCopy& copy : copies) {
|
||||
buffer.MarkUsage(copy.src_offset, copy.size);
|
||||
}
|
||||
runtime.PreCopyBarrier();
|
||||
for (size_t i = 0; i < window_ids.size(); ++i) {
|
||||
const std::span<const BufferCopy> group_span(groups[i].data(), groups[i].size());
|
||||
runtime.CopyToUnifiedMemory(window_ids[i], buffer, group_span);
|
||||
}
|
||||
runtime.UnifiedMemoryHostBarrier();
|
||||
runtime.Finish();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <class P>
|
||||
void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
|
||||
[[maybe_unused]] u64 total_size_bytes,
|
||||
|
|
@ -1902,6 +2072,12 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64
|
|||
}
|
||||
|
||||
if constexpr (USE_MEMORY_MAPS) {
|
||||
if constexpr (USE_UNIFIED_MEMORY) {
|
||||
if (runtime.HasUnifiedMemory() &&
|
||||
TryUnifiedDownloadMemory(buffer, std::span(copies.data(), copies.size()))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
|
||||
const u8* const mapped_memory = download_staging.mapped_span.data();
|
||||
const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size());
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@ class BufferCache : public VideoCommon::ChannelSetupCaches<BufferCacheChannelInf
|
|||
static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS;
|
||||
static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS;
|
||||
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = P::USE_MEMORY_MAPS_FOR_UPLOADS;
|
||||
static constexpr bool USE_UNIFIED_MEMORY = P::USE_UNIFIED_MEMORY;
|
||||
|
||||
#ifdef YUZU_LEGACY
|
||||
static constexpr s64 TARGET_THRESHOLD = 3_GiB;
|
||||
|
|
@ -462,6 +463,15 @@ private:
|
|||
|
||||
void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies);
|
||||
|
||||
bool TryUnifiedDownloadMemory(Buffer& buffer, std::span<BufferCopy> copies);
|
||||
|
||||
using UnifiedWindowGroups =
|
||||
boost::container::small_vector<boost::container::small_vector<BufferCopy, 16>, 4>;
|
||||
|
||||
bool ResolveUnifiedWindows(DAddr device_addr, u64 buffer_offset, u64 size,
|
||||
boost::container::small_vector<u64, 4>& window_ids,
|
||||
UnifiedWindowGroups& groups);
|
||||
|
||||
void DownloadBufferMemory(Buffer& buffer_id);
|
||||
|
||||
void DownloadBufferMemory(Buffer& buffer_id, DAddr device_addr, u64 size);
|
||||
|
|
@ -517,9 +527,14 @@ private:
|
|||
std::deque<Common::RangeSet<DAddr>> committed_gpu_modified_ranges;
|
||||
|
||||
// Async Buffers
|
||||
struct AsyncDownloadBatch {
|
||||
boost::container::small_vector<BufferCopy, 4> staging_copies;
|
||||
boost::container::small_vector<BufferCopy, 4> unified_copies;
|
||||
};
|
||||
|
||||
Common::OverlapRangeSet<DAddr> async_downloads;
|
||||
std::deque<std::optional<Async_Buffer>> async_buffers;
|
||||
std::deque<boost::container::small_vector<BufferCopy, 4>> pending_downloads;
|
||||
std::deque<AsyncDownloadBatch> pending_downloads;
|
||||
std::optional<Async_Buffer> current_buffer;
|
||||
|
||||
std::deque<Async_Buffer> async_buffers_death_ring;
|
||||
|
|
|
|||
|
|
@ -262,6 +262,7 @@ struct BufferCacheParams {
|
|||
|
||||
// TODO: Investigate why OpenGL seems to perform worse with persistently mapped buffer uploads
|
||||
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = false;
|
||||
static constexpr bool USE_UNIFIED_MEMORY = false;
|
||||
};
|
||||
|
||||
using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
|
||||
|
|
|
|||
|
|
@ -374,6 +374,93 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
|
|||
scheduler_, staging_pool_);
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::TryEnableUnifiedMemory(void* base, size_t size,
|
||||
std::span<AHardwareBuffer* const> hardware_buffers,
|
||||
size_t hardware_buffer_window,
|
||||
size_t hardware_buffer_base) {
|
||||
unified_memory = std::make_unique<HostMemoryImport>(
|
||||
device, base, size, hardware_buffers, hardware_buffer_window, hardware_buffer_base);
|
||||
if (!unified_memory->IsValid()) {
|
||||
unified_memory.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::CopyToUnifiedMemory(
|
||||
size_t window_index, VkBuffer src_buffer,
|
||||
std::span<const VideoCommon::BufferCopy> copies) {
|
||||
if (!unified_memory || src_buffer == VK_NULL_HANDLE || copies.empty() ||
|
||||
window_index >= unified_memory->GetWindowCount()) {
|
||||
return;
|
||||
}
|
||||
const VkBuffer dst_buffer = unified_memory->GetWindowBuffer(window_index);
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void BufferCacheRuntime::UnifiedMemoryHostBarrier() {
|
||||
static constexpr VkMemoryBarrier HOST_BARRIER{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_HOST_READ_BIT,
|
||||
};
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0,
|
||||
HOST_BARRIER);
|
||||
});
|
||||
}
|
||||
|
||||
StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) {
|
||||
return staging_pool.Request(size, MemoryUsage::Upload);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
|
|
@ -110,6 +112,31 @@ public:
|
|||
|
||||
void TickFrame(Common::SlotVector<Buffer>& slot_buffers) noexcept;
|
||||
|
||||
void TryEnableUnifiedMemory(void* base, size_t size,
|
||||
std::span<AHardwareBuffer* const> hardware_buffers,
|
||||
size_t hardware_buffer_window, size_t hardware_buffer_base);
|
||||
|
||||
[[nodiscard]] bool HasUnifiedMemory() const noexcept {
|
||||
return unified_memory != nullptr && unified_memory->IsValid();
|
||||
}
|
||||
|
||||
[[nodiscard]] u64 UnifiedMemorySize() const noexcept {
|
||||
return unified_memory ? unified_memory->GetSize() : 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] u64 UnifiedMemoryBase() const noexcept {
|
||||
return unified_memory ? unified_memory->GetBaseOffset() : 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] u64 UnifiedMemoryWindowSize() const noexcept {
|
||||
return unified_memory ? unified_memory->GetWindowSize() : 0;
|
||||
}
|
||||
|
||||
void CopyToUnifiedMemory(size_t window_index, VkBuffer src_buffer,
|
||||
std::span<const VideoCommon::BufferCopy> copies);
|
||||
|
||||
void UnifiedMemoryHostBarrier();
|
||||
|
||||
u64 CurrentTick();
|
||||
|
||||
u64 KnownGpuTick();
|
||||
|
|
@ -257,6 +284,7 @@ private:
|
|||
std::shared_ptr<QuadStripIndexBuffer> quad_strip_index_buffer;
|
||||
|
||||
vk::Buffer null_buffer;
|
||||
std::unique_ptr<HostMemoryImport> unified_memory;
|
||||
|
||||
std::unique_ptr<Uint8Pass> uint8_pass;
|
||||
QuadIndexedPass quad_index_pass;
|
||||
|
|
@ -283,6 +311,7 @@ struct BufferCacheParams {
|
|||
static constexpr bool USE_MEMORY_MAPS = true;
|
||||
static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = false;
|
||||
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = true;
|
||||
static constexpr bool USE_UNIFIED_MEMORY = true;
|
||||
};
|
||||
|
||||
using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
|
||||
|
|
|
|||
|
|
@ -225,6 +225,13 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
|
|||
fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler),
|
||||
wfi_event(device.GetLogical().CreateEvent()) {
|
||||
scheduler.SetQueryCache(query_cache);
|
||||
if (Settings::values.use_unified_memory.GetValue() && device_memory.IsBackingShared()) {
|
||||
buffer_cache_runtime.TryEnableUnifiedMemory(
|
||||
device_memory.GetPhysicalBase(), device_memory.GetPhysicalSize(),
|
||||
device_memory.GetBackingHardwareBuffers(),
|
||||
device_memory.GetBackingHardwareBufferWindowSize(),
|
||||
device_memory.GetBackingHardwareBufferBase());
|
||||
}
|
||||
}
|
||||
|
||||
RasterizerVulkan::~RasterizerVulkan() {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include "common/assert.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/host_memory.h"
|
||||
#include "common/literals.h"
|
||||
#include <ranges>
|
||||
#include "common/settings.h"
|
||||
|
|
@ -1045,6 +1046,7 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
|||
|
||||
FOR_EACH_VK_FEATURE_EXT(FEATURE_EXTENSION);
|
||||
FOR_EACH_VK_EXTENSION(EXTENSION);
|
||||
FOR_EACH_VK_PLATFORM_EXTENSION(EXTENSION);
|
||||
|
||||
extensions.depth_stencil_resolve =
|
||||
extensions.depth_stencil_resolve &&
|
||||
|
|
@ -1063,6 +1065,17 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
|||
extensions.robustness_2 = false;
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
if (extensions.external_memory_ahb && !extensions.queue_family_foreign) {
|
||||
LOG_INFO(Render_Vulkan,
|
||||
"Not loading {} because its dependency {} is unavailable",
|
||||
VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
|
||||
VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME);
|
||||
loaded_extensions.erase(VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME);
|
||||
extensions.external_memory_ahb = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef FEATURE_EXTENSION
|
||||
#undef EXTENSION
|
||||
|
||||
|
|
@ -1229,6 +1242,21 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
|||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_PROPERTIES_EXT;
|
||||
SetNext(next, properties.custom_border_color);
|
||||
}
|
||||
if (extensions.external_memory_host) {
|
||||
properties.external_memory_host.sType =
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT;
|
||||
SetNext(next, properties.external_memory_host);
|
||||
}
|
||||
if (extensions.maintenance3 || instance_version >= VK_API_VERSION_1_1) {
|
||||
properties.maintenance3.sType =
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES;
|
||||
SetNext(next, properties.maintenance3);
|
||||
}
|
||||
if (extensions.maintenance4 || features.maintenance4.maintenance4) {
|
||||
properties.maintenance4.sType =
|
||||
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_4_PROPERTIES;
|
||||
SetNext(next, properties.maintenance4);
|
||||
}
|
||||
|
||||
// Perform the property fetch.
|
||||
physical.GetProperties2(properties2);
|
||||
|
|
@ -1623,12 +1651,27 @@ void Device::CollectPhysicalMemoryInfo() {
|
|||
device_access_memory = 0;
|
||||
u64 device_initial_usage = 0;
|
||||
u64 local_memory = 0;
|
||||
const auto heap_has_usable_type = [&mem_properties](size_t heap) {
|
||||
for (u32 index = 0; index < mem_properties.memoryTypeCount; ++index) {
|
||||
if (mem_properties.memoryTypes[index].heapIndex != heap) {
|
||||
continue;
|
||||
}
|
||||
if ((mem_properties.memoryTypes[index].propertyFlags &
|
||||
VK_MEMORY_PROPERTY_PROTECTED_BIT) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
for (size_t element = 0; element < num_properties; ++element) {
|
||||
const bool is_heap_local =
|
||||
(mem_properties.memoryHeaps[element].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0;
|
||||
if (!is_integrated && !is_heap_local) {
|
||||
continue;
|
||||
}
|
||||
if (!heap_has_usable_type(element)) {
|
||||
continue;
|
||||
}
|
||||
valid_heap_memory.push_back(element);
|
||||
if (is_heap_local) {
|
||||
local_memory += mem_properties.memoryHeaps[element].size;
|
||||
|
|
@ -1640,6 +1683,13 @@ void Device::CollectPhysicalMemoryInfo() {
|
|||
}
|
||||
device_access_memory += mem_properties.memoryHeaps[element].size;
|
||||
}
|
||||
const u64 committed_backing = Common::GetCommittedBackingSize();
|
||||
if (committed_backing != 0) {
|
||||
LOG_INFO(Render_Vulkan, "Discounting {} MiB of guest memory committed by the host",
|
||||
committed_backing >> 20);
|
||||
local_memory -= std::min(local_memory, committed_backing);
|
||||
device_access_memory -= std::min(device_access_memory, committed_backing);
|
||||
}
|
||||
if (is_integrated) {
|
||||
const s64 available_memory = static_cast<s64>(device_access_memory - device_initial_usage);
|
||||
const u64 memory_size = Settings::values.vram_usage_mode.GetValue() == Settings::VramUsageMode::Aggressive ? 6_GiB : 4_GiB;
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
|||
EXTENSION(EXT, CONDITIONAL_RENDERING, conditional_rendering) \
|
||||
EXTENSION(EXT, CONSERVATIVE_RASTERIZATION, conservative_rasterization) \
|
||||
EXTENSION(EXT, DEPTH_RANGE_UNRESTRICTED, depth_range_unrestricted) \
|
||||
EXTENSION(EXT, EXTERNAL_MEMORY_HOST, external_memory_host) \
|
||||
EXTENSION(EXT, MEMORY_BUDGET, memory_budget) \
|
||||
EXTENSION(EXT, ROBUSTNESS_2, robustness_2) \
|
||||
EXTENSION(EXT, SAMPLER_FILTER_MINMAX, sampler_filter_minmax) \
|
||||
|
|
@ -117,6 +118,14 @@ VK_DEFINE_HANDLE(VmaAllocator)
|
|||
EXTENSION(IMG, FILTER_CUBIC, filter_cubic_img) \
|
||||
EXTENSION(QCOM, FILTER_CUBIC_WEIGHTS, filter_cubic_weights)
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#define FOR_EACH_VK_PLATFORM_EXTENSION(EXTENSION) \
|
||||
EXTENSION(EXT, QUEUE_FAMILY_FOREIGN, queue_family_foreign) \
|
||||
EXTENSION(ANDROID, EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER, external_memory_ahb)
|
||||
#else
|
||||
#define FOR_EACH_VK_PLATFORM_EXTENSION(EXTENSION)
|
||||
#endif
|
||||
|
||||
// Define extensions which must be supported.
|
||||
#define FOR_EACH_VK_MANDATORY_EXTENSION(EXTENSION_NAME) \
|
||||
EXTENSION_NAME(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME) \
|
||||
|
|
@ -891,6 +900,30 @@ FN_MAX_LIMIT_LIST
|
|||
return extensions.conditional_rendering;
|
||||
}
|
||||
|
||||
bool IsExtExternalMemoryHostSupported() const {
|
||||
return extensions.external_memory_host;
|
||||
}
|
||||
|
||||
bool IsExtExternalMemoryAhbSupported() const {
|
||||
#ifdef __ANDROID__
|
||||
return extensions.external_memory_ahb && extensions.queue_family_foreign;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
u64 GetMinImportedHostPointerAlignment() const {
|
||||
return properties.external_memory_host.minImportedHostPointerAlignment;
|
||||
}
|
||||
|
||||
u64 GetMaxBufferSize() const {
|
||||
return properties.maintenance4.maxBufferSize;
|
||||
}
|
||||
|
||||
u64 GetMaxMemoryAllocationSize() const {
|
||||
return properties.maintenance3.maxMemoryAllocationSize;
|
||||
}
|
||||
|
||||
bool IsExtAstcDecodeModeSupported() const {
|
||||
return extensions.astc_decode_mode;
|
||||
}
|
||||
|
|
@ -1163,6 +1196,7 @@ private:
|
|||
FOR_EACH_VK_FEATURE_1_4(FEATURE);
|
||||
FOR_EACH_VK_FEATURE_EXT(FEATURE);
|
||||
FOR_EACH_VK_EXTENSION(EXTENSION);
|
||||
FOR_EACH_VK_PLATFORM_EXTENSION(EXTENSION);
|
||||
|
||||
#undef EXTENSION
|
||||
#undef FEATURE
|
||||
|
|
@ -1194,9 +1228,12 @@ private:
|
|||
VkPhysicalDeviceDescriptorBufferPropertiesEXT descriptor_buffer{};
|
||||
VkPhysicalDeviceSubgroupSizeControlProperties subgroup_size_control{};
|
||||
VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{};
|
||||
VkPhysicalDeviceMaintenance3Properties maintenance3{};
|
||||
VkPhysicalDeviceMaintenance4Properties maintenance4{};
|
||||
VkPhysicalDeviceMaintenance5PropertiesKHR maintenance5{};
|
||||
VkPhysicalDeviceDepthStencilResolveProperties depth_stencil_resolve{};
|
||||
VkPhysicalDeviceCustomBorderColorPropertiesEXT custom_border_color{};
|
||||
VkPhysicalDeviceExternalMemoryHostPropertiesEXT external_memory_host{};
|
||||
|
||||
VkPhysicalDeviceProperties properties{};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,9 +25,34 @@
|
|||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/hardware_buffer.h>
|
||||
#endif
|
||||
|
||||
namespace Vulkan {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] std::optional<u32> FindImportMemoryType(
|
||||
const VkPhysicalDeviceMemoryProperties &props, u32 type_mask) {
|
||||
const auto find = [&](VkMemoryPropertyFlags wanted) -> std::optional<u32> {
|
||||
for (u32 i = 0; i < props.memoryTypeCount; ++i) {
|
||||
if (((type_mask >> i) & 1u) != 0 &&
|
||||
(props.memoryTypes[i].propertyFlags & wanted) == wanted) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
};
|
||||
auto type_index = find(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
||||
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
|
||||
VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
|
||||
if (!type_index) {
|
||||
type_index = find(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
||||
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||
}
|
||||
return type_index;
|
||||
}
|
||||
|
||||
// Helpers translating MemoryUsage to flags/usage
|
||||
|
||||
[[maybe_unused]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) {
|
||||
|
|
@ -181,6 +206,291 @@ void MemoryCommit::Release() {
|
|||
size = 0;
|
||||
}
|
||||
|
||||
HostMemoryImport::HostMemoryImport(const Device &device_, void *base, size_t size,
|
||||
std::span<AHardwareBuffer *const> hardware_buffers,
|
||||
size_t hardware_buffer_window, size_t hardware_buffer_base)
|
||||
: device{device_} {
|
||||
if (ImportHardwareBuffers(hardware_buffers, hardware_buffer_window, hardware_buffer_base,
|
||||
size)) {
|
||||
return;
|
||||
}
|
||||
if (device.IsTiler()) {
|
||||
LOG_INFO(Render_Vulkan,
|
||||
"Unified memory disabled, hardware buffer import is the only path supported "
|
||||
"by tiler drivers");
|
||||
return;
|
||||
}
|
||||
if (ImportHostPointer(base, size)) {
|
||||
return;
|
||||
}
|
||||
LOG_INFO(Render_Vulkan, "Unified memory disabled, no host memory import path");
|
||||
}
|
||||
|
||||
bool HostMemoryImport::ImportHostPointer(void *base, size_t size) {
|
||||
if (!device.IsExtExternalMemoryHostSupported()) {
|
||||
LOG_INFO(Render_Vulkan,
|
||||
"Unified memory disabled, VK_EXT_external_memory_host is not supported");
|
||||
return false;
|
||||
}
|
||||
const u64 alignment = device.GetMinImportedHostPointerAlignment();
|
||||
if (alignment == 0 || !Common::IsAligned(reinterpret_cast<uintptr_t>(base), alignment) ||
|
||||
!Common::IsAligned(size, alignment)) {
|
||||
LOG_INFO(Render_Vulkan,
|
||||
"Unified memory disabled, host allocation does not satisfy alignment {}",
|
||||
alignment);
|
||||
return false;
|
||||
}
|
||||
using namespace Common::Literals;
|
||||
constexpr VkDeviceSize DesktopWindowSize = 4_GiB;
|
||||
VkDeviceSize candidate_window = DesktopWindowSize;
|
||||
const u64 max_buffer_size = device.GetMaxBufferSize();
|
||||
if (max_buffer_size != 0 && max_buffer_size < candidate_window) {
|
||||
candidate_window = max_buffer_size;
|
||||
}
|
||||
const u64 max_allocation_size = device.GetMaxMemoryAllocationSize();
|
||||
if (max_allocation_size != 0 && max_allocation_size < candidate_window) {
|
||||
candidate_window = max_allocation_size;
|
||||
}
|
||||
candidate_window = Common::AlignDown(candidate_window, alignment);
|
||||
if (candidate_window == 0) {
|
||||
return false;
|
||||
}
|
||||
window_size = candidate_window;
|
||||
|
||||
const auto &logical = device.GetLogical();
|
||||
const auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
|
||||
|
||||
for (size_t offset = 0; offset < size; offset += window_size) {
|
||||
u8 *const window_base = static_cast<u8 *>(base) + offset;
|
||||
const VkDeviceSize window_len =
|
||||
(std::min)(static_cast<VkDeviceSize>(size - offset), window_size);
|
||||
VkMemoryHostPointerPropertiesEXT host_props{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_HOST_POINTER_PROPERTIES_EXT,
|
||||
.pNext = nullptr,
|
||||
.memoryTypeBits = 0,
|
||||
};
|
||||
if (logical.GetMemoryHostPointerPropertiesEXT(
|
||||
VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, window_base,
|
||||
&host_props) != VK_SUCCESS ||
|
||||
host_props.memoryTypeBits == 0) {
|
||||
break;
|
||||
}
|
||||
const VkExternalMemoryBufferCreateInfo external_info{
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT,
|
||||
};
|
||||
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 & host_props.memoryTypeBits;
|
||||
if (type_mask == 0 || requirements.size > window_len) {
|
||||
logical.DestroyBufferRaw(new_buffer);
|
||||
break;
|
||||
}
|
||||
const auto type_index = FindImportMemoryType(memory_props, type_mask);
|
||||
if (!type_index) {
|
||||
logical.DestroyBufferRaw(new_buffer);
|
||||
break;
|
||||
}
|
||||
constexpr VkDeviceSize MaxHeapFractionDenominator = 2;
|
||||
const u32 heap_index = memory_props.memoryTypes[*type_index].heapIndex;
|
||||
const VkDeviceSize heap_size = memory_props.memoryHeaps[heap_index].size;
|
||||
const VkDeviceSize heap_import_limit = heap_size / MaxHeapFractionDenominator;
|
||||
if (imported_size + window_len > heap_import_limit) {
|
||||
LOG_INFO(Render_Vulkan,
|
||||
"Stopping guest memory import at {} MiB to leave room on heap {} of {} MiB",
|
||||
imported_size >> 20, heap_index, heap_size >> 20);
|
||||
logical.DestroyBufferRaw(new_buffer);
|
||||
break;
|
||||
}
|
||||
const VkImportMemoryHostPointerInfoEXT import_info{
|
||||
.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT,
|
||||
.pHostPointer = window_base,
|
||||
};
|
||||
const VkMemoryAllocateInfo alloc_info{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.pNext = &import_info,
|
||||
.allocationSize = window_len,
|
||||
.memoryTypeIndex = *type_index,
|
||||
};
|
||||
vk::DeviceMemory memory = logical.TryAllocateMemory(alloc_info);
|
||||
if (!memory) {
|
||||
logical.DestroyBufferRaw(new_buffer);
|
||||
break;
|
||||
}
|
||||
if (logical.BindBufferMemory(new_buffer, *memory, 0) != VK_SUCCESS) {
|
||||
logical.DestroyBufferRaw(new_buffer);
|
||||
break;
|
||||
}
|
||||
windows.push_back(Window{
|
||||
.memory = std::move(memory),
|
||||
.buffer = new_buffer,
|
||||
});
|
||||
imported_size += static_cast<size_t>(window_len);
|
||||
}
|
||||
if (windows.empty()) {
|
||||
LOG_INFO(Render_Vulkan, "Host pointer import failed");
|
||||
return false;
|
||||
}
|
||||
LOG_INFO(Render_Vulkan,
|
||||
"Imported {} MiB of guest memory for unified memory access in {} windows",
|
||||
imported_size >> 20, windows.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HostMemoryImport::ImportHardwareBuffers(
|
||||
[[maybe_unused]] std::span<AHardwareBuffer *const> hardware_buffers,
|
||||
[[maybe_unused]] size_t hardware_buffer_window,
|
||||
[[maybe_unused]] size_t hardware_buffer_base, [[maybe_unused]] size_t size) {
|
||||
#ifdef __ANDROID__
|
||||
if (hardware_buffers.empty() || hardware_buffer_window == 0 ||
|
||||
!device.IsExtExternalMemoryAhbSupported()) {
|
||||
return false;
|
||||
}
|
||||
using namespace Common::Literals;
|
||||
u64 max_allocation_size = device.GetMaxMemoryAllocationSize();
|
||||
if (device.IsTiler()) {
|
||||
constexpr u64 TilerAllocationLimit = 1_GiB;
|
||||
max_allocation_size = max_allocation_size != 0
|
||||
? (std::min)(max_allocation_size, TilerAllocationLimit)
|
||||
: TilerAllocationLimit;
|
||||
}
|
||||
if (max_allocation_size != 0 && hardware_buffer_window > max_allocation_size) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Hardware buffer windows of {} MiB exceed the {} MiB allocation limit",
|
||||
hardware_buffer_window >> 20, max_allocation_size >> 20);
|
||||
return false;
|
||||
}
|
||||
if (hardware_buffer_base >= size) {
|
||||
return false;
|
||||
}
|
||||
const auto &logical = device.GetLogical();
|
||||
const auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
|
||||
window_size = hardware_buffer_window;
|
||||
base_offset = hardware_buffer_base;
|
||||
for (size_t i = 0; i < hardware_buffers.size(); ++i) {
|
||||
const size_t offset = hardware_buffer_base + i * hardware_buffer_window;
|
||||
if (offset >= size) {
|
||||
break;
|
||||
}
|
||||
const VkDeviceSize window_len = (std::min)(
|
||||
static_cast<VkDeviceSize>(size - offset),
|
||||
static_cast<VkDeviceSize>(hardware_buffer_window));
|
||||
VkAndroidHardwareBufferPropertiesANDROID ahb_props{
|
||||
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
|
||||
.pNext = nullptr,
|
||||
.allocationSize = 0,
|
||||
.memoryTypeBits = 0,
|
||||
};
|
||||
if (logical.GetAndroidHardwareBufferPropertiesANDROID(hardware_buffers[i],
|
||||
&ahb_props) != VK_SUCCESS ||
|
||||
ahb_props.memoryTypeBits == 0 || ahb_props.allocationSize < window_len) {
|
||||
break;
|
||||
}
|
||||
const VkExternalMemoryBufferCreateInfo external_info{
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.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);
|
||||
break;
|
||||
}
|
||||
const auto type_index = FindImportMemoryType(memory_props, type_mask);
|
||||
if (!type_index) {
|
||||
logical.DestroyBufferRaw(new_buffer);
|
||||
break;
|
||||
}
|
||||
const VkImportAndroidHardwareBufferInfoANDROID import_info{
|
||||
.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID,
|
||||
.pNext = nullptr,
|
||||
.buffer = hardware_buffers[i],
|
||||
};
|
||||
const VkMemoryDedicatedAllocateInfo dedicated_info{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
|
||||
.pNext = &import_info,
|
||||
.image = VK_NULL_HANDLE,
|
||||
.buffer = new_buffer,
|
||||
};
|
||||
const VkMemoryAllocateInfo alloc_info{
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.pNext = &dedicated_info,
|
||||
.allocationSize = ahb_props.allocationSize,
|
||||
.memoryTypeIndex = *type_index,
|
||||
};
|
||||
vk::DeviceMemory memory = logical.TryAllocateMemory(alloc_info);
|
||||
if (!memory) {
|
||||
logical.DestroyBufferRaw(new_buffer);
|
||||
break;
|
||||
}
|
||||
if (logical.BindBufferMemory(new_buffer, *memory, 0) != VK_SUCCESS) {
|
||||
logical.DestroyBufferRaw(new_buffer);
|
||||
break;
|
||||
}
|
||||
windows.push_back(Window{
|
||||
.memory = std::move(memory),
|
||||
.buffer = new_buffer,
|
||||
});
|
||||
imported_size += static_cast<size_t>(window_len);
|
||||
}
|
||||
if (windows.empty()) {
|
||||
LOG_INFO(Render_Vulkan, "Hardware buffer import failed");
|
||||
window_size = 0;
|
||||
base_offset = 0;
|
||||
return false;
|
||||
}
|
||||
foreign_ownership = true;
|
||||
LOG_INFO(Render_Vulkan,
|
||||
"Imported {} MiB of guest memory at {:#x} via hardware buffers in {} windows",
|
||||
imported_size >> 20, base_offset, windows.size());
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
HostMemoryImport::~HostMemoryImport() {
|
||||
for (Window &window : windows) {
|
||||
if (window.buffer != VK_NULL_HANDLE) {
|
||||
device.GetLogical().DestroyBufferRaw(window.buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MemoryAllocator::MemoryAllocator(const Device &device_)
|
||||
: device{device_}, allocator{device.GetAllocator()},
|
||||
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#include "video_core/vulkan_common/vma.h"
|
||||
|
||||
struct AHardwareBuffer;
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Device;
|
||||
|
|
@ -84,6 +86,66 @@ namespace Vulkan {
|
|||
void *mapped_ptr{}; ///< Optional persistent mapped pointer
|
||||
};
|
||||
|
||||
class HostMemoryImport {
|
||||
public:
|
||||
explicit HostMemoryImport(const Device &device_, void *base, size_t size,
|
||||
std::span<AHardwareBuffer *const> hardware_buffers,
|
||||
size_t hardware_buffer_window, size_t hardware_buffer_base);
|
||||
|
||||
~HostMemoryImport();
|
||||
|
||||
HostMemoryImport(const HostMemoryImport &) = delete;
|
||||
|
||||
HostMemoryImport &operator=(const HostMemoryImport &) = delete;
|
||||
|
||||
[[nodiscard]] bool IsValid() const noexcept {
|
||||
return !windows.empty();
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t GetSize() const noexcept {
|
||||
return imported_size;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t GetBaseOffset() const noexcept {
|
||||
return base_offset;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool NeedsForeignOwnershipTransfer() const noexcept {
|
||||
return foreign_ownership;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkDeviceSize GetWindowSize() const noexcept {
|
||||
return window_size;
|
||||
}
|
||||
|
||||
[[nodiscard]] VkBuffer GetWindowBuffer(size_t index) const noexcept {
|
||||
return windows[index].buffer;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t GetWindowCount() const noexcept {
|
||||
return windows.size();
|
||||
}
|
||||
|
||||
private:
|
||||
struct Window {
|
||||
vk::DeviceMemory memory;
|
||||
VkBuffer buffer{};
|
||||
};
|
||||
|
||||
bool ImportHostPointer(void *base, size_t size);
|
||||
|
||||
bool ImportHardwareBuffers(std::span<AHardwareBuffer *const> hardware_buffers,
|
||||
size_t hardware_buffer_window, size_t hardware_buffer_base,
|
||||
size_t size);
|
||||
|
||||
const Device &device;
|
||||
std::vector<Window> windows;
|
||||
VkDeviceSize window_size{};
|
||||
size_t imported_size{};
|
||||
size_t base_offset{};
|
||||
bool foreign_ownership{};
|
||||
};
|
||||
|
||||
/// Memory allocator container.
|
||||
/// Allocates and releases memory allocations on demand.
|
||||
class MemoryAllocator {
|
||||
|
|
|
|||
|
|
@ -217,12 +217,16 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
|
|||
X(vkGetBufferMemoryRequirements2);
|
||||
X(vkGetDeviceQueue);
|
||||
X(vkGetEventStatus);
|
||||
X(vkGetMemoryHostPointerPropertiesEXT);
|
||||
X(vkGetFenceStatus);
|
||||
X(vkGetImageMemoryRequirements);
|
||||
X(vkGetPipelineCacheData);
|
||||
X(vkGetMemoryFdKHR);
|
||||
#ifdef _WIN32
|
||||
X(vkGetMemoryWin32HandleKHR);
|
||||
#endif
|
||||
#ifdef __ANDROID__
|
||||
X(vkGetAndroidHardwareBufferPropertiesANDROID);
|
||||
#endif
|
||||
X(vkGetQueryPoolResults);
|
||||
X(vkGetPipelineExecutablePropertiesKHR);
|
||||
|
|
|
|||
|
|
@ -333,12 +333,16 @@ struct DeviceDispatch : InstanceDispatch {
|
|||
PFN_vkGetBufferMemoryRequirements2 vkGetBufferMemoryRequirements2{};
|
||||
PFN_vkGetDeviceQueue vkGetDeviceQueue{};
|
||||
PFN_vkGetEventStatus vkGetEventStatus{};
|
||||
PFN_vkGetMemoryHostPointerPropertiesEXT vkGetMemoryHostPointerPropertiesEXT{};
|
||||
PFN_vkGetFenceStatus vkGetFenceStatus{};
|
||||
PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements{};
|
||||
PFN_vkGetPipelineCacheData vkGetPipelineCacheData{};
|
||||
PFN_vkGetMemoryFdKHR vkGetMemoryFdKHR{};
|
||||
#ifdef _WIN32
|
||||
PFN_vkGetMemoryWin32HandleKHR vkGetMemoryWin32HandleKHR{};
|
||||
#endif
|
||||
#ifdef __ANDROID__
|
||||
PFN_vkGetAndroidHardwareBufferPropertiesANDROID vkGetAndroidHardwareBufferPropertiesANDROID{};
|
||||
#endif
|
||||
PFN_vkGetPipelineExecutablePropertiesKHR vkGetPipelineExecutablePropertiesKHR{};
|
||||
PFN_vkGetPipelineExecutableStatisticsKHR vkGetPipelineExecutableStatisticsKHR{};
|
||||
|
|
@ -1104,6 +1108,34 @@ public:
|
|||
VkMemoryRequirements GetBufferMemoryRequirements(VkBuffer buffer,
|
||||
void* pnext = nullptr) const noexcept;
|
||||
|
||||
VkResult GetMemoryHostPointerPropertiesEXT(
|
||||
VkExternalMemoryHandleTypeFlagBits handle_type, const void* host_pointer,
|
||||
VkMemoryHostPointerPropertiesEXT* out_properties) const noexcept {
|
||||
return dld->vkGetMemoryHostPointerPropertiesEXT(handle, handle_type, host_pointer,
|
||||
out_properties);
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
VkResult GetAndroidHardwareBufferPropertiesANDROID(
|
||||
const struct AHardwareBuffer* buffer,
|
||||
VkAndroidHardwareBufferPropertiesANDROID* out_properties) const noexcept {
|
||||
return dld->vkGetAndroidHardwareBufferPropertiesANDROID(handle, buffer, out_properties);
|
||||
}
|
||||
#endif
|
||||
|
||||
VkResult CreateBufferRaw(const VkBufferCreateInfo& ci, VkBuffer* out_buffer) const noexcept {
|
||||
return dld->vkCreateBuffer(handle, &ci, nullptr, out_buffer);
|
||||
}
|
||||
|
||||
void DestroyBufferRaw(VkBuffer buffer) const noexcept {
|
||||
dld->vkDestroyBuffer(handle, buffer, nullptr);
|
||||
}
|
||||
|
||||
VkResult BindBufferMemory(VkBuffer buffer, VkDeviceMemory memory,
|
||||
VkDeviceSize offset) const noexcept {
|
||||
return dld->vkBindBufferMemory(handle, buffer, memory, offset);
|
||||
}
|
||||
|
||||
VkMemoryRequirements GetImageMemoryRequirements(VkImage image) const noexcept;
|
||||
|
||||
std::vector<VkPipelineExecutablePropertiesKHR> GetPipelineExecutablePropertiesKHR(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue