refactor(renderer): simplify buffer and texture cache ownership

This commit is contained in:
nmzik
2026-08-21 20:11:38 +02:00
parent 49ea1680f8
commit 26c4168160
20 changed files with 1116 additions and 1411 deletions
+132
View File
@@ -0,0 +1,132 @@
#ifndef EMULATOR_SRC_COMMON_SLOTVECTOR_H_
#define EMULATOR_SRC_COMMON_SLOTVECTOR_H_
#include "common/assert.h"
#include "common/abi.h"
#include <compare>
#include <cstdint>
#include <deque>
#include <functional>
#include <limits>
#include <optional>
#include <utility>
#include <vector>
namespace Common {
struct SlotId {
static constexpr uint32_t INVALID_INDEX = std::numeric_limits<uint32_t>::max();
constexpr SlotId() noexcept = default;
constexpr SlotId(uint32_t value) noexcept: index(value), generation(1) {}
constexpr SlotId(uint32_t value, uint32_t slot_generation) noexcept
: index(value), generation(slot_generation) {}
[[nodiscard]] constexpr explicit operator bool() const noexcept { return index != INVALID_INDEX; }
constexpr auto operator<=>(const SlotId&) const noexcept = default;
uint32_t index = INVALID_INDEX;
uint32_t generation = 0;
};
// Stable-address slot storage for cache resources that are intentionally non-movable.
template <typename T>
class SlotVector {
public:
SlotVector() = default;
KYTY_CLASS_NO_COPY(SlotVector);
[[nodiscard]] T& operator[](SlotId id) noexcept {
EXIT_IF(!is_allocated(id));
return *m_values[id.index].value;
}
[[nodiscard]] const T& operator[](SlotId id) const noexcept {
EXIT_IF(!is_allocated(id));
return *m_values[id.index].value;
}
[[nodiscard]] T* try_get(SlotId id) noexcept {
return is_allocated(id) ? &*m_values[id.index].value : nullptr;
}
[[nodiscard]] const T* try_get(SlotId id) const noexcept {
return is_allocated(id) ? &*m_values[id.index].value : nullptr;
}
[[nodiscard]] bool is_allocated(SlotId id) const noexcept {
return id && id.index < m_values.size() &&
m_values[id.index].generation == id.generation && m_values[id.index].value.has_value();
}
template <typename... Args>
[[nodiscard]] SlotId insert(Args&&... args) {
uint32_t index = 0;
if (m_free_list.empty()) {
index = static_cast<uint32_t>(m_values.size());
m_values.emplace_back();
m_values.back().value.emplace(std::forward<Args>(args)...);
} else {
index = m_free_list.back();
m_free_list.pop_back();
EXIT_IF(m_values[index].value.has_value());
m_values[index].value.emplace(std::forward<Args>(args)...);
}
++m_size;
return SlotId {index, m_values[index].generation};
}
void erase(SlotId id) noexcept {
EXIT_IF(!is_allocated(id));
auto& slot = m_values[id.index];
slot.value.reset();
if (++slot.generation == 0) {
slot.generation = 1;
}
m_free_list.push_back(id.index);
--m_size;
}
[[nodiscard]] size_t size() const noexcept { return m_size; }
[[nodiscard]] size_t capacity() const noexcept { return m_values.size(); }
template <typename F>
void ForEach(F&& fn) {
for (uint32_t index = 0; index < m_values.size(); ++index) {
if (m_values[index].value) {
fn(SlotId {index, m_values[index].generation}, *m_values[index].value);
}
}
}
template <typename F>
void ForEach(F&& fn) const {
for (uint32_t index = 0; index < m_values.size(); ++index) {
if (m_values[index].value) {
fn(SlotId {index, m_values[index].generation}, *m_values[index].value);
}
}
}
private:
struct Slot {
std::optional<T> value;
uint32_t generation = 1;
};
std::deque<Slot> m_values;
std::vector<uint32_t> m_free_list;
size_t m_size = 0;
};
} // namespace Common
template <>
struct std::hash<Common::SlotId> {
[[nodiscard]] size_t operator()(Common::SlotId id) const noexcept {
return std::hash<uint64_t> {}((static_cast<uint64_t>(id.generation) << 32u) | id.index);
}
};
#endif // EMULATOR_SRC_COMMON_SLOTVECTOR_H_
+1 -1
View File
@@ -1275,7 +1275,7 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
if (eop_event_type == 0x2f && cache_action == 0x00 && event_index == 0x06) { if (eop_event_type == 0x2f && cache_action == 0x00 && event_index == 0x06) {
auto* dst = static_cast<uint32_t*>(dst_gpu_addr); auto* dst = static_cast<uint32_t*>(dst_gpu_addr);
SynchronizeGpu(); SynchronizeGpu();
Sync::ReadGds(m_renderer.GetBufferCache().GetGdsBuffer(), dst, value & 0xffffu, Sync::ReadGds(*m_renderer.GetBufferCache().GetGdsBuffer(), dst, value & 0xffffu,
value >> 16u); value >> 16u);
Sync::WriteAtEndOfPipeGds32(m_submit_id, CurrentBuffer(), dst, value & 0xffffu, Sync::WriteAtEndOfPipeGds32(m_submit_id, CurrentBuffer(), dst, value & 0xffffu,
value >> 16u); value >> 16u);
-1
View File
@@ -24,7 +24,6 @@ struct GraphicContext: public VulkanInstance {
[[nodiscard]] uint64_t GetDeviceMemoryUsage() const; [[nodiscard]] uint64_t GetDeviceMemoryUsage() const;
[[nodiscard]] uint64_t GetTotalMemoryBudget() const; [[nodiscard]] uint64_t GetTotalMemoryBudget() const;
void CreateBuffer(uint64_t size, VulkanBuffer& buffer); void CreateBuffer(uint64_t size, VulkanBuffer& buffer);
void DeleteBuffer(VulkanBuffer& buffer);
[[nodiscard]] bool CreateImage(const vk::ImageCreateInfo& info, VulkanImage& image); [[nodiscard]] bool CreateImage(const vk::ImageCreateInfo& info, VulkanImage& image);
void DeleteImage(VulkanImage& image); void DeleteImage(VulkanImage& image);
void MapMemory(VulkanMemory& memory, void*& data); void MapMemory(VulkanMemory& memory, void*& data);
+222 -289
View File
@@ -28,57 +28,22 @@ constexpr uint64_t GdsBufferSize = 64 * 1024;
} // namespace } // namespace
uint64_t BufferCache::AlignDown(uint64_t value) noexcept { void BufferCache::WriteDataBuffer(Buffer& buffer, uint64_t address, const void* source,
return value & ~(CACHING_PAGE_SIZE - 1); uint64_t size) {
}
uint64_t BufferCache::AlignUp(uint64_t value) {
if (value > UINT64_MAX - (CACHING_PAGE_SIZE - 1)) {
EXIT("BufferCache: address alignment overflow\n");
}
return (value + CACHING_PAGE_SIZE - 1) & ~(CACHING_PAGE_SIZE - 1);
}
void BufferCache::Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset,
const void* source, uint64_t size) {
auto* bytes = static_cast<const uint8_t*>(source); auto* bytes = static_cast<const uint8_t*>(source);
while (size != 0) { while (size != 0) {
const auto chunk = std::min(size, m_staging_buffer.Size()); const auto chunk = std::min(size, m_staging_buffer.Size());
const auto stage_offset = m_staging_buffer.Copy(bytes, chunk, 4); const auto offset = m_staging_buffer.Copy(bytes, chunk, 4);
destination.CopyFrom(command, m_staging_buffer, stage_offset, destination_offset, chunk, buffer.CopyFrom(m_scheduler.Current(), m_staging_buffer, offset, buffer.Offset(address),
vk::AccessFlagBits::eHostWrite); chunk, vk::AccessFlagBits::eHostWrite);
bytes += chunk; bytes += chunk;
destination_offset += chunk; address += chunk;
size -= chunk; size -= chunk;
} }
} }
bool BufferCache::ResolveOverlap(CacheRange& merged, CacheRange candidate) noexcept {
if (merged.address == 0 || merged.size == 0 || candidate.address == 0 || candidate.size == 0 ||
merged.size > UINT64_MAX - merged.address ||
candidate.size > UINT64_MAX - candidate.address) {
EXIT("BufferCache: invalid overlap-merge range\n");
}
const auto merged_end = merged.address + merged.size;
const auto candidate_end = candidate.address + candidate.size;
if (merged.address >= candidate_end || candidate.address >= merged_end) {
return false;
}
const auto address = std::min(merged.address, candidate.address);
const auto end = std::max(merged_end, candidate_end);
merged = {.address = address, .size = end - address};
return true;
}
struct BufferCache::CachedBuffer {
uint64_t vaddr = 0;
uint64_t size = 0;
std::shared_ptr<Buffer> buffer;
uint64_t tick_accessed_last = 0;
};
struct BufferCache::DownloadCopy { struct BufferCache::DownloadCopy {
std::shared_ptr<Buffer> owner; Buffer* buffer = nullptr;
uint64_t source_offset = 0; uint64_t source_offset = 0;
uint64_t address = 0; uint64_t address = 0;
uint64_t size = 0; uint64_t size = 0;
@@ -90,15 +55,37 @@ struct BufferCache::DownloadRange {
uint64_t offset = 0; uint64_t offset = 0;
}; };
struct BufferCache::RetiredBuffer { void BufferCache::Unregister(BufferId id) {
uint64_t address = 0; auto& buffer = m_slot_buffers[id];
uint64_t size = 0; if (buffer.is_deleted) {
std::shared_ptr<Buffer> owner; return;
}; }
const auto found = m_buffers.find(buffer.CpuAddress());
EXIT_IF(found == m_buffers.end() || found->second != id);
m_buffers.erase(found);
if (buffer.Size() > m_total_used_memory) {
EXIT("BufferCache: allocation accounting underflow\n");
}
m_total_used_memory -= buffer.Size();
buffer.is_deleted = true;
}
void BufferCache::DeleteBuffer(BufferId id) {
auto* buffer = m_slot_buffers.try_get(id);
if (buffer == nullptr || buffer->is_deleted) {
return;
}
Unregister(id);
if (m_scheduler.Active()) {
m_scheduler.DeferOperation([this, id] { m_slot_buffers.erase(id); });
} else {
m_slot_buffers.erase(id);
}
}
std::pair<uint64_t, uint64_t> BufferCache::DownloadEnvelope(const DownloadCopy& copy) { std::pair<uint64_t, uint64_t> BufferCache::DownloadEnvelope(const DownloadCopy& copy) {
if (copy.owner == nullptr || copy.size == 0 || copy.source_offset > copy.owner->Size() || if (copy.buffer == nullptr || copy.size == 0 || copy.source_offset > copy.buffer->Size() ||
copy.size > copy.owner->Size() - copy.source_offset) { copy.size > copy.buffer->Size() - copy.source_offset) {
EXIT("BufferCache: invalid download copy\n"); EXIT("BufferCache: invalid download copy\n");
} }
const auto begin = copy.source_offset & ~uint64_t {3}; const auto begin = copy.source_offset & ~uint64_t {3};
@@ -107,7 +94,7 @@ std::pair<uint64_t, uint64_t> BufferCache::DownloadEnvelope(const DownloadCopy&
EXIT("BufferCache: download copy alignment overflow\n"); EXIT("BufferCache: download copy alignment overflow\n");
} }
const auto end = (copy.source_offset + copy.size + 3) & ~uint64_t {3}; const auto end = (copy.source_offset + copy.size + 3) & ~uint64_t {3};
if (end > copy.owner->Size()) { if (end > copy.buffer->Size()) {
EXIT("BufferCache: aligned download copy exceeds its owner\n"); EXIT("BufferCache: aligned download copy exceeds its owner\n");
} }
return {begin, end - begin}; return {begin, end - begin};
@@ -144,7 +131,7 @@ BufferCache::RecordDownloads(std::span<const DownloadCopy> copies) {
for (const auto& copy: copies) { for (const auto& copy: copies) {
const auto [source_begin, envelope_size] = DownloadEnvelope(copy); const auto [source_begin, envelope_size] = DownloadEnvelope(copy);
const auto prefix = copy.source_offset - source_begin; const auto prefix = copy.source_offset - source_begin;
download.CopyFrom(m_scheduler.Current(), *copy.owner, source_begin, base_offset + cursor, download.CopyFrom(m_scheduler.Current(), *copy.buffer, source_begin, base_offset + cursor,
envelope_size, vk::AccessFlagBits::eMemoryWrite, vk::AccessFlags {}, envelope_size, vk::AccessFlagBits::eMemoryWrite, vk::AccessFlags {},
vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite, vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
vk::AccessFlagBits::eHostRead); vk::AccessFlagBits::eHostRead);
@@ -163,26 +150,28 @@ void BufferCache::PublishDownloads(std::span<const DownloadRange> downloads) {
} }
} }
void BufferCache::QueueGarbageDownload(std::span<const DownloadCopy> copies, RetiredBuffer retire) { void BufferCache::QueueGarbageDownload(std::span<const DownloadCopy> copies, BufferId id,
if (copies.empty()) { uint64_t address, uint64_t size) {
return; EXIT_IF(copies.empty());
}
auto downloads = RecordDownloads(copies); auto downloads = RecordDownloads(copies);
m_scheduler.DeferOperation( auto retired = downloads;
[this, downloads = std::move(downloads), retire = std::move(retire)]() mutable { m_scheduler.DeferPriorityOperation(
PublishDownloads(downloads); [this, downloads = std::move(downloads)]() mutable { PublishDownloads(downloads); });
for (const auto& range: downloads) { m_scheduler.DeferOperation([this, retired = std::move(retired), id, address, size]() mutable {
for (const auto& range: retired) {
m_gpu_modified_ranges.Subtract(range.address, range.size); m_gpu_modified_ranges.Subtract(range.address, range.size);
} }
// ForEachDownloadRange reports full tracker pages, and every exact GPU-owned // ForEachDownloadRange reports full tracker pages, and every exact GPU-owned
// interval on those pages was downloaded and removed. Clearing the original // interval on those pages was downloaded and removed. Clearing the original
// query therefore cannot orphan a dirty sibling on an edge page. // query therefore cannot orphan a dirty sibling on an edge page.
m_memory_tracker.UnmarkRegionAsGpuModified(retire.address, retire.size); m_memory_tracker.UnmarkRegionAsGpuModified(address, size);
if (m_memory_tracker.IsRegionGpuModified(retire.address, retire.size) || if (m_memory_tracker.IsRegionGpuModified(address, size) ||
!m_gpu_modified_ranges.Intersections(retire.address, retire.size).empty()) { !m_gpu_modified_ranges.Intersections(address, size).empty()) {
EXIT("BufferCache: asynchronous garbage collection retained GPU ownership\n"); EXIT("BufferCache: asynchronous garbage collection retained GPU ownership\n");
} }
m_memory_tracker.UntrackMemory(retire.address, retire.size); m_memory_tracker.UntrackMemory(address, size);
EXIT_IF(!m_slot_buffers[id].is_deleted);
m_slot_buffers.erase(id);
}); });
} }
@@ -198,6 +187,10 @@ BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
m_texture_cache(texture_cache) { m_texture_cache(texture_cache) {
std::memset(m_gds_buffer.Mapped().data(), 0, static_cast<size_t>(m_gds_buffer.Size())); std::memset(m_gds_buffer.Mapped().data(), 0, static_cast<size_t>(m_gds_buffer.Size()));
m_gds_buffer.Flush(0, m_gds_buffer.Size()); m_gds_buffer.Flush(0, m_gds_buffer.Size());
const auto null_id =
m_slot_buffers.insert(m_graphics, m_scheduler, MemoryUsage::DeviceLocal, 0, AllFlags, 16);
EXIT_IF(null_id != NULL_BUFFER_ID);
SetVulkanObjectNameF(m_graphics.device, GetBuffer(null_id).Handle(), "Kyty.NullBuffer");
if (!m_graphics.CanReportMemoryUsage()) { if (!m_graphics.CanReportMemoryUsage()) {
return; return;
} }
@@ -216,38 +209,16 @@ BufferCache::~BufferCache() {
if (!m_gpu_modified_ranges.Empty()) { if (!m_gpu_modified_ranges.Empty()) {
EXIT("BufferCache: destroyed with pending GPU-modified ranges\n"); EXIT("BufferCache: destroyed with pending GPU-modified ranges\n");
} }
for (const auto& [vaddr, cached]: m_buffers) { for (const auto& [vaddr, id]: m_buffers) {
(void)vaddr; (void)vaddr;
if (m_memory_tracker.IsRegionGpuModified(cached->vaddr, cached->size)) { const auto& buffer = m_slot_buffers[id];
if (m_memory_tracker.IsRegionGpuModified(buffer.CpuAddress(), buffer.Size())) {
EXIT("BufferCache: destroyed with GPU-modified buffer\n"); EXIT("BufferCache: destroyed with GPU-modified buffer\n");
} }
} }
m_buffers.clear(); m_buffers.clear();
} }
StreamBuffer& BufferCache::GetUtilityBuffer(MemoryUsage usage) noexcept {
switch (usage) {
case MemoryUsage::Upload: return m_staging_buffer;
case MemoryUsage::Stream: return m_stream_buffer;
case MemoryUsage::Download: return m_download_buffer;
case MemoryUsage::DeviceLocal: return m_device_buffer;
}
EXIT("BufferCache: invalid utility-buffer usage\n");
}
BufferBinding BufferCache::UploadTransient(const void* data, uint64_t size, uint64_t alignment) {
EXIT_IF(data == nullptr || size == 0);
if (auto [mapped, offset] = m_stream_buffer.Map(size, alignment, false); mapped != nullptr) {
std::memcpy(mapped, data, static_cast<size_t>(size));
m_stream_buffer.Commit();
return {{}, m_stream_buffer.Handle(), offset};
}
auto owner =
std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::Upload, 0, AllFlags, size);
owner->Write(0, data, size);
return {owner, owner->Handle(), 0};
}
void BufferCache::InvalidateMemory(uint64_t vaddr, uint64_t size) { void BufferCache::InvalidateMemory(uint64_t vaddr, uint64_t size) {
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE || if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
size > TRACKER_ADDRESS_SIZE - vaddr) { size > TRACKER_ADDRESS_SIZE - vaddr) {
@@ -288,14 +259,14 @@ void BufferCache::ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write)
if (owner == m_buffers.begin()) { if (owner == m_buffers.begin()) {
EXIT("BufferCache: invalidation readback has no buffer owner\n"); EXIT("BufferCache: invalidation readback has no buffer owner\n");
} }
auto& cached = *std::prev(owner)->second; auto& buffer = m_slot_buffers[std::prev(owner)->second];
if (!cached.buffer->IsInBounds(copy_address, 1)) { if (!buffer.IsInBounds(copy_address, 1)) {
EXIT("BufferCache: invalidation readback is outside its buffer owner\n"); EXIT("BufferCache: invalidation readback is outside its buffer owner\n");
} }
const auto copy_size = const auto copy_size = std::min(
std::min(range.size - copied, cached.vaddr + cached.size - copy_address); range.size - copied, buffer.CpuAddress() + buffer.Size() - copy_address);
copies.push_back({cached.buffer, cached.buffer->Offset(copy_address), copies.push_back(
copy_address, copy_size}); {&buffer, buffer.Offset(copy_address), copy_address, copy_size});
copied += copy_size; copied += copy_size;
} }
} }
@@ -330,38 +301,39 @@ void BufferCache::UnmapMemory(uint64_t vaddr, uint64_t size) {
std::vector<DownloadCopy> copies; std::vector<DownloadCopy> copies;
std::vector<std::pair<uint64_t, uint64_t>> modified_buffers; std::vector<std::pair<uint64_t, uint64_t>> modified_buffers;
std::vector<std::pair<uint64_t, uint64_t>> retired_buffers; std::vector<std::pair<uint64_t, uint64_t>> retired_buffers;
for (const auto& [begin, cached]: m_buffers) { for (const auto& [begin, id]: m_buffers) {
if (vaddr < begin + cached->size && begin < vaddr + size) { const auto& buffer = m_slot_buffers[id];
retired_buffers.emplace_back(begin, cached->size); if (vaddr < begin + buffer.Size() && begin < vaddr + size) {
retired_buffers.emplace_back(begin, buffer.Size());
} }
} }
for (const auto& [begin, cached]: m_buffers) { for (const auto& [begin, id]: m_buffers) {
if (vaddr >= begin + cached->size || begin >= vaddr + size || const auto& buffer = m_slot_buffers[id];
!m_memory_tracker.IsRegionGpuModified(begin, cached->size)) { if (vaddr >= begin + buffer.Size() || begin >= vaddr + size ||
!m_memory_tracker.IsRegionGpuModified(begin, buffer.Size())) {
continue; continue;
} }
const auto dirty = m_gpu_modified_ranges.Intersections(begin, cached->size); const auto dirty = m_gpu_modified_ranges.Intersections(begin, buffer.Size());
if (dirty.empty()) { if (dirty.empty()) {
EXIT("BufferCache: GPU-modified buffer has no dirty ranges\n"); EXIT("BufferCache: GPU-modified buffer has no dirty ranges\n");
} }
modified_buffers.emplace_back(begin, cached->size); modified_buffers.emplace_back(begin, buffer.Size());
} }
for (const auto& [begin, bytes]: modified_buffers) { for (const auto& [begin, bytes]: modified_buffers) {
auto owner = m_buffers.find(begin); auto owner = m_buffers.find(begin);
if (owner == m_buffers.end() || owner->second->size != bytes) { if (owner == m_buffers.end() || m_slot_buffers[owner->second].Size() != bytes) {
EXIT("BufferCache: unmap owner changed during collection\n"); EXIT("BufferCache: unmap owner changed during collection\n");
} }
auto& cached = *owner->second; auto& buffer = m_slot_buffers[owner->second];
m_memory_tracker.ForEachDownloadRange<false>( m_memory_tracker.ForEachDownloadRange<false>(
begin, cached.size, begin, buffer.Size(),
[&](uint64_t address, uint64_t bytes) noexcept { [&](uint64_t address, uint64_t bytes) noexcept {
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes, m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes,
"unmap"); "unmap");
}, },
[&](uint64_t address, uint64_t bytes) noexcept { [&](uint64_t address, uint64_t bytes) noexcept {
for (const auto& range: m_gpu_modified_ranges.Intersections(address, bytes)) { for (const auto& range: m_gpu_modified_ranges.Intersections(address, bytes)) {
copies.push_back( copies.push_back({&buffer, range.address - begin, range.address, range.size});
{cached.buffer, range.address - begin, range.address, range.size});
} }
}); });
} }
@@ -385,86 +357,92 @@ void BufferCache::UnmapMemory(uint64_t vaddr, uint64_t size) {
EXIT("BufferCache: unmap retained dirty byte ranges\n"); EXIT("BufferCache: unmap retained dirty byte ranges\n");
} }
m_memory_tracker.UntrackMemory(vaddr, size); m_memory_tracker.UntrackMemory(vaddr, size);
for (auto it = m_buffers.begin(); it != m_buffers.end();) { for (const auto& [begin, bytes]: retired_buffers) {
if (vaddr < it->first + it->second->size && it->first < vaddr + size) { (void)bytes;
if (it->second->size > m_total_used_memory) { const auto found = m_buffers.find(begin);
EXIT("BufferCache: allocation accounting underflow\n"); if (found != m_buffers.end()) {
} DeleteBuffer(found->second);
m_total_used_memory -= it->second->size;
it = m_buffers.erase(it);
} else {
++it;
} }
} }
} }
BufferCache::CachedBuffer& BufferCache::GetOrCreateBuffer(CommandBuffer& command, uint64_t vaddr, BufferId BufferCache::FindBuffer(uint64_t vaddr, uint64_t size) {
uint64_t size) { if (vaddr == 0) {
const auto begin = AlignDown(vaddr); return NULL_BUFFER_ID;
const auto end = AlignUp(vaddr + size); }
if (size == 0 || vaddr >= TRACKER_ADDRESS_SIZE || size > TRACKER_ADDRESS_SIZE - vaddr) {
EXIT("BufferCache: invalid buffer discovery request\n");
}
auto& command = m_scheduler.Current();
EXIT_IF(command.IsInvalid() || command.IsExecute());
auto begin = vaddr & ~(CACHING_PAGE_SIZE - 1);
auto end = (vaddr + size + CACHING_PAGE_SIZE - 1) & ~(CACHING_PAGE_SIZE - 1);
const auto next = m_buffers.upper_bound(vaddr); const auto next = m_buffers.upper_bound(vaddr);
if (next != m_buffers.begin()) { if (next != m_buffers.begin()) {
auto owner = std::prev(next); const auto owner = std::prev(next);
if (owner->second->buffer->IsInBounds(vaddr, size)) { auto& buffer = m_slot_buffers[owner->second];
owner->second->tick_accessed_last = m_gc_tick; if (buffer.IsInBounds(vaddr, size)) {
return *owner->second; buffer.tick_accessed_last = m_gc_tick;
return owner->second;
} }
} }
CacheRange merged {.address = begin, .size = end - begin}; auto merge = [&](const Buffer& buffer) {
using Iterator = decltype(m_buffers.begin()); const auto buffer_begin = buffer.CpuAddress();
std::vector<Iterator> overlaps; const auto buffer_end = buffer_begin + buffer.Size();
if (begin >= buffer_end || buffer_begin >= end) {
return false;
}
begin = std::min(begin, buffer_begin);
end = std::max(end, buffer_end);
return true;
};
std::vector<BufferId> overlaps;
auto first = m_buffers.lower_bound(begin); auto first = m_buffers.lower_bound(begin);
if (first != m_buffers.begin()) { if (first != m_buffers.begin()) {
auto previous = std::prev(first); const auto previous = std::prev(first);
if (ResolveOverlap(merged, {previous->second->vaddr, previous->second->size})) { if (merge(m_slot_buffers[previous->second])) {
first = previous; first = previous;
} }
} }
for (auto candidate = first; candidate != m_buffers.end(); ++candidate) { for (auto candidate = first; candidate != m_buffers.end(); ++candidate) {
if (candidate->first >= merged.address + merged.size) { if (candidate->first >= end) {
break; break;
} }
if (ResolveOverlap(merged, {candidate->second->vaddr, candidate->second->size})) { if (merge(m_slot_buffers[candidate->second])) {
overlaps.push_back(candidate); overlaps.push_back(candidate->second);
} }
} }
auto cached = std::make_unique<CachedBuffer>(); const auto id = m_slot_buffers.insert(m_graphics, m_scheduler, MemoryUsage::DeviceLocal, begin,
cached->vaddr = merged.address; AllFlags, end - begin);
cached->size = merged.size; auto& buffer = m_slot_buffers[id];
cached->tick_accessed_last = m_gc_tick; buffer.tick_accessed_last = m_gc_tick;
cached->buffer = std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::DeviceLocal, SetVulkanObjectNameF(m_graphics.device, buffer.Handle(),
merged.address, AllFlags, merged.size); "Kyty.GameBuffer[guest=0x{:016x} size=0x{:x}]", begin, end - begin);
SetVulkanObjectNameF(m_graphics.device, cached->buffer->Handle(),
"Kyty.GameBuffer[guest=0x{:016x} size=0x{:x}]", cached->vaddr,
cached->size);
for (const auto overlap: overlaps) { for (const auto overlap: overlaps) {
const auto& old = *overlap->second; const auto& old = m_slot_buffers[overlap];
cached->buffer->CopyFrom(command, *old.buffer, 0, old.vaddr - cached->vaddr, old.size); buffer.CopyFrom(command, old, 0, old.CpuAddress() - begin, old.Size());
command.RetainResourceUntilFence(old.buffer);
} }
for (const auto overlap: overlaps) { for (const auto overlap: overlaps) {
if (overlap->second->size > m_total_used_memory) { DeleteBuffer(overlap);
EXIT("BufferCache: allocation accounting underflow\n");
} }
m_total_used_memory -= overlap->second->size; m_total_used_memory += buffer.Size();
m_buffers.erase(overlap); const auto [it, inserted] = m_buffers.emplace(begin, id);
} (void)it;
m_total_used_memory += cached->size; EXIT_IF(!inserted);
return *m_buffers.emplace(cached->vaddr, std::move(cached)).first->second; return id;
} }
bool BufferCache::SynchronizeBuffer(CommandBuffer& command, Buffer& buffer, uint64_t vaddr, bool BufferCache::SynchronizeBuffer(Buffer& buffer, uint64_t vaddr, uint64_t size, bool is_written,
uint64_t size, bool is_written, bool is_texel_buffer) { bool is_texel_buffer) {
std::vector<std::pair<uint64_t, uint64_t>> uploads; std::vector<std::pair<uint64_t, uint64_t>> uploads;
m_memory_tracker.ForEachUploadRange( m_memory_tracker.ForEachUploadRange(
vaddr, size, is_written, vaddr, size, is_written,
[&](uint64_t address, uint64_t bytes) noexcept { uploads.emplace_back(address, bytes); }, [&](uint64_t address, uint64_t bytes) noexcept { uploads.emplace_back(address, bytes); },
[&]() noexcept { [&]() noexcept {
for (const auto& [address, bytes]: uploads) { for (const auto& [address, bytes]: uploads) {
Upload(command, buffer, buffer.Offset(address), WriteDataBuffer(buffer, address, reinterpret_cast<const void*>(address), bytes);
reinterpret_cast<const void*>(address), bytes);
} }
}); });
if (is_texel_buffer && !is_written) { if (is_texel_buffer && !is_written) {
@@ -473,71 +451,51 @@ bool BufferCache::SynchronizeBuffer(CommandBuffer& command, Buffer& buffer, uint
return false; return false;
} }
BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size, std::pair<Buffer*, uint64_t> BufferCache::ObtainBuffer(uint64_t vaddr, uint64_t size,
bool is_written, bool is_read, bool is_formatted) { bool is_written, bool is_texel_buffer,
if (command.IsInvalid() || command.IsExecute()) { BufferId id) {
auto& command = m_scheduler.Current();
if (command.IsInvalid() || command.IsExecute() || vaddr == 0 || size == 0 ||
vaddr >= TRACKER_ADDRESS_SIZE || size > TRACKER_ADDRESS_SIZE - vaddr) {
EXIT("BufferCache: buffer request requires a recording command buffer\n"); EXIT("BufferCache: buffer request requires a recording command buffer\n");
} }
if (is_read && !is_written && size <= CACHING_PAGE_SIZE && if (!is_written && size <= CACHING_PAGE_SIZE &&
!m_memory_tracker.IsRegionGpuModified(vaddr, size) && !m_memory_tracker.IsRegionGpuModified(vaddr, size) &&
m_memory_tracker.IsRegionCpuModified(vaddr, size)) { m_memory_tracker.IsRegionCpuModified(vaddr, size)) {
const auto alignment = std::max<uint64_t>( const auto alignment = std::max<uint64_t>(
m_graphics.physical_device_properties.limits.minUniformBufferOffsetAlignment, 1); m_graphics.physical_device_properties.limits.minUniformBufferOffsetAlignment, 1);
if (auto [mapped, offset] = m_stream_buffer.Map(size, alignment, false); auto [mapped, offset] = m_stream_buffer.Map(size, alignment);
mapped != nullptr) { EXIT_IF(mapped == nullptr || !Libs::LibKernel::Memory::TryReadBacking(vaddr, mapped, size));
if (Libs::LibKernel::Memory::TryReadBacking(vaddr, mapped, size)) {
m_stream_buffer.Commit(); m_stream_buffer.Commit();
return {{}, m_stream_buffer.Handle(), offset}; return {&m_stream_buffer, offset};
}
} else {
auto owner = std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::Upload, 0,
AllFlags, size);
if (Libs::LibKernel::Memory::TryReadBacking(vaddr, owner->Mapped().data(), size)) {
owner->Flush(0, size);
return {owner, owner->Handle(), 0};
}
}
} }
if (is_formatted && is_written) { auto* buffer = m_slot_buffers.try_get(id);
(void)m_texture_cache.InvalidateMemoryFromGPU(vaddr, size, true); if (buffer == nullptr || buffer->is_deleted || !buffer->IsInBounds(vaddr, size)) {
id = FindBuffer(vaddr, size);
buffer = &m_slot_buffers[id];
} }
(void)SynchronizeBuffer(*buffer, vaddr, size, is_written, is_texel_buffer);
auto& cached = GetOrCreateBuffer(command, vaddr, size);
(void)SynchronizeBuffer(command, *cached.buffer, vaddr, size, is_written,
is_formatted && is_read);
if (is_written) { if (is_written) {
m_gpu_modified_ranges.Add(vaddr, size); m_gpu_modified_ranges.Add(vaddr, size);
} }
auto owner = cached.buffer; return {buffer, buffer->Offset(vaddr)};
uint64_t offset = cached.buffer->Offset(vaddr);
return {owner, owner->Handle(), offset};
} }
std::shared_ptr<Buffer> BufferCache::ObtainNullBuffer() { std::pair<Buffer*, uint64_t> BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t size) {
if (m_null_buffer != nullptr) {
return m_null_buffer;
}
m_null_buffer = std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::DeviceLocal, 0,
AllFlags, 16);
const std::array<uint8_t, 16> zeros {};
Upload(m_scheduler.Current(), *m_null_buffer, 0, zeros.data(), zeros.size());
return m_null_buffer;
}
ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t size) {
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE || if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
size > TRACKER_ADDRESS_SIZE - vaddr) { size > TRACKER_ADDRESS_SIZE - vaddr) {
EXIT("BufferCache: invalid image source\n"); EXIT("BufferCache: invalid image source\n");
} }
auto find_owner = [&]() -> CachedBuffer* { auto find_owner = [&]() -> Buffer* {
auto owner = m_buffers.upper_bound(vaddr); auto owner = m_buffers.upper_bound(vaddr);
if (owner == m_buffers.begin()) { if (owner == m_buffers.begin()) {
return nullptr; return nullptr;
} }
--owner; --owner;
return owner->second->buffer->IsInBounds(vaddr, size) ? owner->second.get() : nullptr; auto* buffer = m_slot_buffers.try_get(owner->second);
return buffer != nullptr && buffer->IsInBounds(vaddr, size) ? buffer : nullptr;
}; };
{ {
@@ -553,17 +511,18 @@ ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t siz
if (!IsRegionRegistered(vaddr, size)) { if (!IsRegionRegistered(vaddr, size)) {
EXIT("BufferCache: GPU-dirty image source has no native buffer\n"); EXIT("BufferCache: GPU-dirty image source has no native buffer\n");
} }
auto& cached = GetOrCreateBuffer(m_scheduler.Current(), vaddr, size); const auto id = FindBuffer(vaddr, size);
owner = &cached; auto& buffer = m_slot_buffers[id];
const auto resolved = m_buffers.find(cached.vaddr); owner = &buffer;
if (resolved == m_buffers.end() || resolved->second.get() != &cached || const auto resolved = m_buffers.find(buffer.CpuAddress());
!cached.buffer->IsInBounds(vaddr, size)) { if (resolved == m_buffers.end() || resolved->second != id ||
!buffer.IsInBounds(vaddr, size)) {
EXIT("BufferCache: merged image source does not contain the requested range\n"); EXIT("BufferCache: merged image source does not contain the requested range\n");
} }
} }
if (owner != nullptr && !cpu_modified && (!gpu_modified || has_dirty_buffer_source)) { if (owner != nullptr && !cpu_modified && (!gpu_modified || has_dirty_buffer_source)) {
owner->tick_accessed_last = m_gc_tick; owner->tick_accessed_last = m_gc_tick;
return {owner->buffer.get(), owner->buffer->Offset(vaddr)}; return {owner, owner->Offset(vaddr)};
} }
if (has_dirty_buffer_source && owner == nullptr) { if (has_dirty_buffer_source && owner == nullptr) {
EXIT("BufferCache: GPU-dirty image source could not resolve its native owner\n"); EXIT("BufferCache: GPU-dirty image source could not resolve its native owner\n");
@@ -588,8 +547,7 @@ ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t siz
return {&m_staging_buffer, stage_offset}; return {&m_staging_buffer, stage_offset};
} }
auto& cached = *owner; owner->tick_accessed_last = m_gc_tick;
cached.tick_accessed_last = m_gc_tick;
std::vector<std::pair<uint64_t, uint64_t>> uploads; std::vector<std::pair<uint64_t, uint64_t>> uploads;
m_memory_tracker.ForEachUploadRange( m_memory_tracker.ForEachUploadRange(
vaddr, size, false, vaddr, size, false,
@@ -598,12 +556,12 @@ ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t siz
}, },
[&]() noexcept { [&]() noexcept {
for (const auto& [address, upload_size]: uploads) { for (const auto& [address, upload_size]: uploads) {
cached.buffer->CopyFrom( owner->CopyFrom(m_scheduler.Current(), m_staging_buffer,
m_scheduler.Current(), m_staging_buffer, stage_offset + address - vaddr, stage_offset + address - vaddr, owner->Offset(address), upload_size,
cached.buffer->Offset(address), upload_size, vk::AccessFlagBits::eHostWrite); vk::AccessFlagBits::eHostWrite);
} }
}); });
return {cached.buffer.get(), cached.buffer->Offset(vaddr)}; return {owner, owner->Offset(vaddr)};
} }
void BufferCache::WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data) { void BufferCache::WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data) {
@@ -613,16 +571,16 @@ void BufferCache::WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data)
Libs::LibKernel::Memory::WriteBacking(vaddr, data.data(), data.size()); Libs::LibKernel::Memory::WriteBacking(vaddr, data.data(), data.size());
const auto end = vaddr + data.size(); const auto end = vaddr + data.size();
for (auto& [address, cached]: m_buffers) { for (const auto& [address, id]: m_buffers) {
const auto cached_end = address + cached->size; auto& buffer = m_slot_buffers[id];
const auto buffer_end = address + buffer.Size();
const auto begin = std::max(vaddr, address); const auto begin = std::max(vaddr, address);
const auto range_end = std::min(end, cached_end); const auto range_end = std::min(end, buffer_end);
if (begin >= range_end) { if (begin >= range_end) {
continue; continue;
} }
Upload(m_scheduler.Current(), *cached->buffer, cached->buffer->Offset(begin), WriteDataBuffer(buffer, begin, data.data() + begin - vaddr, range_end - begin);
data.data() + begin - vaddr, range_end - begin); buffer.tick_accessed_last = m_gc_tick;
cached->tick_accessed_last = m_gc_tick;
} }
} }
@@ -660,12 +618,11 @@ void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool
} }
} }
auto& command = m_scheduler.Current(); m_texture_cache.InvalidateMemoryFromGPU(vaddr, size);
auto dst = ObtainBuffer(command, vaddr, size, true, false, true); const auto id = FindBuffer(vaddr, size);
EXIT_IF(dst.buffer == nullptr || dst.owner == nullptr); auto [dst, dst_offset] = ObtainBuffer(vaddr, size, true, true, id);
command.RetainResourceUntilFence(dst.owner); EXIT_IF(dst == nullptr);
auto owner = std::static_pointer_cast<Buffer>(dst.owner); dst->Fill(dst_offset, size, value);
owner->Fill(dst.offset, size, value);
} }
void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds, void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds,
@@ -707,30 +664,20 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
} }
auto& command = m_scheduler.Current(); auto& command = m_scheduler.Current();
auto src = src_memory ? ObtainBuffer(command, src_vaddr, size, false, true, true) if (dst_memory) {
: BufferBinding {.buffer = m_gds_buffer.Handle(), .offset = src_vaddr}; m_texture_cache.InvalidateMemoryFromGPU(dst_vaddr, size);
auto dst = dst_memory ? ObtainBuffer(command, dst_vaddr, size, true, false, true)
: BufferBinding {.buffer = m_gds_buffer.Handle(), .offset = dst_vaddr};
EXIT_IF(src.buffer == nullptr || dst.buffer == nullptr || (dst_memory && dst.owner == nullptr));
if (src.owner != nullptr) {
command.RetainResourceUntilFence(src.owner);
} }
if (dst.owner != nullptr) { const auto src_id = src_memory ? FindBuffer(src_vaddr, size) : BufferId {};
command.RetainResourceUntilFence(dst.owner); const auto dst_id = dst_memory ? FindBuffer(dst_vaddr, size) : BufferId {};
} auto [src, src_offset] = src_memory ? ObtainBuffer(src_vaddr, size, false, true, src_id)
if (src.buffer == dst.buffer && src.offset < dst.offset + size && : std::pair {&m_gds_buffer, src_vaddr};
dst.offset < src.offset + size) { auto [dst, dst_offset] = dst_memory ? ObtainBuffer(dst_vaddr, size, true, true, dst_id)
: std::pair {&m_gds_buffer, dst_vaddr};
EXIT_IF(src == nullptr || dst == nullptr);
if (src == dst && src_offset < dst_offset + size && dst_offset < src_offset + size) {
EXIT("BufferCache: resolved Vulkan copy ranges overlap\n"); EXIT("BufferCache: resolved Vulkan copy ranges overlap\n");
} }
auto& source = src.owner != nullptr ? *std::static_pointer_cast<Buffer>(src.owner) dst->CopyFrom(command, *src, src_offset, dst_offset, size);
: src_gds ? m_gds_buffer
: m_stream_buffer;
auto& destination =
dst.owner != nullptr ? *std::static_pointer_cast<Buffer>(dst.owner) : m_gds_buffer;
if (source.Handle() != src.buffer || destination.Handle() != dst.buffer) {
EXIT("BufferCache: resolved copy owner does not match its Vulkan handle\n");
}
destination.CopyFrom(command, source, src.offset, dst.offset, size);
} }
bool BufferCache::IsRegionRegistered(uint64_t vaddr, uint64_t size) { bool BufferCache::IsRegionRegistered(uint64_t vaddr, uint64_t size) {
@@ -744,8 +691,8 @@ bool BufferCache::IsRegionRegistered(uint64_t vaddr, uint64_t size) {
if (candidate == m_buffers.begin()) { if (candidate == m_buffers.begin()) {
return false; return false;
} }
const auto& [address, cached] = *std::prev(candidate); const auto& [address, id] = *std::prev(candidate);
return address + cached->size > vaddr; return address + m_slot_buffers[id].Size() > vaddr;
} }
bool BufferCache::IsRegionGpuModified(uint64_t vaddr, uint64_t size) { bool BufferCache::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
@@ -773,70 +720,56 @@ void BufferCache::RunGarbageCollector() {
const uint64_t age = std::min<uint64_t>(aggressive ? 80 : 160, tick); const uint64_t age = std::min<uint64_t>(aggressive ? 80 : 160, tick);
const size_t limit = aggressive ? 64 : 32; const size_t limit = aggressive ? 64 : 32;
std::vector<RetiredBuffer> retires;
std::vector<std::pair<RetiredBuffer, std::vector<DownloadCopy>>> dirty_retires;
{
std::vector<uint64_t> candidates; std::vector<uint64_t> candidates;
for (const auto& [address, owner]: m_buffers) { for (const auto& [address, id]: m_buffers) {
const auto& cached = *owner; const auto& buffer = m_slot_buffers[id];
if (tick - std::min(tick, cached.tick_accessed_last) < age) { if (tick - std::min(tick, buffer.tick_accessed_last) >= age) {
continue;
}
candidates.push_back(address); candidates.push_back(address);
} }
}
std::ranges::sort(candidates, [&](uint64_t left, uint64_t right) { std::ranges::sort(candidates, [&](uint64_t left, uint64_t right) {
return m_buffers.at(left)->tick_accessed_last < m_buffers.at(right)->tick_accessed_last; return m_slot_buffers[m_buffers.at(left)].tick_accessed_last <
m_slot_buffers[m_buffers.at(right)].tick_accessed_last;
}); });
if (candidates.size() > limit) { if (candidates.size() > limit) {
candidates.resize(limit); candidates.resize(limit);
} }
for (const auto address: candidates) { for (const auto address: candidates) {
auto& cached = *m_buffers.at(address); auto found = m_buffers.find(address);
m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, cached.vaddr, EXIT_IF(found == m_buffers.end());
cached.size, "garbage collection"); const auto id = found->second;
retires.push_back({address, cached.size, cached.buffer}); auto& buffer = m_slot_buffers[id];
// GC runs immediately before submission. Preserve every source referenced by commands m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, buffer.CpuAddress(),
// already recorded in the active batch. buffer.Size(), "garbage collection");
m_scheduler.Current().RetainResourceUntilFence(cached.buffer); const bool dirty = m_memory_tracker.IsRegionGpuModified(buffer.CpuAddress(), buffer.Size());
} std::vector<DownloadCopy> copies;
for (const auto& retire: retires) { if (dirty) {
if (!m_memory_tracker.IsRegionGpuModified(retire.address, retire.size)) {
continue;
}
auto& copies = dirty_retires.emplace_back(retire, std::vector<DownloadCopy> {}).second;
m_memory_tracker.ForEachDownloadRange<false>( m_memory_tracker.ForEachDownloadRange<false>(
retire.address, retire.size, buffer.CpuAddress(), buffer.Size(),
[&](uint64_t address, uint64_t size) noexcept { [&](uint64_t dirty_address, uint64_t dirty_size) noexcept {
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, size, m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, dirty_address,
"garbage collection"); dirty_size, "garbage collection");
}, },
[&](uint64_t address, uint64_t size) noexcept { [&](uint64_t dirty_address, uint64_t dirty_size) noexcept {
for (const auto range: m_gpu_modified_ranges.Intersections(address, size)) { for (const auto range:
copies.push_back({retire.owner, range.address - retire.address, m_gpu_modified_ranges.Intersections(dirty_address, dirty_size)) {
copies.push_back({&buffer, range.address - buffer.CpuAddress(),
range.address, range.size}); range.address, range.size});
} }
}); });
} }
}
for (auto& [retire, copies]: dirty_retires) { const auto retire_address = buffer.CpuAddress();
QueueGarbageDownload(copies, std::move(retire)); const auto retire_size = buffer.Size();
if (dirty) {
EXIT_IF(copies.empty());
Unregister(id);
QueueGarbageDownload(copies, id, retire_address, retire_size);
} else {
m_memory_tracker.UntrackMemory(retire_address, retire_size);
DeleteBuffer(id);
} }
for (const auto& retire: retires) {
auto found = m_buffers.find(retire.address);
if (found == m_buffers.end() || found->second->size != retire.size ||
found->second->buffer != retire.owner) {
EXIT("BufferCache: garbage-collection owner changed during download\n");
}
if (!m_memory_tracker.IsRegionGpuModified(retire.address, retire.size)) {
m_memory_tracker.UntrackMemory(retire.address, retire.size);
}
if (retire.size > m_total_used_memory) {
EXIT("BufferCache: allocation accounting underflow\n");
}
m_total_used_memory -= retire.size;
m_buffers.erase(found);
} }
} }
+32 -46
View File
@@ -3,12 +3,12 @@
#include "common/abi.h" #include "common/abi.h"
#include "common/common.h" #include "common/common.h"
#include "common/slotVector.h"
#include "graphics/host_gpu/memoryTracker.h" #include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/rangeSet.h" #include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/renderer/cache/streamBuffer.h" #include "graphics/host_gpu/renderer/cache/streamBuffer.h"
#include <map> #include <map>
#include <memory>
#include <span> #include <span>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -16,27 +16,15 @@
namespace Libs::Graphics { namespace Libs::Graphics {
struct GraphicContext; struct GraphicContext;
class CommandBuffer;
class CommandScheduler; class CommandScheduler;
class TextureCache; class TextureCache;
struct BufferBinding { using BufferId = Common::SlotId;
std::shared_ptr<void> owner; inline constexpr BufferId NULL_BUFFER_ID {0};
vk::Buffer buffer = nullptr;
uint64_t offset = 0;
};
struct ImageBufferSource {
Buffer* buffer = nullptr;
uint64_t offset = 0;
};
class BufferCache { class BufferCache {
public: public:
static constexpr uint64_t CACHING_PAGE_SIZE = 16ull * 1024ull; static constexpr uint64_t CACHING_PAGE_SIZE = 16ull * 1024ull;
static constexpr uint64_t GetBufferOffset(uint64_t vaddr) {
return vaddr & (CACHING_PAGE_SIZE - 1);
}
BufferCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager, BufferCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
TextureCache& texture_cache); TextureCache& texture_cache);
@@ -46,19 +34,26 @@ public:
void InvalidateMemory(uint64_t vaddr, uint64_t size); void InvalidateMemory(uint64_t vaddr, uint64_t size);
void ReadMemory(uint64_t vaddr, uint64_t size, bool is_write = false); void ReadMemory(uint64_t vaddr, uint64_t size, bool is_write = false);
void UnmapMemory(uint64_t vaddr, uint64_t size); void UnmapMemory(uint64_t vaddr, uint64_t size);
[[nodiscard]] BufferBinding ObtainBuffer(CommandBuffer& command, uint64_t vaddr, uint64_t size, [[nodiscard]] Buffer& GetBuffer(BufferId id) { return m_slot_buffers[id]; }
bool is_written = false, bool is_read = true, [[nodiscard]] BufferId FindBuffer(uint64_t vaddr, uint64_t size);
bool is_formatted = false); [[nodiscard]] std::pair<Buffer*, uint64_t> ObtainBuffer(uint64_t vaddr, uint64_t size,
[[nodiscard]] StreamBuffer& GetUtilityBuffer(MemoryUsage usage) noexcept; bool is_written,
[[nodiscard]] Buffer& GetGdsBuffer() noexcept { return m_gds_buffer; } bool is_texel_buffer = false,
[[nodiscard]] const Buffer& GetGdsBuffer() const noexcept { return m_gds_buffer; } BufferId id = {});
[[nodiscard]] BufferBinding UploadTransient(const void* data, uint64_t size, [[nodiscard]] StreamBuffer& GetUtilityBuffer(MemoryUsage usage) noexcept {
uint64_t alignment); switch (usage) {
[[nodiscard]] std::shared_ptr<Buffer> ObtainNullBuffer(); case MemoryUsage::Upload: return m_staging_buffer;
[[nodiscard]] ImageBufferSource ObtainBufferForImage(uint64_t vaddr, uint64_t size); case MemoryUsage::Stream: return m_stream_buffer;
void FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds = false); case MemoryUsage::Download: return m_download_buffer;
void CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds = false, case MemoryUsage::DeviceLocal: return m_device_buffer;
bool src_gds = false); }
EXIT("BufferCache: invalid utility-buffer usage\n");
}
[[nodiscard]] const Buffer* GetGdsBuffer() const noexcept { return &m_gds_buffer; }
[[nodiscard]] std::pair<Buffer*, uint64_t> ObtainBufferForImage(uint64_t vaddr, uint64_t size);
void FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds);
void CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds,
bool src_gds);
// Cache-index and exact dirty-range queries require GPU-thread serialization. // Cache-index and exact dirty-range queries require GPU-thread serialization.
[[nodiscard]] bool IsRegionRegistered(uint64_t vaddr, uint64_t size); [[nodiscard]] bool IsRegionRegistered(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size); [[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size);
@@ -69,40 +64,31 @@ public:
private: private:
friend struct BufferCacheTestAccess; friend struct BufferCacheTestAccess;
struct CacheRange {
uint64_t address = 0;
uint64_t size = 0;
};
struct CachedBuffer;
struct DownloadCopy; struct DownloadCopy;
struct DownloadRange; struct DownloadRange;
struct RetiredBuffer;
static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64; static constexpr uint64_t DOWNLOAD_ALIGNMENT = 64;
[[nodiscard]] static uint64_t AlignDown(uint64_t value) noexcept;
[[nodiscard]] static uint64_t AlignUp(uint64_t value);
[[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept { [[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept {
return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1); return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1);
} }
[[nodiscard]] static std::pair<uint64_t, uint64_t> DownloadEnvelope(const DownloadCopy& copy); [[nodiscard]] static std::pair<uint64_t, uint64_t> DownloadEnvelope(const DownloadCopy& copy);
[[nodiscard]] static bool ResolveOverlap(CacheRange& merged, CacheRange candidate) noexcept; void WriteDataBuffer(Buffer& buffer, uint64_t address, const void* source, uint64_t size);
void Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset, void Unregister(BufferId id);
const void* source, uint64_t size); void DeleteBuffer(BufferId id);
[[nodiscard]] CachedBuffer& GetOrCreateBuffer(CommandBuffer& command, uint64_t vaddr, [[nodiscard]] bool SynchronizeBuffer(Buffer& buffer, uint64_t vaddr, uint64_t size,
uint64_t size); bool is_written, bool is_texel_buffer);
[[nodiscard]] bool SynchronizeBuffer(CommandBuffer& command, Buffer& buffer, uint64_t vaddr,
uint64_t size, bool is_written, bool is_texel_buffer);
[[nodiscard]] bool SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uint64_t size); [[nodiscard]] bool SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uint64_t size);
[[nodiscard]] std::vector<DownloadRange> RecordDownloads(std::span<const DownloadCopy> copies); [[nodiscard]] std::vector<DownloadRange> RecordDownloads(std::span<const DownloadCopy> copies);
void PublishDownloads(std::span<const DownloadRange> downloads); void PublishDownloads(std::span<const DownloadRange> downloads);
void QueueGarbageDownload(std::span<const DownloadCopy> copies, RetiredBuffer retire); void QueueGarbageDownload(std::span<const DownloadCopy> copies, BufferId id, uint64_t address,
uint64_t size);
void WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data); void WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data);
void ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write); void ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write);
GraphicContext& m_graphics; GraphicContext& m_graphics;
CommandScheduler& m_scheduler; CommandScheduler& m_scheduler;
Buffer m_gds_buffer; Buffer m_gds_buffer;
std::shared_ptr<Buffer> m_null_buffer; Common::SlotVector<Buffer> m_slot_buffers;
std::map<uint64_t, std::unique_ptr<CachedBuffer>> m_buffers; std::map<uint64_t, BufferId> m_buffers;
RangeSet m_gpu_modified_ranges; RangeSet m_gpu_modified_ranges;
MemoryTracker m_memory_tracker; MemoryTracker m_memory_tracker;
StreamBuffer m_staging_buffer; StreamBuffer m_staging_buffer;
+4
View File
@@ -65,6 +65,10 @@ public:
vk::AccessFlagBits::eMemoryWrite); vk::AccessFlagBits::eMemoryWrite);
void Fill(uint64_t offset, uint64_t size, uint32_t value); void Fill(uint64_t offset, uint64_t size, uint32_t value);
// BufferCache state lives directly on the resource.
bool is_deleted = false;
uint64_t tick_accessed_last = 0;
protected: protected:
[[nodiscard]] GraphicContext& Graphics() const noexcept { return *m_graphics; } [[nodiscard]] GraphicContext& Graphics() const noexcept { return *m_graphics; }
[[nodiscard]] CommandScheduler& Scheduler() const noexcept { return *m_scheduler; } [[nodiscard]] CommandScheduler& Scheduler() const noexcept { return *m_scheduler; }
File diff suppressed because it is too large Load Diff
+16 -33
View File
@@ -4,17 +4,17 @@
#include "common/abi.h" #include "common/abi.h"
#include "common/common.h" #include "common/common.h"
#include "common/lruCache.h" #include "common/lruCache.h"
#include "common/slotVector.h"
#include "graphics/host_gpu/pageManager.h" #include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/regionManager.h" #include "graphics/host_gpu/regionManager.h"
#include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h" #include "graphics/host_gpu/renderer/cache/multiLevelPageTable.h"
#include "graphics/host_gpu/renderer/image/blitHelper.h" #include "graphics/host_gpu/renderer/image/blitHelper.h"
#include "graphics/host_gpu/renderer/image/image.h" #include "graphics/host_gpu/renderer/image/image.h"
#include "graphics/host_gpu/renderer/image/tiler.h"
#include <compare>
#include <map> #include <map>
#include <memory> #include <unordered_map>
#include <set> #include <unordered_set>
#include <utility>
#include <vector> #include <vector>
namespace Libs::Graphics { namespace Libs::Graphics {
@@ -25,8 +25,6 @@ class BufferCache;
class CommandBuffer; class CommandBuffer;
class CommandScheduler; class CommandScheduler;
class RenderExecutor; class RenderExecutor;
class StreamBuffer;
class TileManager;
struct TextureCacheTestAccess; struct TextureCacheTestAccess;
class TextureCache { class TextureCache {
@@ -57,15 +55,17 @@ public:
[[nodiscard]] vk::ImageView FindTexture(ImageId id, const ImageDesc& desc); [[nodiscard]] vk::ImageView FindTexture(ImageId id, const ImageDesc& desc);
[[nodiscard]] vk::ImageView FindRenderTarget(ImageId id, const ImageDesc& desc); [[nodiscard]] vk::ImageView FindRenderTarget(ImageId id, const ImageDesc& desc);
[[nodiscard]] vk::ImageView FindDepthTarget(ImageId id, const ImageDesc& desc); [[nodiscard]] vk::ImageView FindDepthTarget(ImageId id, const ImageDesc& desc);
[[nodiscard]] Image& GetImage(ImageId id); [[nodiscard]] Image& GetImage(ImageId id) {
[[nodiscard]] const Image& GetImage(ImageId id) const; auto& image = m_slot_images[id];
TouchImage(image);
return image;
}
void MarkGpuWritten(ImageId id); void MarkGpuWritten(ImageId id);
[[nodiscard]] bool ClearImageFromBuffer(CommandBuffer& command, uint64_t address, uint64_t size, [[nodiscard]] bool ClearImageFromBuffer(CommandBuffer& command, uint64_t address, uint64_t size,
uint32_t packed_clear); uint32_t packed_clear);
void InvalidateMemory(uint64_t address, uint64_t size); void InvalidateMemory(uint64_t address, uint64_t size);
[[nodiscard]] bool InvalidateMemoryFromGPU(uint64_t address, uint64_t size, void InvalidateMemoryFromGPU(uint64_t address, uint64_t size);
bool formatted_buffer_write = false);
[[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size); [[nodiscard]] RegionInfo QueryRegion(uint64_t address, uint64_t size);
[[nodiscard]] bool IsMeta(uint64_t address); [[nodiscard]] bool IsMeta(uint64_t address);
@@ -86,16 +86,11 @@ private:
struct ColorTransferPlan; struct ColorTransferPlan;
struct DownloadPlan; struct DownloadPlan;
struct Slot {
std::shared_ptr<Image> image;
uint32_t generation = 1;
};
struct MetaDataInfo { struct MetaDataInfo {
// A guest metadata-fill dispatch may initialize DCC before its render target is bound. // A guest metadata-fill dispatch may initialize DCC before its render target is bound.
// PendingDcc retains that exact fill until FindRenderTarget classifies the address, // PendingDcc retains that exact fill until FindRenderTarget classifies the address,
// without exposing an unconfirmed buffer address to the normal metadata heuristics. // without exposing an unconfirmed buffer address to the normal metadata heuristics.
// Keep all surface metadata in this one shadPS4-style entry so CMask/FMask can be // Keep all surface metadata in one entry so CMask/FMask can be
// registered beside HTile and DCC without introducing parallel tracking paths. // registered beside HTile and DCC without introducing parallel tracking paths.
enum class Type : uint8_t { PendingDcc, CMask, FMask, HTile, Dcc }; enum class Type : uint8_t { PendingDcc, CMask, FMask, HTile, Dcc };
@@ -114,16 +109,12 @@ private:
using ImageIds = InlinePageOwnerList<ImageId, 16>; using ImageIds = InlinePageOwnerList<ImageId, 16>;
using ImagePageTable = MultiLevelPageTable<ImageIds, 20, 40, 10>; using ImagePageTable = MultiLevelPageTable<ImageIds, 20, 40, 10>;
[[nodiscard]] Image& ResolveImage(ImageId id);
[[nodiscard]] const Image& ResolveImage(ImageId id) const;
[[nodiscard]] std::shared_ptr<Image> ResolveOwner(ImageId id) const;
[[nodiscard]] ImageId InsertImage(const ImageInfo& info); [[nodiscard]] ImageId InsertImage(const ImageInfo& info);
[[nodiscard]] ImageId GetNullImage(const ImageDesc& desc); [[nodiscard]] ImageId GetNullImage(const ImageDesc& desc);
void RegisterImage(ImageId id); void RegisterImage(ImageId id);
void UnregisterImage(ImageId id); void UnregisterImage(ImageId id);
void DeleteImage(ImageId id); void DeleteImage(ImageId id);
void FreeImage(ImageId id); void FreeImage(ImageId id);
void RetainImage(CommandBuffer& command, ImageId id);
void TouchImage(Image& image); void TouchImage(Image& image);
void TrackImage(ImageId id); void TrackImage(ImageId id);
void TrackImageHead(ImageId id); void TrackImageHead(ImageId id);
@@ -132,8 +123,7 @@ private:
void UntrackImageHead(ImageId id); void UntrackImageHead(ImageId id);
void UntrackImageTail(ImageId id); void UntrackImageTail(ImageId id);
void MarkAsMaybeDirty(ImageId id, Image& image); void MarkAsMaybeDirty(ImageId id, Image& image);
void TrackImageDownload(ImageId id); void TrackImageDownload(ImageId id, Image& image);
void TrackImageDownloadLocked(ImageId id, Image& image);
[[nodiscard]] static bool SameBacking(const ImageInfo& cached, const ImageInfo& requested, [[nodiscard]] static bool SameBacking(const ImageInfo& cached, const ImageInfo& requested,
bool exact_format); bool exact_format);
[[nodiscard]] static BindingType UploadBinding(const Image& image); [[nodiscard]] static BindingType UploadBinding(const Image& image);
@@ -162,31 +152,24 @@ private:
[[nodiscard]] bool CopyD16(Image& destination, Image& source); [[nodiscard]] bool CopyD16(Image& destination, Image& source);
void CopyImage(ImageId destination, ImageId source); void CopyImage(ImageId destination, ImageId source);
void AssociateStencil(ImageId depth, GuestRange stencil); void AssociateStencil(ImageId depth, GuestRange stencil);
void AssociateStencilLocked(ImageId depth, GuestRange stencil);
void CopyImageMip(ImageId destination, ImageId source, uint32_t mip, uint32_t layer); void CopyImageMip(ImageId destination, ImageId source, uint32_t mip, uint32_t layer);
void ValidateImageDesc(const ImageDesc& desc) const; void ValidateImageDesc(const ImageDesc& desc) const;
void InvalidateCpuAliases(uint64_t address, uint64_t size); void InvalidateCpuAliases(uint64_t address, uint64_t size);
void ClearGpuModified(ImageId id);
void DownloadImage(ImageId id);
[[nodiscard]] bool TryDownloadImage(ImageId id); [[nodiscard]] bool TryDownloadImage(ImageId id);
[[nodiscard]] std::pair<uint8_t*, uint64_t> MapDownload(uint64_t size, uint64_t alignment);
void QueueDownload(GuestRange range, StreamBuffer& download, uint8_t* mapped, uint64_t offset);
GraphicContext& m_graphics; GraphicContext& m_graphics;
CommandScheduler& m_scheduler; CommandScheduler& m_scheduler;
TrackingSpinLock m_lock; TrackingSpinLock m_lock;
PageManager& m_page_manager; PageManager& m_page_manager;
BlitHelper m_blit_helper; BlitHelper m_blit_helper;
std::unique_ptr<TileManager> m_tiler; TileManager m_tiler;
BufferCache& m_buffer_cache; BufferCache& m_buffer_cache;
std::vector<Slot> m_slots; Common::SlotVector<Image> m_slot_images;
std::vector<uint32_t> m_free_slots;
ImagePageTable m_image_page_table; ImagePageTable m_image_page_table;
std::map<vk::Format, ImageId> m_null_images; std::unordered_map<vk::Format, ImageId> m_null_images;
Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache; Common::LeastRecentlyUsedCache<ImageId, uint64_t> m_lru_cache;
std::set<ImageId> m_download_images; std::unordered_set<ImageId> m_download_images;
std::map<uint64_t, MetaDataInfo> m_surface_metas; std::map<uint64_t, MetaDataInfo> m_surface_metas;
uint64_t m_total_used_memory = 0; uint64_t m_total_used_memory = 0;
uint64_t m_trigger_gc_memory = 0; uint64_t m_trigger_gc_memory = 0;
@@ -226,12 +226,17 @@ void CommandScheduler::Wait(uint64_t tick) {
EXIT_IF(tick > CurrentTick()); EXIT_IF(tick > CurrentTick());
if (tick >= CurrentTick()) { if (tick >= CurrentTick()) {
// A stream-buffer wrap can wait while a draw is being prepared through a reference to // A stream-buffer wrap can wait while a draw is being prepared through a reference to
// Current(). Recycle the same command object so that reference remains valid. // Current(). Recycle the same command object so that reference remains valid. Deferred
FinishCurrent(); // resources are released only at the next GPU operation boundary.
SubmitInfo submit;
auto& submitted = SubmitCurrent(submit);
submitted.WaitForFenceAndReset();
m_master.Refresh();
submitted.Begin();
m_recording = true;
return; return;
} }
m_master.Wait(tick); m_master.Wait(tick);
PopPendingOperations();
} }
void CommandScheduler::PopPendingOperations() { void CommandScheduler::PopPendingOperations() {
@@ -243,17 +248,18 @@ void CommandScheduler::PopPendingOperations(bool refresh_gpu_tick) {
m_master.Refresh(); m_master.Refresh();
} }
for (;;) { for (;;) {
Common::UniqueFunction<void> callback; PendingOperation operation;
{ {
std::lock_guard lock(m_operation_mutex); std::lock_guard lock(m_operation_mutex);
if (m_pending_operations.empty() || if (m_pending_operations.empty() ||
!m_master.IsFree(m_pending_operations.front().tick)) { !m_master.IsFree(m_pending_operations.front().tick)) {
return; return;
} }
callback = std::move(m_pending_operations.front().callback); operation = std::move(m_pending_operations.front());
m_pending_operations.pop(); m_pending_operations.pop();
} }
RunOperation(std::move(callback)); WaitPriorityOperations(operation.tick);
RunOperation(std::move(operation.callback));
} }
} }
@@ -423,10 +429,8 @@ void CommandScheduler::BeginNext() {
EXIT_IF(m_recording); EXIT_IF(m_recording);
auto candidate = FindReusableBuffer(m_master.KnownGpuTick()); auto candidate = FindReusableBuffer(m_master.KnownGpuTick());
bool refreshed = false;
if (candidate < 0) { if (candidate < 0) {
m_master.Refresh(); m_master.Refresh();
refreshed = true;
candidate = FindReusableBuffer(m_master.KnownGpuTick()); candidate = FindReusableBuffer(m_master.KnownGpuTick());
} }
if (candidate < 0) { if (candidate < 0) {
@@ -435,7 +439,6 @@ void CommandScheduler::BeginNext() {
m_current = candidate; m_current = candidate;
Current().WaitForFenceAndReset(); Current().WaitForFenceAndReset();
PopPendingOperations(!refreshed);
BindCurrent(); BindCurrent();
Current().Begin(); Current().Begin();
m_recording = true; m_recording = true;
+1 -50
View File
@@ -19,7 +19,6 @@
#include <bit> #include <bit>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <memory>
namespace Libs::Graphics { namespace Libs::Graphics {
namespace { namespace {
@@ -40,27 +39,6 @@ void ReportVulkanFatal(const char* what, vk::Result result, uint32_t slot, uint6
} // namespace } // namespace
FenceResourceRetainer::~FenceResourceRetainer() {
if (!m_resources.empty()) {
EXIT("fence resource retainer destroyed before release\n");
}
}
void FenceResourceRetainer::Retain(std::shared_ptr<void> resource) {
if (resource == nullptr) {
EXIT("cannot retain a null fence resource\n");
}
if (std::ranges::none_of(m_resources, [&resource](const auto& retained) {
return retained.get() == resource.get();
})) {
m_resources.push_back(std::move(resource));
}
}
void FenceResourceRetainer::ReleaseAfterFence() noexcept {
m_resources.clear();
}
CommandBuffer::CommandBuffer(CommandScheduler& scheduler) CommandBuffer::CommandBuffer(CommandScheduler& scheduler)
: m_context(scheduler.Context()), m_scheduler(scheduler), m_graphics(scheduler.Graphics()), : m_context(scheduler.Context()), m_scheduler(scheduler), m_graphics(scheduler.Graphics()),
m_slot(scheduler.AllocateCommandBuffer()) {} m_slot(scheduler.AllocateCommandBuffer()) {}
@@ -90,26 +68,12 @@ void CommandBuffer::Release() {
m_slot->busy = false; m_slot->busy = false;
m_slot->Reset(); m_slot->Reset();
ReleaseResourcesAfterFence(); RecycleDescriptorsAfterFence();
m_slot = nullptr; m_slot = nullptr;
EXIT_NOT_IMPLEMENTED(!IsInvalid()); EXIT_NOT_IMPLEMENTED(!IsInvalid());
} }
void CommandBuffer::RetireBufferAfterFence(std::unique_ptr<VulkanBuffer> buffer) {
if (IsInvalid() || m_execute || buffer == nullptr || buffer->buffer == nullptr) {
EXIT("cannot retire a buffer on an invalid or submitted command buffer\n");
}
m_retired_buffers.push_back(std::move(buffer));
}
void CommandBuffer::RetainResourceUntilFence(std::shared_ptr<void> resource) {
if (IsInvalid() || m_execute) {
EXIT("cannot retain a resource on an invalid or submitted command buffer\n");
}
m_fence_resources.Retain(std::move(resource));
}
void CommandBuffer::RecycleDescriptorAfterFence(VulkanDescriptorSet& set) { void CommandBuffer::RecycleDescriptorAfterFence(VulkanDescriptorSet& set) {
m_descriptor_sets_after_fence.push_back(&set); m_descriptor_sets_after_fence.push_back(&set);
} }
@@ -255,21 +219,8 @@ void CommandBuffer::FinalizeFence(bool reset_recording) {
} }
} }
if (was_executed) { if (was_executed) {
ReleaseResourcesAfterFence();
}
DeleteBuffersAfterFence();
}
void CommandBuffer::ReleaseResourcesAfterFence() {
RecycleDescriptorsAfterFence(); RecycleDescriptorsAfterFence();
m_fence_resources.ReleaseAfterFence();
}
void CommandBuffer::DeleteBuffersAfterFence() {
for (const auto& buffer: m_retired_buffers) {
m_graphics.DeleteBuffer(*buffer);
} }
m_retired_buffers.clear();
} }
void CommandBuffer::BeginRendering(const RenderState& state) const { void CommandBuffer::BeginRendering(const RenderState& state) const {
+2 -9
View File
@@ -2,6 +2,7 @@
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_ #define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_IMAGE_H_
#include "common/assert.h" #include "common/assert.h"
#include "common/slotVector.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/image/imageInfo.h" #include "graphics/host_gpu/renderer/image/imageInfo.h"
@@ -19,15 +20,7 @@ class Buffer;
class CommandScheduler; class CommandScheduler;
struct ImageTestAccess; struct ImageTestAccess;
struct ImageId { using ImageId = Common::SlotId;
uint32_t index = std::numeric_limits<uint32_t>::max();
uint32_t generation = 0;
[[nodiscard]] explicit operator bool() const noexcept {
return index != std::numeric_limits<uint32_t>::max();
}
auto operator<=>(const ImageId&) const = default;
};
struct CachedImageView { struct CachedImageView {
ImageViewInfo info; ImageViewInfo info;
@@ -6,6 +6,7 @@
#include "common/common.h" #include "common/common.h"
#include "common/threads.h" #include "common/threads.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h" #include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/shader/shaderBindings.h" #include "graphics/shader/shaderBindings.h"
@@ -36,7 +37,6 @@ struct VulkanDescriptorSet {
}; };
struct BufferView { struct BufferView {
std::shared_ptr<void> owner;
vk::Buffer buffer = nullptr; vk::Buffer buffer = nullptr;
vk::DeviceSize offset = 0; vk::DeviceSize offset = 0;
vk::DeviceSize range = VK_WHOLE_SIZE; vk::DeviceSize range = VK_WHOLE_SIZE;
@@ -68,6 +68,8 @@ public:
std::shared_ptr<const ShaderRecompiler::IR::Program> program; std::shared_ptr<const ShaderRecompiler::IR::Program> program;
std::shared_ptr<const ShaderRecompiler::IR::ResourceSnapshot> snapshot; std::shared_ptr<const ShaderRecompiler::IR::ResourceSnapshot> snapshot;
NativeDescriptors resources; NativeDescriptors resources;
std::vector<BufferId> buffer_ids;
std::vector<BufferId> address_ids;
std::vector<uint32_t> flattened_srt; std::vector<uint32_t> flattened_srt;
std::vector<uint32_t> user_data; std::vector<uint32_t> user_data;
vk::ShaderStageFlags shader_stage; vk::ShaderStageFlags shader_stage;
@@ -56,9 +56,7 @@ static const char* ShaderStageResourceName(DescriptorCache::Stage stage) {
} }
static void BindNullStorageBuffer(RenderContext& context, BufferView& dst) { static void BindNullStorageBuffer(RenderContext& context, BufferView& dst) {
auto owner = context.GetBufferCache().ObtainNullBuffer(); dst.buffer = context.GetBufferCache().GetBuffer(NULL_BUFFER_ID).Handle();
dst.buffer = owner->Handle();
dst.owner = std::move(owner);
dst.offset = 0; dst.offset = 0;
dst.range = 16; dst.range = 16;
} }
@@ -89,11 +87,11 @@ static bool IsMultisampledTexture(Prospero::ImageType type) {
type == Prospero::ImageType::kColor2DMsaaArray; type == Prospero::ImageType::kColor2DMsaaArray;
} }
static BufferView NativeStorageBuffer(RenderContext& context, CommandBuffer& command_buffer, static BufferView
const ShaderBufferResource& descriptor, NativeStorageBuffer(RenderContext& context, const ShaderBufferResource& descriptor,
const ShaderRecompiler::IR::BufferResource& resource, const ShaderRecompiler::IR::BufferResource& resource,
DescriptorCache::Stage stage, uint32_t slot, DescriptorCache::Stage stage, uint32_t slot, uint32_t& buffer_offset,
uint32_t& buffer_offset) { BufferId id) {
BufferView result; BufferView result;
buffer_offset = 0; buffer_offset = 0;
@@ -115,19 +113,21 @@ static BufferView NativeStorageBuffer(RenderContext& context, CommandBuffer& com
size > graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange) { size > graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange) {
EXIT("storage buffer range or device alignment is unsupported\n"); EXIT("storage buffer range or device alignment is unsupported\n");
} }
auto binding = context.GetBufferCache().ObtainBuffer( auto [buffer, offset] = context.GetBufferCache().ObtainBuffer(
command_buffer, address, size, resource.written, resource.read, resource.formatted); address, size, resource.written, resource.formatted, id);
const auto aligned_offset = binding.offset - binding.offset % alignment; const auto aligned_offset = offset - offset % alignment;
const auto adjustment = binding.offset - aligned_offset; const auto adjustment = offset - aligned_offset;
const auto max_range = graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange; const auto max_range = graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange;
if (adjustment % sizeof(uint32_t) != 0 || adjustment >= 256 || size > max_range - adjustment) { if (adjustment % sizeof(uint32_t) != 0 || adjustment >= 256 || size > max_range - adjustment) {
EXIT("storage buffer offset adjustment is unsupported\n"); EXIT("storage buffer offset adjustment is unsupported\n");
} }
buffer_offset = static_cast<uint32_t>(adjustment); buffer_offset = static_cast<uint32_t>(adjustment);
result.owner = std::move(binding.owner); result.buffer = buffer->Handle();
result.buffer = binding.buffer;
result.offset = aligned_offset; result.offset = aligned_offset;
result.range = static_cast<vk::DeviceSize>(size + adjustment); result.range = static_cast<vk::DeviceSize>(size + adjustment);
if (resource.formatted && resource.written) {
context.GetTextureCache().InvalidateMemoryFromGPU(address, size);
}
SetVulkanObjectNameF( SetVulkanObjectNameF(
graphics.device, result.buffer, graphics.device, result.buffer,
"Kyty.{}.StorageBuffer[slot={} guest=0x{:016x} size=0x{:x} access={} formatted={}]", "Kyty.{}.StorageBuffer[slot={} guest=0x{:016x} size=0x{:x} access={} formatted={}]",
@@ -136,11 +136,10 @@ static BufferView NativeStorageBuffer(RenderContext& context, CommandBuffer& com
return result; return result;
} }
static BufferView static BufferView NativeAddressBuffer(
NativeAddressBuffer(RenderContext& context, CommandBuffer& command_buffer, RenderContext& context, const ShaderRecompiler::IR::AddressResource& resource,
const ShaderRecompiler::IR::AddressResource& resource,
const ShaderRecompiler::IR::ResourceSnapshot::Address& address, const ShaderRecompiler::IR::ResourceSnapshot::Address& address,
DescriptorCache::Stage stage, uint32_t slot, uint32_t& address_offset) { DescriptorCache::Stage stage, uint32_t slot, uint32_t& address_offset, BufferId id) {
BufferView result; BufferView result;
address_offset = 0; address_offset = 0;
if (address.binding_base == 0) { if (address.binding_base == 0) {
@@ -163,15 +162,14 @@ NativeAddressBuffer(RenderContext& context, CommandBuffer& command_buffer,
} }
const auto& graphics = context.GetGraphics(); const auto& graphics = context.GetGraphics();
const auto alignment = graphics.StorageMinAlignment(); const auto alignment = graphics.StorageMinAlignment();
auto binding = auto [buffer, offset] =
context.GetBufferCache().ObtainBuffer(command_buffer, address.binding_base, size); context.GetBufferCache().ObtainBuffer(address.binding_base, size, false, false, id);
const auto aligned_offset = binding.offset - binding.offset % alignment; const auto aligned_offset = offset - offset % alignment;
const auto adjustment = binding.offset - aligned_offset; const auto adjustment = offset - aligned_offset;
const auto max_range = graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange; const auto max_range = graphics.GetPhysicalDeviceProperties().limits.maxStorageBufferRange;
const auto visible_size = std::min(size, static_cast<uint64_t>(max_range - adjustment)); const auto visible_size = std::min(size, static_cast<uint64_t>(max_range - adjustment));
address_offset = static_cast<uint32_t>(adjustment); address_offset = static_cast<uint32_t>(adjustment);
result.owner = std::move(binding.owner); result.buffer = buffer->Handle();
result.buffer = binding.buffer;
result.offset = aligned_offset; result.offset = aligned_offset;
result.range = static_cast<vk::DeviceSize>(visible_size + adjustment); result.range = static_cast<vk::DeviceSize>(visible_size + adjustment);
SetVulkanObjectNameF(graphics.device, result.buffer, SetVulkanObjectNameF(graphics.device, result.buffer,
@@ -811,22 +809,19 @@ static vk::Sampler NativeSampler(RenderContext& context,
return context.GetSamplerCache().GetSampler(descriptor); return context.GetSamplerCache().GetSampler(descriptor);
} }
static BufferView NativeUpload(RenderContext& context, CommandBuffer& command_buffer, static BufferView NativeUpload(RenderContext& context, std::span<const uint32_t> data) {
std::span<const uint32_t> data) {
EXIT_IF(data.empty()); EXIT_IF(data.empty());
auto& command_buffer = context.GetCommandScheduler().Current();
EXIT_IF(command_buffer.IsInvalid() || command_buffer.IsExecute()); EXIT_IF(command_buffer.IsInvalid() || command_buffer.IsExecute());
auto binding = context.GetBufferCache().UploadTransient(data.data(), data.size_bytes(), 256); auto& buffer = context.GetBufferCache().GetUtilityBuffer(MemoryUsage::Stream);
return {.owner = std::move(binding.owner), const auto offset = buffer.Copy(data.data(), data.size_bytes(), 256);
.buffer = binding.buffer, return {.buffer = buffer.Handle(), .offset = offset, .range = data.size_bytes()};
.offset = binding.offset,
.range = data.size_bytes()};
} }
void RenderExecutor::TrackImageBinding(ImageId id) { void RenderExecutor::TrackImageBinding(ImageId id) {
auto image = m_context.GetTextureCache().ResolveOwner(id); EXIT_IF(m_context.GetTextureCache().m_slot_images.try_get(id) == nullptr);
EXIT_IF(image == nullptr); if (std::ranges::find(m_bound_images, id) == m_bound_images.end()) {
if (std::ranges::find(m_bound_images, image) == m_bound_images.end()) { m_bound_images.push_back(id);
m_bound_images.push_back(std::move(image));
} }
} }
@@ -850,16 +845,17 @@ void RenderExecutor::BindRenderTarget(ImageId id) {
} }
void RenderExecutor::ResetBindings() { void RenderExecutor::ResetBindings() {
for (const auto& image: m_bound_images) { for (const auto id: m_bound_images) {
if (auto* image = m_context.GetTextureCache().m_slot_images.try_get(id); image != nullptr) {
image->binding = {}; image->binding = {};
} }
}
m_bound_images.clear(); m_bound_images.clear();
} }
DescriptorCache::PreparedBindings RenderExecutor::PrepareBindings(CommandBuffer& buffer, DescriptorCache::PreparedBindings
const ShaderStageRuntime& runtime, RenderExecutor::PrepareBindings(const ShaderStageRuntime& runtime,
vk::ShaderStageFlags shader_stage, vk::ShaderStageFlags shader_stage, DescriptorCache::Stage stage) {
DescriptorCache::Stage stage) {
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
EXIT_IF(!runtime); EXIT_IF(!runtime);
const auto& program = *runtime.program; const auto& program = *runtime.program;
@@ -899,19 +895,68 @@ DescriptorCache::PreparedBindings RenderExecutor::PrepareBindings(CommandBuffer&
prepared.user_data.resize(program.bindings.ShaderDataDwords()); prepared.user_data.resize(program.bindings.ShaderDataDwords());
if (ShaderRecompiler::IR::FindBinding( if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) { program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) {
descriptors.gds.buffer = m_context.GetBufferCache().GetGdsBuffer().Handle(); descriptors.gds.buffer = m_context.GetBufferCache().GetGdsBuffer()->Handle();
} }
return prepared; return prepared;
} }
void RenderExecutor::RebindBuffers(CommandBuffer& buffer, void RenderExecutor::FindBuffers(DescriptorCache::PreparedBindings& prepared) {
DescriptorCache::PreparedBindings& prepared) { KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.program == nullptr || prepared.snapshot == nullptr);
const auto& program = *prepared.program;
const auto& snapshot = *prepared.snapshot;
auto& cache = m_context.GetBufferCache();
prepared.buffer_ids.clear();
prepared.buffer_ids.reserve(program.info.buffers.size());
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
ShaderBufferResource descriptor;
CopyNativeDescriptor(snapshot.buffers[i], descriptor.fields);
const auto address = descriptor.Base48();
const auto stride = descriptor.Stride();
const auto records = descriptor.NumRecords();
EXIT_IF(stride != 0 && records > UINT64_MAX / stride);
const auto requested_size =
stride != 0 ? static_cast<uint64_t>(stride) * records : records;
if (address == 0 || requested_size == 0) {
prepared.buffer_ids.emplace_back();
continue;
}
const auto size = Libs::LibKernel::Memory::ClampRangeSize(address, requested_size);
prepared.buffer_ids.push_back(cache.FindBuffer(address, size));
}
prepared.address_ids.clear();
prepared.address_ids.reserve(program.info.addresses.size());
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
const auto& resource = program.info.addresses[i];
const auto& address = snapshot.addresses[i];
if (address.binding_base == 0) {
prepared.address_ids.emplace_back();
continue;
}
EXIT_IF(resource.written);
const auto limit =
resource.kind == ShaderRecompiler::IR::ResourceKind::Flat
? ShaderRecompiler::IR::FlatAddressWindowSize
: static_cast<uint64_t>(m_context.GetGraphics()
.GetPhysicalDeviceProperties()
.limits.maxStorageBufferRange);
uint64_t size = 0;
EXIT_IF(!HostMemoryQueryRange(address.binding_base, limit, HostMemoryAccess::Mapped, size));
prepared.address_ids.push_back(cache.FindBuffer(address.binding_base, size));
}
}
void RenderExecutor::RebindBuffers(DescriptorCache::PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.program == nullptr || prepared.snapshot == nullptr); EXIT_IF(prepared.program == nullptr || prepared.snapshot == nullptr);
const auto& program = *prepared.program; const auto& program = *prepared.program;
const auto& snapshot = *prepared.snapshot; const auto& snapshot = *prepared.snapshot;
auto& resources = prepared.resources; auto& resources = prepared.resources;
const auto& layout = program.bindings; const auto& layout = program.bindings;
EXIT_IF(prepared.buffer_ids.size() != program.info.buffers.size() ||
prepared.address_ids.size() != program.info.addresses.size());
resources.buffers.clear(); resources.buffers.clear();
resources.buffers.reserve(program.info.buffers.size()); resources.buffers.reserve(program.info.buffers.size());
@@ -926,9 +971,9 @@ void RenderExecutor::RebindBuffers(CommandBuffer& buffer,
ShaderBufferResource descriptor; ShaderBufferResource descriptor;
CopyNativeDescriptor(snapshot.buffers[i], descriptor.fields); CopyNativeDescriptor(snapshot.buffers[i], descriptor.fields);
uint32_t buffer_offset = 0; uint32_t buffer_offset = 0;
resources.buffers.push_back(NativeStorageBuffer(m_context, buffer, descriptor, resources.buffers.push_back(NativeStorageBuffer(m_context, descriptor,
program.info.buffers[i], prepared.stage, i, program.info.buffers[i], prepared.stage, i,
buffer_offset)); buffer_offset, prepared.buffer_ids[i]));
pack_memory_offset(i, buffer_offset); pack_memory_offset(i, buffer_offset);
} }
resources.addresses.clear(); resources.addresses.clear();
@@ -936,14 +981,20 @@ void RenderExecutor::RebindBuffers(CommandBuffer& buffer,
for (uint32_t i = 0; i < program.info.addresses.size(); i++) { for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
uint32_t address_offset = 0; uint32_t address_offset = 0;
resources.addresses.push_back( resources.addresses.push_back(
NativeAddressBuffer(m_context, buffer, program.info.addresses[i], snapshot.addresses[i], NativeAddressBuffer(m_context, program.info.addresses[i], snapshot.addresses[i],
prepared.stage, i, address_offset)); prepared.stage, i, address_offset, prepared.address_ids[i]));
pack_memory_offset(static_cast<uint32_t>(program.info.buffers.size()) + i, address_offset); pack_memory_offset(static_cast<uint32_t>(program.info.buffers.size()) + i, address_offset);
} }
if (!prepared.flattened_srt.empty()) {
resources.flattened_srt = NativeUpload(m_context, prepared.flattened_srt);
}
if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::UserData) != nullptr) {
resources.user_data = NativeUpload(m_context, prepared.user_data);
}
} }
void RenderExecutor::RebindImages(CommandBuffer& buffer, void RenderExecutor::RebindImages(DescriptorCache::PreparedBindings& prepared) {
DescriptorCache::PreparedBindings& prepared) {
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
EXIT_IF(prepared.program == nullptr || prepared.snapshot == nullptr); EXIT_IF(prepared.program == nullptr || prepared.snapshot == nullptr);
const auto& program = *prepared.program; const auto& program = *prepared.program;
@@ -952,7 +1003,7 @@ void RenderExecutor::RebindImages(CommandBuffer& buffer,
EXIT_IF(images.size() != program.info.images.size()); EXIT_IF(images.size() != program.info.images.size());
auto& texture_cache = m_context.GetTextureCache(); auto& texture_cache = m_context.GetTextureCache();
for (uint32_t i = 0; i < program.info.images.size(); i++) { for (uint32_t i = 0; i < program.info.images.size(); i++) {
const auto old_image = texture_cache.ResolveOwner(images[i].image_id); const auto old_image = texture_cache.m_slot_images.try_get(images[i].image_id);
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) || if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
old_image->binding.needs_rebind) { old_image->binding.needs_rebind) {
if (old_image != nullptr) { if (old_image != nullptr) {
@@ -962,6 +1013,8 @@ void RenderExecutor::RebindImages(CommandBuffer& buffer,
BindImage(images[i].image_id, BindImage(images[i].image_id,
images[i].desc.type == TextureCache::BindingType::Storage); images[i].desc.type == TextureCache::BindingType::Storage);
} }
}
for (uint32_t i = 0; i < program.info.images.size(); i++) {
auto& binding = images[i]; auto& binding = images[i];
binding.mip_views.clear(); binding.mip_views.clear();
const auto& resource = program.info.images[i]; const auto& resource = program.info.images[i];
@@ -991,42 +1044,31 @@ void RenderExecutor::RebindImages(CommandBuffer& buffer,
} }
RenderExecutor::GraphicsBindings RenderExecutor::GraphicsBindings
RenderExecutor::PrepareGraphicsBindings(CommandBuffer& buffer, const ShaderStageRuntime& vertex, RenderExecutor::PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
const ShaderStageRuntime& pixel, bool pixel_active) { const ShaderStageRuntime& pixel, bool pixel_active) {
GraphicsBindings bindings { GraphicsBindings bindings {
.vertex = PrepareBindings(buffer, vertex, vk::ShaderStageFlagBits::eVertex, .vertex = PrepareBindings(vertex, vk::ShaderStageFlagBits::eVertex,
DescriptorCache::Stage::Vertex), DescriptorCache::Stage::Vertex),
}; };
RebindBuffers(buffer, bindings.vertex);
RebindImages(buffer, bindings.vertex);
if (pixel_active) { if (pixel_active) {
bindings.pixel.emplace(PrepareBindings(buffer, pixel, vk::ShaderStageFlagBits::eFragment, bindings.pixel.emplace(PrepareBindings(pixel, vk::ShaderStageFlagBits::eFragment,
DescriptorCache::Stage::Pixel)); DescriptorCache::Stage::Pixel));
RebindBuffers(buffer, *bindings.pixel); }
RebindImages(buffer, *bindings.pixel); FindBuffers(bindings.vertex);
if (bindings.pixel) {
FindBuffers(*bindings.pixel);
}
RebindBuffers(bindings.vertex);
if (bindings.pixel) {
RebindBuffers(*bindings.pixel);
}
RebindImages(bindings.vertex);
if (bindings.pixel) {
RebindImages(*bindings.pixel);
} }
return bindings; return bindings;
} }
static void RetainBindings(CommandBuffer& buffer, DescriptorCache::NativeDescriptors& resources) {
if (resources.flattened_srt.owner != nullptr) {
buffer.RetainResourceUntilFence(std::move(resources.flattened_srt.owner));
}
if (resources.user_data.owner != nullptr) {
buffer.RetainResourceUntilFence(std::move(resources.user_data.owner));
}
for (auto& view: resources.buffers) {
if (view.owner != nullptr) {
buffer.RetainResourceUntilFence(std::move(view.owner));
}
}
for (auto& view: resources.addresses) {
if (view.owner != nullptr) {
buffer.RetainResourceUntilFence(std::move(view.owner));
}
}
}
void RenderExecutor::CommitBindings(CommandBuffer& buffer, void RenderExecutor::CommitBindings(CommandBuffer& buffer,
vk::PipelineBindPoint pipeline_bind_point, vk::PipelineBindPoint pipeline_bind_point,
vk::PipelineLayout layout, vk::PipelineLayout layout,
@@ -1036,15 +1078,6 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
prepared.committed); prepared.committed);
const auto& program = *prepared.program; const auto& program = *prepared.program;
auto& descriptors = prepared.resources; auto& descriptors = prepared.resources;
if (!prepared.flattened_srt.empty()) {
descriptors.flattened_srt = NativeUpload(m_context, buffer, prepared.flattened_srt);
}
if (ShaderRecompiler::IR::FindBinding(
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::UserData) != nullptr) {
descriptors.user_data = NativeUpload(m_context, buffer, prepared.user_data);
}
RetainBindings(buffer, descriptors);
auto vk_buffer = buffer.Handle(); auto vk_buffer = buffer.Handle();
const auto shader_stages = ShaderPipelineStages(prepared.shader_stage); const auto shader_stages = ShaderPipelineStages(prepared.shader_stage);
if (descriptors.gds.buffer != nullptr) { if (descriptors.gds.buffer != nullptr) {
+7 -29
View File
@@ -9,7 +9,6 @@
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include <array> #include <array>
#include <memory>
#include <vector> #include <vector>
namespace Libs::Graphics { namespace Libs::Graphics {
@@ -24,7 +23,6 @@ struct GraphicContext;
struct ShaderBufferResource; struct ShaderBufferResource;
struct ShaderComputeInputInfo; struct ShaderComputeInputInfo;
struct CommandSlot; struct CommandSlot;
struct VulkanBuffer;
struct VulkanDescriptorSet; struct VulkanDescriptorSet;
struct RenderDepthInfo; struct RenderDepthInfo;
struct RenderColorInfo; struct RenderColorInfo;
@@ -49,20 +47,6 @@ enum class CommandBufferDebugOp : uint32_t {
Unknown, Unknown,
}; };
class FenceResourceRetainer {
public:
FenceResourceRetainer() = default;
~FenceResourceRetainer();
KYTY_CLASS_NO_COPY(FenceResourceRetainer);
void Retain(std::shared_ptr<void> resource);
void ReleaseAfterFence() noexcept;
[[nodiscard]] bool Empty() const noexcept { return m_resources.empty(); }
private:
std::vector<std::shared_ptr<void>> m_resources;
};
struct SubmitInfo { struct SubmitInfo {
static constexpr uint32_t MaxSemaphores = 3; static constexpr uint32_t MaxSemaphores = 3;
@@ -108,8 +92,6 @@ public:
void WaitForFenceOnly(); void WaitForFenceOnly();
void WaitForFence(); void WaitForFence();
void WaitForFenceAndReset(); void WaitForFenceAndReset();
void RetireBufferAfterFence(std::unique_ptr<VulkanBuffer> buffer);
void RetainResourceUntilFence(std::shared_ptr<void> resource);
void RecycleDescriptorAfterFence(VulkanDescriptorSet& set); void RecycleDescriptorAfterFence(VulkanDescriptorSet& set);
[[nodiscard]] vk::CommandBuffer Handle() const; [[nodiscard]] vk::CommandBuffer Handle() const;
@@ -120,8 +102,6 @@ public:
private: private:
void Release(); void Release();
void FinalizeFence(bool reset_recording); void FinalizeFence(bool reset_recording);
void ReleaseResourcesAfterFence();
void DeleteBuffersAfterFence();
void RecycleDescriptorsAfterFence(); void RecycleDescriptorsAfterFence();
RenderContext& m_context; RenderContext& m_context;
@@ -138,8 +118,6 @@ private:
uint32_t m_debug_arg2 = 0; uint32_t m_debug_arg2 = 0;
uint32_t m_debug_arg3 = 0; uint32_t m_debug_arg3 = 0;
uint64_t m_debug_arg4 = 0; uint64_t m_debug_arg4 = 0;
std::vector<std::unique_ptr<VulkanBuffer>> m_retired_buffers;
FenceResourceRetainer m_fence_resources;
std::vector<VulkanDescriptorSet*> m_descriptor_sets_after_fence; std::vector<VulkanDescriptorSet*> m_descriptor_sets_after_fence;
mutable RenderState m_render_state; mutable RenderState m_render_state;
mutable bool m_rendering = false; mutable bool m_rendering = false;
@@ -182,10 +160,11 @@ public:
uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode); uint32_t thread_group_y, uint32_t thread_group_z, uint32_t mode);
[[nodiscard]] DescriptorCache::PreparedBindings [[nodiscard]] DescriptorCache::PreparedBindings
PrepareBindings(CommandBuffer& buffer, const ShaderStageRuntime& runtime, PrepareBindings(const ShaderStageRuntime& runtime, vk::ShaderStageFlags shader_stage,
vk::ShaderStageFlags shader_stage, DescriptorCache::Stage stage); DescriptorCache::Stage stage);
void RebindBuffers(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings); void FindBuffers(DescriptorCache::PreparedBindings& bindings);
void RebindImages(CommandBuffer& buffer, DescriptorCache::PreparedBindings& bindings); void RebindBuffers(DescriptorCache::PreparedBindings& bindings);
void RebindImages(DescriptorCache::PreparedBindings& bindings);
void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point, void CommitBindings(CommandBuffer& buffer, vk::PipelineBindPoint pipeline_bind_point,
vk::PipelineLayout layout, DescriptorCache::PreparedBindings& bindings); vk::PipelineLayout layout, DescriptorCache::PreparedBindings& bindings);
@@ -198,8 +177,7 @@ private:
[[nodiscard]] DescriptorCache::TextureBinding [[nodiscard]] DescriptorCache::TextureBinding
ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource, ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
const ShaderRecompiler::IR::DescriptorValue& value); const ShaderRecompiler::IR::DescriptorValue& value);
[[nodiscard]] GraphicsBindings PrepareGraphicsBindings(CommandBuffer& buffer, [[nodiscard]] GraphicsBindings PrepareGraphicsBindings(const ShaderStageRuntime& vertex,
const ShaderStageRuntime& vertex,
const ShaderStageRuntime& pixel, const ShaderStageRuntime& pixel,
bool pixel_active); bool pixel_active);
void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer, void ResolveRenderColorTarget(uint64_t submit_id, RenderCommandBuffer& buffer,
@@ -230,7 +208,7 @@ private:
const RenderCommandBuffer& buffer); const RenderCommandBuffer& buffer);
RenderContext& m_context; RenderContext& m_context;
std::vector<std::shared_ptr<Image>> m_bound_images; std::vector<ImageId> m_bound_images;
friend struct RenderExecutorTestAccess; friend struct RenderExecutorTestAccess;
}; };
@@ -168,6 +168,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
uint32_t thread_group_x, uint32_t thread_group_y, uint32_t thread_group_x, uint32_t thread_group_y,
uint32_t thread_group_z, uint32_t mode) { uint32_t thread_group_z, uint32_t mode) {
EXIT_IF(buffer.IsInvalid()); EXIT_IF(buffer.IsInvalid());
m_context.GetCommandScheduler().PopPendingOperations();
auto& ctx = buffer.GetRegisters(); auto& ctx = buffer.GetRegisters();
auto& sh_ctx = buffer.GetShaders(); auto& sh_ctx = buffer.GetShaders();
@@ -333,10 +334,11 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
buffer.EndRendering(); buffer.EndRendering();
auto& pipeline = auto& pipeline =
m_context.GetPipelineCache().CreateComputePipeline(input_info, sh_ctx.GetCs(), cs_shader); m_context.GetPipelineCache().CreateComputePipeline(input_info, sh_ctx.GetCs(), cs_shader);
auto bindings = PrepareBindings(buffer, input_info.stage, vk::ShaderStageFlagBits::eCompute, auto bindings = PrepareBindings(input_info.stage, vk::ShaderStageFlagBits::eCompute,
DescriptorCache::Stage::Compute); DescriptorCache::Stage::Compute);
RebindBuffers(buffer, bindings); FindBuffers(bindings);
RebindImages(buffer, bindings); RebindBuffers(bindings);
RebindImages(bindings);
auto vk_buffer = buffer.Handle(); auto vk_buffer = buffer.Handle();
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline.pipeline_layout, bindings); CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline.pipeline_layout, bindings);
+25 -49
View File
@@ -616,7 +616,7 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
for (uint32_t i = 0; i < color_count; i++) { for (uint32_t i = 0; i < color_count; i++) {
auto& target = colors[i]; auto& target = colors[i];
EXIT_IF(!target.image_id); EXIT_IF(!target.image_id);
const auto old_image = cache.ResolveOwner(target.image_id); const auto old_image = cache.m_slot_images.try_get(target.image_id);
if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) || if (old_image == nullptr || (!old_image->registered && !old_image->info.data.Empty()) ||
old_image->binding.needs_rebind) { old_image->binding.needs_rebind) {
if (old_image != nullptr) { if (old_image != nullptr) {
@@ -668,7 +668,7 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo
attachment.is_clear = target.color_clear_enable || metadata_clear; attachment.is_clear = target.color_clear_enable || metadata_clear;
} }
if (depth.image_id) { if (depth.image_id) {
const auto owner = cache.ResolveOwner(depth.image_id); const auto owner = cache.m_slot_images.try_get(depth.image_id);
if (owner == nullptr || !owner->registered || owner->binding.needs_rebind) { if (owner == nullptr || !owner->registered || owner->binding.needs_rebind) {
EXIT("depth target changed after render-state discovery\n"); EXIT("depth target changed after render-state discovery\n");
} }
@@ -786,7 +786,6 @@ struct DrawIndexBufferSource {
}; };
struct PreparedIndexBuffer { struct PreparedIndexBuffer {
std::shared_ptr<void> owner;
vk::Buffer buffer = nullptr; vk::Buffer buffer = nullptr;
uint64_t size = 0; uint64_t size = 0;
vk::DeviceSize offset = 0; vk::DeviceSize offset = 0;
@@ -802,7 +801,7 @@ struct VertexBufferRange {
uint64_t base_address = 0; uint64_t base_address = 0;
uint64_t requested_end = 0; uint64_t requested_end = 0;
uint64_t acquired_end = 0; uint64_t acquired_end = 0;
BufferBinding binding; std::pair<Buffer*, uint64_t> binding;
[[nodiscard]] uint64_t RequestedSize() const { return requested_end - base_address; } [[nodiscard]] uint64_t RequestedSize() const { return requested_end - base_address; }
}; };
@@ -812,9 +811,7 @@ struct PreparedVertexBuffers {
std::array<vk::Buffer, MaxBuffers> buffers {}; std::array<vk::Buffer, MaxBuffers> buffers {};
std::array<vk::DeviceSize, MaxBuffers> offsets {}; std::array<vk::DeviceSize, MaxBuffers> offsets {};
std::array<std::shared_ptr<void>, MaxBuffers> owners {};
uint32_t count = 0; uint32_t count = 0;
uint32_t owner_count = 0;
}; };
static PreparedVertexBuffers AcquireVertexBuffers(RenderCommandBuffer& buffer, static PreparedVertexBuffers AcquireVertexBuffers(RenderCommandBuffer& buffer,
@@ -864,8 +861,9 @@ static PreparedVertexBuffers AcquireVertexBuffers(RenderCommandBuffer& b
const auto size = const auto size =
Libs::LibKernel::Memory::ClampRangeSize(range.base_address, range.RequestedSize()); Libs::LibKernel::Memory::ClampRangeSize(range.base_address, range.RequestedSize());
range.acquired_end = range.base_address + size; range.acquired_end = range.base_address + size;
range.binding = cache.ObtainBuffer(buffer, range.base_address, size); range.binding = cache.ObtainBuffer(range.base_address, size, false);
SetVulkanObjectNameF(buffer.GetContext().GetGraphics().device, range.binding.buffer, SetVulkanObjectNameF(buffer.GetContext().GetGraphics().device,
range.binding.first->Handle(),
"Kyty.VertexBufferRange[guest=0x{:016x} size=0x{:x}]", "Kyty.VertexBufferRange[guest=0x{:016x} size=0x{:x}]",
range.base_address, size); range.base_address, size);
} }
@@ -873,15 +871,13 @@ static PreparedVertexBuffers AcquireVertexBuffers(RenderCommandBuffer& b
// Rebuild slot bindings, offsetting non-empty slots into their acquired merged range. // Rebuild slot bindings, offsetting non-empty slots into their acquired merged range.
PreparedVertexBuffers prepared; PreparedVertexBuffers prepared;
prepared.count = static_cast<uint32_t>(vs_input_info.buffers_num); prepared.count = static_cast<uint32_t>(vs_input_info.buffers_num);
std::shared_ptr<Buffer> null_owner;
vk::Buffer null_buffer = nullptr; vk::Buffer null_buffer = nullptr;
for (int i = 0; i < vs_input_info.buffers_num; i++) { for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i]; const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex); const auto size = VertexBufferDescriptorSize(vertex);
if (size == 0) { if (size == 0) {
if (null_owner == nullptr) { if (null_buffer == nullptr) {
null_owner = cache.ObtainNullBuffer(); null_buffer = cache.GetBuffer(NULL_BUFFER_ID).Handle();
null_buffer = null_owner->Handle();
} }
prepared.buffers[i] = null_buffer; prepared.buffers[i] = null_buffer;
prepared.offsets[i] = 0; prepared.offsets[i] = 0;
@@ -898,23 +894,14 @@ static PreparedVertexBuffers AcquireVertexBuffers(RenderCommandBuffer& b
vertex.addr); vertex.addr);
} }
prepared.buffers[i] = range->binding.buffer; prepared.buffers[i] = range->binding.first->Handle();
prepared.offsets[i] = range->binding.offset + vertex.addr - range->base_address; prepared.offsets[i] = range->binding.second + vertex.addr - range->base_address;
SetVulkanObjectNameF( SetVulkanObjectNameF(
buffer.GetContext().GetGraphics().device, prepared.buffers[i], buffer.GetContext().GetGraphics().device, prepared.buffers[i],
"Kyty.VertexBuffer[slot={} guest=0x{:016x} size=0x{:x} stride={} records={}]", i, "Kyty.VertexBuffer[slot={} guest=0x{:016x} size=0x{:x} stride={} records={}]", i,
vertex.addr, size, vertex.stride, vertex.num_records); vertex.addr, size, vertex.stride, vertex.num_records);
} }
if (null_owner != nullptr) {
prepared.owners[prepared.owner_count++] = std::move(null_owner);
}
for (uint32_t i = 0; i < merged_count; i++) {
if (merged_ranges[i].binding.owner != nullptr) {
EXIT_IF(prepared.owner_count >= PreparedVertexBuffers::MaxBuffers);
prepared.owners[prepared.owner_count++] = std::move(merged_ranges[i].binding.owner);
}
}
return prepared; return prepared;
} }
@@ -1100,17 +1087,14 @@ static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffe
prepared.size = source.size; prepared.size = source.size;
prepared.type = source.type; prepared.type = source.type;
if (source.host_data != nullptr) { if (source.host_data != nullptr) {
auto binding = auto& stream = buffer.GetContext().GetBufferCache().GetUtilityBuffer(MemoryUsage::Stream);
buffer.GetContext().GetBufferCache().UploadTransient(source.host_data, source.size, 16); prepared.offset = stream.Copy(source.host_data, source.size, 16);
prepared.owner = std::move(binding.owner); prepared.buffer = stream.Handle();
prepared.buffer = binding.buffer;
prepared.offset = binding.offset;
} else { } else {
auto binding = auto [buffer_ptr, offset] = buffer.GetContext().GetBufferCache().ObtainBuffer(
buffer.GetContext().GetBufferCache().ObtainBuffer(buffer, source.address, source.size); source.address, source.size, false);
prepared.owner = std::move(binding.owner); prepared.buffer = buffer_ptr->Handle();
prepared.buffer = binding.buffer; prepared.offset = offset;
prepared.offset = binding.offset;
} }
if (source.host_data != nullptr) { if (source.host_data != nullptr) {
SetVulkanObjectNameF(buffer.GetContext().GetGraphics().device, prepared.buffer, SetVulkanObjectNameF(buffer.GetContext().GetGraphics().device, prepared.buffer,
@@ -1124,13 +1108,7 @@ static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffe
return prepared; return prepared;
} }
static void CommitVertexBuffers(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer, static void CommitVertexBuffers(vk::CommandBuffer vk_buffer, const PreparedVertexBuffers& prepared) {
PreparedVertexBuffers& prepared) {
for (uint32_t i = 0; i < prepared.owner_count; i++) {
if (prepared.owners[i] != nullptr) {
buffer.RetainResourceUntilFence(std::move(prepared.owners[i]));
}
}
for (uint32_t i = 0; i < prepared.count; i++) { for (uint32_t i = 0; i < prepared.count; i++) {
EXIT_IF(prepared.buffers[i] == nullptr); EXIT_IF(prepared.buffers[i] == nullptr);
} }
@@ -1140,14 +1118,10 @@ static void CommitVertexBuffers(RenderCommandBuffer& buffer, vk::CommandBuffer v
} }
} }
static void CommitIndexBuffer(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer, static void CommitIndexBuffer(vk::CommandBuffer vk_buffer, const PreparedIndexBuffer& prepared) {
PreparedIndexBuffer& prepared) {
if (prepared.size == 0) { if (prepared.size == 0) {
return; return;
} }
if (prepared.owner != nullptr) {
buffer.RetainResourceUntilFence(std::move(prepared.owner));
}
EXIT_IF(prepared.buffer == nullptr); EXIT_IF(prepared.buffer == nullptr);
vk_buffer.bindIndexBuffer(prepared.buffer, prepared.offset, prepared.type); vk_buffer.bindIndexBuffer(prepared.buffer, prepared.offset, prepared.type);
} }
@@ -1230,8 +1204,8 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
auto& ucfg = buffer.GetUserConfig(); auto& ucfg = buffer.GetUserConfig();
LogDrawPhase(draw.name, "PrepareBindings"); LogDrawPhase(draw.name, "PrepareBindings");
auto bindings = PrepareGraphicsBindings(buffer, state.vs_input_info.stage, auto bindings = PrepareGraphicsBindings(state.vs_input_info.stage, state.ps_input_info.stage,
state.ps_input_info.stage, state.ps_active); state.ps_active);
auto vertex_bindings = PrepareVertexBuffers(submit_id, buffer, draw, state.vs_input_info); auto vertex_bindings = PrepareVertexBuffers(submit_id, buffer, draw, state.vs_input_info);
auto index_binding = PrepareIndexBuffer(buffer, index_source); auto index_binding = PrepareIndexBuffer(buffer, index_source);
state.rendering = state.rendering =
@@ -1255,7 +1229,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
if (set_auto_debug) { if (set_auto_debug) {
SetDrawDebugPhase(buffer, submit_id, draw, 0x200u); SetDrawDebugPhase(buffer, submit_id, draw, 0x200u);
} }
CommitVertexBuffers(buffer, vk_buffer, vertex_bindings); CommitVertexBuffers(vk_buffer, vertex_bindings);
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout, CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
bindings.vertex); bindings.vertex);
if (bindings.pixel.has_value()) { if (bindings.pixel.has_value()) {
@@ -1265,7 +1239,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, RenderCommandBuffer
CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout, CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline.pipeline_layout,
*bindings.pixel); *bindings.pixel);
} }
CommitIndexBuffer(buffer, vk_buffer, index_binding); CommitIndexBuffer(vk_buffer, index_binding);
const auto dynamic_params = const auto dynamic_params =
BuildGraphicsDynamicParams(buffer, state.color_info, state.color_count, state.depth_info); BuildGraphicsDynamicParams(buffer, state.color_info, state.color_count, state.depth_info);
@@ -1310,6 +1284,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, RenderCommandBuffer& buffer,
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
EXIT_IF(buffer.IsInvalid()); EXIT_IF(buffer.IsInvalid());
m_context.GetCommandScheduler().PopPendingOperations();
auto& ucfg = buffer.GetUserConfig(); auto& ucfg = buffer.GetUserConfig();
auto& sh_ctx = buffer.GetShaders(); auto& sh_ctx = buffer.GetShaders();
@@ -1441,6 +1416,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, RenderCommandBuffer& buffer, u
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
EXIT_IF(buffer.IsInvalid()); EXIT_IF(buffer.IsInvalid());
m_context.GetCommandScheduler().PopPendingOperations();
auto& ucfg = buffer.GetUserConfig(); auto& ucfg = buffer.GetUserConfig();
auto& sh_ctx = buffer.GetShaders(); auto& sh_ctx = buffer.GetShaders();
+1 -1
View File
@@ -390,7 +390,7 @@ int DeleteEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id) {
return result; return result;
} }
void ReadGds(Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) { void ReadGds(const Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
const auto offset = uint64_t {dw_offset} * sizeof(uint32_t); const auto offset = uint64_t {dw_offset} * sizeof(uint32_t);
const auto size = uint64_t {dw_size} * sizeof(uint32_t); const auto size = uint64_t {dw_size} * sizeof(uint32_t);
EXIT_IF(dst == nullptr || offset > gds.Size() || size > gds.Size() - offset || EXIT_IF(dst == nullptr || offset > gds.Size() || size > gds.Size() - offset ||
+1 -1
View File
@@ -62,7 +62,7 @@ void WriteAtEndOfPipeWithInterruptWriteBackFlip32(uint64_t submit_id, CommandBuf
int AddEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id, int AddEqEvent(RenderContext& renderer, LibKernel::EventQueue::KernelEqueue eq, int id,
void* udata); void* udata);
int DeleteEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id); int DeleteEqEvent(LibKernel::EventQueue::KernelEqueue eq, int id);
void ReadGds(Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size); void ReadGds(const Buffer& gds, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
} // namespace Sync } // namespace Sync
} // namespace Libs::Graphics } // namespace Libs::Graphics
-14
View File
@@ -199,20 +199,6 @@ void GraphicContext::CreateBuffer(uint64_t size, VulkanBuffer& buffer) {
VulkanTrackAllocation(buffer.memory); VulkanTrackAllocation(buffer.memory);
} }
void GraphicContext::DeleteBuffer(VulkanBuffer& buffer) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(allocator == nullptr || buffer.buffer == nullptr ||
buffer.memory.allocation == nullptr);
VulkanUntrackAllocation(buffer.memory);
vmaDestroyBuffer(allocator, buffer.buffer, buffer.memory.allocation);
buffer.buffer = nullptr;
buffer.memory.memory = nullptr;
buffer.memory.allocation = nullptr;
buffer.memory.allocation_info = {};
buffer.memory.offset = 0;
}
bool GraphicContext::CreateImage(const vk::ImageCreateInfo& image_info, VulkanImage& image) { bool GraphicContext::CreateImage(const vk::ImageCreateInfo& image_info, VulkanImage& image) {
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
EXIT_IF(allocator == nullptr || image.image != nullptr || image.memory.allocation != nullptr); EXIT_IF(allocator == nullptr || image.image != nullptr || image.memory.allocation != nullptr);
File diff suppressed because it is too large Load Diff