mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-26 19:11:00 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b807013aac |
@@ -8,7 +8,14 @@
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#else
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -19,78 +26,92 @@
|
|||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
static std::vector<std::pair<u64, u64>> vector_regions {};
|
|
||||||
|
|
||||||
// Workaround for handling non-commited memory accessed by Dynarmic; usually result of an error
|
struct VectorRegion {
|
||||||
|
u64 start_page;
|
||||||
|
u64 end_page;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::mutex& GetVectorRegionsMutex() {
|
||||||
|
static std::mutex* m = new std::mutex();
|
||||||
|
return *m;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<VectorRegion>& GetVectorRegions() {
|
||||||
|
static std::vector<VectorRegion>* v = new std::vector<VectorRegion>();
|
||||||
|
return *v;
|
||||||
|
}
|
||||||
|
|
||||||
static LONG WINAPI FakePageFaultHandler(PEXCEPTION_POINTERS info) {
|
static LONG WINAPI FakePageFaultHandler(PEXCEPTION_POINTERS info) {
|
||||||
DWORD code = info->ExceptionRecord->ExceptionCode;
|
if (info->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION) {
|
||||||
u64 exception_addr = reinterpret_cast<u64>(info->ExceptionRecord->ExceptionAddress);
|
|
||||||
|
|
||||||
if (code != EXCEPTION_ACCESS_VIOLATION) {
|
|
||||||
// Not our problem
|
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 addr = 0, addr2 = 0;
|
const u64 fault_addr = info->ExceptionRecord->ExceptionInformation[1];
|
||||||
|
const u64 access_type = info->ExceptionRecord->ExceptionInformation[0];
|
||||||
|
const bool is_write = (access_type == 1);
|
||||||
|
const u64 fault_page = fault_addr >> HostPageBits;
|
||||||
|
|
||||||
for (auto region: vector_regions) {
|
u64 addr = 0;
|
||||||
auto addr_shifted = exception_addr >> HostPageBits;
|
u64 addr2 = 0;
|
||||||
if (region.first <= addr_shifted && addr_shifted <= region.second) {
|
|
||||||
addr = addr_shifted;
|
{
|
||||||
|
std::lock_guard lock(GetVectorRegionsMutex());
|
||||||
|
for (const auto& region : GetVectorRegions()) {
|
||||||
|
if (fault_page >= region.start_page && fault_page < region.end_page) {
|
||||||
|
addr = fault_page;
|
||||||
}
|
}
|
||||||
|
const u64 page2 = (fault_addr + 0x3F) >> HostPageBits;
|
||||||
// Page-boundary accesses
|
if (page2 != fault_page && page2 >= region.start_page && page2 < region.end_page) {
|
||||||
if (auto addr_ = (exception_addr + 0x40) >> HostPageBits; addr_ != addr_shifted && region.first <= addr_ && addr_ <= region.second) {
|
addr2 = page2;
|
||||||
addr2 = addr_;
|
|
||||||
}
|
}
|
||||||
|
if (addr != 0 || addr2 != 0) break;
|
||||||
if (addr != 0 || addr2 != 0) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addr == 0 && addr2 == 0) {
|
if (addr == 0 && addr2 == 0) {
|
||||||
// Not our problem
|
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_ERROR(HW_Memory, "Accessing an unallocated region of a SparseLargeVector at {:#x}; this shouldn't happen and is likely a Dynarmic error!", exception_addr);
|
LOG_ERROR(HW_Memory, "Accessing an unallocated region of a SparseLargeVector at {:#x}; this shouldn't happen and is likely a Dynarmic error!", fault_addr);
|
||||||
|
|
||||||
// Commit this region
|
if (addr != 0 && !CommitVectorPage(addr << HostPageBits, is_write)) {
|
||||||
if (addr != 0) {
|
|
||||||
if (!CommitVectorPage(addr << HostPageBits, false)) {
|
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
}
|
}
|
||||||
}
|
if (addr2 != 0 && !CommitVectorPage(addr2 << HostPageBits, is_write)) {
|
||||||
// Commit next region if needed
|
|
||||||
if (addr2 != 0) {
|
|
||||||
if (!CommitVectorPage(addr2 << HostPageBits, false)) {
|
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return EXCEPTION_CONTINUE_EXECUTION;
|
return EXCEPTION_CONTINUE_EXECUTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CommitVectorPage(uintptr_t addr, bool write) noexcept {
|
bool CommitVectorPage(uintptr_t addr, bool write) noexcept {
|
||||||
MEMORY_BASIC_INFORMATION info {};
|
MEMORY_BASIC_INFORMATION info {};
|
||||||
auto res = VirtualQuery(reinterpret_cast<void*>(addr), &info, sizeof(info));
|
const auto res = VirtualQuery(reinterpret_cast<void*>(addr), &info, sizeof(info));
|
||||||
|
const DWORD perm = write ? PAGE_READWRITE : PAGE_READONLY;
|
||||||
|
|
||||||
if (res == 0) {
|
if (res == 0) {
|
||||||
LOG_CRITICAL(HW_Memory, "Failed to query large buffer region at {:#x} with error {}, will try committing anyway", addr, GetLastError());
|
LOG_CRITICAL(HW_Memory, "Failed to query large buffer region at {:#x} with error {}, will try committing anyway", addr, GetLastError());
|
||||||
|
} else if (info.State == MEM_COMMIT) {
|
||||||
|
DWORD old_protect {};
|
||||||
|
if (!VirtualProtect(reinterpret_cast<void*>(addr), HostPageSize, perm, &old_protect)) {
|
||||||
|
LOG_ERROR(HW_Memory, "VirtualProtect failed at {:#x}, error {}", addr, GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
} else if (info.State != MEM_RESERVE) {
|
} else if (info.State != MEM_RESERVE) {
|
||||||
LOG_ERROR(HW_Memory, "Tried to commit an unreserved large buffer region at {:#x} that is not mapped or is already committed (state {:#x})", addr, info.State);
|
LOG_ERROR(HW_Memory, "Tried to commit an unreserved large buffer region at {:#x} (state {:#x})", addr, info.State);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto perm = write ? PAGE_READWRITE : PAGE_READONLY;
|
if (VirtualAlloc(reinterpret_cast<LPVOID>(addr), HostPageSize, MEM_COMMIT, perm) == nullptr) {
|
||||||
void* res2 = VirtualAlloc(reinterpret_cast<LPVOID>(addr), HostPageSize, MEM_COMMIT, perm);
|
|
||||||
if (res2 == nullptr) {
|
|
||||||
LOG_ERROR(HW_Memory, "Failed to commit large buffer region at {:#x}, error {}", addr, GetLastError());
|
LOG_ERROR(HW_Memory, "Failed to commit large buffer region at {:#x}, error {}", addr, GetLastError());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef MAP_NOCORE
|
#ifndef MAP_NOCORE
|
||||||
@@ -102,56 +123,102 @@ bool CommitVectorPage(uintptr_t addr, bool write) noexcept {
|
|||||||
|
|
||||||
void DecommitVectorPage(uintptr_t base) noexcept {
|
void DecommitVectorPage(uintptr_t base) noexcept {
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
VirtualFree(reinterpret_cast<LPVOID>(base), HostPageSize, MEM_DECOMMIT);
|
if (!VirtualFree(reinterpret_cast<LPVOID>(base), HostPageSize, MEM_DECOMMIT)) {
|
||||||
|
LOG_WARNING(HW_Memory, "VirtualFree(MEM_DECOMMIT) failed at {:#x}, error {}", base, GetLastError());
|
||||||
|
}
|
||||||
#elif defined(__linux__)
|
#elif defined(__linux__)
|
||||||
// Linux's MADV_DONTNEED zeros out pages for us
|
if (madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_DONTNEED) != 0) {
|
||||||
madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_DONTNEED);
|
LOG_WARNING(HW_Memory, "madvise(MADV_DONTNEED) failed at {:#x}: {}", base, std::strerror(errno));
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_FREE);
|
if (madvise(reinterpret_cast<void*>(base), HostPageSize, MADV_FREE) != 0) {
|
||||||
|
LOG_WARNING(HW_Memory, "madvise(MADV_FREE) failed at {:#x}: {}", base, std::strerror(errno));
|
||||||
|
}
|
||||||
std::memset(reinterpret_cast<void*>(base), 0, HostPageSize);
|
std::memset(reinterpret_cast<void*>(base), 0, HostPageSize);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void* AllocateMemoryPages(std::size_t size) noexcept {
|
void* AllocateMemoryPages(std::size_t size) noexcept {
|
||||||
if (auto page = HostPageSize; size % page != 0) {
|
if (size == 0) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto page = HostPageSize;
|
||||||
|
if (size % page != 0) {
|
||||||
LOG_WARNING(HW_Memory, "Allocating unaligned large vector with size {:#x}; aligning to {} page size", size, page);
|
LOG_WARNING(HW_Memory, "Allocating unaligned large vector with size {:#x}; aligning to {} page size", size, page);
|
||||||
|
if (size > SIZE_MAX - (page - 1)) {
|
||||||
|
LOG_CRITICAL(HW_Memory, "Size {:#x} would overflow page alignment", size);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
size = AlignUp(size, page);
|
size = AlignUp(size, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// We will never use this memory entirely so instead of committing it up front let's just reserve it and commit each page individually
|
|
||||||
void* base = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE);
|
void* base = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE);
|
||||||
|
|
||||||
if (base != nullptr) {
|
if (base != nullptr) {
|
||||||
vector_regions.emplace_back(reinterpret_cast<u64>(base), reinterpret_cast<u64>(base) + size);
|
{
|
||||||
|
std::lock_guard lock(GetVectorRegionsMutex());
|
||||||
|
GetVectorRegions().push_back({
|
||||||
|
reinterpret_cast<u64>(base) >> HostPageBits,
|
||||||
|
(reinterpret_cast<u64>(base) + size) >> HostPageBits,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static std::once_flag flag;
|
static std::once_flag flag;
|
||||||
std::call_once(flag, []() { AddVectoredExceptionHandler(1, FakePageFaultHandler); });
|
std::call_once(flag, []() { AddVectoredExceptionHandler(1, FakePageFaultHandler); });
|
||||||
} else {
|
} else {
|
||||||
// Try committing everything instead??
|
|
||||||
LOG_WARNING(HW_Memory, "Failed to reserve large vector region with error {}, trying to commit instead..", GetLastError());
|
LOG_WARNING(HW_Memory, "Failed to reserve large vector region with error {}, trying to commit instead..", GetLastError());
|
||||||
base = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE);
|
base = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE);
|
||||||
}
|
}
|
||||||
ASSERT_MSG(base, "Failed to reserve {:#x} sized region with error {}", size, GetLastError());
|
ASSERT_MSG(base, "Failed to reserve {:#x} sized region with error {}", size, GetLastError());
|
||||||
#else
|
#else
|
||||||
void* base = mmap(nullptr, size, PROT_READ, MAP_ANON | MAP_PRIVATE | MAP_NOCORE, -1, 0);
|
int flags = MAP_ANON | MAP_PRIVATE;
|
||||||
if (base == MAP_FAILED)
|
#ifdef MAP_NORESERVE
|
||||||
base = nullptr;
|
flags |= MAP_NORESERVE;
|
||||||
ASSERT_MSG(base, "Failed to allocate {:#x} sized region with error {}", size, strerror(errno));
|
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(MAP_NOCORE)
|
||||||
|
flags |= MAP_NOCORE;
|
||||||
|
#endif
|
||||||
|
void* base = mmap(nullptr, size, PROT_READ, flags, -1, 0);
|
||||||
|
if (base == MAP_FAILED) {
|
||||||
|
base = nullptr;
|
||||||
|
}
|
||||||
|
#ifdef MADV_HUGEPAGE
|
||||||
|
if (base != nullptr) {
|
||||||
|
madvise(base, size, MADV_HUGEPAGE);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
ASSERT_MSG(base, "Failed to allocate {:#x} sized region with error {}", size, std::strerror(errno));
|
||||||
|
#endif
|
||||||
|
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept {
|
void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept {
|
||||||
if (auto page = HostPageSize; size % page != 0) {
|
if (base == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto page = HostPageSize; size % page != 0) {
|
||||||
size = AlignUp(size, page);
|
size = AlignUp(size, page);
|
||||||
}
|
}
|
||||||
if (!base)
|
|
||||||
return;
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
ASSERT(VirtualFree(base, 0, MEM_RELEASE));
|
{
|
||||||
|
std::lock_guard lock(GetVectorRegionsMutex());
|
||||||
|
auto& regions = GetVectorRegions();
|
||||||
|
const u64 base_page = reinterpret_cast<u64>(base) >> HostPageBits;
|
||||||
|
regions.erase(std::remove_if(regions.begin(), regions.end(),
|
||||||
|
[base_page](const VectorRegion& r) { return r.start_page == base_page; }), regions.end());
|
||||||
|
}
|
||||||
|
if (!VirtualFree(base, 0, MEM_RELEASE)) {
|
||||||
|
LOG_ERROR(HW_Memory, "VirtualFree failed, error {}", GetLastError());
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
ASSERT(munmap(base, size) == 0);
|
if (munmap(base, size) != 0) {
|
||||||
|
LOG_ERROR(HW_Memory, "munmap failed: {}", std::strerror(errno));
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+145
-107
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
/* virtual_buffer.h */
|
/* virtual_buffer.h */
|
||||||
@@ -7,10 +7,14 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <bit>
|
#include <bit>
|
||||||
#include <utility>
|
#include <cerrno>
|
||||||
#include <vector>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -28,9 +32,9 @@ constexpr u64 HostPageBits = 12;
|
|||||||
constexpr u64 HostPageMask = ~(HostPageSize - 1);
|
constexpr u64 HostPageMask = ~(HostPageSize - 1);
|
||||||
bool CommitVectorPage(uintptr_t addr, bool write) noexcept;
|
bool CommitVectorPage(uintptr_t addr, bool write) noexcept;
|
||||||
#else
|
#else
|
||||||
const u64 HostPageSize = sysconf(_SC_PAGESIZE);
|
inline const u64 HostPageSize = static_cast<u64>(sysconf(_SC_PAGESIZE));
|
||||||
const u64 HostPageBits = std::countr_zero(HostPageSize);
|
inline const u64 HostPageBits = std::countr_zero(HostPageSize);
|
||||||
const u64 HostPageMask = ~(HostPageSize - 1);
|
inline const u64 HostPageMask = ~(HostPageSize - 1);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void* AllocateMemoryPages(std::size_t size) noexcept;
|
void* AllocateMemoryPages(std::size_t size) noexcept;
|
||||||
@@ -43,20 +47,18 @@ template <typename T>
|
|||||||
// requires std::is_trivially_copyable_v<T>
|
// requires std::is_trivially_copyable_v<T>
|
||||||
class SparseLargeVector final {
|
class SparseLargeVector final {
|
||||||
public:
|
public:
|
||||||
constexpr SparseLargeVector() = default;
|
SparseLargeVector() = default;
|
||||||
|
|
||||||
explicit SparseLargeVector(std::size_t count) noexcept
|
explicit SparseLargeVector(std::size_t count) noexcept {
|
||||||
: alloc_size{count * sizeof(T)}
|
if (count > SIZE_MAX / sizeof(T)) {
|
||||||
{
|
LOG_CRITICAL(Common_Memory, "SparseLargeVector size overflow: {} elements", count);
|
||||||
base_ptr = static_cast<T*>(AllocateMemoryPages(alloc_size));
|
return;
|
||||||
|
}
|
||||||
// each item in vector holds information for 64 pages
|
Allocate(count * sizeof(T));
|
||||||
auto denom = HostPageSize * 64;
|
|
||||||
committed_pages = std::vector<std::atomic<u64>>((alloc_size + denom - 1) / denom);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~SparseLargeVector() noexcept {
|
~SparseLargeVector() noexcept {
|
||||||
FreeMemoryPages(base_ptr, alloc_size);
|
Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
SparseLargeVector(const SparseLargeVector&) = delete;
|
SparseLargeVector(const SparseLargeVector&) = delete;
|
||||||
@@ -65,145 +67,181 @@ public:
|
|||||||
SparseLargeVector& operator=(SparseLargeVector&& other) = delete;
|
SparseLargeVector& operator=(SparseLargeVector&& other) = delete;
|
||||||
|
|
||||||
void ResizeAndClear(std::size_t count) noexcept {
|
void ResizeAndClear(std::size_t count) noexcept {
|
||||||
if (auto const new_size = count * sizeof(T); new_size != alloc_size) {
|
if (count > SIZE_MAX / sizeof(T)) {
|
||||||
FreeMemoryPages(base_ptr, alloc_size);
|
LOG_CRITICAL(Common_Memory, "SparseLargeVector resize overflow: {} elements", count);
|
||||||
alloc_size = new_size;
|
return;
|
||||||
base_ptr = static_cast<T*>(AllocateMemoryPages(alloc_size));
|
|
||||||
|
|
||||||
auto denom = HostPageSize * 64;
|
|
||||||
committed_pages = std::vector<std::atomic<u64>>((alloc_size + denom - 1) / denom);
|
|
||||||
}
|
}
|
||||||
|
const std::size_t new_size = count * sizeof(T);
|
||||||
|
if (new_size == alloc_size) {
|
||||||
|
ZeroRegion(0, alloc_size / sizeof(T));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Release();
|
||||||
|
Allocate(new_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the value of the requested index and allocates memory if needed.
|
|
||||||
T& GetAndFault(std::size_t index) noexcept {
|
T& GetAndFault(std::size_t index) noexcept {
|
||||||
if (index > alloc_size / sizeof(T)) {
|
if (base_ptr == nullptr || index >= size()) [[unlikely]] {
|
||||||
UNREACHABLE_MSG("Out of bounds RW access on SparseLargeVector @ {}", index);
|
LOG_CRITICAL(Common_Memory, "SparseLargeVector RW access out of bounds @ {} (size {})", index, size());
|
||||||
|
std::abort();
|
||||||
}
|
}
|
||||||
|
const u64 byte_offset = static_cast<u64>(index) * sizeof(T);
|
||||||
if (!IsCommittedPage(index)) {
|
if (!CommitPage(byte_offset)) [[unlikely]] {
|
||||||
CommitPage(index);
|
LOG_CRITICAL(Common_Memory, "SparseLargeVector commit failed @ {} (offset {:#x})", index, byte_offset);
|
||||||
|
std::abort();
|
||||||
}
|
}
|
||||||
return base_ptr[index];
|
return base_ptr[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the value of the requested index if initialized, or will otherwise return a zero-initialized object.
|
const T& GetOrDefault(std::size_t index) const noexcept {
|
||||||
const T& GetOrDefault(std::size_t index) const {
|
if (base_ptr == nullptr || index >= size()) [[unlikely]] {
|
||||||
|
LOG_CRITICAL(Common_Memory, "SparseLargeVector RO access out of bounds @ {}", index);
|
||||||
|
return DefaultValue();
|
||||||
|
}
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if (!IsCommittedPage(index)) {
|
if (!IsPageCommitted(static_cast<u64>(index) * sizeof(T))) {
|
||||||
return *reinterpret_cast<const T*>(&default_val);
|
return DefaultValue();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// On non-Windows, OS page table should optimize this by pointing to a zero page if unallocated.
|
|
||||||
return base_ptr[index];
|
return base_ptr[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
void Set(std::size_t index, const T& value) noexcept {
|
void Set(std::size_t index, const T& value) noexcept {
|
||||||
if (index > alloc_size / sizeof(T)) {
|
if (base_ptr == nullptr || index >= size()) [[unlikely]] {
|
||||||
LOG_CRITICAL(Common_Memory, "Out of bounds write on SparseLargeVector @ {}", index);
|
LOG_CRITICAL(Common_Memory, "SparseLargeVector write out of bounds @ {}", index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const u64 byte_offset = static_cast<u64>(index) * sizeof(T);
|
||||||
|
if (!CommitPage(byte_offset)) [[unlikely]] {
|
||||||
|
LOG_CRITICAL(Common_Memory, "SparseLargeVector commit failed for write @ {}", index);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!IsCommittedPage(index))
|
|
||||||
CommitPage(index);
|
|
||||||
base_ptr[index] = value;
|
base_ptr[index] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZeroRegion(std::size_t start, std::size_t end_) noexcept {
|
void ZeroRegion(std::size_t start, std::size_t end_) noexcept {
|
||||||
u64 base = reinterpret_cast<u64>(&base_ptr[start]);
|
if (base_ptr == nullptr || start >= end_) return;
|
||||||
const u64 end = reinterpret_cast<u64>(&base_ptr[end_]);
|
|
||||||
|
|
||||||
const u64 end_page = AlignUp(base, HostPageSize);
|
const u64 start_off = static_cast<u64>(start) * sizeof(T);
|
||||||
const u64 first_size = (std::min)(end_page, end) - base;
|
const u64 end_off = static_cast<u64>(end_) * sizeof(T);
|
||||||
|
const u64 first_page_end = (start_off + HostPageSize - 1) & HostPageMask;
|
||||||
|
|
||||||
if (IsCommittedPage(start)) {
|
if (start_off < first_page_end) {
|
||||||
std::memset(reinterpret_cast<void*>(base), 0, first_size);
|
const u64 chunk_end = (std::min)(first_page_end, end_off);
|
||||||
|
const u64 chunk_size = chunk_end - start_off;
|
||||||
|
if (chunk_size != 0 && IsPageCommitted(start_off)) {
|
||||||
|
std::memset(reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(base_ptr) + start_off), 0, chunk_size);
|
||||||
|
}
|
||||||
|
if (end_off <= first_page_end) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (end <= end_page)
|
for (u64 off = first_page_end; off < end_off; off += HostPageSize) {
|
||||||
return;
|
if (!IsPageCommitted(off)) continue;
|
||||||
|
const u64 remaining = end_off - off;
|
||||||
base = end_page;
|
if (remaining >= HostPageSize) {
|
||||||
|
DecommitPage(off);
|
||||||
for (u64 page = base; page < end; page += HostPageSize) {
|
|
||||||
auto index = (page - reinterpret_cast<u64>(base_ptr)) / sizeof(T);
|
|
||||||
if (!IsCommittedPage(index)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (end - page >= HostPageSize) {
|
|
||||||
DecommitPage(index);
|
|
||||||
} else {
|
} else {
|
||||||
std::memset(reinterpret_cast<void*>(page), 0, end - page);
|
std::memset(reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(base_ptr) + off), 0, remaining);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr void CommitRegion(size_t index, size_t end_) {
|
void CommitRegion(std::size_t index, std::size_t end_) noexcept {
|
||||||
const u64 base = static_cast<u64>(index) * sizeof(T);
|
if (base_ptr == nullptr || index >= end_) return;
|
||||||
const u64 end = static_cast<u64>(end_) * sizeof(T);
|
const u64 start_off = static_cast<u64>(index) * sizeof(T);
|
||||||
|
const u64 end_off = static_cast<u64>(end_) * sizeof(T);
|
||||||
for (u64 page = AlignDown(base, HostPageSize); page < end; page += HostPageSize) {
|
const u64 start_page = start_off & HostPageMask;
|
||||||
if (!IsCommittedPage(page / sizeof(T))) {
|
for (u64 off = start_page; off < end_off; off += HostPageSize) {
|
||||||
CommitPage(page / sizeof(T));
|
if (!IsPageCommitted(off)) {
|
||||||
|
(void)CommitPage(off);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr T& GetUnchecked(size_t index) {
|
T& GetUnchecked(std::size_t index) noexcept { return base_ptr[index]; }
|
||||||
return base_ptr[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr const T& operator[](std::size_t index) const noexcept {
|
[[nodiscard]] const T& operator[](std::size_t index) const noexcept { return GetOrDefault(index); }
|
||||||
return GetOrDefault(index);
|
[[nodiscard]] const T* data() const noexcept { return base_ptr; }
|
||||||
}
|
[[nodiscard]] std::size_t size() const noexcept { return alloc_size / sizeof(T); }
|
||||||
|
|
||||||
[[nodiscard]] constexpr const T* data() const noexcept {
|
|
||||||
return base_ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr std::size_t size() const noexcept {
|
|
||||||
return alloc_size / sizeof(T);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] constexpr bool IsCommittedPage(std::size_t index) const noexcept {
|
void Allocate(std::size_t new_size) noexcept {
|
||||||
if (index > alloc_size / sizeof(T)) {
|
alloc_size = new_size;
|
||||||
LOG_CRITICAL(Common_Memory, "Out of bounds access on large vector @ {}", index);
|
if (alloc_size == 0) {
|
||||||
|
base_ptr = nullptr;
|
||||||
|
committed_pages.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
base_ptr = static_cast<T*>(AllocateMemoryPages(alloc_size));
|
||||||
|
const std::size_t num_pages = NumPages();
|
||||||
|
const std::size_t num_words = (num_pages + 63) / 64;
|
||||||
|
committed_pages = std::make_unique<std::atomic<u64>[]>(num_words);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Release() noexcept {
|
||||||
|
if (base_ptr != nullptr) {
|
||||||
|
FreeMemoryPages(base_ptr, alloc_size);
|
||||||
|
base_ptr = nullptr;
|
||||||
|
}
|
||||||
|
committed_pages.reset();
|
||||||
|
alloc_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] u64 NumPages() const noexcept {
|
||||||
|
return (alloc_size + HostPageSize - 1) >> HostPageBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsPageCommitted(u64 byte_offset) const noexcept {
|
||||||
|
const u64 page_index = byte_offset >> HostPageBits;
|
||||||
|
if (committed_pages == nullptr || page_index >= NumPages()) return false;
|
||||||
|
const auto val = committed_pages[page_index >> 6].load(std::memory_order_acquire);
|
||||||
|
return (val >> (page_index & 63)) & 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetPageBit(u64 page_index, bool value) noexcept {
|
||||||
|
if (committed_pages == nullptr) return;
|
||||||
|
const u64 bit = 1ULL << (page_index & 63);
|
||||||
|
auto& atom = committed_pages[page_index >> 6];
|
||||||
|
if (value) {
|
||||||
|
atom.fetch_or(bit, std::memory_order_release);
|
||||||
|
} else {
|
||||||
|
atom.fetch_and(~bit, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommitPage(u64 byte_offset) noexcept {
|
||||||
|
const u64 page_index = byte_offset >> HostPageBits;
|
||||||
|
const uintptr_t page_addr = (reinterpret_cast<uintptr_t>(base_ptr) + byte_offset) & HostPageMask;
|
||||||
|
|
||||||
|
if (IsPageCommitted(byte_offset)) return true;
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
if (!CommitVectorPage(page_addr, true)) return false;
|
||||||
|
#else
|
||||||
|
if (mprotect(reinterpret_cast<void*>(page_addr), HostPageSize, PROT_READ | PROT_WRITE) != 0) {
|
||||||
|
LOG_ERROR(Common_Memory, "mprotect failed at {:#x}: {}", page_addr, std::strerror(errno));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto page = (index * sizeof(T)) >> HostPageBits;
|
|
||||||
auto val = committed_pages[page >> 6].load(std::memory_order_acquire);
|
|
||||||
return (val >> (page & 63)) & 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr void CommitPage(std::size_t index) noexcept {
|
|
||||||
auto page_index = (index * sizeof(T)) >> HostPageBits;
|
|
||||||
auto page = reinterpret_cast<uintptr_t>(base_ptr + index) & HostPageMask;
|
|
||||||
#if defined(_WIN32)
|
|
||||||
CommitVectorPage(page, true);
|
|
||||||
#else
|
|
||||||
mprotect(reinterpret_cast<void*>(page), HostPageSize, PROT_READ | PROT_WRITE);
|
|
||||||
#endif
|
#endif
|
||||||
|
SetPageBit(page_index, true);
|
||||||
committed_pages[page_index >> 6].fetch_or(1ULL << (page_index & 63), std::memory_order_release);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr void DecommitPage(std::size_t index) noexcept {
|
void DecommitPage(u64 byte_offset) noexcept {
|
||||||
auto page_index = (index * sizeof(T)) >> HostPageBits;
|
const u64 page_index = byte_offset >> HostPageBits;
|
||||||
auto page = reinterpret_cast<uintptr_t>(base_ptr + index) & HostPageMask;
|
const uintptr_t page_addr = (reinterpret_cast<uintptr_t>(base_ptr) + byte_offset) & HostPageMask;
|
||||||
|
DecommitVectorPage(page_addr);
|
||||||
|
SetPageBit(page_index, false);
|
||||||
|
}
|
||||||
|
|
||||||
committed_pages[page_index >> 6].fetch_and(~(1ULL << (page_index & 63)), std::memory_order_release);
|
[[nodiscard]] const T& DefaultValue() const noexcept {
|
||||||
DecommitVectorPage(page);
|
return *reinterpret_cast<const T*>(&default_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t alloc_size{};
|
std::size_t alloc_size{};
|
||||||
T* base_ptr{};
|
T* base_ptr{};
|
||||||
|
std::unique_ptr<std::atomic<u64>[]> committed_pages{};
|
||||||
std::vector<std::atomic<u64>> committed_pages{};
|
alignas(T) const std::array<u8, sizeof(T)> default_val{};
|
||||||
#ifdef _WIN32
|
|
||||||
const std::array<u8, sizeof(T)> default_val{};
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
@@ -200,7 +200,7 @@ public:
|
|||||||
if (host_visible) {
|
if (host_visible) {
|
||||||
return StagingBufferRef{};
|
return StagingBufferRef{};
|
||||||
}
|
}
|
||||||
return staging_pool.Request(device, size_bytes, MemoryUsage::Upload);
|
return staging_pool.Request(size_bytes, MemoryUsage::Upload);
|
||||||
}();
|
}();
|
||||||
|
|
||||||
u8* staging_data = host_visible ? buffer.Mapped().data() : staging.mapped_span.data();
|
u8* staging_data = host_visible ? buffer.Mapped().data() : staging.mapped_span.data();
|
||||||
@@ -375,11 +375,11 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
|
|||||||
}
|
}
|
||||||
|
|
||||||
StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) {
|
StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) {
|
||||||
return staging_pool.Request(device, size, MemoryUsage::Upload);
|
return staging_pool.Request(size, MemoryUsage::Upload);
|
||||||
}
|
}
|
||||||
|
|
||||||
StagingBufferRef BufferCacheRuntime::DownloadStagingBuffer(size_t size, bool deferred) {
|
StagingBufferRef BufferCacheRuntime::DownloadStagingBuffer(size_t size, bool deferred) {
|
||||||
return staging_pool.Request(device, size, MemoryUsage::Download, deferred);
|
return staging_pool.Request(size, MemoryUsage::Download, deferred);
|
||||||
}
|
}
|
||||||
|
|
||||||
VkFormat BufferCacheRuntime::TexelBufferFormat(VideoCore::Surface::PixelFormat format) const {
|
VkFormat BufferCacheRuntime::TexelBufferFormat(VideoCore::Surface::PixelFormat format) const {
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ public:
|
|||||||
std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage,
|
std::span<u8> BindMappedUniformBuffer([[maybe_unused]] size_t stage,
|
||||||
[[maybe_unused]] u32 binding_index,
|
[[maybe_unused]] u32 binding_index,
|
||||||
u32 size) {
|
u32 size) {
|
||||||
const StagingBufferRef ref = staging_pool.Request(device, size, MemoryUsage::Upload);
|
const StagingBufferRef ref = staging_pool.Request(size, MemoryUsage::Upload);
|
||||||
guest_descriptor_queue.AddBuffer(ref.buffer, ref.device_address,
|
guest_descriptor_queue.AddBuffer(ref.buffer, ref.device_address,
|
||||||
static_cast<u32>(ref.offset), size);
|
static_cast<u32>(ref.offset), size);
|
||||||
return ref.mapped_span;
|
return ref.mapped_span;
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ Uint8Pass::~Uint8Pass() = default;
|
|||||||
std::pair<VkBuffer, VkDeviceSize> Uint8Pass::Assemble(u32 num_vertices, VkBuffer src_buffer,
|
std::pair<VkBuffer, VkDeviceSize> Uint8Pass::Assemble(u32 num_vertices, VkBuffer src_buffer,
|
||||||
u32 src_offset) {
|
u32 src_offset) {
|
||||||
const u32 staging_size = static_cast<u32>(num_vertices * sizeof(u16));
|
const u32 staging_size = static_cast<u32>(num_vertices * sizeof(u16));
|
||||||
const auto staging = staging_buffer_pool.Request(device, staging_size, MemoryUsage::DeviceLocal);
|
const auto staging = staging_buffer_pool.Request(staging_size, MemoryUsage::DeviceLocal);
|
||||||
|
|
||||||
compute_pass_descriptor_queue.Acquire(scheduler, 2);
|
compute_pass_descriptor_queue.Acquire(scheduler, 2);
|
||||||
compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, num_vertices);
|
compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, num_vertices);
|
||||||
@@ -345,7 +345,7 @@ std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble(
|
|||||||
const u32 num_tri_vertices = (is_strip ? (num_vertices - 2) / 2 : num_vertices / 4) * 6;
|
const u32 num_tri_vertices = (is_strip ? (num_vertices - 2) / 2 : num_vertices / 4) * 6;
|
||||||
|
|
||||||
const std::size_t staging_size = num_tri_vertices * sizeof(u32);
|
const std::size_t staging_size = num_tri_vertices * sizeof(u32);
|
||||||
const auto staging = staging_buffer_pool.Request(device, staging_size, MemoryUsage::DeviceLocal);
|
const auto staging = staging_buffer_pool.Request(staging_size, MemoryUsage::DeviceLocal);
|
||||||
|
|
||||||
compute_pass_descriptor_queue.Acquire(scheduler, 2);
|
compute_pass_descriptor_queue.Acquire(scheduler, 2);
|
||||||
compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, input_size);
|
compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, input_size);
|
||||||
|
|||||||
@@ -852,7 +852,7 @@ public:
|
|||||||
|
|
||||||
void PushUnsyncedQueries() override {
|
void PushUnsyncedQueries() override {
|
||||||
CloseCounter();
|
CloseCounter();
|
||||||
auto staging_ref = staging_pool.Request(device,
|
auto staging_ref = staging_pool.Request(
|
||||||
pending_flush_queries.size() * TFBQueryBank::QUERY_SIZE, MemoryUsage::Download, true);
|
pending_flush_queries.size() * TFBQueryBank::QUERY_SIZE, MemoryUsage::Download, true);
|
||||||
size_t offset_base = staging_ref.offset;
|
size_t offset_base = staging_ref.offset;
|
||||||
for (auto q : pending_flush_queries) {
|
for (auto q : pending_flush_queries) {
|
||||||
@@ -1657,7 +1657,7 @@ void QueryCacheRuntime::SyncValues(std::span<SyncValuesType> values, VkBuffer ba
|
|||||||
impl->copies_setup.clear();
|
impl->copies_setup.clear();
|
||||||
impl->copies_setup.resize(impl->little_cache.size());
|
impl->copies_setup.resize(impl->little_cache.size());
|
||||||
if constexpr (SyncValuesType::GeneratesBaseBuffer) {
|
if constexpr (SyncValuesType::GeneratesBaseBuffer) {
|
||||||
ref = impl->staging_pool.Request(impl->device, total_size, MemoryUsage::Upload);
|
ref = impl->staging_pool.Request(total_size, MemoryUsage::Upload);
|
||||||
size_t current_offset = ref.offset;
|
size_t current_offset = ref.offset;
|
||||||
size_t accumulated_size = 0;
|
size_t accumulated_size = 0;
|
||||||
for (size_t i = 0; i < values.size(); i++) {
|
for (size_t i = 0; i < values.size(); i++) {
|
||||||
|
|||||||
@@ -28,42 +28,55 @@ using namespace Common::Literals;
|
|||||||
// Maximum potential alignment of a Vulkan buffer
|
// Maximum potential alignment of a Vulkan buffer
|
||||||
constexpr VkDeviceSize MAX_ALIGNMENT = 256;
|
constexpr VkDeviceSize MAX_ALIGNMENT = 256;
|
||||||
|
|
||||||
size_t GetStreamBufferSize(const Device& device, size_t max_stream_buffer_size, size_t max_alignment) {
|
// Stream buffer size in bytes
|
||||||
|
// *NIX drivers are more sensitive to increased buffers for streaming.
|
||||||
|
// Windows ones however, can intake bigger buffers and generally do not OOM.
|
||||||
|
// - GTX 960 on Windows will not OOM with 256mib
|
||||||
|
// - GT 1030 on ^NIX will OOM with 256mib
|
||||||
|
#if defined(__FreeBSD__)
|
||||||
|
constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB;
|
||||||
|
#else
|
||||||
|
constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 256_MiB;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
size_t GetStreamBufferSize(const Device& device) {
|
||||||
if (!device.HasDebuggingToolAttached()) {
|
if (!device.HasDebuggingToolAttached()) {
|
||||||
return max_stream_buffer_size;
|
return MAX_STREAM_BUFFER_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkDeviceSize size{0};
|
VkDeviceSize size{0};
|
||||||
bool has_device_local_host_visible_heap{};
|
bool has_device_local_host_visible_heap{};
|
||||||
ForEachDeviceLocalHostVisibleHeap(device, [&size, &has_device_local_host_visible_heap](size_t index, VkMemoryHeap& heap) {
|
ForEachDeviceLocalHostVisibleHeap(device, [&size, &has_device_local_host_visible_heap](
|
||||||
|
size_t index, VkMemoryHeap& heap) {
|
||||||
has_device_local_host_visible_heap = true;
|
has_device_local_host_visible_heap = true;
|
||||||
size = std::max<size_t>(size, heap.size);
|
size = (std::max)(size, heap.size);
|
||||||
});
|
});
|
||||||
if (has_device_local_host_visible_heap) {
|
if (has_device_local_host_visible_heap) {
|
||||||
// If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be
|
// If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be
|
||||||
// loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue
|
// loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue
|
||||||
// as the heap will be much larger.
|
// as the heap will be much larger.
|
||||||
if (size <= max_stream_buffer_size) {
|
if (size <= MAX_STREAM_BUFFER_SIZE) {
|
||||||
size = size * 40 / 100;
|
size = size * 40 / 100;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
size = max_stream_buffer_size;
|
size = MAX_STREAM_BUFFER_SIZE;
|
||||||
}
|
}
|
||||||
return std::min<size_t>(Common::AlignUp(size, max_alignment), max_stream_buffer_size);
|
return (std::min)(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE);
|
||||||
}
|
}
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
StagingBufferPool::StagingBufferPool(const Device& device, MemoryAllocator& memory_allocator_, Scheduler& scheduler_)
|
StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_,
|
||||||
: memory_allocator{memory_allocator_}, scheduler{scheduler_}
|
Scheduler& scheduler_)
|
||||||
, stream_buffer_size{GetStreamBufferSize(device, 256_MiB, MAX_ALIGNMENT)}
|
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
|
||||||
{
|
stream_buffer_size{GetStreamBufferSize(device)}, region_size{stream_buffer_size /
|
||||||
|
StagingBufferPool::NUM_SYNCS} {
|
||||||
VkBufferCreateInfo stream_ci = {
|
VkBufferCreateInfo stream_ci = {
|
||||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
.size = stream_buffer_size,
|
.size = stream_buffer_size,
|
||||||
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
|
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
|
||||||
| VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
||||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||||
.queueFamilyIndexCount = 0,
|
.queueFamilyIndexCount = 0,
|
||||||
.pQueueFamilyIndices = nullptr,
|
.pQueueFamilyIndices = nullptr,
|
||||||
@@ -74,16 +87,7 @@ StagingBufferPool::StagingBufferPool(const Device& device, MemoryAllocator& memo
|
|||||||
if (device.IsBufferDeviceAddressSupported()) {
|
if (device.IsBufferDeviceAddressSupported()) {
|
||||||
stream_ci.usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
|
stream_ci.usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
|
||||||
}
|
}
|
||||||
// Some drivers are more sensitive to increased buffer sizes
|
|
||||||
try {
|
|
||||||
stream_buffer = memory_allocator.CreateBuffer(stream_ci, MemoryUsage::Stream);
|
stream_buffer = memory_allocator.CreateBuffer(stream_ci, MemoryUsage::Stream);
|
||||||
} catch (vk::Exception& e) {
|
|
||||||
LOG_ERROR(Render_Vulkan, "Can't fit {} bytes buffer, halving", stream_ci.size);
|
|
||||||
stream_buffer_size = GetStreamBufferSize(device, 128_MiB, MAX_ALIGNMENT);
|
|
||||||
stream_ci.size = stream_buffer_size;
|
|
||||||
stream_buffer = memory_allocator.CreateBuffer(stream_ci, MemoryUsage::Stream);
|
|
||||||
}
|
|
||||||
region_size = stream_buffer_size / StagingBufferPool::NUM_SYNCS;
|
|
||||||
if (device.HasDebuggingToolAttached()) {
|
if (device.HasDebuggingToolAttached()) {
|
||||||
stream_buffer.SetObjectNameEXT("Stream Buffer");
|
stream_buffer.SetObjectNameEXT("Stream Buffer");
|
||||||
}
|
}
|
||||||
@@ -96,10 +100,11 @@ StagingBufferPool::StagingBufferPool(const Device& device, MemoryAllocator& memo
|
|||||||
|
|
||||||
StagingBufferPool::~StagingBufferPool() = default;
|
StagingBufferPool::~StagingBufferPool() = default;
|
||||||
|
|
||||||
StagingBufferRef StagingBufferPool::Request(const Device& device, size_t size, MemoryUsage usage, bool deferred) {
|
StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) {
|
||||||
return (!deferred && usage == MemoryUsage::Upload && size <= region_size)
|
if (!deferred && usage == MemoryUsage::Upload && size <= region_size) {
|
||||||
? GetStreamBuffer(device, size)
|
return GetStreamBuffer(size);
|
||||||
: GetStagingBuffer(device, size, usage, deferred);
|
}
|
||||||
|
return GetStagingBuffer(size, usage, deferred);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StagingBufferPool::FreeDeferred(StagingBufferRef& ref) {
|
void StagingBufferPool::FreeDeferred(StagingBufferRef& ref) {
|
||||||
@@ -122,10 +127,11 @@ void StagingBufferPool::TickFrame() {
|
|||||||
ReleaseCache(MemoryUsage::Download);
|
ReleaseCache(MemoryUsage::Download);
|
||||||
}
|
}
|
||||||
|
|
||||||
StagingBufferRef StagingBufferPool::GetStreamBuffer(const Device& device, size_t size) {
|
StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) {
|
||||||
if (AreRegionsActive(Region(free_iterator) + 1, (std::min)(Region(iterator + size) + 1, NUM_SYNCS))) {
|
if (AreRegionsActive(Region(free_iterator) + 1,
|
||||||
|
(std::min)(Region(iterator + size) + 1, NUM_SYNCS))) {
|
||||||
// Avoid waiting for the previous usages to be free
|
// Avoid waiting for the previous usages to be free
|
||||||
return GetStagingBuffer(device, size, MemoryUsage::Upload);
|
return GetStagingBuffer(size, MemoryUsage::Upload);
|
||||||
}
|
}
|
||||||
const u64 current_tick = scheduler.CurrentTick();
|
const u64 current_tick = scheduler.CurrentTick();
|
||||||
std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + Region(iterator),
|
std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + Region(iterator),
|
||||||
@@ -134,14 +140,15 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(const Device& device, size_t
|
|||||||
free_iterator = (std::max)(free_iterator, iterator + size);
|
free_iterator = (std::max)(free_iterator, iterator + size);
|
||||||
|
|
||||||
if (iterator + size >= stream_buffer_size) {
|
if (iterator + size >= stream_buffer_size) {
|
||||||
std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS, current_tick);
|
std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS,
|
||||||
|
current_tick);
|
||||||
used_iterator = 0;
|
used_iterator = 0;
|
||||||
iterator = 0;
|
iterator = 0;
|
||||||
free_iterator = size;
|
free_iterator = size;
|
||||||
|
|
||||||
if (AreRegionsActive(0, Region(size) + 1)) {
|
if (AreRegionsActive(0, Region(size) + 1)) {
|
||||||
// Avoid waiting for the previous usages to be free
|
// Avoid waiting for the previous usages to be free
|
||||||
return GetStagingBuffer(device, size, MemoryUsage::Upload);
|
return GetStagingBuffer(size, MemoryUsage::Upload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const size_t offset = iterator;
|
const size_t offset = iterator;
|
||||||
@@ -149,7 +156,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(const Device& device, size_t
|
|||||||
return StagingBufferRef{
|
return StagingBufferRef{
|
||||||
.buffer = *stream_buffer,
|
.buffer = *stream_buffer,
|
||||||
.device_address = stream_buffer_address,
|
.device_address = stream_buffer_address,
|
||||||
.offset = VkDeviceSize(offset),
|
.offset = static_cast<VkDeviceSize>(offset),
|
||||||
.mapped_span = stream_pointer.subspan(offset, size),
|
.mapped_span = stream_pointer.subspan(offset, size),
|
||||||
.usage{},
|
.usage{},
|
||||||
.log2_level{},
|
.log2_level{},
|
||||||
@@ -159,18 +166,21 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(const Device& device, size_t
|
|||||||
|
|
||||||
bool StagingBufferPool::AreRegionsActive(size_t region_begin, size_t region_end) const {
|
bool StagingBufferPool::AreRegionsActive(size_t region_begin, size_t region_end) const {
|
||||||
const u64 gpu_tick = scheduler.GetMasterSemaphore().KnownGpuTick();
|
const u64 gpu_tick = scheduler.GetMasterSemaphore().KnownGpuTick();
|
||||||
return std::any_of(sync_ticks.begin() + region_begin, sync_ticks.begin() + region_end, [gpu_tick](u64 sync_tick) {
|
return std::any_of(sync_ticks.begin() + region_begin, sync_ticks.begin() + region_end,
|
||||||
return gpu_tick < sync_tick;
|
[gpu_tick](u64 sync_tick) { return gpu_tick < sync_tick; });
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
StagingBufferRef StagingBufferPool::GetStagingBuffer(const Device& device, size_t size, MemoryUsage usage, bool deferred) {
|
StagingBufferRef StagingBufferPool::GetStagingBuffer(size_t size, MemoryUsage usage,
|
||||||
if (const std::optional<StagingBufferRef> ref = TryGetReservedBuffer(size, usage, deferred))
|
bool deferred) {
|
||||||
|
if (const std::optional<StagingBufferRef> ref = TryGetReservedBuffer(size, usage, deferred)) {
|
||||||
return *ref;
|
return *ref;
|
||||||
return CreateStagingBuffer(device, size, usage, deferred);
|
}
|
||||||
|
return CreateStagingBuffer(size, usage, deferred);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<StagingBufferRef> StagingBufferPool::TryGetReservedBuffer(size_t size, MemoryUsage usage, bool deferred) {
|
std::optional<StagingBufferRef> StagingBufferPool::TryGetReservedBuffer(size_t size,
|
||||||
|
MemoryUsage usage,
|
||||||
|
bool deferred) {
|
||||||
StagingBuffers& cache_level = GetCache(usage)[Common::Log2Ceil(size)];
|
StagingBuffers& cache_level = GetCache(usage)[Common::Log2Ceil(size)];
|
||||||
|
|
||||||
const auto is_free = [this](const StagingBuffer& entry) {
|
const auto is_free = [this](const StagingBuffer& entry) {
|
||||||
@@ -192,7 +202,7 @@ std::optional<StagingBufferRef> StagingBufferPool::TryGetReservedBuffer(size_t s
|
|||||||
return it->Ref();
|
return it->Ref();
|
||||||
}
|
}
|
||||||
|
|
||||||
StagingBufferRef StagingBufferPool::CreateStagingBuffer(const Device& device, size_t size, MemoryUsage usage, bool deferred) {
|
StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, MemoryUsage usage, bool deferred) {
|
||||||
auto const log2_size = Common::Log2Ceil<u32>(u32(size));
|
auto const log2_size = Common::Log2Ceil<u32>(u32(size));
|
||||||
VkBufferCreateInfo buffer_ci = {
|
VkBufferCreateInfo buffer_ci = {
|
||||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||||
|
|||||||
@@ -33,10 +33,11 @@ class StagingBufferPool {
|
|||||||
public:
|
public:
|
||||||
static constexpr size_t NUM_SYNCS = 16;
|
static constexpr size_t NUM_SYNCS = 16;
|
||||||
|
|
||||||
explicit StagingBufferPool(const Device& device, MemoryAllocator& memory_allocator, Scheduler& scheduler);
|
explicit StagingBufferPool(const Device& device, MemoryAllocator& memory_allocator,
|
||||||
|
Scheduler& scheduler);
|
||||||
~StagingBufferPool();
|
~StagingBufferPool();
|
||||||
|
|
||||||
StagingBufferRef Request(const Device& device, size_t size, MemoryUsage usage, bool deferred = false);
|
StagingBufferRef Request(size_t size, MemoryUsage usage, bool deferred = false);
|
||||||
void FreeDeferred(StagingBufferRef& ref);
|
void FreeDeferred(StagingBufferRef& ref);
|
||||||
|
|
||||||
[[nodiscard]] VkBuffer StreamBuf() const noexcept {
|
[[nodiscard]] VkBuffer StreamBuf() const noexcept {
|
||||||
@@ -83,18 +84,27 @@ private:
|
|||||||
static constexpr size_t NUM_LEVELS = sizeof(size_t) * CHAR_BIT;
|
static constexpr size_t NUM_LEVELS = sizeof(size_t) * CHAR_BIT;
|
||||||
using StagingBuffersCache = std::array<StagingBuffers, NUM_LEVELS>;
|
using StagingBuffersCache = std::array<StagingBuffers, NUM_LEVELS>;
|
||||||
|
|
||||||
StagingBufferRef GetStreamBuffer(const Device& device, size_t size);
|
StagingBufferRef GetStreamBuffer(size_t size);
|
||||||
|
|
||||||
bool AreRegionsActive(size_t region_begin, size_t region_end) const;
|
bool AreRegionsActive(size_t region_begin, size_t region_end) const;
|
||||||
StagingBufferRef GetStagingBuffer(const Device& device, size_t size, MemoryUsage usage, bool deferred = false);
|
|
||||||
std::optional<StagingBufferRef> TryGetReservedBuffer(size_t size, MemoryUsage usage, bool deferred);
|
StagingBufferRef GetStagingBuffer(size_t size, MemoryUsage usage, bool deferred = false);
|
||||||
StagingBufferRef CreateStagingBuffer(const Device& device, size_t size, MemoryUsage usage, bool deferred);
|
|
||||||
|
std::optional<StagingBufferRef> TryGetReservedBuffer(size_t size, MemoryUsage usage,
|
||||||
|
bool deferred);
|
||||||
|
|
||||||
|
StagingBufferRef CreateStagingBuffer(size_t size, MemoryUsage usage, bool deferred);
|
||||||
|
|
||||||
StagingBuffersCache& GetCache(MemoryUsage usage);
|
StagingBuffersCache& GetCache(MemoryUsage usage);
|
||||||
|
|
||||||
void ReleaseCache(MemoryUsage usage);
|
void ReleaseCache(MemoryUsage usage);
|
||||||
|
|
||||||
void ReleaseLevel(StagingBuffersCache& cache, size_t log2);
|
void ReleaseLevel(StagingBuffersCache& cache, size_t log2);
|
||||||
size_t Region(size_t iter) const noexcept {
|
size_t Region(size_t iter) const noexcept {
|
||||||
return iter / region_size;
|
return iter / region_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Device& device;
|
||||||
MemoryAllocator& memory_allocator;
|
MemoryAllocator& memory_allocator;
|
||||||
Scheduler& scheduler;
|
Scheduler& scheduler;
|
||||||
|
|
||||||
|
|||||||
@@ -975,11 +975,11 @@ void TextureCacheRuntime::Finish() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StagingBufferRef TextureCacheRuntime::UploadStagingBuffer(size_t size, bool deferred) {
|
StagingBufferRef TextureCacheRuntime::UploadStagingBuffer(size_t size, bool deferred) {
|
||||||
return staging_buffer_pool.Request(device, size, MemoryUsage::Upload, deferred);
|
return staging_buffer_pool.Request(size, MemoryUsage::Upload, deferred);
|
||||||
}
|
}
|
||||||
|
|
||||||
StagingBufferRef TextureCacheRuntime::DownloadStagingBuffer(size_t size, bool deferred) {
|
StagingBufferRef TextureCacheRuntime::DownloadStagingBuffer(size_t size, bool deferred) {
|
||||||
return staging_buffer_pool.Request(device, size, MemoryUsage::Download, deferred);
|
return staging_buffer_pool.Request(size, MemoryUsage::Download, deferred);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureCacheRuntime::FreeDeferredStagingBuffer(StagingBufferRef& ref) {
|
void TextureCacheRuntime::FreeDeferredStagingBuffer(StagingBufferRef& ref) {
|
||||||
|
|||||||
Reference in New Issue
Block a user