Compare commits

..

24 Commits

Author SHA1 Message Date
CamilleLaVey f2f05293a6 Pure and simply meow 2026-08-27 02:30:35 -04:00
CamilleLaVey ad075400a4 Let's try this on memory reclamations 2026-08-27 01:21:10 -04:00
CamilleLaVey 06dba55923 Extended the impact on AHB fallback 2026-08-26 22:10:00 -04:00
CamilleLaVey 8d19ee6dc5 What if we just rely entirely on SGSR common than using EdgeDir? 2026-08-26 19:06:23 -04:00
CamilleLaVey 3e91569d36 Another intent 2026-08-26 18:40:10 -04:00
CamilleLaVey 6010f60803 Just cries internally 2026-08-26 18:05:03 -04:00
CamilleLaVey c14b1de71e Meow. 2026-08-26 17:47:12 -04:00
CamilleLaVey 14cc49e727 Adjustments on upload for textures 2026-08-26 17:00:18 -04:00
CamilleLaVey e88923331c Fix build 2026-08-26 15:04:18 -04:00
CamilleLaVey 78918e995d Fix msvc build 2026-08-26 14:50:42 -04:00
CamilleLaVey 522440bcb2 [TEST] Bring the unswizzle work from tiled-gpu-v2 x2 + revert of vertex, indexes from AHB path 2026-08-26 14:49:01 -04:00
CamilleLaVey c1d8b7dd42 Just a quick test 2026-08-26 05:09:12 -04:00
CamilleLaVey 28d7c97d71 Added vertex, uniform and indexes on the upload path 2026-08-26 04:52:52 -04:00
CamilleLaVey 8a8763d975 Meowly The Build Destroyer 2026-08-26 04:11:59 -04:00
CamilleLaVey 54d8d5b0a8 Another intent on write/read on the fly 2026-08-26 04:06:25 -04:00
CamilleLaVey b9b9a0d704 Changes on the maps counts again 2026-08-26 03:12:06 -04:00
CamilleLaVey b8ce711575 Step back + keeping fixes from unified download + shared mem refactor 2026-08-25 21:36:17 -04:00
CamilleLaVey 46cd1e5d65 another take 2026-08-25 21:36:17 -04:00
CamilleLaVey ae561d49aa Keep AHB limitations 2026-08-25 21:36:17 -04:00
CamilleLaVey 9f45d357e9 [TEST] Another increased on windows size 2026-08-25 21:36:17 -04:00
CamilleLaVey 2c8c3062c4 Increased windows size 2026-08-25 21:36:17 -04:00
CamilleLaVey 51bdb62d74 [TEST] Increased window limiter on AHB 2026-08-25 21:36:17 -04:00
CamilleLaVey ed3838ccbb [TEST] Adjustment AHB to tiled-gpu-v2 state 2026-08-25 21:36:17 -04:00
CamilleLaVey e8c253f511 [memory, vulkan] Initial implementation for Unified Memory 2026-08-25 21:36:17 -04:00
59 changed files with 2907 additions and 925 deletions
@@ -27,6 +27,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_ASYNCHRONOUS_GPU_EMULATION("use_asynchronous_gpu_emulation"), RENDERER_ASYNCHRONOUS_GPU_EMULATION("use_asynchronous_gpu_emulation"),
RENDERER_ASYNC_PRESENTATION("async_presentation"), RENDERER_ASYNC_PRESENTATION("async_presentation"),
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"), RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
RENDERER_UNIFIED_MEMORY("use_unified_memory"),
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"), RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
ENABLE_BUFFER_HISTORY("enable_buffer_history"), ENABLE_BUFFER_HISTORY("enable_buffer_history"),
USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"), USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"),
@@ -789,6 +789,13 @@ abstract class SettingsItem(
descriptionId = R.string.renderer_asynchronous_shaders_description descriptionId = R.string.renderer_asynchronous_shaders_description
) )
) )
put(
SwitchSetting(
BooleanSetting.RENDERER_UNIFIED_MEMORY,
titleId = R.string.renderer_unified_memory,
descriptionId = R.string.renderer_unified_memory_description
)
)
put( put(
SingleChoiceSetting( SingleChoiceSetting(
IntSetting.FAST_GPU_TIME, IntSetting.FAST_GPU_TIME,
@@ -344,6 +344,7 @@ class SettingsFragmentPresenter(
add(BooleanSetting.FIX_BLOOM_EFFECTS.key) add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
add(BooleanSetting.EMULATE_BGR565.key) add(BooleanSetting.EMULATE_BGR565.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key) add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
add(BooleanSetting.RENDERER_UNIFIED_MEMORY.key)
add(IntSetting.ANDROID_PIPELINE_WORKERS.key) add(IntSetting.ANDROID_PIPELINE_WORKERS.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key) add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key) add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
@@ -585,6 +585,8 @@
<string name="rescale_hack_description">Enables a legacy handling for the rescale configuration pass for games by using a quick rescale path</string> <string name="rescale_hack_description">Enables a legacy handling for the rescale configuration pass for games by using a quick rescale path</string>
<string name="renderer_asynchronous_shaders">Use asynchronous shaders</string> <string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string> <string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string>
<string name="renderer_unified_memory">Unified memory access</string>
<string name="renderer_unified_memory_description">Allows GPU write buffer readbacks directly into guest memory, skipping the CPU staging copy.</string>
<string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string> <string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string>
<string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string> <string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string>
<string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string> <string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string>
+22 -28
View File
@@ -4,34 +4,17 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <fstream>
#include "common/heap_tracker.h" #include "common/heap_tracker.h"
#include "common/logging.h" #include "common/logging.h"
#include "common/memory_detect.h"
#include "common/assert.h" #include "common/assert.h"
namespace Common { namespace Common {
namespace {
s64 GetMaxPermissibleResidentMapCount() {
// Default value.
s64 value = 65530;
// Try to read how many mappings we can make.
std::ifstream s("/proc/sys/vm/max_map_count");
s >> value;
// Print, for debug.
LOG_INFO(HW_Memory, "Current maximum map count: {}", value);
// Allow 20000 maps for other code and to account for split inaccuracy.
return std::max<s64>(value - 20000, 0);
}
} // namespace
HeapTracker::HeapTracker(Common::HostMemory& buffer) HeapTracker::HeapTracker(Common::HostMemory& buffer)
: m_buffer(buffer), m_max_resident_map_count(GetMaxPermissibleResidentMapCount()) {} : m_buffer(buffer),
m_has_hardware_buffer_backing(!buffer.BackingHardwareBuffers().empty()),
m_max_resident_map_count(static_cast<s64>(GetPermissibleMapCount())) {}
HeapTracker::~HeapTracker() = default; HeapTracker::~HeapTracker() = default;
void HeapTracker::Map(size_t virtual_offset, size_t host_offset, size_t length, void HeapTracker::Map(size_t virtual_offset, size_t host_offset, size_t length,
@@ -85,7 +68,8 @@ void HeapTracker::Unmap(size_t virtual_offset, size_t size, bool is_separate_hea
// If resident, erase from resident map. // If resident, erase from resident map.
if (item->is_resident) { if (item->is_resident) {
ASSERT(--m_resident_map_count >= 0); m_resident_map_count -= this->HostMapCount(item->paddr, item->size);
ASSERT(m_resident_map_count >= 0);
m_resident_mappings.erase(m_resident_mappings.iterator_to(*item)); m_resident_mappings.erase(m_resident_mappings.iterator_to(*item));
} }
@@ -191,7 +175,7 @@ bool HeapTracker::DeferredMapSeparateHeap(size_t virtual_offset) {
// This map is now resident. // This map is now resident.
it->is_resident = true; it->is_resident = true;
m_resident_map_count++; m_resident_map_count += this->HostMapCount(it->paddr, it->size);
m_resident_mappings.insert(*it); m_resident_mappings.insert(*it);
} }
@@ -213,17 +197,17 @@ void HeapTracker::RebuildSeparateHeapAddressSpace() {
// Despite being worse in theory, this has proven to be better in practice than more // Despite being worse in theory, this has proven to be better in practice than more
// regularly dumping a smaller amount, because it significantly reduces average case // regularly dumping a smaller amount, because it significantly reduces average case
// lock contention. // lock contention.
std::size_t const desired_count = (std::min)(m_resident_map_count, m_max_resident_map_count) / 2; s64 const desired_count = (std::min)(m_resident_map_count, m_max_resident_map_count) / 2;
std::size_t const evict_count = m_resident_map_count - desired_count;
auto it = m_resident_mappings.begin(); auto it = m_resident_mappings.begin();
for (size_t i = 0; i < evict_count && it != m_resident_mappings.end(); i++) { while (m_resident_map_count > desired_count && it != m_resident_mappings.end()) {
// Unmark and unmap. // Unmark and unmap.
it->is_resident = false; it->is_resident = false;
m_buffer.Unmap(it->vaddr, it->size, false); m_buffer.Unmap(it->vaddr, it->size, false);
// Advance. // Advance.
ASSERT(--m_resident_map_count >= 0); m_resident_map_count -= this->HostMapCount(it->paddr, it->size);
ASSERT(m_resident_map_count >= 0);
it = m_resident_mappings.erase(it); it = m_resident_mappings.erase(it);
} }
} }
@@ -245,6 +229,7 @@ void HeapTracker::SplitHeapMapLocked(VAddr offset) {
// Cache the original values. // Cache the original values.
auto* const left = std::addressof(*it); auto* const left = std::addressof(*it);
const size_t orig_size = left->size; const size_t orig_size = left->size;
const s64 orig_host_map_count = this->HostMapCount(left->paddr, orig_size);
// Adjust the left map. // Adjust the left map.
const size_t left_size = offset - left->vaddr; const size_t left_size = offset - left->vaddr;
@@ -266,11 +251,20 @@ void HeapTracker::SplitHeapMapLocked(VAddr offset) {
// If resident, also insert into resident map. // If resident, also insert into resident map.
if (right->is_resident) { if (right->is_resident) {
m_resident_map_count++; m_resident_map_count += this->HostMapCount(left->paddr, left->size) +
this->HostMapCount(right->paddr, right->size) -
orig_host_map_count;
m_resident_mappings.insert(*right); m_resident_mappings.insert(*right);
} }
} }
s64 HeapTracker::HostMapCount(PAddr paddr, size_t size) const {
if (!m_has_hardware_buffer_backing) {
return size != 0 ? 1 : 0;
}
return static_cast<s64>(m_buffer.BackingMapCount(paddr, size));
}
HeapTracker::AddrTree::iterator HeapTracker::GetNearestHeapMapLocked(VAddr offset) { HeapTracker::AddrTree::iterator HeapTracker::GetNearestHeapMapLocked(VAddr offset) {
const SeparateHeapMap key{ const SeparateHeapMap key{
.vaddr = offset, .vaddr = offset,
+6
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -82,10 +85,13 @@ private:
AddrTree::iterator GetNearestHeapMapLocked(VAddr offset); AddrTree::iterator GetNearestHeapMapLocked(VAddr offset);
s64 HostMapCount(PAddr paddr, size_t size) const;
void RebuildSeparateHeapAddressSpace(); void RebuildSeparateHeapAddressSpace();
private: private:
Common::HostMemory& m_buffer; Common::HostMemory& m_buffer;
const bool m_has_hardware_buffer_backing;
const s64 m_max_resident_map_count; const s64 m_max_resident_map_count;
std::shared_mutex m_rebuild_lock{}; std::shared_mutex m_rebuild_lock{};
+393 -6
View File
@@ -51,14 +51,65 @@
#endif // ^^^ POSIX ^^^ #endif // ^^^ POSIX ^^^
#include <atomic>
#include <mutex> #include <mutex>
#include <random> #include <random>
#include <vector>
#include "common/alignment.h" #include "common/alignment.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/free_region_manager.h" #include "common/free_region_manager.h"
#include "common/host_memory.h" #include "common/host_memory.h"
#include "common/logging.h" #include "common/logging.h"
#include "common/memory_detect.h"
#include "common/settings.h"
#ifdef __ANDROID__
#include <cerrno>
#include <dlfcn.h>
#include <sys/ioctl.h>
#include <android/hardware_buffer.h>
namespace {
struct NativeHandle {
int version;
int numFds;
int numInts;
int data[1];
};
using PFN_AHardwareBuffer_getNativeHandle = const NativeHandle* (*)(const AHardwareBuffer*);
PFN_AHardwareBuffer_getNativeHandle ResolveGetNativeHandle() {
void* const lib = dlopen("libnativewindow.so", RTLD_NOW);
if (lib == nullptr) {
return nullptr;
}
return reinterpret_cast<PFN_AHardwareBuffer_getNativeHandle>(
dlsym(lib, "AHardwareBuffer_getNativeHandle"));
}
struct DmaBufSync {
u64 flags;
};
constexpr u64 DmaBufSyncRead = 1ULL << 0;
constexpr u64 DmaBufSyncWrite = 1ULL << 1;
constexpr u64 DmaBufSyncStart = 0ULL << 2;
constexpr u64 DmaBufSyncEnd = 1ULL << 2;
void SyncDmaBufCpuAccess(int fd, u64 phase) {
DmaBufSync sync{.flags = phase | DmaBufSyncRead | DmaBufSyncWrite};
while (ioctl(fd, _IOW('b', 0, DmaBufSync), &sync) != 0) {
if (errno != EINTR) {
return;
}
}
}
} // namespace
#endif
#if defined(__ANDROID__) && __ANDROID_API__ < 30 #if defined(__ANDROID__) && __ANDROID_API__ < 30
#include <sys/syscall.h> #include <sys/syscall.h>
@@ -75,6 +126,12 @@ namespace Common {
[[maybe_unused]] constexpr size_t PageAlignment = 0x1000; [[maybe_unused]] constexpr size_t PageAlignment = 0x1000;
[[maybe_unused]] constexpr size_t HugePageSize = 0x200000; [[maybe_unused]] constexpr size_t HugePageSize = 0x200000;
static std::atomic<u64> committed_backing_size{};
u64 GetCommittedBackingSize() noexcept {
return committed_backing_size.load(std::memory_order_relaxed);
}
#ifdef _WIN32 #ifdef _WIN32
// Manually imported for MinGW compatibility // Manually imported for MinGW compatibility
@@ -123,7 +180,7 @@ static void GetFuncAddress(Common::DynamicLibrary& dll, const char* name, T& pfn
class HostMemory::Impl { class HostMemory::Impl {
public: public:
explicit Impl(size_t backing_size_, size_t virtual_size_) explicit Impl(size_t backing_size_, size_t virtual_size_, size_t)
: backing_size{backing_size_} : backing_size{backing_size_}
, virtual_size{virtual_size_} , virtual_size{virtual_size_}
, process{GetCurrentProcess()} , process{GetCurrentProcess()}
@@ -229,6 +286,10 @@ public:
UNREACHABLE(); UNREACHABLE();
} }
bool IsBackingShared() const noexcept {
return true;
}
const size_t backing_size; ///< Size of the backing memory in bytes const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
@@ -501,9 +562,10 @@ static int shm_open_anon(int flags, mode_t mode) {
class HostMemory::Impl { class HostMemory::Impl {
public: public:
explicit Impl(size_t backing_size_, size_t virtual_size_) explicit Impl(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
: backing_size{backing_size_} : backing_size{backing_size_}
, virtual_size{virtual_size_} , virtual_size{virtual_size_}
, preferred_offset{preferred_offset_}
{} {}
bool Init() { bool Init() {
@@ -543,10 +605,15 @@ public:
LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones"); LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones");
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)); backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
if (fd > 0) { if (fd > 0) {
fd = -1;
close(fd); close(fd);
} }
fd = -1;
} else { } else {
#ifdef __ANDROID__
if (InitAhbBacking()) {
return InitVirtual();
}
#endif
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
} }
if (backing_base == MAP_FAILED) { if (backing_base == MAP_FAILED) {
@@ -554,7 +621,10 @@ public:
return false; return false;
} }
// Virtual memory initialization return InitVirtual();
}
bool InitVirtual() {
virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size)); virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size));
if (virtual_base == MAP_FAILED) { if (virtual_base == MAP_FAILED) {
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno)); LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
@@ -567,6 +637,244 @@ public:
return true; return true;
} }
#ifdef __ANDROID__
static AHardwareBuffer_Desc MakeBlobDesc(size_t len) {
return AHardwareBuffer_Desc{
.width = static_cast<u32>(len),
.height = 1,
.layers = 1,
.format = AHARDWAREBUFFER_FORMAT_BLOB,
.usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER,
.stride = 0,
.rfu0 = 0,
.rfu1 = 0,
};
}
static bool ProbeAhbBacking(PFN_AHardwareBuffer_getNativeHandle get_native_handle) {
const AHardwareBuffer_Desc desc = MakeBlobDesc(PageAlignment * 2);
AHardwareBuffer* buffer{};
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
return false;
}
const NativeHandle* const handle = get_native_handle(buffer);
if (handle == nullptr || handle->numFds < 1) {
AHardwareBuffer_release(buffer);
return false;
}
const int probe_fd = handle->data[0];
bool ok = true;
const auto try_map = [&](int prot, off_t offset) {
if (!ok) {
return;
}
void* const ptr = mmap(nullptr, PageAlignment, prot, MAP_SHARED, probe_fd, offset);
if (ptr == MAP_FAILED) {
ok = false;
return;
}
munmap(ptr, PageAlignment);
};
try_map(PROT_READ | PROT_WRITE, 0);
try_map(PROT_READ | PROT_WRITE, static_cast<off_t>(PageAlignment));
#ifdef ARCHITECTURE_arm64
try_map(PROT_READ | PROT_EXEC, 0);
#endif
AHardwareBuffer_release(buffer);
return ok;
}
size_t ComputeAhbBudget(size_t window_size) const {
const u64 total_physical = Common::GetMemInfo().TotalPhysicalMemory;
constexpr u64 BaselineFootprint = 6ULL << 30;
if (total_physical <= BaselineFootprint) {
return 0;
}
const u64 permissible_maps = Common::GetPermissibleMapCount();
if (permissible_maps == 0) {
return 0;
}
u64 budget = (total_physical - BaselineFootprint) / 2;
constexpr u64 MapSlotsPerWindow = 64;
const u64 affordable_windows = permissible_maps / MapSlotsPerWindow;
budget = (std::min)(budget, affordable_windows * window_size);
const u64 available = Common::GetAvailablePhysicalMemory();
if (available != 0) {
budget = (std::min)(budget, available / 2);
}
budget = (std::min)(budget, static_cast<u64>(backing_size));
budget = Common::AlignDown(budget, window_size);
constexpr u64 MinimumBudget = 256ULL << 20;
if (budget < MinimumBudget) {
return 0;
}
return static_cast<size_t>(budget);
}
bool InitAhbBacking() {
if (!Settings::values.use_unified_memory.GetValue()) {
return false;
}
static const PFN_AHardwareBuffer_getNativeHandle get_native_handle =
ResolveGetNativeHandle();
if (get_native_handle == nullptr) {
return false;
}
constexpr size_t window_size = 256ULL << 20;
const size_t budget = ComputeAhbBudget(window_size);
if (budget == 0) {
return false;
}
if (!ProbeAhbBacking(get_native_handle)) {
return false;
}
const size_t aligned_backing = Common::AlignDown(backing_size, window_size);
const size_t max_windows = (std::min)(budget, aligned_backing) / window_size;
std::vector<AHardwareBuffer*> buffers;
std::vector<int> buffer_fds;
const auto cleanup = [&] {
for (AHardwareBuffer* buffer : buffers) {
AHardwareBuffer_release(buffer);
}
buffers.clear();
buffer_fds.clear();
};
for (size_t i = 0; i < max_windows; ++i) {
const AHardwareBuffer_Desc desc = MakeBlobDesc(window_size);
AHardwareBuffer* buffer{};
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
break;
}
const NativeHandle* const handle = get_native_handle(buffer);
if (handle == nullptr || handle->numFds < 1) {
AHardwareBuffer_release(buffer);
break;
}
const int buffer_fd = handle->data[0];
const off_t buffer_len = lseek(buffer_fd, 0, SEEK_END);
if (buffer_len < static_cast<off_t>(window_size)) {
AHardwareBuffer_release(buffer);
break;
}
buffers.push_back(buffer);
buffer_fds.push_back(buffer_fd);
}
const size_t num_windows = buffers.size();
if (num_windows == 0) {
return false;
}
const size_t region_size = num_windows * window_size;
const size_t region_base = Common::AlignDown(
(std::min)(preferred_offset, aligned_backing - region_size), window_size);
u8* const base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
if (base == MAP_FAILED) {
cleanup();
return false;
}
const auto map_over_reservation = [&](size_t offset, size_t len, int map_fd,
off_t map_offset) {
if (len == 0) {
return true;
}
if (mmap(base + offset, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, map_fd,
map_offset) == MAP_FAILED) {
munmap(base, backing_size);
cleanup();
return false;
}
return true;
};
if (!map_over_reservation(0, region_base, fd, 0)) {
return false;
}
for (size_t i = 0; i < num_windows; ++i) {
if (!map_over_reservation(region_base + i * window_size, window_size, buffer_fds[i],
0)) {
return false;
}
}
const size_t tail_offset = region_base + region_size;
if (!map_over_reservation(tail_offset, backing_size - tail_offset, fd,
static_cast<off_t>(tail_offset))) {
return false;
}
backing_base = base;
ahb_windows = std::move(buffers);
ahb_fds = std::move(buffer_fds);
ahb_window_size = window_size;
ahb_base = region_base;
ahb_bytes = region_size;
committed_backing_size.store(region_size, std::memory_order_relaxed);
for (const int window_fd : ahb_fds) {
SyncDmaBufCpuAccess(window_fd, DmaBufSyncStart);
}
return true;
}
void MapBackingRange(size_t virtual_offset, size_t host_offset, size_t length, int prot_flags) {
while (length > 0) {
int map_fd = fd;
off_t map_offset = static_cast<off_t>(host_offset);
size_t chunk = length;
if (host_offset < ahb_base) {
chunk = (std::min)(chunk, ahb_base - host_offset);
} else if (host_offset < ahb_base + ahb_bytes) {
const size_t relative = host_offset - ahb_base;
const size_t window = relative / ahb_window_size;
const size_t local = relative % ahb_window_size;
map_fd = ahb_fds[window];
map_offset = static_cast<off_t>(local);
chunk = (std::min)(chunk, ahb_window_size - local);
}
void* const ret = mmap(virtual_base + virtual_offset, chunk, prot_flags,
MAP_SHARED | MAP_FIXED, map_fd, map_offset);
ASSERT_MSG(ret != MAP_FAILED, "mmap: {}", strerror(errno));
virtual_offset += chunk;
host_offset += chunk;
length -= chunk;
}
}
size_t BackingMapCount(size_t host_offset, size_t length) const noexcept {
if (length == 0) {
return 0;
}
if (ahb_bytes == 0) {
return 1;
}
size_t count = 0;
while (length > 0) {
size_t chunk = length;
if (host_offset < ahb_base) {
chunk = (std::min)(chunk, ahb_base - host_offset);
} else if (host_offset < ahb_base + ahb_bytes) {
const size_t local = (host_offset - ahb_base) % ahb_window_size;
chunk = (std::min)(chunk, ahb_window_size - local);
}
host_offset += chunk;
length -= chunk;
++count;
}
return count;
}
std::span<AHardwareBuffer* const> AhbWindows() const noexcept {
return ahb_windows;
}
size_t AhbWindowSize() const noexcept {
return ahb_bytes != 0 ? ahb_window_size : 0;
}
size_t AhbBase() const noexcept {
return ahb_base;
}
#endif
~Impl() { ~Impl() {
Release(); Release();
} }
@@ -587,6 +895,12 @@ public:
#ifdef ARCHITECTURE_arm64 #ifdef ARCHITECTURE_arm64
if (True(perms & MemoryPermission::Execute)) if (True(perms & MemoryPermission::Execute))
prot_flags |= PROT_EXEC; prot_flags |= PROT_EXEC;
#endif
#ifdef __ANDROID__
if (ahb_bytes != 0) {
MapBackingRange(virtual_offset, host_offset, length, prot_flags);
return;
}
#endif #endif
int flags = (fd >= 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED; int flags = (fd >= 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED;
void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset); void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset);
@@ -632,8 +946,18 @@ public:
virtual_base = nullptr; virtual_base = nullptr;
} }
bool IsBackingShared() const noexcept {
#ifdef __ANDROID__
if (ahb_bytes != 0) {
return true;
}
#endif
return fd >= 0;
}
const size_t backing_size; ///< Size of the backing memory in bytes const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
const size_t preferred_offset;
u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)}; u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)};
u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)}; u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)};
@@ -656,6 +980,21 @@ private:
int ret = close(fd); int ret = close(fd);
ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno)); ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno));
} }
#ifdef __ANDROID__
for (const int window_fd : ahb_fds) {
SyncDmaBufCpuAccess(window_fd, DmaBufSyncEnd);
}
for (AHardwareBuffer* buffer : ahb_windows) {
AHardwareBuffer_release(buffer);
}
ahb_windows.clear();
ahb_fds.clear();
if (ahb_bytes != 0) {
committed_backing_size.store(0, std::memory_order_relaxed);
ahb_bytes = 0;
}
#endif
} }
void AdjustMap(size_t* virtual_offset, size_t* length) { void AdjustMap(size_t* virtual_offset, size_t* length) {
@@ -681,11 +1020,19 @@ private:
int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
FreeRegionManager free_manager{}; FreeRegionManager free_manager{};
#ifdef __ANDROID__
std::vector<AHardwareBuffer*> ahb_windows;
std::vector<int> ahb_fds;
size_t ahb_window_size{};
size_t ahb_base{};
size_t ahb_bytes{};
#endif
}; };
#endif // ^^^ POSIX ^^^ #endif // ^^^ POSIX ^^^
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_) HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
: backing_size(backing_size_) : backing_size(backing_size_)
, virtual_size(virtual_size_) , virtual_size(virtual_size_)
{ {
@@ -697,7 +1044,7 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
#else #else
// Try to allocate a fastmem arena. // Try to allocate a fastmem arena.
// The implementation will fail with std::bad_alloc on errors. // The implementation will fail with std::bad_alloc on errors.
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize); impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize, preferred_offset_);
if (impl->Init()) { if (impl->Init()) {
backing_base = impl->backing_base; backing_base = impl->backing_base;
virtual_base = impl->virtual_base; virtual_base = impl->virtual_base;
@@ -767,6 +1114,46 @@ void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 f
std::memset(backing_base + physical_offset, fill_value, length); std::memset(backing_base + physical_offset, fill_value, length);
} }
std::span<AHardwareBuffer* const> HostMemory::BackingHardwareBuffers() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbWindows() : std::span<AHardwareBuffer* const>{};
#else
return {};
#endif
}
size_t HostMemory::BackingMapCount(size_t host_offset, size_t length) const noexcept {
#ifdef __ANDROID__
return impl ? impl->BackingMapCount(host_offset, length) : (length != 0 ? 1 : 0);
#else
return length != 0 ? 1 : 0;
#endif
}
size_t HostMemory::BackingHardwareBufferWindowSize() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbWindowSize() : 0;
#else
return 0;
#endif
}
bool HostMemory::IsBackingShared() const noexcept {
#if defined(__OPENORBIS__) || defined(__managarm__)
return false;
#else
return impl && impl->IsBackingShared();
#endif
}
size_t HostMemory::BackingHardwareBufferBase() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbBase() : 0;
#else
return 0;
#endif
}
void HostMemory::EnableDirectMappedAddress() { void HostMemory::EnableDirectMappedAddress() {
#if !(defined(__OPENORBIS__) || defined(__managarm__)) #if !(defined(__OPENORBIS__) || defined(__managarm__))
if (impl) { if (impl) {
+20 -1
View File
@@ -8,12 +8,17 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span>
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/virtual_buffer.h" #include "common/virtual_buffer.h"
struct AHardwareBuffer;
namespace Common { namespace Common {
[[nodiscard]] u64 GetCommittedBackingSize() noexcept;
enum class MemoryPermission : u32 { enum class MemoryPermission : u32 {
Read = 1 << 0, Read = 1 << 0,
Write = 1 << 1, Write = 1 << 1,
@@ -28,7 +33,7 @@ DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission)
*/ */
class HostMemory { class HostMemory {
public: public:
explicit HostMemory(size_t backing_size_, size_t virtual_size_); explicit HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_ = 0);
~HostMemory(); ~HostMemory();
/** /**
@@ -62,6 +67,20 @@ public:
return backing_base; return backing_base;
} }
[[nodiscard]] size_t BackingSize() const noexcept {
return backing_size;
}
[[nodiscard]] size_t BackingMapCount(size_t host_offset, size_t length) const noexcept;
[[nodiscard]] std::span<AHardwareBuffer* const> BackingHardwareBuffers() const noexcept;
[[nodiscard]] size_t BackingHardwareBufferWindowSize() const noexcept;
[[nodiscard]] size_t BackingHardwareBufferBase() const noexcept;
[[nodiscard]] bool IsBackingShared() const noexcept;
[[nodiscard]] u8* VirtualBasePointer() noexcept { [[nodiscard]] u8* VirtualBasePointer() noexcept {
return virtual_base; return virtual_base;
} }
+4 -4
View File
@@ -224,7 +224,7 @@ struct ColorConsoleBackend final : public Backend {
auto const df = GetDirectFormatArgs(entry); auto const df = GetDirectFormatArgs(entry);
// more restrictive, because take for example this simple prelude: // more restrictive, because take for example this simple prelude:
// [ 50.872256] Config <Info> common/settings.cpp:142:LogSettings: // [ 50.872256] Config <Info> common/settings.cpp:142:LogSettings:
char buffer[256]; char buffer[128];
auto result = fmt::format_to_n(buffer, sizeof(buffer) - 1, "\x1b{}[{:4d}.{:06d}] {} <{}> {}:{}:{}: ", color_str, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message); auto result = fmt::format_to_n(buffer, sizeof(buffer) - 1, "\x1b{}[{:4d}.{:06d}] {} <{}> {}:{}:{}: ", color_str, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message);
std::fwrite(buffer, 1, (std::min)(sizeof(buffer) - 1, result.size), stdout); std::fwrite(buffer, 1, (std::min)(sizeof(buffer) - 1, result.size), stdout);
std::fwrite(entry.message, 1, entry.message_len, stdout); std::fwrite(entry.message, 1, entry.message_len, stdout);
@@ -425,14 +425,14 @@ void SetColorConsoleBackendEnabled(bool enabled) {
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::string_view format, const fmt::format_args& args) { void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::string_view format, const fmt::format_args& args) {
if (logging_instance && logging_instance->filter.CheckMessage(log_class, log_level)) { if (logging_instance && logging_instance->filter.CheckMessage(log_class, log_level)) {
auto const flush = ::Settings::values.log_flush_line.GetValue();
char buffer[BUFSIZ]; char buffer[BUFSIZ];
auto result = fmt::vformat_to_n(buffer, sizeof(buffer) - 1, format, args); auto result = fmt::vformat_to_n(buffer, sizeof(buffer) - 1, format, args);
buffer[(std::min)(result.size, sizeof(buffer) - 1)] = '\0'; buffer[result.size] = '\0';
auto const flush = ::Settings::values.log_flush_line.GetValue();
logging_instance->ForEachBackend([=](Backend& backend) { logging_instance->ForEachBackend([=](Backend& backend) {
backend.Write(Entry{ backend.Write(Entry{
.message = buffer, .message = buffer,
.message_len = (std::min)(result.size, sizeof(buffer) - 1), .message_len = (std::min)(sizeof(buffer) - 1, result.size),
.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - logging_instance->time_origin), .timestamp = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - logging_instance->time_origin),
.log_class = log_class, .log_class = log_class,
.log_level = log_level, .log_level = log_level,
+64
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -17,6 +20,10 @@
#endif #endif
#endif #endif
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "common/memory_detect.h" #include "common/memory_detect.h"
namespace Common { namespace Common {
@@ -69,4 +76,61 @@ const MemoryInfo& GetMemInfo() {
return mem_info; return mem_info;
} }
u64 GetPermissibleMapCount() {
constexpr u64 DefaultMapCount = 65530;
constexpr u64 ReservedMaps = 20000;
u64 count = DefaultMapCount;
#ifdef __linux__
if (std::FILE* const file = std::fopen("/proc/sys/vm/max_map_count", "re")) {
char line[32];
if (std::fgets(line, sizeof(line), file) != nullptr) {
const u64 parsed = std::strtoull(line, nullptr, 10);
if (parsed != 0) {
count = parsed;
}
}
std::fclose(file);
}
#endif
if (count <= ReservedMaps) {
return 0;
}
return count - ReservedMaps;
}
u64 GetAvailablePhysicalMemory() {
#ifdef _WIN32
MEMORYSTATUSEX memorystatus;
memorystatus.dwLength = sizeof(memorystatus);
if (GlobalMemoryStatusEx(&memorystatus) == 0) {
return 0;
}
return memorystatus.ullAvailPhys;
#elif defined(__linux__)
static constexpr char AvailableKey[] = "MemAvailable:";
if (std::FILE* const file = std::fopen("/proc/meminfo", "re")) {
char line[256];
u64 available = 0;
while (std::fgets(line, sizeof(line), file) != nullptr) {
if (std::strncmp(line, AvailableKey, sizeof(AvailableKey) - 1) != 0) {
continue;
}
available = std::strtoull(line + sizeof(AvailableKey) - 1, nullptr, 10) * 1024;
break;
}
std::fclose(file);
if (available != 0) {
return available;
}
}
struct sysinfo meminfo;
if (sysinfo(&meminfo) != 0) {
return 0;
}
return static_cast<u64>(meminfo.freeram) * static_cast<u64>(meminfo.mem_unit);
#else
return 0;
#endif
}
} // namespace Common } // namespace Common
+7
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -18,4 +21,8 @@ struct MemoryInfo {
*/ */
[[nodiscard]] const MemoryInfo& GetMemInfo(); [[nodiscard]] const MemoryInfo& GetMemInfo();
[[nodiscard]] u64 GetPermissibleMapCount();
[[nodiscard]] u64 GetAvailablePhysicalMemory();
} // namespace Common } // namespace Common
+4 -2
View File
@@ -132,9 +132,11 @@ void LogSettings() {
} }
} }
} }
LOG_INFO(Config, "Eden Configuration:");
std::string settings_str{};
for (auto const& e : settings_list) for (auto const& e : settings_list)
LOG_INFO(Config, "{}", e); settings_str += e;
LOG_INFO(Config, "Eden Configuration:\n{}", settings_str);
#define LOG_PATH(NAME) \ #define LOG_PATH(NAME) \
LOG_INFO(Config, #NAME ": {}", Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::NAME))) LOG_INFO(Config, #NAME ": {}", Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::NAME)))
LOG_PATH(CacheDir); LOG_PATH(CacheDir);
+3
View File
@@ -645,6 +645,9 @@ struct Values {
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders", SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
Category::RendererHacks}; Category::RendererHacks};
SwitchableSetting<bool> use_unified_memory{linkage, false, "use_unified_memory",
Category::RendererHacks};
SwitchableSetting<GpuUnswizzleSize> gpu_unswizzle_texture_size{linkage, SwitchableSetting<GpuUnswizzleSize> gpu_unswizzle_texture_size{linkage,
GpuUnswizzleSize::Large, GpuUnswizzleSize::Large,
"gpu_unswizzle_texture_size", "gpu_unswizzle_texture_size",
+5 -1
View File
@@ -119,6 +119,7 @@ struct System::Impl {
is_multicore = Settings::values.use_multi_core.GetValue(); is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb; extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
unified_memory = Settings::values.use_unified_memory.GetValue();
core_timing.SetMulticore(is_multicore); core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); }); core_timing.Initialize([&system]() { system.RegisterHostThread(); });
@@ -146,7 +147,8 @@ struct System::Impl {
!device_memory.has_value() || !device_memory.has_value() ||
is_multicore != Settings::values.use_multi_core.GetValue() || is_multicore != Settings::values.use_multi_core.GetValue() ||
extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() != extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
Settings::MemoryLayout::Memory_4Gb); Settings::MemoryLayout::Memory_4Gb) ||
unified_memory != Settings::values.use_unified_memory.GetValue();
if (!must_reinitialize) { if (!must_reinitialize) {
return; return;
@@ -157,6 +159,7 @@ struct System::Impl {
is_multicore = Settings::values.use_multi_core.GetValue(); is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout = extended_memory_layout =
Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb; Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
unified_memory = Settings::values.use_unified_memory.GetValue();
Initialize(system); Initialize(system);
} }
@@ -503,6 +506,7 @@ struct System::Impl {
std::atomic_bool is_powered_on{}; std::atomic_bool is_powered_on{};
bool is_multicore : 1 = false; bool is_multicore : 1 = false;
bool extended_memory_layout : 1 = false; bool extended_memory_layout : 1 = false;
bool unified_memory : 1 = false;
bool exit_locked : 1 = false; bool exit_locked : 1 = false;
bool exit_requested : 1 = false; bool exit_requested : 1 = false;
bool nvdec_active : 1 = false; bool nvdec_active : 1 = false;
+13 -1
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -12,9 +15,18 @@ constexpr size_t VirtualReserveSize = 1ULL << 38;
constexpr size_t VirtualReserveSize = 1ULL << 39; constexpr size_t VirtualReserveSize = 1ULL << 39;
#endif #endif
namespace {
size_t ApplicationPoolOffset() {
using Init = Kernel::Board::Nintendo::Nx::KSystemControl::Init;
const size_t dram_size = Init::GetIntendedMemorySize();
const size_t application_pool_size = Init::GetApplicationPoolSize();
return dram_size > application_pool_size ? dram_size - application_pool_size : 0;
}
}
DeviceMemory::DeviceMemory() DeviceMemory::DeviceMemory()
: buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(), : buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(),
VirtualReserveSize} {} VirtualReserveSize, ApplicationPoolOffset()} {}
DeviceMemory::~DeviceMemory() = default; DeviceMemory::~DeviceMemory() = default;
+35
View File
@@ -20,6 +20,8 @@
#include "common/scratch_buffer.h" #include "common/scratch_buffer.h"
#include "common/virtual_buffer.h" #include "common/virtual_buffer.h"
struct AHardwareBuffer;
namespace Core { namespace Core {
constexpr size_t DEVICE_PAGEBITS = 12ULL; constexpr size_t DEVICE_PAGEBITS = 12ULL;
@@ -95,6 +97,34 @@ public:
ApplyOpOnPAddr(address, buffer, operation); ApplyOpOnPAddr(address, buffer, operation);
} }
u8* GetPhysicalBase() noexcept {
return reinterpret_cast<u8*>(physical_base);
}
const u8* GetPhysicalBase() const noexcept {
return reinterpret_cast<const u8*>(physical_base);
}
size_t GetPhysicalSize() const noexcept {
return physical_size;
}
std::span<AHardwareBuffer* const> GetBackingHardwareBuffers() const noexcept {
return ahb_windows;
}
size_t GetBackingHardwareBufferWindowSize() const noexcept {
return ahb_window_size;
}
size_t GetBackingHardwareBufferBase() const noexcept {
return ahb_base;
}
bool IsBackingShared() const noexcept {
return backing_is_shared;
}
PAddr GetPhysicalRawAddressFromDAddr(DAddr address) const { PAddr GetPhysicalRawAddressFromDAddr(DAddr address) const {
PAddr subbits = PAddr(address & page_mask); PAddr subbits = PAddr(address & page_mask);
auto paddr = tracked_entries[(address >> page_bits)].compressed_physical_ptr; auto paddr = tracked_entries[(address >> page_bits)].compressed_physical_ptr;
@@ -171,6 +201,11 @@ private:
std::unique_ptr<DeviceMemoryManagerAllocator<Traits>> impl; std::unique_ptr<DeviceMemoryManagerAllocator<Traits>> impl;
const uintptr_t physical_base; const uintptr_t physical_base;
const size_t physical_size;
const std::span<AHardwareBuffer* const> ahb_windows;
const size_t ahb_window_size;
const size_t ahb_base;
const bool backing_is_shared;
DeviceInterface* device_inter; DeviceInterface* device_inter;
struct TrackedEntry { struct TrackedEntry {
+5
View File
@@ -171,6 +171,11 @@ struct DeviceMemoryManagerAllocator {
template <typename Traits> template <typename Traits>
DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_) DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_)
: physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())} : physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())}
, physical_size{device_memory_.buffer.BackingSize()}
, ahb_windows{device_memory_.buffer.BackingHardwareBuffers()}
, ahb_window_size{device_memory_.buffer.BackingHardwareBufferWindowSize()}
, ahb_base{device_memory_.buffer.BackingHardwareBufferBase()}
, backing_is_shared{device_memory_.buffer.IsBackingShared()}
, device_inter{nullptr} , device_inter{nullptr}
, compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS)) , compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS))
, tracked_entries(device_as_size >> Memory::YUZU_PAGEBITS) , tracked_entries(device_as_size >> Memory::YUZU_PAGEBITS)
@@ -227,6 +227,8 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
tr("Preserves GPU-modified data by reading it back before uploading.\nSome games require this to render certain effects properly.")); tr("Preserves GPU-modified data by reading it back before uploading.\nSome games require this to render certain effects properly."));
INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"), INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"),
tr("May reduce shader stutter.")); tr("May reduce shader stutter."));
INSERT(Settings, use_unified_memory, tr("Enable unified memory access"),
tr("Lets the GPU write buffer readbacks directly into guest memory."));
INSERT(Settings, gpu_clock, tr("GPU Clocks"), INSERT(Settings, gpu_clock, tr("GPU Clocks"),
tr("Makes the game believe GPU work finishes faster than it does, so it stops lowering " tr("Makes the game believe GPU work finishes faster than it does, so it stops lowering "
"resolution and render distance to fit the Switch's clocks.")); "resolution and render distance to fit the Switch's clocks."));
@@ -13,6 +13,7 @@
namespace Shader::Backend::SPIRV { namespace Shader::Backend::SPIRV {
namespace { namespace {
Id SharedPointer(EmitContext& ctx, Id offset, u32 index_offset = 0) { Id SharedPointer(EmitContext& ctx, Id offset, u32 index_offset = 0) {
offset = ctx.BoundSharedOffset(offset, 4 + index_offset * 4);
const Id shift_id{ctx.Const(2U)}; const Id shift_id{ctx.Const(2U)};
Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)};
if (index_offset > 0) { if (index_offset > 0) {
@@ -160,7 +161,8 @@ Id EmitSharedAtomicExchange32(EmitContext& ctx, Id offset, Id value) {
Id EmitSharedAtomicExchange64(EmitContext& ctx, Id offset, Id value) { Id EmitSharedAtomicExchange64(EmitContext& ctx, Id offset, Id value) {
if (ctx.profile.support_shared_int64_atomics && ctx.uses_explicit_workgroup_layout) { if (ctx.profile.support_shared_int64_atomics && ctx.uses_explicit_workgroup_layout) {
const Id shift_id{ctx.Const(3U)}; const Id shift_id{ctx.Const(3U)};
const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; const Id index{
ctx.OpShiftRightArithmetic(ctx.U32[1], ctx.BoundSharedOffset(offset, 8), shift_id)};
const Id pointer{ const Id pointer{
ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)}; ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)};
const auto [scope, semantics]{AtomicArgs(ctx)}; const auto [scope, semantics]{AtomicArgs(ctx)};
@@ -31,6 +31,7 @@ std::pair<Id, Id> ExtractArgs(EmitContext& ctx, Id offset, u32 mask, u32 count)
} // Anonymous namespace } // Anonymous namespace
Id EmitLoadSharedU8(EmitContext& ctx, Id offset) { Id EmitLoadSharedU8(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 1);
if (ctx.uses_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{ const Id pointer{
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
@@ -42,6 +43,7 @@ Id EmitLoadSharedU8(EmitContext& ctx, Id offset) {
} }
Id EmitLoadSharedS8(EmitContext& ctx, Id offset) { Id EmitLoadSharedS8(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 1);
if (ctx.uses_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{ const Id pointer{
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
@@ -53,6 +55,7 @@ Id EmitLoadSharedS8(EmitContext& ctx, Id offset) {
} }
Id EmitLoadSharedU16(EmitContext& ctx, Id offset) { Id EmitLoadSharedU16(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 2);
if (ctx.uses_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer)); return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer));
@@ -63,6 +66,7 @@ Id EmitLoadSharedU16(EmitContext& ctx, Id offset) {
} }
Id EmitLoadSharedS16(EmitContext& ctx, Id offset) { Id EmitLoadSharedS16(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 2);
if (ctx.uses_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer)); return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer));
@@ -73,6 +77,7 @@ Id EmitLoadSharedS16(EmitContext& ctx, Id offset) {
} }
Id EmitLoadSharedU32(EmitContext& ctx, Id offset) { Id EmitLoadSharedU32(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 4);
if (ctx.uses_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2)}; const Id pointer{Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2)};
return ctx.OpLoad(ctx.U32[1], pointer); return ctx.OpLoad(ctx.U32[1], pointer);
@@ -82,6 +87,7 @@ Id EmitLoadSharedU32(EmitContext& ctx, Id offset) {
} }
Id EmitLoadSharedU64(EmitContext& ctx, Id offset) { Id EmitLoadSharedU64(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 8);
if (ctx.uses_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)}; const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)};
return ctx.OpLoad(ctx.U32[2], pointer); return ctx.OpLoad(ctx.U32[2], pointer);
@@ -97,6 +103,7 @@ Id EmitLoadSharedU64(EmitContext& ctx, Id offset) {
} }
Id EmitLoadSharedU128(EmitContext& ctx, Id offset) { Id EmitLoadSharedU128(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 16);
if (ctx.uses_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)}; const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)};
return ctx.OpLoad(ctx.U32[4], pointer); return ctx.OpLoad(ctx.U32[4], pointer);
@@ -113,6 +120,7 @@ Id EmitLoadSharedU128(EmitContext& ctx, Id offset) {
} }
void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) { void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) {
offset = ctx.BoundSharedOffset(offset, 1);
if (ctx.uses_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{ const Id pointer{
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
@@ -123,6 +131,7 @@ void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) {
} }
void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) { void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) {
offset = ctx.BoundSharedOffset(offset, 2);
if (ctx.uses_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
ctx.OpStore(pointer, ctx.OpUConvert(ctx.U16, value)); ctx.OpStore(pointer, ctx.OpUConvert(ctx.U16, value));
@@ -132,6 +141,7 @@ void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) {
} }
void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) { void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) {
offset = ctx.BoundSharedOffset(offset, 4);
Id pointer{}; Id pointer{};
if (ctx.uses_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
pointer = Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2); pointer = Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2);
@@ -144,6 +154,7 @@ void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) {
} }
void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) { void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) {
offset = ctx.BoundSharedOffset(offset, 8);
if (ctx.uses_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)}; const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)};
ctx.OpStore(pointer, value); ctx.OpStore(pointer, value);
@@ -159,6 +170,7 @@ void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) {
} }
void EmitWriteSharedU128(EmitContext& ctx, Id offset, Id value) { void EmitWriteSharedU128(EmitContext& ctx, Id offset, Id value) {
offset = ctx.BoundSharedOffset(offset, 16);
if (ctx.uses_explicit_workgroup_layout) { if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)}; const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)};
ctx.OpStore(pointer, value); ctx.OpStore(pointer, value);
@@ -600,6 +600,16 @@ void EmitContext::DefineLocalMemory(const IR::Program& program) {
} }
} }
Id EmitContext::BoundSharedOffset(Id offset, u32 access_bytes) {
if (shared_memory_declared_bytes == 0) {
return offset;
}
const u32 last_valid{shared_memory_declared_bytes > access_bytes
? shared_memory_declared_bytes - access_bytes
: 0U};
return OpUMin(U32[1], offset, Const(last_valid));
}
void EmitContext::DefineSharedMemory(const IR::Program& program) { void EmitContext::DefineSharedMemory(const IR::Program& program) {
uses_explicit_workgroup_layout = uses_explicit_workgroup_layout =
profile.support_explicit_workgroup_layout && profile.support_explicit_workgroup_layout &&
@@ -608,8 +618,15 @@ void EmitContext::DefineSharedMemory(const IR::Program& program) {
if (program.shared_memory_size == 0) { if (program.shared_memory_size == 0) {
return; return;
} }
const u32 device_limit{profile.max_shared_memory_size};
const u32 shared_memory_size{device_limit != 0 && program.shared_memory_size > device_limit
? device_limit
: program.shared_memory_size};
if (shared_memory_size != program.shared_memory_size) {
shared_memory_declared_bytes = shared_memory_size;
}
const auto make{[&](Id element_type, u32 element_size) { const auto make{[&](Id element_type, u32 element_size) {
const u32 num_elements{Common::DivCeil(program.shared_memory_size, element_size)}; const u32 num_elements{Common::DivCeil(shared_memory_size, element_size)};
const Id array_type{TypeArray(element_type, Const(num_elements))}; const Id array_type{TypeArray(element_type, Const(num_elements))};
Decorate(array_type, spv::Decoration::ArrayStride, element_size); Decorate(array_type, spv::Decoration::ArrayStride, element_size);
@@ -644,7 +661,7 @@ void EmitContext::DefineSharedMemory(const IR::Program& program) {
std::tie(shared_memory_u32x4, shared_u32x4, std::ignore) = make(U32[4], 16); std::tie(shared_memory_u32x4, shared_u32x4, std::ignore) = make(U32[4], 16);
return; return;
} }
const u32 num_elements{Common::DivCeil(program.shared_memory_size, 4U)}; const u32 num_elements{Common::DivCeil(shared_memory_size, 4U)};
const Id type{TypeArray(U32[1], Const(num_elements))}; const Id type{TypeArray(U32[1], Const(num_elements))};
shared_memory_u32_type = TypePointer(spv::StorageClass::Workgroup, type); shared_memory_u32_type = TypePointer(spv::StorageClass::Workgroup, type);
@@ -312,6 +312,8 @@ public:
Id local_memory{}; Id local_memory{};
bool uses_explicit_workgroup_layout{}; bool uses_explicit_workgroup_layout{};
u32 shared_memory_declared_bytes{};
[[nodiscard]] Id BoundSharedOffset(Id offset, u32 access_bytes);
Id shared_memory_u8{}; Id shared_memory_u8{};
Id shared_memory_u16{}; Id shared_memory_u16{};
Id shared_memory_u32{}; Id shared_memory_u32{};
+3
View File
@@ -103,6 +103,9 @@ struct Profile {
u32 gl_max_compute_smem_size{}; u32 gl_max_compute_smem_size{};
/// Largest workgroup shared memory allocation the device accepts, 0 when unconstrained
u32 max_shared_memory_size{};
/// Maxwell and earlier nVidia architectures have broken robust support /// Maxwell and earlier nVidia architectures have broken robust support
bool has_broken_robust{}; bool has_broken_robust{};
+20
View File
@@ -20,6 +20,8 @@ namespace VideoCommon {
enum class BufferFlagBits { enum class BufferFlagBits {
Picked = 1 << 0, Picked = 1 << 0,
CachedWrites = 1 << 1,
PreemtiveDownload = 1 << 2,
}; };
DECLARE_ENUM_FLAG_OPERATORS(BufferFlagBits) DECLARE_ENUM_FLAG_OPERATORS(BufferFlagBits)
@@ -56,6 +58,15 @@ public:
flags |= BufferFlagBits::Picked; flags |= BufferFlagBits::Picked;
} }
void MarkPreemtiveDownload() noexcept {
flags |= BufferFlagBits::PreemtiveDownload;
}
/// Unmark buffer as picked
void Unpick() noexcept {
flags &= ~BufferFlagBits::Picked;
}
/// Increases the likeliness of this being a stream buffer /// Increases the likeliness of this being a stream buffer
void IncreaseStreamScore(int score) noexcept { void IncreaseStreamScore(int score) noexcept {
stream_score += score; stream_score += score;
@@ -76,6 +87,15 @@ public:
return True(flags & BufferFlagBits::Picked); return True(flags & BufferFlagBits::Picked);
} }
/// Returns true when the buffer has pending cached writes
[[nodiscard]] bool HasCachedWrites() const noexcept {
return True(flags & BufferFlagBits::CachedWrites);
}
bool IsPreemtiveDownload() const noexcept {
return True(flags & BufferFlagBits::PreemtiveDownload);
}
/// Returns the base CPU address of the buffer /// Returns the base CPU address of the buffer
[[nodiscard]] VAddr CpuAddr() const noexcept { [[nodiscard]] VAddr CpuAddr() const noexcept {
return cpu_addr; return cpu_addr;
+318 -198
View File
@@ -7,7 +7,6 @@
#pragma once #pragma once
#include <algorithm> #include <algorithm>
#include <limits>
#include <memory> #include <memory>
#include <numeric> #include <numeric>
@@ -122,6 +121,25 @@ void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) {
memory_tracker.MarkRegionAsCpuModified(device_addr, size); memory_tracker.MarkRegionAsCpuModified(device_addr, size);
} }
template <class P>
void BufferCache<P>::CachedWriteMemory(DAddr device_addr, u64 size) {
const bool is_dirty = IsRegionRegistered(device_addr, size);
if (!is_dirty) {
return;
}
DAddr aligned_start = Common::AlignDown(device_addr, DEVICE_PAGESIZE);
DAddr aligned_end = Common::AlignUp(device_addr + size, DEVICE_PAGESIZE);
if (!IsRegionGpuModified(aligned_start, aligned_end - aligned_start)) {
WriteMemory(device_addr, size);
return;
}
tmp_buffer.resize_destructive(size);
device_memory.ReadBlockUnsafe(device_addr, tmp_buffer.data(), size);
InlineMemoryImplementation(device_addr, size, tmp_buffer);
}
template <class P> template <class P>
bool BufferCache<P>::OnCPUWrite(DAddr device_addr, u64 size) { bool BufferCache<P>::OnCPUWrite(DAddr device_addr, u64 size) {
const bool is_dirty = IsRegionRegistered(device_addr, size); const bool is_dirty = IsRegionRegistered(device_addr, size);
@@ -404,7 +422,7 @@ void BufferCache<P>::UnbindGraphicsStorageBuffers(size_t stage) {
} }
template <class P> template <class P>
void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, bool BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index,
u32 cbuf_offset, bool is_written) { u32 cbuf_offset, bool is_written) {
const bool already_enabled = const bool already_enabled =
((channel_state->enabled_storage_buffers[stage] >> ssbo_index) & 1U) != 0; ((channel_state->enabled_storage_buffers[stage] >> ssbo_index) & 1U) != 0;
@@ -415,7 +433,7 @@ void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index,
LOG_WARNING(HW_GPU, LOG_WARNING(HW_GPU,
"Skipping graphics storage buffer {} due to driver limit {}", "Skipping graphics storage buffer {} due to driver limit {}",
ssbo_index, max_bindings); ssbo_index, max_bindings);
return; return false;
} }
} }
} }
@@ -431,6 +449,7 @@ void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index,
const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset; const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset;
channel_state->storage_buffers[stage][ssbo_index] = channel_state->storage_buffers[stage][ssbo_index] =
StorageBufferBinding(ssbo_addr, cbuf_index, is_written); StorageBufferBinding(ssbo_addr, cbuf_index, is_written);
return (channel_state->storage_buffers[stage][ssbo_index].buffer_id != NULL_BUFFER_ID);
} }
template <class P> template <class P>
@@ -552,7 +571,11 @@ void BufferCache<P>::AccumulateFlushes() {
template <class P> template <class P>
bool BufferCache<P>::ShouldWaitAsyncFlushes() const noexcept { bool BufferCache<P>::ShouldWaitAsyncFlushes() const noexcept {
return (!async_buffers.empty() && async_buffers.front().has_value()); if (async_buffers.empty()) {
return false;
}
return async_buffers.front().has_value() ||
!pending_downloads.front().unified_copies.empty();
} }
template <class P> template <class P>
@@ -560,6 +583,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
AccumulateFlushes(); AccumulateFlushes();
if (committed_gpu_modified_ranges.empty()) { if (committed_gpu_modified_ranges.empty()) {
pending_downloads.emplace_back();
async_buffers.emplace_back(std::optional<Async_Buffer>{}); async_buffers.emplace_back(std::optional<Async_Buffer>{});
return; return;
} }
@@ -619,27 +643,84 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
} }
committed_gpu_modified_ranges.clear(); committed_gpu_modified_ranges.clear();
if (downloads.empty()) { if (downloads.empty()) {
pending_downloads.emplace_back();
async_buffers.emplace_back(std::optional<Async_Buffer>{}); async_buffers.emplace_back(std::optional<Async_Buffer>{});
return; return;
} }
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes, true);
boost::container::small_vector<BufferCopy, 4> normalized_copies; struct QueuedUnifiedCopy {
runtime.PreCopyBarrier(); u64 window;
BufferId buffer_id;
boost::container::small_vector<BufferCopy, 16> copies;
};
AsyncDownloadBatch batch;
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> staging_downloads;
boost::container::small_vector<QueuedUnifiedCopy, 4> unified_copy_queue;
boost::container::small_vector<u64, 4> window_ids;
UnifiedWindowGroups groups;
u64 staging_size_bytes = 0;
for (auto& [copy, buffer_id] : downloads) { for (auto& [copy, buffer_id] : downloads) {
copy.dst_offset += download_staging.offset;
const std::array copies{copy};
BufferCopy second_copy{copy};
Buffer& buffer = slot_buffers[buffer_id]; Buffer& buffer = slot_buffers[buffer_id];
second_copy.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset; const DAddr orig_device_addr = buffer.CpuAddr() + copy.src_offset;
const DAddr orig_device_addr = static_cast<DAddr>(second_copy.src_offset); bool unified = false;
if constexpr (USE_UNIFIED_MEMORY) {
if (runtime.HasUnifiedMemory()) {
window_ids.clear();
groups.clear();
unified = ResolveUnifiedWindows(orig_device_addr, copy.src_offset, copy.size,
window_ids, groups);
}
}
BufferCopy record{copy};
record.src_offset = static_cast<size_t>(orig_device_addr);
if (unified) {
async_downloads.Add(orig_device_addr, copy.size);
buffer.MarkUsage(copy.src_offset, copy.size);
for (size_t i = 0; i < window_ids.size(); ++i) {
unified_copy_queue.push_back(
QueuedUnifiedCopy{window_ids[i], buffer_id, std::move(groups[i])});
}
batch.unified_copies.push_back(record);
continue;
}
copy.dst_offset = staging_size_bytes;
constexpr u64 align = 64ULL;
staging_size_bytes += (copy.size + align - 1) & ~(align - 1ULL);
staging_downloads.push_back({copy, buffer_id});
}
std::optional<Async_Buffer> download_staging;
if (!staging_downloads.empty()) {
download_staging = runtime.DownloadStagingBuffer(staging_size_bytes, true);
}
runtime.PreCopyBarrier();
for (auto& [copy, buffer_id] : staging_downloads) {
copy.dst_offset += download_staging->offset;
const std::array copies{copy};
Buffer& buffer = slot_buffers[buffer_id];
BufferCopy record{copy};
record.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
const DAddr orig_device_addr = static_cast<DAddr>(record.src_offset);
async_downloads.Add(orig_device_addr, copy.size); async_downloads.Add(orig_device_addr, copy.size);
buffer.MarkUsage(copy.src_offset, copy.size); buffer.MarkUsage(copy.src_offset, copy.size);
runtime.CopyBuffer(download_staging.buffer, buffer, copies, false); runtime.CopyBuffer(download_staging->buffer, buffer, copies, false);
normalized_copies.push_back(second_copy); batch.staging_copies.push_back(record);
}
if constexpr (USE_UNIFIED_MEMORY) {
for (const auto& queued : unified_copy_queue) {
const std::span<const BufferCopy> group_span(queued.copies.data(),
queued.copies.size());
runtime.CopyToUnifiedMemory(queued.window, slot_buffers[queued.buffer_id], group_span);
}
if (!unified_copy_queue.empty()) {
runtime.FlushUnifiedMemoryCopies();
runtime.UnifiedMemoryHostBarrier();
}
} }
runtime.PostCopyBarrier(); runtime.PostCopyBarrier();
pending_downloads.emplace_back(std::move(normalized_copies)); pending_downloads.emplace_back(std::move(batch));
async_buffers.emplace_back(download_staging); async_buffers.emplace_back(std::move(download_staging));
} }
template <class P> template <class P>
@@ -654,32 +735,49 @@ void BufferCache<P>::PopAsyncFlushes() {
template <class P> template <class P>
void BufferCache<P>::PopAsyncBuffers() { void BufferCache<P>::PopAsyncBuffers() {
if (async_buffers.empty()) { struct Writeback {
return; DAddr addr;
} const u8* src;
if (!async_buffers.front().has_value()) { u64 size;
};
boost::container::small_vector<Writeback, 8> writebacks;
{
std::scoped_lock lock{mutex};
if (async_buffers.empty()) {
return;
}
auto& batch = pending_downloads.front();
auto& async_buffer = async_buffers.front();
if (async_buffer.has_value()) {
const u8* base = async_buffer->mapped_span.data();
const size_t base_offset = async_buffer->offset;
for (const auto& copy : batch.staging_copies) {
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
const u64 dst_offset = copy.dst_offset - base_offset;
const u8* read_mapped_memory = base + dst_offset;
async_downloads.ForEachInRange(
device_addr, copy.size, [&](DAddr start, DAddr end, s32) {
writebacks.push_back(
{start, &read_mapped_memory[start - device_addr], end - start});
});
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
gpu_modified_ranges.Subtract(start, end - start);
});
}
async_buffers_death_ring.emplace_back(*async_buffer);
}
for (const auto& copy : batch.unified_copies) {
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
gpu_modified_ranges.Subtract(start, end - start);
});
}
async_buffers.pop_front(); async_buffers.pop_front();
return; pending_downloads.pop_front();
} }
auto& downloads = pending_downloads.front(); for (const auto& wb : writebacks) {
auto& async_buffer = async_buffers.front(); device_memory.WriteBlockUnsafe(wb.addr, wb.src, wb.size);
u8* base = async_buffer->mapped_span.data();
const size_t base_offset = async_buffer->offset;
for (const auto& copy : downloads) {
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
const u64 dst_offset = copy.dst_offset - base_offset;
const u8* read_mapped_memory = base + dst_offset;
async_downloads.ForEachInRange(device_addr, copy.size, [&](DAddr start, DAddr end, s32) {
device_memory.WriteBlockUnsafe(start, &read_mapped_memory[start - device_addr],
end - start);
});
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
gpu_modified_ranges.Subtract(start, end - start);
});
} }
async_buffers_death_ring.emplace_back(*async_buffer);
async_buffers.pop_front();
pending_downloads.pop_front();
} }
template <class P> template <class P>
@@ -743,6 +841,16 @@ void BufferCache<P>::BindHostIndexBuffer() {
} }
} }
template <class P>
void BufferCache<P>::BindHostVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size,
u32 stride) {
if constexpr (IS_OPENGL) {
runtime.BindVertexBuffer(index, buffer, offset, size, stride);
} else {
runtime.BindVertexBuffer(index, buffer.Handle(), offset, size, stride);
}
}
template <class P> template <class P>
Binding& BufferCache<P>::VertexBufferSlot(u32 index) { Binding& BufferCache<P>::VertexBufferSlot(u32 index) {
ASSERT(index < NUM_VERTEX_BUFFERS); ASSERT(index < NUM_VERTEX_BUFFERS);
@@ -977,11 +1085,25 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
Buffer& buffer = slot_buffers[binding.buffer_id]; Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id); TouchBuffer(buffer, binding.buffer_id);
const u32 size = binding.size; const u32 size = binding.size;
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
if constexpr (USE_UNIFIED_MEMORY) {
const auto window = TryResolveUnifiedRange(binding.device_addr, size);
if (window && runtime.IsUnifiedStorageRange(size, window->offset)) {
if (is_written) {
memory_tracker.MarkRegionAsCpuModified(binding.device_addr, size);
}
runtime.BindStorageBuffer(runtime.UnifiedWindowBuffer(window->window),
runtime.UnifiedWindowAddress(window->window),
static_cast<u32>(window->offset), size, is_written);
return;
}
}
SynchronizeBuffer(buffer, binding.device_addr, size); SynchronizeBuffer(buffer, binding.device_addr, size);
const u32 offset = buffer.Offset(binding.device_addr); const u32 offset = buffer.Offset(binding.device_addr);
buffer.MarkUsage(offset, size); buffer.MarkUsage(offset, size);
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
if (is_written) { if (is_written) {
MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size); MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size);
@@ -1168,7 +1290,7 @@ void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
if (is_indexed) { if (is_indexed) {
UpdateIndexBuffer(); UpdateIndexBuffer();
} }
UpdateVertexBuffers(is_indexed); UpdateVertexBuffers();
UpdateTransformFeedbackBuffers(); UpdateTransformFeedbackBuffers();
for (size_t stage = 0; stage < NUM_STAGES; ++stage) { for (size_t stage = 0; stage < NUM_STAGES; ++stage) {
UpdateUniformBuffers(stage); UpdateUniformBuffers(stage);
@@ -1222,14 +1344,9 @@ void BufferCache<P>::UpdateIndexBuffer() {
const GPUVAddr gpu_addr_begin = index_buffer_ref.StartAddress(); const GPUVAddr gpu_addr_begin = index_buffer_ref.StartAddress();
const GPUVAddr gpu_addr_end = index_buffer_ref.EndAddress(); const GPUVAddr gpu_addr_end = index_buffer_ref.EndAddress();
const std::optional<DAddr> device_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin); const std::optional<DAddr> device_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
u64 address_size = 0; const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
if (gpu_addr_end > gpu_addr_begin) { const u32 draw_size = (index_buffer_ref.count + index_buffer_ref.first) * u32(index_buffer_ref.FormatSizeInBytes());
address_size = (std::min)(gpu_addr_end - gpu_addr_begin, const u32 size = (std::min)(address_size, draw_size);
u64{(std::numeric_limits<u32>::max)()});
}
const u64 draw_size = (u64{index_buffer_ref.count} + u64{index_buffer_ref.first}) *
u64{index_buffer_ref.FormatSizeInBytes()};
const u32 size = static_cast<u32>((std::min)(address_size, draw_size));
if (size == 0 || !device_addr) { if (size == 0 || !device_addr) {
channel_state->index_buffer = NULL_BINDING; channel_state->index_buffer = NULL_BINDING;
return; return;
@@ -1242,142 +1359,20 @@ void BufferCache<P>::UpdateIndexBuffer() {
} }
template <class P> template <class P>
u64 BufferCache<P>::DrawMaxIndex() { void BufferCache<P>::UpdateVertexBuffers() {
if (max_index_scanned) {
return cached_max_index;
}
max_index_scanned = true;
cached_max_index = 0;
const auto& index_buffer_ref = maxwell3d->draw_manager.draw_state.index_buffer;
const u32 count = index_buffer_ref.count;
if (count == 0) {
return 0;
}
index_scan_buffer.resize_destructive(count);
gpu_memory->ReadBlockUnsafe(index_buffer_ref.IndexStart(), index_scan_buffer.data(),
size_t{count} * sizeof(u32));
u32 restart_index = (std::numeric_limits<u32>::max)();
if (maxwell3d->regs.primitive_restart.enabled != 0) {
restart_index = maxwell3d->regs.primitive_restart.index;
}
u32 max_index = 0;
for (u32 i = 0; i < count; ++i) {
const u32 value = index_scan_buffer[i];
if (value == restart_index) {
continue;
}
max_index = (std::max)(max_index, value);
}
cached_max_index = max_index;
return cached_max_index;
}
template <class P>
u64 BufferCache<P>::StreamAttributeExtent(u32 index) {
using VertexAttribute = typename Maxwell::VertexAttribute;
if (stream_extents_valid) {
return stream_extents[index];
}
stream_extents_valid = true;
stream_extents.fill(0);
for (size_t i = 0; i < Maxwell::NumVertexAttributes; ++i) {
const auto& attribute = maxwell3d->regs.vertex_attrib_format[i];
if (attribute.constant != 0 || attribute.size == VertexAttribute::Size::Invalid) {
continue;
}
const u32 buffer = attribute.buffer.Value();
if (buffer >= NUM_VERTEX_BUFFERS) {
continue;
}
const u64 end = static_cast<u64>(attribute.offset.Value()) +
static_cast<u64>(attribute.SizeInBytes());
stream_extents[buffer] = (std::max)(stream_extents[buffer], end);
}
return stream_extents[index];
}
template <class P>
u64 BufferCache<P>::DrawVertexBound(u32 index, bool is_indexed) {
const auto& array = maxwell3d->regs.vertex_streams[index];
if (array.enable == 0) {
return 0;
}
const u64 extent = StreamAttributeExtent(index);
if (extent == 0) {
return 0;
}
const u64 stride = static_cast<u64>(array.stride);
if (stride == 0) {
return extent;
}
const auto& draw_state = maxwell3d->draw_manager.draw_state;
u64 elements = 0;
if (maxwell3d->regs.vertex_stream_instances.IsInstancingEnabled(index)) {
if (draw_instance_count == 0) {
return 0;
}
const u64 base_instance = static_cast<u64>(draw_state.base_instance);
elements = base_instance + 1;
if (array.frequency != 0) {
elements = (base_instance + static_cast<u64>(draw_instance_count) - 1) /
static_cast<u64>(array.frequency) +
1;
}
} else if (!is_indexed) {
elements = static_cast<u64>(draw_state.vertex_buffer.first) +
static_cast<u64>(draw_state.vertex_buffer.count);
} else {
const auto format = draw_state.index_buffer.format;
u64 max_index = 0xFF;
if (format == Maxwell::IndexFormat::UnsignedShort) {
max_index = 0xFFFF;
} else if (format != Maxwell::IndexFormat::UnsignedByte) {
const auto& limit = maxwell3d->regs.vertex_stream_limits[index];
const GPUVAddr gpu_addr_begin = array.Address();
const GPUVAddr gpu_addr_end = limit.Address() + 1;
if (gpu_addr_end <= gpu_addr_begin) {
return 0;
}
const bool walks = gpu_addr_end - gpu_addr_begin >= IMPLAUSIBLE_VERTEX_SIZE ||
!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end);
if (!walks) {
return 0;
}
max_index = DrawMaxIndex();
}
elements = static_cast<u64>(draw_state.base_index) + max_index + 1;
}
if (elements == 0) {
return extent;
}
return (elements - 1) * stride + extent;
}
template <class P>
void BufferCache<P>::UpdateVertexBuffers(bool is_indexed) {
auto& flags = maxwell3d->dirty.flags; auto& flags = maxwell3d->dirty.flags;
max_index_scanned = false;
stream_extents_valid = false;
for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
const u64 bound = DrawVertexBound(index, is_indexed);
if (bound <= last_draw_bounds[index]) {
continue;
}
flags[Dirty::VertexBuffer0 + index] = true;
flags[Dirty::VertexBuffers] = true;
}
if (!maxwell3d->dirty.flags[Dirty::VertexBuffers]) { if (!maxwell3d->dirty.flags[Dirty::VertexBuffers]) {
return; return;
} }
flags[Dirty::VertexBuffers] = false; flags[Dirty::VertexBuffers] = false;
for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) { for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
UpdateVertexBuffer(index, is_indexed); UpdateVertexBuffer(index);
} }
} }
template <class P> template <class P>
void BufferCache<P>::UpdateVertexBuffer(u32 index, bool is_indexed) { void BufferCache<P>::UpdateVertexBuffer(u32 index) {
if (!maxwell3d->dirty.flags[Dirty::VertexBuffer0 + index]) { if (!maxwell3d->dirty.flags[Dirty::VertexBuffer0 + index]) {
return; return;
} }
@@ -1386,31 +1381,15 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index, bool is_indexed) {
const GPUVAddr gpu_addr_begin = array.Address(); const GPUVAddr gpu_addr_begin = array.Address();
const GPUVAddr gpu_addr_end = limit.Address() + 1; const GPUVAddr gpu_addr_end = limit.Address() + 1;
const std::optional<DAddr> device_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin); const std::optional<DAddr> device_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
if (array.enable == 0 || !device_addr || gpu_addr_end <= gpu_addr_begin) { const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
u32 size = address_size; // TODO: Analyze stride and number of vertices
if (array.enable == 0 || size == 0 || !device_addr) {
channel_state->vertex_buffers[index] = NULL_BINDING; channel_state->vertex_buffers[index] = NULL_BINDING;
UpdateVertexBufferSlot(index, NULL_BINDING); UpdateVertexBufferSlot(index, NULL_BINDING);
return; return;
} }
// TODO: Analyze stride and number of vertices if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || size >= 64_MiB) {
constexpr u64 implausible_size = IMPLAUSIBLE_VERTEX_SIZE; size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size));
u64 address_size = gpu_addr_end - gpu_addr_begin;
if (address_size > u64{(std::numeric_limits<u32>::max)()}) {
address_size = implausible_size;
}
const u64 draw_bound = DrawVertexBound(index, is_indexed);
last_draw_bounds[index] = (std::numeric_limits<u64>::max)();
if (draw_bound != 0) {
last_draw_bounds[index] = draw_bound;
address_size = (std::min)(address_size, draw_bound);
}
if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || address_size >= implausible_size) {
address_size = gpu_memory->MaxContinuousRange(gpu_addr_begin, address_size);
}
const u32 size = static_cast<u32>(address_size);
if (size == 0) {
channel_state->vertex_buffers[index] = NULL_BINDING;
UpdateVertexBufferSlot(index, NULL_BINDING);
return;
} }
const BufferId buffer_id = FindBuffer(*device_addr, size); const BufferId buffer_id = FindBuffer(*device_addr, size);
const Binding binding{ const Binding binding{
@@ -1692,10 +1671,9 @@ template <class P>
BufferId BufferCache<P>::CreateBuffer(DAddr device_addr, u32 wanted_size) { BufferId BufferCache<P>::CreateBuffer(DAddr device_addr, u32 wanted_size) {
DAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, CACHING_PAGESIZE); DAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, CACHING_PAGESIZE);
device_addr = Common::AlignDown(device_addr, CACHING_PAGESIZE); device_addr = Common::AlignDown(device_addr, CACHING_PAGESIZE);
constexpr u64 max_buffer_size = u64{(std::numeric_limits<u32>::max)()}; wanted_size = static_cast<u32>(device_addr_end - device_addr);
wanted_size = static_cast<u32>((std::min)(device_addr_end - device_addr, max_buffer_size));
const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size); const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size);
const u32 size = static_cast<u32>((std::min)(overlap.end - overlap.begin, max_buffer_size)); const u32 size = static_cast<u32>(overlap.end - overlap.begin);
const BufferId new_buffer_id = slot_buffers.insert(runtime, overlap.begin, size); const BufferId new_buffer_id = slot_buffers.insert(runtime, overlap.begin, size);
auto& new_buffer = slot_buffers[new_buffer_id]; auto& new_buffer = slot_buffers[new_buffer_id];
const size_t size_bytes = new_buffer.SizeBytes(); const size_t size_bytes = new_buffer.SizeBytes();
@@ -1814,6 +1792,142 @@ void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer,
} }
} }
template <class P>
bool BufferCache<P>::ResolveUnifiedWindows(
[[maybe_unused]] DAddr device_addr, [[maybe_unused]] u64 buffer_offset,
[[maybe_unused]] u64 size, [[maybe_unused]] boost::container::small_vector<u64, 4>& window_ids,
[[maybe_unused]] UnifiedWindowGroups& groups) {
if constexpr (USE_UNIFIED_MEMORY) {
const u8* const physical_base = device_memory.GetPhysicalBase();
const u64 unified_base = runtime.UnifiedMemoryBase();
const u64 unified_size = runtime.UnifiedMemorySize();
const u64 window_size = runtime.UnifiedMemoryWindowSize();
if (window_size == 0) {
return false;
}
const auto group_for = [&](u64 window) -> boost::container::small_vector<BufferCopy, 16>& {
for (size_t i = 0; i < window_ids.size(); ++i) {
if (window_ids[i] == window) {
return groups[i];
}
}
window_ids.push_back(window);
groups.emplace_back();
return groups.back();
};
u64 downloaded = 0;
while (downloaded < size) {
const DAddr page_addr = device_addr + downloaded;
const u8* const ptr = device_memory.GetPointer<u8>(page_addr);
if (ptr == nullptr) {
return false;
}
const u64 page_offset = page_addr & Core::DEVICE_PAGEMASK;
u64 chunk = (std::min)(size - downloaded,
static_cast<u64>(Core::DEVICE_PAGESIZE) - page_offset);
const u64 phys_offset = static_cast<u64>(ptr - physical_base);
if (phys_offset < unified_base || phys_offset - unified_base + chunk > unified_size) {
return false;
}
const u64 relative = phys_offset - unified_base;
const u64 window = relative / window_size;
const u64 local_offset = relative % window_size;
chunk = (std::min)(chunk, window_size - local_offset);
auto& group = group_for(window);
if (!group.empty()) {
BufferCopy& last = group.back();
if (last.src_offset + last.size == buffer_offset + downloaded &&
last.dst_offset + last.size == local_offset) {
last.size += chunk;
downloaded += chunk;
continue;
}
}
group.push_back(BufferCopy{
.src_offset = buffer_offset + downloaded,
.dst_offset = local_offset,
.size = chunk,
});
downloaded += chunk;
}
return true;
} else {
return false;
}
}
template <class P>
std::optional<typename BufferCache<P>::UnifiedWindowRange>
BufferCache<P>::TryResolveUnifiedRange([[maybe_unused]] DAddr device_addr,
[[maybe_unused]] u64 size) {
if constexpr (USE_UNIFIED_MEMORY) {
if (size == 0 || !runtime.IsUnifiedMemoryBindable()) {
return std::nullopt;
}
const u64 window_size = runtime.UnifiedMemoryWindowSize();
if (window_size == 0) {
return std::nullopt;
}
const u8* const first = device_memory.GetSpan(device_addr, size);
if (first == nullptr) {
return std::nullopt;
}
const u64 phys_offset = static_cast<u64>(first - device_memory.GetPhysicalBase());
const u64 unified_base = runtime.UnifiedMemoryBase();
if (phys_offset < unified_base) {
return std::nullopt;
}
const u64 relative = phys_offset - unified_base;
const u64 unified_size = runtime.UnifiedMemorySize();
if (relative >= unified_size || unified_size - relative < size) {
return std::nullopt;
}
const u64 local_offset = relative % window_size;
if (window_size - local_offset < size) {
return std::nullopt;
}
if (memory_tracker.IsRegionGpuModified(device_addr, size) ||
IsRegionGpuModified(device_addr, size)) {
return std::nullopt;
}
return UnifiedWindowRange{
.window = static_cast<size_t>(relative / window_size),
.offset = local_offset,
};
} else {
return std::nullopt;
}
}
template <class P>
bool BufferCache<P>::TryUnifiedDownloadMemory([[maybe_unused]] Buffer& buffer,
[[maybe_unused]] std::span<BufferCopy> copies) {
if constexpr (USE_UNIFIED_MEMORY) {
boost::container::small_vector<u64, 4> window_ids;
UnifiedWindowGroups groups;
for (const BufferCopy& copy : copies) {
if (!ResolveUnifiedWindows(buffer.CpuAddr() + copy.src_offset, copy.src_offset,
copy.size, window_ids, groups)) {
return false;
}
}
for (const BufferCopy& copy : copies) {
buffer.MarkUsage(copy.src_offset, copy.size);
}
runtime.PreCopyBarrier();
for (size_t i = 0; i < window_ids.size(); ++i) {
const std::span<const BufferCopy> group_span(groups[i].data(), groups[i].size());
runtime.CopyToUnifiedMemory(window_ids[i], buffer, group_span);
}
runtime.FlushUnifiedMemoryCopies();
runtime.UnifiedMemoryHostBarrier();
runtime.Finish();
return true;
} else {
return false;
}
}
template <class P> template <class P>
void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer, void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
[[maybe_unused]] u64 total_size_bytes, [[maybe_unused]] u64 total_size_bytes,
@@ -1917,6 +2031,12 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64
} }
if constexpr (USE_MEMORY_MAPS) { if constexpr (USE_MEMORY_MAPS) {
if constexpr (USE_UNIFIED_MEMORY) {
if (runtime.HasUnifiedMemory() &&
TryUnifiedDownloadMemory(buffer, std::span(copies.data(), copies.size()))) {
return;
}
}
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes); auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
const u8* const mapped_memory = download_staging.mapped_span.data(); const u8* const mapped_memory = download_staging.mapped_span.data();
const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size()); const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size());
+31 -23
View File
@@ -12,6 +12,7 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <optional>
#include <numeric> #include <numeric>
#include <span> #include <span>
#include <vector> #include <vector>
@@ -51,7 +52,6 @@ constexpr u32 NUM_VERTEX_BUFFERS = 16;
#else #else
constexpr u32 NUM_VERTEX_BUFFERS = 32; constexpr u32 NUM_VERTEX_BUFFERS = 32;
#endif #endif
constexpr u64 IMPLAUSIBLE_VERTEX_SIZE = 64_MiB;
constexpr u32 NUM_TRANSFORM_FEEDBACK_BUFFERS = 4; constexpr u32 NUM_TRANSFORM_FEEDBACK_BUFFERS = 4;
constexpr u32 NUM_GRAPHICS_UNIFORM_BUFFERS = 18; constexpr u32 NUM_GRAPHICS_UNIFORM_BUFFERS = 18;
constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8; constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8;
@@ -181,6 +181,7 @@ class BufferCache : public VideoCommon::ChannelSetupCaches<BufferCacheChannelInf
static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS; static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS;
static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS; static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS;
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = P::USE_MEMORY_MAPS_FOR_UPLOADS; static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = P::USE_MEMORY_MAPS_FOR_UPLOADS;
static constexpr bool USE_UNIFIED_MEMORY = P::USE_UNIFIED_MEMORY;
#ifdef YUZU_LEGACY #ifdef YUZU_LEGACY
static constexpr s64 TARGET_THRESHOLD = 3_GiB; static constexpr s64 TARGET_THRESHOLD = 3_GiB;
@@ -218,6 +219,8 @@ public:
void WriteMemory(DAddr device_addr, u64 size); void WriteMemory(DAddr device_addr, u64 size);
void CachedWriteMemory(DAddr device_addr, u64 size);
bool OnCPUWrite(DAddr device_addr, u64 size); bool OnCPUWrite(DAddr device_addr, u64 size);
void DownloadMemory(DAddr device_addr, u64 size); void DownloadMemory(DAddr device_addr, u64 size);
@@ -247,7 +250,7 @@ public:
void UnbindGraphicsStorageBuffers(size_t stage); void UnbindGraphicsStorageBuffers(size_t stage);
void BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset, bool BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
bool is_written); bool is_written);
void UnbindGraphicsTextureBuffers(size_t stage); void UnbindGraphicsTextureBuffers(size_t stage);
@@ -308,10 +311,6 @@ public:
current_draw_indirect = current_draw_indirect_; current_draw_indirect = current_draw_indirect_;
} }
void SetDrawInstanceCount(u32 draw_instance_count_) {
draw_instance_count = draw_instance_count_;
}
[[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectCount(); [[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectCount();
[[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectBuffer(); [[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectBuffer();
@@ -379,6 +378,8 @@ private:
void BindHostTransformFeedbackBuffers(); void BindHostTransformFeedbackBuffers();
void BindHostVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride);
void BindHostComputeUniformBuffers(); void BindHostComputeUniformBuffers();
void BindHostComputeStorageBuffers(); void BindHostComputeStorageBuffers();
@@ -391,15 +392,9 @@ private:
void UpdateIndexBuffer(); void UpdateIndexBuffer();
void UpdateVertexBuffers(bool is_indexed); void UpdateVertexBuffers();
void UpdateVertexBuffer(u32 index, bool is_indexed); void UpdateVertexBuffer(u32 index);
[[nodiscard]] u64 DrawVertexBound(u32 index, bool is_indexed);
[[nodiscard]] u64 DrawMaxIndex();
[[nodiscard]] u64 StreamAttributeExtent(u32 index);
void UpdateDrawIndirect(); void UpdateDrawIndirect();
@@ -450,6 +445,22 @@ private:
void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies); void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies);
bool TryUnifiedDownloadMemory(Buffer& buffer, std::span<BufferCopy> copies);
struct UnifiedWindowRange {
size_t window;
u64 offset;
};
std::optional<UnifiedWindowRange> TryResolveUnifiedRange(DAddr device_addr, u64 size);
using UnifiedWindowGroups =
boost::container::small_vector<boost::container::small_vector<BufferCopy, 16>, 4>;
bool ResolveUnifiedWindows(DAddr device_addr, u64 buffer_offset, u64 size,
boost::container::small_vector<u64, 4>& window_ids,
UnifiedWindowGroups& groups);
void DownloadBufferMemory(Buffer& buffer_id); void DownloadBufferMemory(Buffer& buffer_id);
void DownloadBufferMemory(Buffer& buffer_id, DAddr device_addr, u64 size); void DownloadBufferMemory(Buffer& buffer_id, DAddr device_addr, u64 size);
@@ -491,14 +502,6 @@ private:
const Tegra::Engines::Maxwell3D::DrawManager::IndirectParams* current_draw_indirect{}; const Tegra::Engines::Maxwell3D::DrawManager::IndirectParams* current_draw_indirect{};
u32 draw_instance_count = 0;
std::array<u64, NUM_VERTEX_BUFFERS> last_draw_bounds{};
Common::ScratchBuffer<u32> index_scan_buffer;
u64 cached_max_index = 0;
bool max_index_scanned = false;
std::array<u64, NUM_VERTEX_BUFFERS> stream_extents{};
bool stream_extents_valid = false;
u32 last_index_count = 0; u32 last_index_count = 0;
u32 enabled_vertex_buffers_mask = 0; u32 enabled_vertex_buffers_mask = 0;
@@ -513,9 +516,14 @@ private:
std::deque<Common::RangeSet<DAddr>> committed_gpu_modified_ranges; std::deque<Common::RangeSet<DAddr>> committed_gpu_modified_ranges;
// Async Buffers // Async Buffers
struct AsyncDownloadBatch {
boost::container::small_vector<BufferCopy, 4> staging_copies;
boost::container::small_vector<BufferCopy, 4> unified_copies;
};
Common::OverlapRangeSet<DAddr> async_downloads; Common::OverlapRangeSet<DAddr> async_downloads;
std::deque<std::optional<Async_Buffer>> async_buffers; std::deque<std::optional<Async_Buffer>> async_buffers;
std::deque<boost::container::small_vector<BufferCopy, 4>> pending_downloads; std::deque<AsyncDownloadBatch> pending_downloads;
std::optional<Async_Buffer> current_buffer; std::optional<Async_Buffer> current_buffer;
std::deque<Async_Buffer> async_buffers_death_ring; std::deque<Async_Buffer> async_buffers_death_ring;
+2 -34
View File
@@ -17,12 +17,14 @@ set(SHADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/astc_decoder.comp ${CMAKE_CURRENT_SOURCE_DIR}/astc_decoder.comp
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag ${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d.comp ${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d.comp
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d_buffer.comp
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_msaa.frag ${CMAKE_CURRENT_SOURCE_DIR}/blit_color_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth.frag ${CMAKE_CURRENT_SOURCE_DIR}/blit_depth.frag
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_msaa.frag ${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_stencil_msaa.frag ${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_stencil_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d.comp ${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d.comp
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_bcn.comp ${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_bcn.comp
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_buffer.comp
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d24s8.frag ${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d24s8.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d32f.frag ${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d32f.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_d32f_to_abgr8.frag ${CMAKE_CURRENT_SOURCE_DIR}/convert_d32f_to_abgr8.frag
@@ -206,40 +208,6 @@ foreach(VARIANT IN ITEMS ${SHADER_TYPE_VARIANTS})
set(SHADER_HEADERS ${SHADER_HEADERS} ${VARIANT_HEADER_FILE}) set(SHADER_HEADERS ${SHADER_HEADERS} ${VARIANT_HEADER_FILE})
endforeach() endforeach()
set(SHADER_DEFINE_VARIANTS
"block_linear_unswizzle_2d.comp|nonarrow|HAS_EXTENDED_TYPES=0"
"pitch_unswizzle.comp|nonarrow|HAS_EXTENDED_TYPES=0"
"block_linear_unswizzle_3d.comp|nonarrow|HAS_EXTENDED_TYPES=0"
)
foreach(VARIANT IN ITEMS ${SHADER_DEFINE_VARIANTS})
string(REPLACE "|" ";" VARIANT_PARTS ${VARIANT})
list(GET VARIANT_PARTS 0 VARIANT_FILENAME)
list(GET VARIANT_PARTS 1 VARIANT_SUFFIX)
list(GET VARIANT_PARTS 2 VARIANT_DEFINE)
set(VARIANT_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/${VARIANT_FILENAME})
get_filename_component(VARIANT_STEM ${VARIANT_FILENAME} NAME_WE)
get_filename_component(VARIANT_EXT ${VARIANT_FILENAME} EXT)
string(REPLACE "." "" VARIANT_EXT ${VARIANT_EXT})
set(VARIANT_NAME ${VARIANT_STEM}_${VARIANT_SUFFIX}_${VARIANT_EXT})
string(TOUPPER ${VARIANT_NAME}_SPV VARIANT_VARIABLE_NAME)
set(VARIANT_HEADER_FILE ${SHADER_DIR}/${VARIANT_NAME}_spv.h)
add_custom_command(
OUTPUT
${VARIANT_HEADER_FILE}
COMMAND
${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -I"${FIDELITYFX_INCLUDE_DIR}" ${GLSL_FLAGS}
-D${VARIANT_DEFINE}
--variable-name ${VARIANT_VARIABLE_NAME} -o ${VARIANT_HEADER_FILE} ${VARIANT_SOURCE}
--target-env ${SPIR_V_VERSION}
MAIN_DEPENDENCY
${VARIANT_SOURCE}
)
set(SHADER_HEADERS ${SHADER_HEADERS} ${VARIANT_HEADER_FILE})
endforeach()
foreach(FILEPATH IN ITEMS ${FIDELITYFX_FILES}) foreach(FILEPATH IN ITEMS ${FIDELITYFX_FILES})
get_filename_component(FILENAME ${FILEPATH} NAME) get_filename_component(FILENAME ${FILEPATH} NAME)
string(REPLACE "." "_" HEADER_NAME ${FILENAME}) string(REPLACE "." "_" HEADER_NAME ${FILENAME})
@@ -5,13 +5,9 @@
#ifdef VULKAN #ifdef VULKAN
#ifndef HAS_EXTENDED_TYPES
#define HAS_EXTENDED_TYPES 1
#endif
#if HAS_EXTENDED_TYPES
#extension GL_EXT_shader_16bit_storage : require #extension GL_EXT_shader_16bit_storage : require
#extension GL_EXT_shader_8bit_storage : require #extension GL_EXT_shader_8bit_storage : require
#endif #define HAS_EXTENDED_TYPES 1
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
#define END_PUSH_CONSTANTS }; #define END_PUSH_CONSTANTS };
#define UNIFORM(n) #define UNIFORM(n)
@@ -0,0 +1,104 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 430
#extension GL_EXT_shader_16bit_storage : require
#extension GL_EXT_shader_8bit_storage : require
#define BINDING_INPUT_BUFFER 0
#define BINDING_OUTPUT_BUFFER 1
layout(push_constant) uniform PushConstants {
uvec3 dim;
uint bytes_per_block_log2;
uvec3 origin;
uint layer_stride;
uint block_size;
uint x_shift;
uint block_height;
uint block_height_mask;
} pc;
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; };
layout(binding = BINDING_OUTPUT_BUFFER, std430) writeonly buffer OutputBuffer {
uint out_u32[];
};
layout(local_size_x = 16, local_size_y = 8, local_size_z = 1) in;
const uint GOB_SIZE_X = 64;
const uint GOB_SIZE_Y = 8;
const uint GOB_SIZE_X_SHIFT = 6;
const uint GOB_SIZE_Y_SHIFT = 3;
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u);
uint SwizzleTable(uint pos) {
const uint t[8] = uint[](
0x12100200, 0x13110301, 0x16140604, 0x17150705,
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
);
const uint i = pos >> 4;
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
return (h << 4) | (pos & 0xf);
}
uint SwizzleOffset(uvec2 pos) {
pos = pos & SWIZZLE_MASK;
return SwizzleTable(pos.y * 64u + pos.x);
}
uvec4 ReadTexel(uint offset) {
switch (pc.bytes_per_block_log2) {
case 2u:
return uvec4(u32data[offset / 4u], 0u, 0u, 0u);
case 3u:
return uvec4(u64data[offset / 8u], 0u, 0u);
case 4u:
return u128data[offset / 16u];
}
return uvec4(0u);
}
void main() {
uvec3 coord = gl_GlobalInvocationID;
if (coord.x >= pc.dim.x || coord.y >= pc.dim.y || coord.z >= pc.dim.z) {
return;
}
uvec3 pos = coord + pc.origin;
pos.x <<= pc.bytes_per_block_log2;
uint swizzle = SwizzleOffset(pos.xy);
uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
uint offset = 0u;
offset += pos.z * pc.layer_stride;
offset += (block_y >> pc.block_height) * pc.block_size;
offset += (block_y & pc.block_height_mask) << GOB_SIZE_SHIFT;
offset += (pos.x >> GOB_SIZE_X_SHIFT) << pc.x_shift;
offset += swizzle;
uvec4 texel = ReadTexel(offset);
uint words = 1u << (pc.bytes_per_block_log2 - 2u);
uint linear_index = coord.x + coord.y * pc.dim.x + coord.z * pc.dim.x * pc.dim.y;
uint out_idx = linear_index * words;
out_u32[out_idx] = texel.x;
if (words > 1u) {
out_u32[out_idx + 1u] = texel.y;
}
if (words > 2u) {
out_u32[out_idx + 2u] = texel.z;
out_u32[out_idx + 3u] = texel.w;
}
}
@@ -5,13 +5,9 @@
#ifdef VULKAN #ifdef VULKAN
#ifndef HAS_EXTENDED_TYPES
#define HAS_EXTENDED_TYPES 1
#endif
#if HAS_EXTENDED_TYPES
#extension GL_EXT_shader_16bit_storage : require #extension GL_EXT_shader_16bit_storage : require
#extension GL_EXT_shader_8bit_storage : require #extension GL_EXT_shader_8bit_storage : require
#endif #define HAS_EXTENDED_TYPES 1
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
#define END_PUSH_CONSTANTS }; #define END_PUSH_CONSTANTS };
#define UNIFORM(n) #define UNIFORM(n)
@@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 430
#define BINDING_INPUT_BUFFER 0
#define BINDING_OUTPUT_BUFFER 1
layout(push_constant) uniform PushConstants {
uvec3 dim;
uint bytes_per_block_log2;
uvec3 origin;
uint slice_size;
uint block_size;
uint x_shift;
uint block_height;
uint block_height_mask;
uint block_depth;
uint block_depth_mask;
} pc;
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; };
layout(binding = BINDING_OUTPUT_BUFFER, std430) writeonly buffer OutputBuffer {
uint out_u32[];
};
layout(local_size_x = 8, local_size_y = 8, local_size_z = 4) in;
const uint GOB_SIZE_X = 64;
const uint GOB_SIZE_Y = 8;
const uint GOB_SIZE_X_SHIFT = 6;
const uint GOB_SIZE_Y_SHIFT = 3;
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u);
uint SwizzleTable(uint pos) {
const uint t[8] = uint[](
0x12100200, 0x13110301, 0x16140604, 0x17150705,
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
);
const uint i = pos >> 4;
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
return (h << 4) | (pos & 0xf);
}
uint SwizzleOffset(uvec2 pos) {
pos = pos & SWIZZLE_MASK;
return SwizzleTable(pos.y * 64u + pos.x);
}
uvec4 ReadTexel(uint offset) {
switch (pc.bytes_per_block_log2) {
case 2u:
return uvec4(u32data[offset / 4u], 0u, 0u, 0u);
case 3u:
return uvec4(u64data[offset / 8u], 0u, 0u);
case 4u:
return u128data[offset / 16u];
}
return uvec4(0u);
}
void main() {
uvec3 coord = gl_GlobalInvocationID;
if (coord.x >= pc.dim.x || coord.y >= pc.dim.y || coord.z >= pc.dim.z) {
return;
}
uvec3 pos = coord + pc.origin;
pos.x <<= pc.bytes_per_block_log2;
uint swizzle = SwizzleOffset(pos.xy);
uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
uint offset = 0u;
offset += (pos.z >> pc.block_depth) * pc.slice_size;
offset += (pos.z & pc.block_depth_mask) << (GOB_SIZE_SHIFT + pc.block_height);
offset += (block_y >> pc.block_height) * pc.block_size;
offset += (block_y & pc.block_height_mask) << GOB_SIZE_SHIFT;
offset += (pos.x >> GOB_SIZE_X_SHIFT) << pc.x_shift;
offset += swizzle;
uvec4 texel = ReadTexel(offset);
uint words = 1u << (pc.bytes_per_block_log2 - 2u);
uint linear_index = coord.x + coord.y * pc.dim.x + coord.z * pc.dim.x * pc.dim.y;
uint out_idx = linear_index * words;
out_u32[out_idx] = texel.x;
if (words > 1u) {
out_u32[out_idx + 1u] = texel.y;
}
if (words > 2u) {
out_u32[out_idx + 2u] = texel.z;
out_u32[out_idx + 3u] = texel.w;
}
}
@@ -5,13 +5,9 @@
#ifdef VULKAN #ifdef VULKAN
#ifndef HAS_EXTENDED_TYPES
#define HAS_EXTENDED_TYPES 1
#endif
#if HAS_EXTENDED_TYPES
#extension GL_EXT_shader_16bit_storage : require #extension GL_EXT_shader_16bit_storage : require
#extension GL_EXT_shader_8bit_storage : require #extension GL_EXT_shader_8bit_storage : require
#endif #define HAS_EXTENDED_TYPES 1
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
#define END_PUSH_CONSTANTS }; #define END_PUSH_CONSTANTS };
#define UNIFORM(n) #define UNIFORM(n)
@@ -6,9 +6,9 @@
precision highp float; precision highp float;
precision highp int; precision highp int;
// Operation modes: RGBA -> 1, RGBY -> 3, LERP -> 4
#define OPERATION_MODE 1
#define EDGE_THRESHOLD (8.0 / 255.0) #define EDGE_THRESHOLD (8.0 / 255.0)
#define DIRECTION_EPSILON 6.5e-05
#define DEVIATION_FLOOR 6.0e-02
layout(push_constant) uniform constants { layout(push_constant) uniform constants {
vec2 scale; vec2 scale;
@@ -21,20 +21,37 @@ layout(set = 0, binding = 0) uniform sampler2D sampler0;
layout(location=0) in vec2 texcoord; layout(location=0) in vec2 texcoord;
layout(location=0) out vec4 frag_color; layout(location=0) out vec4 frag_color;
vec4 weightY(vec4 dx, vec4 dy, vec4 std) { mediump vec4 fastLanczos2(mediump vec4 x) {
vec4 x = ((dx * dx) + (dy * dy)) * 0.55f + std; mediump vec4 wA = x - 4.0f;
return (x - 1.f) * (x - 4.f) * 3.8125f; // approx. of (x - 1) * (x - 4)^3 mediump vec4 wB = x * wA - wA;
wA *= wA;
return wB * wA;
}
mediump vec2 edgeDirection(mediump vec4 left, mediump vec4 right) {
mediump float RxLz = right.x - left.z;
mediump float RwLy = right.w - left.y;
mediump vec2 delta = vec2(RxLz + RwLy, RxLz - RwLy);
mediump float length_inv =
inversesqrt((delta.x * delta.x + DIRECTION_EPSILON) + delta.y * delta.y);
return delta * length_inv;
}
mediump vec4 weightY(mediump vec4 dx, mediump vec4 dy, mediump vec4 c, mediump float std,
mediump vec2 dir) {
mediump vec4 edge_dis = dx * dir.y + dy * dir.x;
mediump vec4 x = (dx * dx + dy * dy) +
(edge_dis * edge_dis) * (clamp((c * c) * std, 0.0f, 1.0f) * 0.7f - 1.0f);
return fastLanczos2(x);
} }
void main() { void main() {
vec4 color = textureLod(sampler0, texcoord.xy, 0.0f); mediump vec4 color = textureLod(sampler0, texcoord.xy, 0.0f);
// image coord highp vec2 icoord = (texcoord * size + vec2(-0.5f, 0.5f));
vec2 icoord = (texcoord * size + vec2(-0.5f, 0.5f)); highp vec2 icoord_pixel = floor(icoord);
vec2 icoord_pixel = floor(icoord); highp vec2 coord = icoord_pixel * scale;
vec2 coord = icoord_pixel * scale; mediump vec2 pl = icoord - icoord_pixel;
vec2 pl = icoord - icoord_pixel; mediump mat3x4 dg = mat3x4(
// left: 0, right: 1, upDown: 2
mat3x4 dg = mat3x4(
textureGather(sampler0, coord, 1), textureGather(sampler0, coord, 1),
textureGather(sampler0, coord + vec2(2.f * scale.x, 0.0f), 1), textureGather(sampler0, coord + vec2(2.f * scale.x, 0.0f), 1),
vec4( vec4(
@@ -42,42 +59,40 @@ void main() {
textureGather(sampler0, coord + vec2(scale.x, +scale.y), 1).yx textureGather(sampler0, coord + vec2(scale.x, +scale.y), 1).yx
) )
); );
float edgeVote = abs(dg[0].z - dg[0].y) + abs(color.y - dg[0].y) + abs(color.y - dg[0].z); mediump float edgeVote =
abs(dg[0].z - dg[0].y) + abs(color.y - dg[0].y) + abs(color.y - dg[0].z);
if (edgeVote > EDGE_THRESHOLD) { if (edgeVote > EDGE_THRESHOLD) {
float mean = (dg[0].y + dg[0].z + dg[1].x + dg[1].w) * 0.25f; mediump float mean = (dg[0].y + dg[0].z + dg[1].x + dg[1].w) * 0.25f;
dg = dg - mean; dg = dg - mean;
vec4 sum = abs(dg[0]) + abs(dg[1]) + abs(dg[2]); mediump float sum = dot(abs(dg[0]) + abs(dg[1]) + abs(dg[2]), vec4(1.0f));
float std = 2.181818f / (sum.x + sum.y + sum.z + sum.w); mediump float sum_mean = 1.014185e+01f / max(sum, DEVIATION_FLOOR);
mat2x4 w = mat2x4( mediump float std = sum_mean * sum_mean;
weightY( mediump vec2 dir = edgeDirection(dg[0], dg[1]);
pl.xxxx + vec4(+1.0f, +0.0f, +0.0f, +1.0f), mediump vec4 w0 = weightY(
pl.yyyy + vec4(-1.0f, -1.0f, +0.0f, +0.0f), pl.xxxx + vec4(+1.0f, +0.0f, +0.0f, +1.0f),
clamp(abs(dg[0]) * std, 0.0f, 1.0f) pl.yyyy + vec4(-1.0f, -1.0f, +0.0f, +0.0f),
) + weightY( dg[0], std, dir
pl.xxxx + vec4(-1.0f, -2.0f, -2.0f, -1.0f),
pl.yyyy + vec4(-1.0f, -1.0f, +0.0f, +0.0f),
clamp(abs(dg[1]) * std, 0.0f, 1.0f)
) + weightY(
pl.xxxx + vec4(+0.0f, -1.0f, -1.0f, +0.0f),
pl.yyyy + vec4(+1.0f, +1.0f, -2.0f, -2.0f),
clamp(abs(dg[2]) * std, 0.0f, 1.0f)
),
dg[0] + dg[1] + dg[2]
); );
// compute final y with bounds mediump vec4 w1 = weightY(
vec2 yb = vec2( pl.xxxx + vec4(-1.0f, -2.0f, -2.0f, -1.0f),
min(min(dg[0].y, dg[0].z), min(dg[1].x, dg[1].w)), // min pl.yyyy + vec4(-1.0f, -1.0f, +0.0f, +0.0f),
max(max(dg[0].y, dg[0].z), max(dg[1].x, dg[1].w)) // max dg[1], std, dir
); );
vec2 fvy = vec2( mediump vec4 w2 = weightY(
w[0].x + w[0].y + w[0].z + w[0].w, pl.xxxx + vec4(+0.0f, -1.0f, -1.0f, +0.0f),
w[1].x + w[1].y + w[1].z + w[1].w pl.yyyy + vec4(+1.0f, +1.0f, -2.0f, -2.0f),
dg[2], std, dir
); );
float fy = clamp((fvy.y / fvy.x) * edge_sharpness, yb[0], yb[1]); mediump float sum_w = dot(w0 + w1 + w2, vec4(1.0f));
// Smooth high contrast input mediump float sum_wc = dot(w0 * dg[0] + w1 * dg[1] + w2 * dg[2], vec4(1.0f));
float dy = clamp(fy - color.y + mean, -23.0f / 255.0f, 23.0f / 255.0f); mediump vec2 yb = vec2(
min(min(dg[0].y, dg[0].z), min(dg[1].x, dg[1].w)),
max(max(dg[0].y, dg[0].z), max(dg[1].x, dg[1].w))
);
mediump float fy = clamp((sum_wc / sum_w) * edge_sharpness, yb[0], yb[1]);
mediump float dy = clamp(fy - color.y + mean, -23.0f / 255.0f, 23.0f / 255.0f);
color = clamp(color + dy, 0.0f, 1.0f); color = clamp(color + dy, 0.0f, 1.0f);
} }
color.w = 1.0f; //assume alpha channel is not used color.w = 1.0f;
frag_color.xyzw = color; frag_color.xyzw = color;
} }
@@ -226,6 +226,22 @@ void BufferCacheRuntime::BindIndexBuffer(Buffer& buffer, u32 offset, u32 size) {
} }
} }
void BufferCacheRuntime::BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size,
u32 stride) {
if (index >= max_attributes) {
return;
}
if (has_unified_vertex_buffers) {
buffer.MakeResident(GL_READ_ONLY);
glBindVertexBuffer(index, 0, 0, static_cast<GLsizei>(stride));
glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, index,
buffer.HostGpuAddr() + offset, static_cast<GLsizeiptr>(size));
} else {
glBindVertexBuffer(index, buffer.Handle(), static_cast<GLintptr>(offset),
static_cast<GLsizei>(stride));
}
}
void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) { void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) {
// TODO: Should HostBindings provide the correct runtime types to avoid these transforms? // TODO: Should HostBindings provide the correct runtime types to avoid these transforms?
std::array<GLuint, 32> buffer_handles; std::array<GLuint, 32> buffer_handles;
@@ -99,6 +99,8 @@ public:
void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size); void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size);
void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride);
void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings); void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings);
void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size); void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size);
@@ -259,6 +261,7 @@ struct BufferCacheParams {
// TODO: Investigate why OpenGL seems to perform worse with persistently mapped buffer uploads // TODO: Investigate why OpenGL seems to perform worse with persistently mapped buffer uploads
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = false; static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = false;
static constexpr bool USE_UNIFIED_MEMORY = false;
}; };
using BufferCache = VideoCommon::BufferCache<BufferCacheParams>; using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
@@ -259,7 +259,6 @@ void RasterizerOpenGL::PrepareDraw(bool is_indexed, Func&& draw_func) {
} }
void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) { void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) {
buffer_cache.SetDrawInstanceCount(instance_count);
PrepareDraw(is_indexed, [this, is_indexed, instance_count](GLenum primitive_mode) { PrepareDraw(is_indexed, [this, is_indexed, instance_count](GLenum primitive_mode) {
const auto& draw_state = maxwell3d->draw_manager.draw_state; const auto& draw_state = maxwell3d->draw_manager.draw_state;
const GLuint base_instance = GLuint(draw_state.base_instance); const GLuint base_instance = GLuint(draw_state.base_instance);
@@ -305,7 +304,6 @@ void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) {
void RasterizerOpenGL::DrawIndirect() { void RasterizerOpenGL::DrawIndirect() {
const auto& params = maxwell3d->draw_manager.indirect_state; const auto& params = maxwell3d->draw_manager.indirect_state;
buffer_cache.SetDrawIndirect(&params); buffer_cache.SetDrawIndirect(&params);
buffer_cache.SetDrawInstanceCount(0);
PrepareDraw(params.is_indexed, [this, &params](GLenum primitive_mode) { PrepareDraw(params.is_indexed, [this, &params](GLenum primitive_mode) {
if (params.is_byte_count) { if (params.is_byte_count) {
const GPUVAddr tfb_object_base_addr = params.indirect_start_address - 4U; const GPUVAddr tfb_object_base_addr = params.indirect_start_address - 4U;
@@ -238,6 +238,7 @@ ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.has_gl_bool_ref_bug = device.HasBoolRefBug(), .has_gl_bool_ref_bug = device.HasBoolRefBug(),
.ignore_nan_fp_comparisons = true, .ignore_nan_fp_comparisons = true,
.gl_max_compute_smem_size = device.GetMaxComputeSharedMemorySize(), .gl_max_compute_smem_size = device.GetMaxComputeSharedMemorySize(),
.max_shared_memory_size = device.GetMaxComputeSharedMemorySize(),
.min_ssbo_alignment = device.GetShaderStorageBufferAlignment(), .min_ssbo_alignment = device.GetShaderStorageBufferAlignment(),
// Use the host limit, but never more than the guest can produce. Maxwell exposes 8 clip // Use the host limit, but never more than the guest can produce. Maxwell exposes 8 clip
// distances and the SPIR-V output array is sized for at most 8, so clamping here keeps a // distances and the SPIR-V output array is sized for at most 8, so clamping here keeps a
@@ -373,6 +373,7 @@ struct TextureCacheParams {
static constexpr bool HAS_DEVICE_MEMORY_INFO = true; static constexpr bool HAS_DEVICE_MEMORY_INFO = true;
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true; static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
static constexpr bool HAS_MSAA_DOWNLOADS = false; static constexpr bool HAS_MSAA_DOWNLOADS = false;
static constexpr bool USE_UNIFIED_MEMORY = false;
using Runtime = OpenGL::TextureCacheRuntime; using Runtime = OpenGL::TextureCacheRuntime;
using Image = OpenGL::Image; using Image = OpenGL::Image;
@@ -7,7 +7,9 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cstring> #include <cstring>
#include <limits>
#include <span> #include <span>
#include <utility>
#include <vector> #include <vector>
#include "video_core/buffer_cache/buffer_cache_base.h" #include "video_core/buffer_cache/buffer_cache_base.h"
@@ -32,6 +34,32 @@ VkBufferCopy MakeBufferCopy(const VideoCommon::BufferCopy& copy) {
}; };
} }
constexpr size_t MAX_WINDOW_BARRIER_RANGES = 8;
using WindowRange = std::pair<VkDeviceSize, VkDeviceSize>;
using WindowRanges = boost::container::small_vector<WindowRange, MAX_WINDOW_BARRIER_RANGES>;
void CoalesceWindowRanges(WindowRanges& ranges) {
if (ranges.size() < 2) {
return;
}
std::sort(ranges.begin(), ranges.end());
size_t merged = 0;
for (size_t index = 1; index < ranges.size(); ++index) {
if (ranges[index].first <= ranges[merged].second) {
ranges[merged].second = (std::max)(ranges[merged].second, ranges[index].second);
} else {
ranges[++merged] = ranges[index];
}
}
ranges.resize(merged + 1);
if (ranges.size() > MAX_WINDOW_BARRIER_RANGES) {
const WindowRange bounding{ranges.front().first, ranges.back().second};
ranges.clear();
ranges.push_back(bounding);
}
}
VkIndexType IndexTypeFromNumElements(const Device& device, u32 num_elements) { VkIndexType IndexTypeFromNumElements(const Device& device, u32 num_elements) {
if (num_elements <= 0xff && device.IsExtIndexTypeUint8Supported()) { if (num_elements <= 0xff && device.IsExtIndexTypeUint8Supported()) {
return VK_INDEX_TYPE_UINT8_EXT; return VK_INDEX_TYPE_UINT8_EXT;
@@ -42,6 +70,16 @@ VkIndexType IndexTypeFromNumElements(const Device& device, u32 num_elements) {
return VK_INDEX_TYPE_UINT32; return VK_INDEX_TYPE_UINT32;
} }
u32 GrowIndexCount(u32 current, u32 requested) {
constexpr u32 MinimumIndices = 4096;
constexpr u32 GrowthLimit = (std::numeric_limits<u32>::max)() / 2;
u32 grown = (std::max)(requested, MinimumIndices);
if (current <= GrowthLimit) {
grown = (std::max)(grown, current * 2);
}
return grown;
}
size_t BytesPerIndex(VkIndexType index_type) { size_t BytesPerIndex(VkIndexType index_type) {
switch (index_type) { switch (index_type) {
case VK_INDEX_TYPE_UINT8_EXT: case VK_INDEX_TYPE_UINT8_EXT:
@@ -158,13 +196,12 @@ public:
virtual ~QuadIndexBuffer() = default; virtual ~QuadIndexBuffer() = default;
void UpdateBuffer(u32 num_indices_) { void UpdateBuffer(u32 num_indices_) {
ReleaseRetiredBuffers();
if (num_indices_ <= num_indices) { if (num_indices_ <= num_indices) {
return; return;
} }
scheduler.Finish(); num_indices = GrowIndexCount(num_indices, num_indices_);
num_indices = num_indices_;
index_type = IndexTypeFromNumElements(device, num_indices); index_type = IndexTypeFromNumElements(device, num_indices);
const u32 num_quads = GetQuadsNum(num_indices); const u32 num_quads = GetQuadsNum(num_indices);
@@ -182,6 +219,12 @@ public:
.queueFamilyIndexCount = 0, .queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr, .pQueueFamilyIndices = nullptr,
}; };
if (buffer) {
retired_buffers.push_back(RetiredBuffer{
.buffer = std::move(buffer),
.tick = scheduler.CurrentTick(),
});
}
buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal); buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
if (device.HasDebuggingToolAttached()) { if (device.HasDebuggingToolAttached()) {
buffer.SetObjectNameEXT("Quad LUT"); buffer.SetObjectNameEXT("Quad LUT");
@@ -249,6 +292,17 @@ protected:
virtual void MakeAndUpdateIndices(u8* staging_data, size_t quad_size, u32 quad, u32 first) = 0; virtual void MakeAndUpdateIndices(u8* staging_data, size_t quad_size, u32 quad, u32 first) = 0;
struct RetiredBuffer {
vk::Buffer buffer;
u64 tick;
};
void ReleaseRetiredBuffers() {
std::erase_if(retired_buffers, [this](const RetiredBuffer& entry) {
return scheduler.IsFree(entry.tick);
});
}
const Device& device; const Device& device;
MemoryAllocator& memory_allocator; MemoryAllocator& memory_allocator;
Scheduler& scheduler; Scheduler& scheduler;
@@ -256,6 +310,7 @@ protected:
vk::Buffer buffer{}; vk::Buffer buffer{};
MemoryCommit memory_commit{}; MemoryCommit memory_commit{};
std::vector<RetiredBuffer> retired_buffers;
VkIndexType index_type{}; VkIndexType index_type{};
u32 num_indices = 0; u32 num_indices = 0;
}; };
@@ -365,6 +420,138 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
scheduler_, staging_pool_); scheduler_, staging_pool_);
} }
void BufferCacheRuntime::TryEnableUnifiedMemory(void* base, size_t size,
std::span<AHardwareBuffer* const> hardware_buffers,
size_t hardware_buffer_window,
size_t hardware_buffer_base) {
unified_memory = memory_allocator.CreateHostMemoryImport(
base, size, hardware_buffers, hardware_buffer_window, hardware_buffer_base);
}
void BufferCacheRuntime::CopyToUnifiedMemory(
size_t window_index, VkBuffer src_buffer,
std::span<const VideoCommon::BufferCopy> copies) {
if (!unified_memory || src_buffer == VK_NULL_HANDLE || copies.empty() ||
window_index >= unified_memory->GetWindowCount() ||
unified_memory->GetWindowBuffer(window_index) == VK_NULL_HANDLE) {
return;
}
PendingUnifiedCopy& pending = pending_unified_copies.emplace_back();
pending.window = window_index;
pending.buffer = src_buffer;
pending.copies.resize(copies.size());
std::ranges::transform(copies, pending.copies.begin(), MakeBufferCopy);
}
void BufferCacheRuntime::FlushUnifiedMemoryCopies() {
if (pending_unified_copies.empty()) {
return;
}
struct UnifiedCopyCommand {
VkBuffer buffer;
boost::container::small_vector<VkBufferCopy, 8> copies;
};
std::stable_sort(pending_unified_copies.begin(), pending_unified_copies.end(),
[](const PendingUnifiedCopy& lhs, const PendingUnifiedCopy& rhs) {
return lhs.window < rhs.window;
});
const bool foreign = unified_memory->NeedsForeignOwnershipTransfer();
const u32 queue_family = device.GetGraphicsFamily();
size_t group_begin = 0;
while (group_begin < pending_unified_copies.size()) {
const size_t window = pending_unified_copies[group_begin].window;
size_t group_end = group_begin;
while (group_end < pending_unified_copies.size() &&
pending_unified_copies[group_end].window == window) {
++group_end;
}
const VkBuffer window_buffer = unified_memory->GetWindowBuffer(window);
WindowRanges ranges;
for (size_t index = group_begin; index < group_end; ++index) {
for (const VkBufferCopy& copy : pending_unified_copies[index].copies) {
ranges.emplace_back(copy.dstOffset, copy.dstOffset + copy.size);
}
}
CoalesceWindowRanges(ranges);
boost::container::small_vector<VkBufferMemoryBarrier, MAX_WINDOW_BARRIER_RANGES> acquire;
boost::container::small_vector<VkBufferMemoryBarrier, MAX_WINDOW_BARRIER_RANGES> release;
if (foreign) {
for (const WindowRange& range : ranges) {
acquire.push_back(VkBufferMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
.dstQueueFamilyIndex = queue_family,
.buffer = window_buffer,
.offset = range.first,
.size = range.second - range.first,
});
release.push_back(VkBufferMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = 0,
.srcQueueFamilyIndex = queue_family,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
.buffer = window_buffer,
.offset = range.first,
.size = range.second - range.first,
});
}
}
boost::container::small_vector<UnifiedCopyCommand, 4> commands;
commands.reserve(group_end - group_begin);
for (size_t index = group_begin; index < group_end; ++index) {
PendingUnifiedCopy& pending = pending_unified_copies[index];
commands.push_back(UnifiedCopyCommand{pending.buffer, std::move(pending.copies)});
}
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([window_buffer, acquire = std::move(acquire), release = std::move(release),
commands = std::move(commands)](vk::CommandBuffer cmdbuf) {
if (!acquire.empty()) {
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {},
VideoCommon::FixSmallVectorADL(acquire), {});
}
for (const UnifiedCopyCommand& command : commands) {
cmdbuf.CopyBuffer(command.buffer, window_buffer,
VideoCommon::FixSmallVectorADL(command.copies));
}
if (!release.empty()) {
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, {},
VideoCommon::FixSmallVectorADL(release), {});
}
});
group_begin = group_end;
}
pending_unified_copies.clear();
}
void BufferCacheRuntime::UnifiedMemoryHostBarrier() {
static constexpr VkMemoryBarrier HOST_BARRIER{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_HOST_READ_BIT,
};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([](vk::CommandBuffer cmdbuf) {
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0,
HOST_BARRIER);
});
}
StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) { StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) {
return staging_pool.Request(size, MemoryUsage::Upload); return staging_pool.Request(size, MemoryUsage::Upload);
} }
@@ -402,6 +589,7 @@ u32 BufferCacheRuntime::GetStorageBufferAlignment() const {
} }
void BufferCacheRuntime::TickFrame(Common::SlotVector<Buffer>& slot_buffers) noexcept { void BufferCacheRuntime::TickFrame(Common::SlotVector<Buffer>& slot_buffers) noexcept {
FlushUnifiedMemoryCopies();
for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) { for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) {
if (scheduler.IsFree(it->LastUsageTick())) { if (scheduler.IsFree(it->LastUsageTick())) {
it->ResetUsageTracking(); it->ResetUsageTracking();
@@ -585,6 +773,29 @@ void BufferCacheRuntime::BindQuadIndexBuffer(PrimitiveTopology topology, u32 fir
} }
} }
void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride) {
if (index >= device.GetMaxVertexInputBindings()) {
return;
}
if (device.IsExtExtendedDynamicStateSupported()) {
scheduler.Record([index, buffer, offset, size, stride](vk::CommandBuffer cmdbuf) {
const VkDeviceSize vk_offset = buffer != VK_NULL_HANDLE ? offset : 0;
const VkDeviceSize vk_size = buffer != VK_NULL_HANDLE ? size : VK_WHOLE_SIZE;
const VkDeviceSize vk_stride = stride;
cmdbuf.BindVertexBuffers2EXT(index, 1, &buffer, &vk_offset, &vk_size, &vk_stride);
});
} else {
if (!device.HasNullDescriptor() && buffer == VK_NULL_HANDLE) {
ReserveNullBuffer();
buffer = *null_buffer;
offset = 0;
}
scheduler.Record([index, buffer, offset](vk::CommandBuffer cmdbuf) {
cmdbuf.BindVertexBuffer(index, buffer, offset);
});
}
}
void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) { void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) {
boost::container::static_vector<VkBuffer, VideoCommon::NUM_VERTEX_BUFFERS> buffer_handles(bindings.buffers.size()); boost::container::static_vector<VkBuffer, VideoCommon::NUM_VERTEX_BUFFERS> buffer_handles(bindings.buffers.size());
for (u32 i = 0; i < bindings.buffers.size(); ++i) { for (u32 i = 0; i < bindings.buffers.size(); ++i) {
@@ -7,6 +7,10 @@
#pragma once #pragma once
#include <limits> #include <limits>
#include <memory>
#include <span>
#include <boost/container/small_vector.hpp>
#include "video_core/buffer_cache/buffer_cache_base.h" #include "video_core/buffer_cache/buffer_cache_base.h"
#include "video_core/buffer_cache/memory_tracker_base.h" #include "video_core/buffer_cache/memory_tracker_base.h"
@@ -97,6 +101,33 @@ public:
void TickFrame(Common::SlotVector<Buffer>& slot_buffers) noexcept; void TickFrame(Common::SlotVector<Buffer>& slot_buffers) noexcept;
void TryEnableUnifiedMemory(void* base, size_t size,
std::span<AHardwareBuffer* const> hardware_buffers,
size_t hardware_buffer_window, size_t hardware_buffer_base);
[[nodiscard]] bool HasUnifiedMemory() const noexcept {
return unified_memory != nullptr && unified_memory->IsValid();
}
[[nodiscard]] u64 UnifiedMemorySize() const noexcept {
return unified_memory ? unified_memory->GetSize() : 0;
}
[[nodiscard]] u64 UnifiedMemoryBase() const noexcept {
return unified_memory ? unified_memory->GetBaseOffset() : 0;
}
[[nodiscard]] u64 UnifiedMemoryWindowSize() const noexcept {
return unified_memory ? unified_memory->GetWindowSize() : 0;
}
void CopyToUnifiedMemory(size_t window_index, VkBuffer src_buffer,
std::span<const VideoCommon::BufferCopy> copies);
void FlushUnifiedMemoryCopies();
void UnifiedMemoryHostBarrier();
u64 CurrentTick(); u64 CurrentTick();
u64 KnownGpuTick(); u64 KnownGpuTick();
@@ -138,6 +169,8 @@ public:
void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count); void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count);
void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride);
void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings); void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings);
void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size); void BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size);
@@ -162,6 +195,29 @@ public:
BindBuffer(buffer, offset, size); BindBuffer(buffer, offset, size);
} }
void BindStorageBuffer(VkBuffer buffer, VkDeviceAddress address, u32 offset, u32 size,
[[maybe_unused]] bool is_written) {
guest_descriptor_queue.AddBuffer(buffer, address, offset, size);
}
[[nodiscard]] bool IsUnifiedMemoryBindable() const noexcept {
return unified_memory != nullptr && unified_memory->IsValid() &&
unified_memory->IsBindable();
}
[[nodiscard]] VkBuffer UnifiedWindowBuffer(size_t index) const noexcept {
return unified_memory->GetWindowBuffer(index);
}
[[nodiscard]] VkDeviceAddress UnifiedWindowAddress(size_t index) const noexcept {
return unified_memory->GetWindowAddress(index);
}
[[nodiscard]] bool IsUnifiedStorageRange(u32 size, u64 offset) const {
return size <= device.GetMaxStorageBufferRange() &&
(offset % device.GetStorageBufferAlignment()) == 0;
}
void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
VideoCore::Surface::PixelFormat format) { VideoCore::Surface::PixelFormat format) {
guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format), guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format),
@@ -178,6 +234,12 @@ public:
} }
private: private:
struct PendingUnifiedCopy {
size_t window;
VkBuffer buffer;
boost::container::small_vector<VkBufferCopy, 8> copies;
};
void BindBuffer(const Buffer& buffer, u32 offset, u32 size) { void BindBuffer(const Buffer& buffer, u32 offset, u32 size) {
const VkBuffer handle = buffer.Handle(); const VkBuffer handle = buffer.Handle();
if (handle == VK_NULL_HANDLE) { if (handle == VK_NULL_HANDLE) {
@@ -202,6 +264,8 @@ private:
std::shared_ptr<QuadStripIndexBuffer> quad_strip_index_buffer; std::shared_ptr<QuadStripIndexBuffer> quad_strip_index_buffer;
vk::Buffer null_buffer; vk::Buffer null_buffer;
HostMemoryImport* unified_memory{};
boost::container::small_vector<PendingUnifiedCopy, 8> pending_unified_copies;
std::unique_ptr<Uint8Pass> uint8_pass; std::unique_ptr<Uint8Pass> uint8_pass;
QuadIndexedPass quad_index_pass; QuadIndexedPass quad_index_pass;
@@ -224,6 +288,7 @@ struct BufferCacheParams {
static constexpr bool USE_MEMORY_MAPS = true; static constexpr bool USE_MEMORY_MAPS = true;
static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = false; static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = false;
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = true; static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = true;
static constexpr bool USE_UNIFIED_MEMORY = true;
}; };
using BufferCache = VideoCommon::BufferCache<BufferCacheParams>; using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
+501 -257
View File
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array> #include <array>
#include <memory> #include <memory>
#include <numeric> #include <numeric>
@@ -12,6 +13,7 @@
#include "video_core/renderer_vulkan/vk_texture_cache.h" #include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "common/alignment.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/div_ceil.h" #include "common/div_ceil.h"
@@ -22,13 +24,9 @@
#include "video_core/host_shaders/resolve_conditional_render_comp_spv.h" #include "video_core/host_shaders/resolve_conditional_render_comp_spv.h"
#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h" #include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h"
#include "video_core/host_shaders/vulkan_uint8_comp_spv.h" #include "video_core/host_shaders/vulkan_uint8_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_2d_comp_spv.h" #include "video_core/host_shaders/block_linear_unswizzle_2d_buffer_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_2d_nonarrow_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h" #include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_3d_comp_spv.h" #include "video_core/host_shaders/block_linear_unswizzle_3d_buffer_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_3d_nonarrow_comp_spv.h"
#include "video_core/host_shaders/pitch_unswizzle_comp_spv.h"
#include "video_core/host_shaders/pitch_unswizzle_nonarrow_comp_spv.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h" #include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/surface.h" #include "video_core/surface.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h"
@@ -879,290 +877,536 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk(
} }
namespace { namespace {
constexpr u32 BL2D_BINDING_INPUT_BUFFER = 0;
constexpr u32 BL2D_BINDING_OUTPUT_BUFFER = 1;
constexpr u32 UNSWIZZLE_BINDING_INPUT_BUFFER = 0; struct alignas(16) BlockLinearUnswizzle2DPushConstants {
constexpr u32 UNSWIZZLE_BINDING_OUTPUT_IMAGE = 1; std::array<u32, 3> dim;
constexpr size_t UNSWIZZLE_NUM_BINDINGS = 2; u32 bytes_per_block_log2;
std::array<u32, 3> origin;
u32 layer_stride;
u32 block_size;
u32 x_shift;
u32 block_height;
u32 block_height_mask;
};
static_assert(sizeof(BlockLinearUnswizzle2DPushConstants) <= 128);
constexpr std::array<VkDescriptorSetLayoutBinding, UNSWIZZLE_NUM_BINDINGS> constexpr std::array<VkDescriptorSetLayoutBinding, 2> BL2D_BINDINGS{{
UNSWIZZLE_DESCRIPTOR_SET_BINDINGS{{ {
{ .binding = BL2D_BINDING_INPUT_BUFFER,
.binding = UNSWIZZLE_BINDING_INPUT_BUFFER, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .descriptorCount = 1,
.descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, .pImmutableSamplers = nullptr,
.pImmutableSamplers = nullptr, },
}, {
{ .binding = BL2D_BINDING_OUTPUT_BUFFER,
.binding = UNSWIZZLE_BINDING_OUTPUT_IMAGE, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .descriptorCount = 1,
.descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, .pImmutableSamplers = nullptr,
.pImmutableSamplers = nullptr, },
}, }};
}};
constexpr std::array<VkDescriptorUpdateTemplateEntry, UNSWIZZLE_NUM_BINDINGS> constexpr std::array<VkDescriptorUpdateTemplateEntry, 2> BL2D_TEMPLATE{{
UNSWIZZLE_DESCRIPTOR_UPDATE_TEMPLATE{{ {
{ .dstBinding = BL2D_BINDING_INPUT_BUFFER,
.dstBinding = UNSWIZZLE_BINDING_INPUT_BUFFER, .dstArrayElement = 0,
.dstArrayElement = 0, .descriptorCount = 1,
.descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .offset = BL2D_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry),
.offset = UNSWIZZLE_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry), .stride = sizeof(DescriptorUpdateEntry),
.stride = sizeof(DescriptorUpdateEntry), },
}, {
{ .dstBinding = BL2D_BINDING_OUTPUT_BUFFER,
.dstBinding = UNSWIZZLE_BINDING_OUTPUT_IMAGE, .dstArrayElement = 0,
.dstArrayElement = 0, .descriptorCount = 1,
.descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .offset = BL2D_BINDING_OUTPUT_BUFFER * sizeof(DescriptorUpdateEntry),
.offset = UNSWIZZLE_BINDING_OUTPUT_IMAGE * sizeof(DescriptorUpdateEntry), .stride = sizeof(DescriptorUpdateEntry),
.stride = sizeof(DescriptorUpdateEntry), },
}, }};
}};
constexpr DescriptorBankInfo UNSWIZZLE_BANK_INFO{ constexpr DescriptorBankInfo BL2D_BANK_INFO{
.uniform_buffers = 0, .uniform_buffers = 0,
.storage_buffers = 1, .storage_buffers = 2,
.texture_buffers = 0, .texture_buffers = 0,
.image_buffers = 0, .image_buffers = 0,
.textures = 0, .textures = 0,
.images = 1, .images = 0,
.score = 2, .score = 2,
}; };
[[nodiscard]] std::span<const u32> UnswizzleSpv(const Device& device,
std::span<const u32> extended,
std::span<const u32> narrow) {
if (device.IsStorageBuffer8BitAccessSupported() &&
device.IsStorageBuffer16BitAccessSupported()) {
return extended;
}
return narrow;
}
struct PitchUnswizzlePushConstants {
alignas(8) std::array<u32, 2> origin;
alignas(8) std::array<s32, 2> destination;
u32 bytes_per_block;
u32 pitch;
};
void RecordUnswizzleEntryBarrier(Scheduler& scheduler, VkPipeline vk_pipeline, VkImage vk_image,
VkImageAspectFlags aspect_mask, bool is_initialized) {
scheduler.Record([vk_pipeline, vk_image, aspect_mask,
is_initialized](vk::CommandBuffer cmdbuf) {
VkAccessFlags src_access = VK_ACCESS_NONE;
VkImageLayout old_layout = VK_IMAGE_LAYOUT_UNDEFINED;
if (is_initialized) {
src_access = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
old_layout = VK_IMAGE_LAYOUT_GENERAL;
}
const VkImageMemoryBarrier image_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = src_access,
.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.oldLayout = old_layout,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = vk_image,
.subresourceRange{
.aspectMask = aspect_mask,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
VkPipelineStageFlags src_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
if (is_initialized) {
src_stage = vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER;
}
cmdbuf.PipelineBarrier(src_stage, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline);
});
}
void RecordUnswizzleExitBarrier(Scheduler& scheduler, VkImage vk_image,
VkImageAspectFlags aspect_mask) {
scheduler.Record([vk_image, aspect_mask](vk::CommandBuffer cmdbuf) {
const VkImageMemoryBarrier image_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = vk_image,
.subresourceRange{
.aspectMask = aspect_mask,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, 0, image_barrier);
});
}
} // Anonymous namespace } // Anonymous namespace
BlockLinearUnswizzle2DPass::BlockLinearUnswizzle2DPass( BlockLinearUnswizzle2DPass::BlockLinearUnswizzle2DPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_) ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass(device_, scheduler_, descriptor_pool_, UNSWIZZLE_DESCRIPTOR_SET_BINDINGS, : ComputePass(device_, scheduler_, descriptor_pool_, BL2D_BINDINGS, BL2D_TEMPLATE,
UNSWIZZLE_DESCRIPTOR_UPDATE_TEMPLATE, UNSWIZZLE_BANK_INFO, BL2D_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof( COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearUnswizzle2DPushConstants)>,
VideoCommon::Accelerated::BlockLinearSwizzle2DParams)>, BLOCK_LINEAR_UNSWIZZLE_2D_BUFFER_COMP_SPV),
UnswizzleSpv(device_, BLOCK_LINEAR_UNSWIZZLE_2D_COMP_SPV, scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
BLOCK_LINEAR_UNSWIZZLE_2D_NONARROW_COMP_SPV)), compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
BlockLinearUnswizzle2DPass::~BlockLinearUnswizzle2DPass() = default; BlockLinearUnswizzle2DPass::~BlockLinearUnswizzle2DPass() = default;
bool BlockLinearUnswizzle2DPass::IsSupported(const Device& device,
const VideoCommon::ImageInfo& info) {
if (info.type != VideoCommon::ImageType::e2D) {
return false;
}
if (info.num_samples > 1) {
return false;
}
if (device.GetStorageBufferAlignment() > Tegra::Texture::GOB_SIZE) {
return false;
}
if (VideoCore::Surface::GetFormatType(info.format) !=
VideoCore::Surface::SurfaceType::ColorTexture) {
return false;
}
if (VideoCore::Surface::IsPixelFormatASTC(info.format) && !device.IsOptimalAstcSupported()) {
return false;
}
if (VideoCore::Surface::IsPixelFormatBCn(info.format) && !device.IsOptimalBcnSupported()) {
return false;
}
const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format);
return bytes_per_block == 4 || bytes_per_block == 8 || bytes_per_block == 16;
}
void BlockLinearUnswizzle2DPass::Unswizzle( void BlockLinearUnswizzle2DPass::Unswizzle(
Image& image, const StagingBufferRef& map, Image& image, const StagingBufferRef& swizzled,
std::span<const VideoCommon::SwizzleParameters> swizzles) { std::span<const VideoCommon::SwizzleParameters> swizzles) {
using namespace VideoCommon::Accelerated; UnswizzleFrom(image, swizzled.buffer, swizzled.offset, swizzles);
scheduler.RequestOutsideRenderPassOperationContext();
const VkPipeline vk_pipeline = *pipeline;
const VkImageAspectFlags aspect_mask = image.AspectMask();
const VkImage vk_image = image.Handle();
const bool is_initialized = image.ExchangeInitialization();
RecordUnswizzleEntryBarrier(scheduler, vk_pipeline, vk_image, aspect_mask, is_initialized);
const u32 num_layers = static_cast<u32>(image.info.resources.layers);
for (const VideoCommon::SwizzleParameters& swizzle : swizzles) {
const size_t input_offset = swizzle.buffer_offset + map.offset;
const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U);
const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U);
compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset,
image.guest_size_bytes - swizzle.buffer_offset);
compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level));
const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info);
scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_layers, params,
descriptor_data](vk::CommandBuffer cmdbuf) {
const VkDescriptorSet set = descriptor_allocator.Commit();
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params);
cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, num_layers);
});
}
RecordUnswizzleExitBarrier(scheduler, vk_image, aspect_mask);
} }
BlockLinearUnswizzleImage3DPass::BlockLinearUnswizzleImage3DPass( void BlockLinearUnswizzle2DPass::UnswizzleFrom(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, Image& image, VkBuffer source_buffer, VkDeviceSize source_offset,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass(device_, scheduler_, descriptor_pool_, UNSWIZZLE_DESCRIPTOR_SET_BINDINGS,
UNSWIZZLE_DESCRIPTOR_UPDATE_TEMPLATE, UNSWIZZLE_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearSwizzle3DParams)>,
UnswizzleSpv(device_, BLOCK_LINEAR_UNSWIZZLE_3D_COMP_SPV,
BLOCK_LINEAR_UNSWIZZLE_3D_NONARROW_COMP_SPV)),
scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
BlockLinearUnswizzleImage3DPass::~BlockLinearUnswizzleImage3DPass() = default;
void BlockLinearUnswizzleImage3DPass::Unswizzle(
Image& image, const StagingBufferRef& map,
std::span<const VideoCommon::SwizzleParameters> swizzles) { std::span<const VideoCommon::SwizzleParameters> swizzles) {
using namespace VideoCommon::Accelerated; const VkImage dst_image = image.Handle();
scheduler.RequestOutsideRenderPassOperationContext(); if (swizzles.empty() || source_buffer == VK_NULL_HANDLE || dst_image == VK_NULL_HANDLE) {
const VkPipeline vk_pipeline = *pipeline; return;
const VkImageAspectFlags aspect_mask = image.AspectMask();
const VkImage vk_image = image.Handle();
const bool is_initialized = image.ExchangeInitialization();
RecordUnswizzleEntryBarrier(scheduler, vk_pipeline, vk_image, aspect_mask, is_initialized);
for (const VideoCommon::SwizzleParameters& swizzle : swizzles) {
const size_t input_offset = swizzle.buffer_offset + map.offset;
const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 16U);
const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 8U);
const u32 num_dispatches_z = Common::DivCeil(swizzle.num_tiles.depth, 8U);
compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset,
image.guest_size_bytes - swizzle.buffer_offset);
compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level));
const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
const auto params = MakeBlockLinearSwizzle3DParams(swizzle, image.info);
scheduler.Record([this, num_dispatches_x, num_dispatches_y, num_dispatches_z, params,
descriptor_data](vk::CommandBuffer cmdbuf) {
const VkDescriptorSet set = descriptor_allocator.Commit();
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params);
cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, num_dispatches_z);
});
} }
RecordUnswizzleExitBarrier(scheduler, vk_image, aspect_mask); const u32 layers = image.info.resources.layers;
} const VkImageAspectFlags aspect = image.AspectMask();
PitchUnswizzlePass::PitchUnswizzlePass( const VkDeviceSize output_alignment =
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, (std::max)(device.GetStorageBufferAlignment(), VkDeviceSize{16});
ComputePassDescriptorQueue& compute_pass_descriptor_queue_) VkDeviceSize total_output = 0;
: ComputePass(device_, scheduler_, descriptor_pool_, UNSWIZZLE_DESCRIPTOR_SET_BINDINGS, for (const VideoCommon::SwizzleParameters& sw : swizzles) {
UNSWIZZLE_DESCRIPTOR_UPDATE_TEMPLATE, UNSWIZZLE_BANK_INFO, const auto params =
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(PitchUnswizzlePushConstants)>, VideoCommon::Accelerated::MakeBlockLinearSwizzle2DParams(sw, image.info);
UnswizzleSpv(device_, PITCH_UNSWIZZLE_COMP_SPV, const VkDeviceSize level_size = static_cast<VkDeviceSize>(sw.num_tiles.width) *
PITCH_UNSWIZZLE_NONARROW_COMP_SPV)), sw.num_tiles.height * layers *
scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {} (1ULL << params.bytes_per_block_log2);
if (level_size == 0) {
continue;
}
total_output = Common::AlignUp(total_output, output_alignment) + level_size;
}
if (total_output == 0) {
return;
}
const StagingBufferRef output =
staging_buffer_pool.Request(static_cast<size_t>(total_output), MemoryUsage::DeviceLocal);
const VkBuffer out_buffer = output.buffer;
if (out_buffer == VK_NULL_HANDLE) {
return;
}
VkDeviceSize level_offset = 0;
PitchUnswizzlePass::~PitchUnswizzlePass() = default;
void PitchUnswizzlePass::Unswizzle(Image& image, const StagingBufferRef& map,
std::span<const VideoCommon::SwizzleParameters> swizzles) {
scheduler.RequestOutsideRenderPassOperationContext(); scheduler.RequestOutsideRenderPassOperationContext();
const VkPipeline vk_pipeline = *pipeline;
const VkImageAspectFlags aspect_mask = image.AspectMask();
const VkImage vk_image = image.Handle();
const bool is_initialized = image.ExchangeInitialization();
RecordUnswizzleEntryBarrier(scheduler, vk_pipeline, vk_image, aspect_mask, is_initialized);
const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(image.info.format); VkAccessFlags pre_access = VK_ACCESS_NONE;
const u32 pitch = image.info.pitch; VkImageLayout pre_layout = VK_IMAGE_LAYOUT_UNDEFINED;
for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { VkPipelineStageFlags pre_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
const size_t input_offset = swizzle.buffer_offset + map.offset; if (image.ExchangeInitialization()) {
const u32 num_dispatches_x = Common::DivCeil(swizzle.num_tiles.width, 32U); pre_access = VK_ACCESS_SHADER_READ_BIT;
const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 32U); pre_layout = VK_IMAGE_LAYOUT_GENERAL;
pre_stage = vk::PIPELINE_STAGE_GRAPHICS_COMPUTE;
compute_pass_descriptor_queue.Acquire(scheduler, 2); }
compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, scheduler.Record([dst_image, aspect, pre_access, pre_layout,
image.guest_size_bytes - swizzle.buffer_offset); pre_stage](vk::CommandBuffer cmdbuf) {
compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); const VkImageMemoryBarrier barrier{
const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
const PitchUnswizzlePushConstants params{ .srcAccessMask = pre_access,
.origin{0, 0}, .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.destination{0, 0}, .oldLayout = pre_layout,
.bytes_per_block = bytes_per_block, .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.pitch = pitch, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange{
.aspectMask = aspect,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
}; };
scheduler.Record([this, num_dispatches_x, num_dispatches_y, params, cmdbuf.PipelineBarrier(pre_stage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, {}, barrier);
descriptor_data](vk::CommandBuffer cmdbuf) { });
const VkDescriptorSet set = descriptor_allocator.Commit();
for (const VideoCommon::SwizzleParameters& sw : swizzles) {
const auto params =
VideoCommon::Accelerated::MakeBlockLinearSwizzle2DParams(sw, image.info);
const u32 width = sw.num_tiles.width;
const u32 height = sw.num_tiles.height;
const u32 bytes_per_block = 1u << params.bytes_per_block_log2;
const VkDeviceSize output_size =
static_cast<VkDeviceSize>(width) * height * layers * bytes_per_block;
if (output_size == 0) {
continue;
}
const u32 level = static_cast<u32>(sw.level);
const u32 texel_width = (std::max)(1u, image.info.size.width >> level);
const u32 texel_height = (std::max)(1u, image.info.size.height >> level);
level_offset = Common::AlignUp(level_offset, output_alignment);
const VkDeviceSize out_offset = output.offset + level_offset;
level_offset += output_size;
BlockLinearUnswizzle2DPushConstants pc{};
pc.dim = {width, height, layers};
pc.bytes_per_block_log2 = params.bytes_per_block_log2;
pc.origin = params.origin;
pc.layer_stride = params.layer_stride;
pc.block_size = params.block_size;
pc.x_shift = params.x_shift;
pc.block_height = params.block_height;
pc.block_height_mask = params.block_height_mask;
compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddBuffer(source_buffer, sw.buffer_offset + source_offset,
image.guest_size_bytes - sw.buffer_offset);
compute_pass_descriptor_queue.AddBuffer(out_buffer, out_offset, output_size);
const void* descriptor_data = compute_pass_descriptor_queue.UpdateData();
const VkDescriptorSet set = descriptor_allocator.Commit();
const u32 gx = Common::DivCeil(width, 16u);
const u32 gy = Common::DivCeil(height, 8u);
scheduler.Record([this, set, descriptor_data, pc, gx, gy, layers, output_size, out_buffer,
out_offset, dst_image, aspect, texel_width, texel_height,
level](vk::CommandBuffer cmdbuf) {
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, params); cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(pc), &pc);
cmdbuf.Dispatch(num_dispatches_x, num_dispatches_y, 1); cmdbuf.Dispatch(gx, gy, layers);
const VkBufferMemoryBarrier buffer_barrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = out_buffer,
.offset = out_offset,
.size = output_size,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, buffer_barrier, {});
const VkBufferImageCopy copy{
.bufferOffset = out_offset,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource{
.aspectMask = aspect,
.mipLevel = level,
.baseArrayLayer = 0,
.layerCount = layers,
},
.imageOffset = {0, 0, 0},
.imageExtent = {texel_width, texel_height, 1},
};
cmdbuf.CopyBufferToImage(out_buffer, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
copy);
}); });
} }
RecordUnswizzleExitBarrier(scheduler, vk_image, aspect_mask);
scheduler.Record([dst_image, aspect](vk::CommandBuffer cmdbuf) {
const VkImageMemoryBarrier barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange{
.aspectMask = aspect,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, {}, {}, barrier);
});
}
namespace {
constexpr u32 BL3DB_BINDING_INPUT_BUFFER = 0;
constexpr u32 BL3DB_BINDING_OUTPUT_BUFFER = 1;
struct alignas(16) BlockLinearUnswizzle3DBufferPushConstants {
std::array<u32, 3> dim;
u32 bytes_per_block_log2;
std::array<u32, 3> origin;
u32 slice_size;
u32 block_size;
u32 x_shift;
u32 block_height;
u32 block_height_mask;
u32 block_depth;
u32 block_depth_mask;
};
static_assert(sizeof(BlockLinearUnswizzle3DBufferPushConstants) <= 128);
constexpr std::array<VkDescriptorSetLayoutBinding, 2> BL3DB_BINDINGS{{
{
.binding = BL3DB_BINDING_INPUT_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
{
.binding = BL3DB_BINDING_OUTPUT_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
}};
constexpr std::array<VkDescriptorUpdateTemplateEntry, 2> BL3DB_TEMPLATE{{
{
.dstBinding = BL3DB_BINDING_INPUT_BUFFER,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.offset = BL3DB_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry),
.stride = sizeof(DescriptorUpdateEntry),
},
{
.dstBinding = BL3DB_BINDING_OUTPUT_BUFFER,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.offset = BL3DB_BINDING_OUTPUT_BUFFER * sizeof(DescriptorUpdateEntry),
.stride = sizeof(DescriptorUpdateEntry),
},
}};
constexpr DescriptorBankInfo BL3DB_BANK_INFO{
.uniform_buffers = 0,
.storage_buffers = 2,
.texture_buffers = 0,
.image_buffers = 0,
.textures = 0,
.images = 0,
.score = 2,
};
} // Anonymous namespace
BlockLinearUnswizzle3DBufferPass::BlockLinearUnswizzle3DBufferPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass(device_, scheduler_, descriptor_pool_, BL3DB_BINDINGS, BL3DB_TEMPLATE,
BL3DB_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearUnswizzle3DBufferPushConstants)>,
BLOCK_LINEAR_UNSWIZZLE_3D_BUFFER_COMP_SPV),
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
BlockLinearUnswizzle3DBufferPass::~BlockLinearUnswizzle3DBufferPass() = default;
bool BlockLinearUnswizzle3DBufferPass::IsSupported(const Device& device,
const VideoCommon::ImageInfo& info) {
if (info.type != VideoCommon::ImageType::e3D) {
return false;
}
if (info.resources.levels != 1 || info.resources.layers != 1) {
return false;
}
if (info.num_samples > 1) {
return false;
}
if (info.size.depth <= 1) {
return false;
}
if (VideoCore::Surface::GetFormatType(info.format) !=
VideoCore::Surface::SurfaceType::ColorTexture) {
return false;
}
if (VideoCore::Surface::IsPixelFormatASTC(info.format)) {
return false;
}
if (VideoCore::Surface::IsPixelFormatBCn(info.format) && !device.IsOptimalBcnSupported()) {
return false;
}
const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format);
return bytes_per_block == 4 || bytes_per_block == 8 || bytes_per_block == 16;
}
void BlockLinearUnswizzle3DBufferPass::Unswizzle(
Image& image, const StagingBufferRef& swizzled,
std::span<const VideoCommon::SwizzleParameters> swizzles) {
if (swizzles.empty()) {
return;
}
const VideoCommon::SwizzleParameters& sw = swizzles.front();
const auto params = VideoCommon::Accelerated::MakeBlockLinearSwizzle3DParams(sw, image.info);
const u32 blocks_x = sw.num_tiles.width;
const u32 blocks_y = sw.num_tiles.height;
const u32 blocks_z = sw.num_tiles.depth;
const u32 bytes_per_block = 1u << params.bytes_per_block_log2;
const VkDeviceSize output_size =
static_cast<VkDeviceSize>(blocks_x) * blocks_y * blocks_z * bytes_per_block;
const StagingBufferRef output =
staging_buffer_pool.Request(static_cast<size_t>(output_size), MemoryUsage::DeviceLocal);
BlockLinearUnswizzle3DBufferPushConstants pc{};
pc.dim = {blocks_x, blocks_y, blocks_z};
pc.bytes_per_block_log2 = params.bytes_per_block_log2;
pc.origin = params.origin;
pc.slice_size = params.slice_size;
pc.block_size = params.block_size;
pc.x_shift = params.x_shift;
pc.block_height = params.block_height;
pc.block_height_mask = params.block_height_mask;
pc.block_depth = params.block_depth;
pc.block_depth_mask = params.block_depth_mask;
scheduler.RequestOutsideRenderPassOperationContext();
compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddBuffer(swizzled.buffer, sw.buffer_offset + swizzled.offset,
image.guest_size_bytes - sw.buffer_offset);
compute_pass_descriptor_queue.AddBuffer(output.buffer, output.offset, output_size);
const void* descriptor_data = compute_pass_descriptor_queue.UpdateData();
const VkDescriptorSet set = descriptor_allocator.Commit();
const u32 gx = Common::DivCeil(blocks_x, 8u);
const u32 gy = Common::DivCeil(blocks_y, 8u);
const u32 gz = Common::DivCeil(blocks_z, 4u);
const bool is_initialized = image.ExchangeInitialization();
const VkBuffer out_buffer = output.buffer;
const VkDeviceSize out_offset = output.offset;
const VkImage dst_image = image.Handle();
const VkImageAspectFlags aspect = image.AspectMask();
const VkExtent3D extent{
.width = image.info.size.width,
.height = image.info.size.height,
.depth = image.info.size.depth,
};
scheduler.Record([this, set, descriptor_data, pc, gx, gy, gz, output_size, out_buffer,
out_offset, dst_image, aspect, extent,
is_initialized](vk::CommandBuffer cmdbuf) {
if (dst_image == VK_NULL_HANDLE || out_buffer == VK_NULL_HANDLE) {
return;
}
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(pc), &pc);
cmdbuf.Dispatch(gx, gy, gz);
const VkBufferMemoryBarrier buffer_barrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = out_buffer,
.offset = out_offset,
.size = output_size,
};
VkAccessFlags pre_copy_access = VK_ACCESS_NONE;
VkImageLayout pre_copy_layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkPipelineStageFlags pre_copy_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
if (is_initialized) {
pre_copy_access = VK_ACCESS_SHADER_READ_BIT;
pre_copy_layout = VK_IMAGE_LAYOUT_GENERAL;
pre_copy_stage = vk::PIPELINE_STAGE_GRAPHICS_COMPUTE;
}
const VkImageMemoryBarrier pre_copy{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = pre_copy_access,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = pre_copy_layout,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange{
.aspectMask = aspect,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | pre_copy_stage,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, buffer_barrier, pre_copy);
const VkBufferImageCopy copy{
.bufferOffset = out_offset,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource{
.aspectMask = aspect,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.imageOffset = {0, 0, 0},
.imageExtent = extent,
};
cmdbuf.CopyBufferToImage(out_buffer, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy);
const VkImageMemoryBarrier post_copy{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange{
.aspectMask = aspect,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE,
0, {}, {}, post_copy);
});
} }
} // namespace Vulkan } // namespace Vulkan
@@ -166,46 +166,43 @@ private:
class BlockLinearUnswizzle2DPass final : public ComputePass { class BlockLinearUnswizzle2DPass final : public ComputePass {
public: public:
explicit BlockLinearUnswizzle2DPass( explicit BlockLinearUnswizzle2DPass(const Device& device_, Scheduler& scheduler_,
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, DescriptorPool& descriptor_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_); StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
~BlockLinearUnswizzle2DPass(); ~BlockLinearUnswizzle2DPass();
void Unswizzle(Image& image, const StagingBufferRef& map, [[nodiscard]] static bool IsSupported(const Device& device,
const VideoCommon::ImageInfo& info);
void Unswizzle(Image& image, const StagingBufferRef& swizzled,
std::span<const VideoCommon::SwizzleParameters> swizzles); std::span<const VideoCommon::SwizzleParameters> swizzles);
void UnswizzleFrom(Image& image, VkBuffer source_buffer, VkDeviceSize source_offset,
std::span<const VideoCommon::SwizzleParameters> swizzles);
private: private:
Scheduler& scheduler; Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
ComputePassDescriptorQueue& compute_pass_descriptor_queue; ComputePassDescriptorQueue& compute_pass_descriptor_queue;
}; };
class BlockLinearUnswizzleImage3DPass final : public ComputePass { class BlockLinearUnswizzle3DBufferPass final : public ComputePass {
public: public:
explicit BlockLinearUnswizzleImage3DPass( explicit BlockLinearUnswizzle3DBufferPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_); ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
~BlockLinearUnswizzleImage3DPass(); ~BlockLinearUnswizzle3DBufferPass();
void Unswizzle(Image& image, const StagingBufferRef& map, [[nodiscard]] static bool IsSupported(const Device& device, const VideoCommon::ImageInfo& info);
std::span<const VideoCommon::SwizzleParameters> swizzles);
void Unswizzle(Image& image, const StagingBufferRef& swizzled,
private:
Scheduler& scheduler;
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class PitchUnswizzlePass final : public ComputePass {
public:
explicit PitchUnswizzlePass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
~PitchUnswizzlePass();
void Unswizzle(Image& image, const StagingBufferRef& map,
std::span<const VideoCommon::SwizzleParameters> swizzles); std::span<const VideoCommon::SwizzleParameters> swizzles);
private: private:
Scheduler& scheduler; Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
ComputePassDescriptorQueue& compute_pass_descriptor_queue; ComputePassDescriptorQueue& compute_pass_descriptor_queue;
}; };
@@ -694,11 +694,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
const size_t num_vertex_arrays = (std::min)( const size_t num_vertex_arrays = (std::min)(
Maxwell::NumVertexArrays, static_cast<size_t>(device.GetMaxVertexInputBindings())); Maxwell::NumVertexArrays, static_cast<size_t>(device.GetMaxVertexInputBindings()));
for (size_t index = 0; index < num_vertex_arrays; ++index) { for (size_t index = 0; index < num_vertex_arrays; ++index) {
const bool instanced = ((key.state.enabled_divisors >> index) & 1) != 0; const bool instanced = key.state.binding_divisors[index] != 0;
auto rate = VK_VERTEX_INPUT_RATE_VERTEX; const auto rate =
if (instanced) { instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
rate = VK_VERTEX_INPUT_RATE_INSTANCE;
}
vertex_bindings.push_back({ vertex_bindings.push_back({
.binding = static_cast<u32>(index), .binding = static_cast<u32>(index),
.stride = key.state.vertex_strides[index], .stride = key.state.vertex_strides[index],
@@ -707,7 +705,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
if (instanced) { if (instanced) {
vertex_binding_divisors.push_back({ vertex_binding_divisors.push_back({
.binding = static_cast<u32>(index), .binding = static_cast<u32>(index),
.divisor = device.GetVertexAttribDivisor(key.state.binding_divisors[index]), .divisor = key.state.binding_divisors[index],
}); });
} }
} }
@@ -447,6 +447,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.has_broken_fp32_denorm_flush = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY, .has_broken_fp32_denorm_flush = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
.ignore_nan_fp_comparisons = false, .ignore_nan_fp_comparisons = false,
.has_broken_spirv_subgroup_mask_vector_extract_dynamic = false, .has_broken_spirv_subgroup_mask_vector_extract_dynamic = false,
.max_shared_memory_size = device.GetMaxComputeSharedMemorySize(),
.has_broken_robust = .has_broken_robust =
device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Pascal, device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Pascal,
.min_ssbo_alignment = device.GetStorageBufferAlignment(), .min_ssbo_alignment = device.GetStorageBufferAlignment(),
@@ -922,19 +923,6 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
} }
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)}; auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
const VkDriverIdKHR driver_id = device.GetDriverID();
const bool needs_shared_mem_clamp =
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
const u32 max_shared_memory = device.GetMaxComputeSharedMemorySize();
if (needs_shared_mem_clamp && program.shared_memory_size > max_shared_memory) {
LOG_WARNING(Render_Vulkan,
"Compute shader {:#016x} requests {}KB shared memory but device max is {}KB - clamping",
key.unique_hash,
program.shared_memory_size / 1024,
max_shared_memory / 1024);
program.shared_memory_size = max_shared_memory;
}
const std::vector<u32> code{EmitSPIRV(profile, program)}; const std::vector<u32> code{EmitSPIRV(profile, program)};
device.SaveShader(code); device.SaveShader(code);
vk::ShaderModule spv_module{BuildShader(device, code)}; vk::ShaderModule spv_module{BuildShader(device, code)};
@@ -102,9 +102,6 @@ VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t in
.maxDepth = src.translate_z + src.scale_z, .maxDepth = src.translate_z + src.scale_z,
}; };
if (!device.IsExtDepthRangeUnrestrictedSupported()) { if (!device.IsExtDepthRangeUnrestrictedSupported()) {
if (viewport.minDepth < 0.0f || viewport.maxDepth > 1.0f) {
viewport.maxDepth = 0.5f;
}
viewport.minDepth = std::clamp(viewport.minDepth, 0.0f, 1.0f); viewport.minDepth = std::clamp(viewport.minDepth, 0.0f, 1.0f);
viewport.maxDepth = std::clamp(viewport.maxDepth, 0.0f, 1.0f); viewport.maxDepth = std::clamp(viewport.maxDepth, 0.0f, 1.0f);
} }
@@ -228,6 +225,13 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler), fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler),
wfi_event(device.GetLogical().CreateEvent()) { wfi_event(device.GetLogical().CreateEvent()) {
scheduler.SetQueryCache(query_cache); scheduler.SetQueryCache(query_cache);
if (Settings::values.use_unified_memory.GetValue() && device_memory.IsBackingShared()) {
buffer_cache_runtime.TryEnableUnifiedMemory(
device_memory.GetPhysicalBase(), device_memory.GetPhysicalSize(),
device_memory.GetBackingHardwareBuffers(),
device_memory.GetBackingHardwareBufferWindowSize(),
device_memory.GetBackingHardwareBufferBase());
}
} }
RasterizerVulkan::~RasterizerVulkan() { RasterizerVulkan::~RasterizerVulkan() {
@@ -263,7 +267,6 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
} }
void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) { void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
buffer_cache.SetDrawInstanceCount(instance_count);
PrepareDraw(is_indexed, [this, is_indexed, instance_count] { PrepareDraw(is_indexed, [this, is_indexed, instance_count] {
const auto& draw_state = maxwell3d->draw_manager.draw_state; const auto& draw_state = maxwell3d->draw_manager.draw_state;
const u32 num_instances{instance_count}; const u32 num_instances{instance_count};
@@ -299,7 +302,6 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
void RasterizerVulkan::DrawIndirect() { void RasterizerVulkan::DrawIndirect() {
const auto& params = maxwell3d->draw_manager.indirect_state; const auto& params = maxwell3d->draw_manager.indirect_state;
buffer_cache.SetDrawIndirect(&params); buffer_cache.SetDrawIndirect(&params);
buffer_cache.SetDrawInstanceCount(0);
PrepareDraw(params.is_indexed, [this, &params] { PrepareDraw(params.is_indexed, [this, &params] {
const auto indirect_buffer = buffer_cache.GetDrawIndirectBuffer(); const auto indirect_buffer = buffer_cache.GetDrawIndirectBuffer();
const auto& buffer = indirect_buffer.first; const auto& buffer = indirect_buffer.first;
@@ -1922,19 +1924,13 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs)
for (u32 binding = 0; binding < max_bindings; ++binding) { for (u32 binding = 0; binding < max_bindings; ++binding) {
const auto& input_binding{regs.vertex_streams[binding]}; const auto& input_binding{regs.vertex_streams[binding]};
const bool is_instanced{regs.vertex_stream_instances.IsInstancingEnabled(binding)}; const bool is_instanced{regs.vertex_stream_instances.IsInstancingEnabled(binding)};
auto input_rate = VK_VERTEX_INPUT_RATE_VERTEX;
u32 divisor = 1;
if (is_instanced) {
input_rate = VK_VERTEX_INPUT_RATE_INSTANCE;
divisor = device.GetVertexAttribDivisor(input_binding.frequency);
}
bindings.push_back({ bindings.push_back({
.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT, .sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT,
.pNext = nullptr, .pNext = nullptr,
.binding = binding, .binding = binding,
.stride = input_binding.stride, .stride = input_binding.stride,
.inputRate = input_rate, .inputRate = is_instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX,
.divisor = divisor, .divisor = is_instanced ? input_binding.frequency : 1,
}); });
} }
@@ -449,9 +449,7 @@ void Scheduler::EndRenderPass()
| VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
| VK_ACCESS_TRANSFER_READ_BIT
| VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL, .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL, .newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
@@ -462,7 +460,7 @@ void Scheduler::EndRenderPass()
} }
cmdbuf.EndRenderPass(); cmdbuf.EndRenderPass();
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE,
0, nullptr, nullptr, vk::Span(barriers.data(), num_images)); 0, nullptr, nullptr, vk::Span(barriers.data(), num_images));
if (has_transform_feedback) { if (has_transform_feedback) {
static constexpr VkMemoryBarrier XFB_OUTPUT_BARRIER{ static constexpr VkMemoryBarrier XFB_OUTPUT_BARRIER{
@@ -129,6 +129,10 @@ public:
return master_semaphore->IsFree(tick); return master_semaphore->IsFree(tick);
} }
void RefreshTick() {
master_semaphore->Refresh();
}
/// Waits for the given GPU tick, optionally pacing frames. /// Waits for the given GPU tick, optionally pacing frames.
void Wait(u64 tick, double target_fps = 0.0) { void Wait(u64 tick, double target_fps = 0.0) {
if (tick > 0) { if (tick > 0) {
@@ -160,55 +160,6 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
info.size.depth == 1; info.size.depth == 1;
} }
[[nodiscard]] PixelFormat UnswizzleViewFormat(u32 bytes_per_block) {
switch (bytes_per_block) {
case 1:
return PixelFormat::R8_UINT;
case 2:
return PixelFormat::R16_UINT;
case 4:
return PixelFormat::R32_UINT;
case 8:
return PixelFormat::R32G32_UINT;
case 16:
return PixelFormat::R32G32B32A32_UINT;
default:
return PixelFormat::Invalid;
}
}
constexpr u32 UNSWIZZLE_WORKGROUP_INVOCATIONS = 32 * 32;
[[nodiscard]] bool SupportsAcceleratedUnswizzleDevice(const Device& device) {
return device.IsKhrImageFormatListSupported() &&
device.GetMaxComputeWorkGroupInvocations() >= UNSWIZZLE_WORKGROUP_INVOCATIONS;
}
[[nodiscard]] bool SupportsAcceleratedUnswizzle(const Device& device, const ImageInfo& info) {
if (!SupportsAcceleratedUnswizzleDevice(device)) {
return false;
}
if (info.num_samples > 1) {
return false;
}
if (info.type != ImageType::e2D && info.type != ImageType::e3D &&
info.type != ImageType::Linear) {
return false;
}
const PixelFormat view_format =
UnswizzleViewFormat(VideoCore::Surface::BytesPerBlock(info.format));
if (view_format == PixelFormat::Invalid) {
return false;
}
if (!VideoCore::Surface::IsViewCompatible(info.format, view_format, false, true)) {
return false;
}
const auto host_format =
MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, view_format);
return device.IsFormatSupported(host_format.format, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT,
FormatType::Optimal);
}
[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info, [[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info,
std::optional<VkFormat> format_override = {}) { std::optional<VkFormat> format_override = {}) {
auto format_info = auto format_info =
@@ -297,18 +248,8 @@ constexpr u32 UNSWIZZLE_WORKGROUP_INVOCATIONS = 32 * 32;
return allocator.CreateImage(image_ci); return allocator.CreateImage(image_ci);
} }
[[nodiscard]] VkImageViewType StorageViewType(ImageType type) {
if (type == ImageType::e3D) {
return VK_IMAGE_VIEW_TYPE_3D;
}
if (type == ImageType::Linear) {
return VK_IMAGE_VIEW_TYPE_2D;
}
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
}
[[nodiscard]] vk::ImageView MakeStorageView(const vk::Device& device, u32 level, VkImage image, [[nodiscard]] vk::ImageView MakeStorageView(const vk::Device& device, u32 level, VkImage image,
VkFormat format, VkImageViewType view_type) { VkFormat format) {
static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{ static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
@@ -319,7 +260,7 @@ constexpr u32 UNSWIZZLE_WORKGROUP_INVOCATIONS = 32 * 32;
.pNext = &storage_image_view_usage_create_info, .pNext = &storage_image_view_usage_create_info,
.flags = 0, .flags = 0,
.image = image, .image = image,
.viewType = view_type, .viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
.format = format, .format = format,
.components{ .components{
.r = VK_COMPONENT_SWIZZLE_IDENTITY, .r = VK_COMPONENT_SWIZZLE_IDENTITY,
@@ -717,11 +658,18 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im
.subresourceRange = subresource_range, .subresourceRange = subresource_range,
}; };
cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
read_barrier); read_barrier);
cmdbuf.CopyBufferToImage(src_buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copies); cmdbuf.CopyBufferToImage(src_buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copies);
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, // TODO: Move this to another API
nullptr, nullptr, write_barrier); cmdbuf.PipelineBarrier(
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0, nullptr, nullptr, write_barrier);
} }
[[nodiscard]] VkImageBlit MakeImageBlit(const Region2D& dst_region, const Region2D& src_region, [[nodiscard]] VkImageBlit MakeImageBlit(const Region2D& dst_region, const Region2D& src_region,
@@ -1020,14 +968,10 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
bl3d_unswizzle_pass.emplace(device, scheduler, descriptor_pool, bl3d_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
staging_buffer_pool, compute_pass_descriptor_queue); staging_buffer_pool, compute_pass_descriptor_queue);
} }
if (SupportsAcceleratedUnswizzleDevice(device)) { bl2d_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
bl_unswizzle_2d_pass.emplace(device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
compute_pass_descriptor_queue); bl3db_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
bl_unswizzle_image_3d_pass.emplace(device, scheduler, descriptor_pool, compute_pass_descriptor_queue);
compute_pass_descriptor_queue);
pitch_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
compute_pass_descriptor_queue);
}
} }
void TextureCacheRuntime::Finish() { void TextureCacheRuntime::Finish() {
@@ -1954,6 +1898,12 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
} }
flags |= VideoCommon::ImageFlagBits::Converted; flags |= VideoCommon::ImageFlagBits::Converted;
flags |= VideoCommon::ImageFlagBits::CostlyLoad; flags |= VideoCommon::ImageFlagBits::CostlyLoad;
} else if (runtime->bl2d_unswizzle_pass &&
BlockLinearUnswizzle2DPass::IsSupported(runtime->device, info)) {
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
} else if (runtime->bl3db_unswizzle_pass &&
BlockLinearUnswizzle3DBufferPass::IsSupported(runtime->device, info)) {
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
} }
if (IsPixelFormatBCn(info.format) && !runtime->device.IsOptimalBcnSupported()) { if (IsPixelFormatBCn(info.format) && !runtime->device.IsOptimalBcnSupported()) {
flags |= VideoCommon::ImageFlagBits::Converted; flags |= VideoCommon::ImageFlagBits::Converted;
@@ -1962,12 +1912,16 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
if (runtime->device.HasDebuggingToolAttached()) { if (runtime->device.HasDebuggingToolAttached()) {
original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str()); original_image.SetObjectNameEXT(VideoCommon::Name(*this).c_str());
} }
if (False(flags & VideoCommon::ImageFlagBits::Converted) &&
SupportsAcceleratedUnswizzle(runtime->device, info)) {
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
}
current_image = &Image::original_image; current_image = &Image::original_image;
storage_image_views.resize(info.resources.levels); storage_image_views.resize(info.resources.levels);
if (WillUseAcceleratedAstcDecode(runtime->device, info)) {
const auto& device = runtime->device.GetLogical();
const VkFormat storage_format = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
for (s32 level = 0; level < info.resources.levels; ++level) {
storage_image_views[level] =
MakeStorageView(device, level, *original_image, storage_format);
}
}
} }
Image::Image(const VideoCommon::NullImageParams& params) : VideoCommon::ImageBase{params} {} Image::Image(const VideoCommon::NullImageParams& params) : VideoCommon::ImageBase{params} {}
@@ -2091,7 +2045,7 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset,
temp_vk_image, info.format, info.num_samples, temp_vk_image, info.format, info.num_samples,
{image_copies.data(), image_copies.size()}, false); {image_copies.data(), image_copies.size()}, false);
} }
InitializationFor(current_image) = true; initialized = true;
runtime->ReleaseMsaaScratchImage(temp_vk_image); runtime->ReleaseMsaaScratchImage(temp_vk_image);
if (is_rescaled) { if (is_rescaled) {
@@ -2112,7 +2066,7 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset,
const VkBuffer src_buffer = buffer; const VkBuffer src_buffer = buffer;
const VkImage vk_image = *original_image; const VkImage vk_image = *original_image;
const VkImageAspectFlags vk_aspect_mask = aspect_mask; const VkImageAspectFlags vk_aspect_mask = aspect_mask;
const bool was_initialized = std::exchange(InitializationFor(&Image::original_image), true); const bool was_initialized = std::exchange(initialized, true);
scheduler->Record([src_buffer, vk_image, vk_aspect_mask, was_initialized, scheduler->Record([src_buffer, vk_image, vk_aspect_mask, was_initialized,
vk_copies](vk::CommandBuffer cmdbuf) { vk_copies](vk::CommandBuffer cmdbuf) {
@@ -2377,46 +2331,16 @@ void Image::DownloadMemory(const StagingBufferRef& map, std::span<const BufferIm
DownloadMemory(buffers, offsets, copies); DownloadMemory(buffers, offsets, copies);
} }
std::vector<vk::ImageView>& Image::StorageViewsFor(vk::Image Image::*image) {
if (image == &Image::scaled_image) {
if (scaled_storage_image_views.empty()) {
scaled_storage_image_views.resize(info.resources.levels);
}
return scaled_storage_image_views;
}
return storage_image_views;
}
bool& Image::InitializationFor(vk::Image Image::*image) noexcept {
if (image == &Image::scaled_image) {
return scaled_initialized;
}
return original_initialized;
}
VkImageView Image::StorageImageView(s32 level) noexcept { VkImageView Image::StorageImageView(s32 level) noexcept {
const bool astc_decode = WillUseAcceleratedAstcDecode(runtime->device, info); auto& view = storage_image_views[level];
const bool unswizzle_upload =
!astc_decode && True(flags & ImageFlagBits::AcceleratedUpload);
vk::Image Image::*target = current_image;
if (astc_decode || unswizzle_upload) {
target = &Image::original_image;
}
auto& view = StorageViewsFor(target)[level];
if (!view) { if (!view) {
auto format_info = auto format_info =
MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format); MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, true, info.format);
if (astc_decode) { if (WillUseAcceleratedAstcDecode(runtime->device, info)) {
format_info.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32; format_info.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
} }
if (unswizzle_upload) { view = MakeStorageView(runtime->device.GetLogical(), level, *(this->*current_image),
const PixelFormat view_format = format_info.format);
UnswizzleViewFormat(VideoCore::Surface::BytesPerBlock(info.format));
format_info = MaxwellToVK::SurfaceFormat(runtime->device, FormatType::Optimal, false,
view_format);
}
view = MakeStorageView(runtime->device.GetLogical(), level, *(this->*target),
format_info.format, StorageViewType(info.type));
} }
return *view; return *view;
} }
@@ -2456,7 +2380,6 @@ bool Image::ScaleUp(bool ignore) {
} }
if (NeedsScaleHelper()) { if (NeedsScaleHelper()) {
if (!BlitScaleHelper(true)) { if (!BlitScaleHelper(true)) {
flags &= ~ImageFlagBits::Rescaled;
current_image = &Image::original_image; current_image = &Image::original_image;
return false; return false;
} }
@@ -2646,10 +2569,6 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
if (device->IsExtAstcDecodeModeSupported() && IsLdrAstcFormat(format_info.format)) { if (device->IsExtAstcDecodeModeSupported() && IsLdrAstcFormat(format_info.format)) {
view_next = &astc_decode_mode; view_next = &astc_decode_mode;
} }
auto subresource_range = MakeSubresourceRange(aspect_mask, info.range);
if (True(flags & VideoCommon::ImageViewFlagBits::Slice)) {
subresource_range.levelCount = 1;
}
const VkImageViewCreateInfo create_info{ const VkImageViewCreateInfo create_info{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = view_next, .pNext = view_next,
@@ -2658,7 +2577,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
.viewType = VkImageViewType{}, .viewType = VkImageViewType{},
.format = format_info.format, .format = format_info.format,
.components = swizzle_mapping, .components = swizzle_mapping,
.subresourceRange = subresource_range, .subresourceRange = MakeSubresourceRange(aspect_mask, info.range),
}; };
const auto create = [&](TextureType tex_type, std::optional<u32> num_layers) { const auto create = [&](TextureType tex_type, std::optional<u32> num_layers) {
VkImageViewCreateInfo ci{create_info}; VkImageViewCreateInfo ci{create_info};
@@ -3228,21 +3147,17 @@ void TextureCacheRuntime::AccelerateImageUpload(
std::span<const VideoCommon::SwizzleParameters> swizzles, std::span<const VideoCommon::SwizzleParameters> swizzles,
u32 z_start, u32 z_count) { u32 z_start, u32 z_count) {
if (IsPixelFormatASTC(image.info.format)) { if (astc_decoder_pass && WillUseAcceleratedAstcDecode(device, image.info)) {
return astc_decoder_pass->Assemble(image, map, swizzles); return astc_decoder_pass->Assemble(image, map, swizzles);
} }
if (bl_unswizzle_2d_pass && image.info.type == ImageType::e2D) { if (bl2d_unswizzle_pass && BlockLinearUnswizzle2DPass::IsSupported(device, image.info)) {
return bl_unswizzle_2d_pass->Unswizzle(image, map, swizzles); return bl2d_unswizzle_pass->Unswizzle(image, map, swizzles);
} }
if (bl_unswizzle_image_3d_pass && image.info.type == ImageType::e3D && if (bl3db_unswizzle_pass && z_count == 0 &&
!IsPixelFormatBCn(image.info.format)) { BlockLinearUnswizzle3DBufferPass::IsSupported(device, image.info)) {
return bl_unswizzle_image_3d_pass->Unswizzle(image, map, swizzles); return bl3db_unswizzle_pass->Unswizzle(image, map, swizzles);
}
if (pitch_unswizzle_pass && image.info.type == ImageType::Linear) {
return pitch_unswizzle_pass->Unswizzle(image, map, swizzles);
} }
if (!Settings::values.gpu_unswizzle_enabled.GetValue() || !bl3d_unswizzle_pass) { if (!Settings::values.gpu_unswizzle_enabled.GetValue() || !bl3d_unswizzle_pass) {
@@ -3260,6 +3175,74 @@ void TextureCacheRuntime::AccelerateImageUpload(
ASSERT(false); ASSERT(false);
} }
bool TextureCacheRuntime::IsUnifiedMemoryBindable() const noexcept {
const HostMemoryImport* const import = memory_allocator.GetHostMemoryImport();
return import != nullptr && import->IsValid() && import->IsBindable();
}
u64 TextureCacheRuntime::UnifiedMemoryBase() const noexcept {
const HostMemoryImport* const import = memory_allocator.GetHostMemoryImport();
if (import == nullptr) {
return 0;
}
return import->GetBaseOffset();
}
u64 TextureCacheRuntime::UnifiedMemorySize() const noexcept {
const HostMemoryImport* const import = memory_allocator.GetHostMemoryImport();
if (import == nullptr) {
return 0;
}
return import->GetSize();
}
u64 TextureCacheRuntime::UnifiedMemoryWindowSize() const noexcept {
const HostMemoryImport* const import = memory_allocator.GetHostMemoryImport();
if (import == nullptr) {
return 0;
}
return import->GetWindowSize();
}
bool TextureCacheRuntime::CanUploadImageDirectly(const VideoCommon::ImageInfo& info) const {
return bl2d_unswizzle_pass.has_value() &&
BlockLinearUnswizzle2DPass::IsSupported(device, info);
}
bool TextureCacheRuntime::UploadImageDirectly(
Image& image, size_t window_index, u64 window_offset,
std::span<const VideoCommon::SwizzleParameters> swizzles) {
if ((window_offset % device.GetStorageBufferAlignment()) != 0) {
return false;
}
if (image.guest_size_bytes > device.GetMaxStorageBufferRange()) {
return false;
}
const HostMemoryImport* const import = memory_allocator.GetHostMemoryImport();
if (import == nullptr || window_index >= import->GetWindowCount()) {
return false;
}
const VkBuffer window_buffer = import->GetWindowBuffer(window_index);
if (window_buffer == VK_NULL_HANDLE) {
return false;
}
bl2d_unswizzle_pass->UnswizzleFrom(image, window_buffer,
static_cast<VkDeviceSize>(window_offset), swizzles);
return true;
}
u64 TextureCacheRuntime::CurrentTick() const noexcept {
return scheduler.CurrentTick();
}
bool TextureCacheRuntime::IsDirectUploadRetired(u64 tick) {
if (scheduler.IsFree(tick)) {
return true;
}
scheduler.RefreshTick();
return scheduler.IsFree(tick);
}
void TextureCacheRuntime::TransitionImageLayout(Image& image) { void TextureCacheRuntime::TransitionImageLayout(Image& image) {
if (!image.ExchangeInitialization()) { if (!image.ExchangeInitialization()) {
VkImageMemoryBarrier barrier{ VkImageMemoryBarrier barrier{
@@ -101,6 +101,23 @@ public:
std::span<const VideoCommon::SwizzleParameters>, std::span<const VideoCommon::SwizzleParameters>,
u32 z_start, u32 z_count); u32 z_start, u32 z_count);
[[nodiscard]] bool IsUnifiedMemoryBindable() const noexcept;
[[nodiscard]] u64 UnifiedMemoryBase() const noexcept;
[[nodiscard]] u64 UnifiedMemorySize() const noexcept;
[[nodiscard]] u64 UnifiedMemoryWindowSize() const noexcept;
[[nodiscard]] bool CanUploadImageDirectly(const VideoCommon::ImageInfo& info) const;
bool UploadImageDirectly(Image& image, size_t window_index, u64 window_offset,
std::span<const VideoCommon::SwizzleParameters> swizzles);
[[nodiscard]] u64 CurrentTick() const noexcept;
[[nodiscard]] bool IsDirectUploadRetired(u64 tick);
void InsertUploadMemoryBarrier() {} void InsertUploadMemoryBarrier() {}
void TransitionImageLayout(Image& image); void TransitionImageLayout(Image& image);
@@ -159,9 +176,8 @@ public:
std::optional<ASTCDecoderPass> astc_decoder_pass; std::optional<ASTCDecoderPass> astc_decoder_pass;
std::optional<BlockLinearUnswizzle3DPass> bl3d_unswizzle_pass; std::optional<BlockLinearUnswizzle3DPass> bl3d_unswizzle_pass;
std::optional<BlockLinearUnswizzle2DPass> bl_unswizzle_2d_pass; std::optional<BlockLinearUnswizzle2DPass> bl2d_unswizzle_pass;
std::optional<BlockLinearUnswizzleImage3DPass> bl_unswizzle_image_3d_pass; std::optional<BlockLinearUnswizzle3DBufferPass> bl3db_unswizzle_pass;
std::optional<PitchUnswizzlePass> pitch_unswizzle_pass;
const Settings::ResolutionScalingInfo& resolution; const Settings::ResolutionScalingInfo& resolution;
std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats; std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
@@ -353,7 +369,7 @@ public:
/// Returns true when the image is already initialized and mark it as initialized /// Returns true when the image is already initialized and mark it as initialized
[[nodiscard]] bool ExchangeInitialization() noexcept { [[nodiscard]] bool ExchangeInitialization() noexcept {
return std::exchange(InitializationFor(current_image), true); return std::exchange(initialized, true);
} }
VkImageView StorageImageView(s32 level) noexcept; VkImageView StorageImageView(s32 level) noexcept;
@@ -373,10 +389,6 @@ private:
bool NeedsScaleHelper() const; bool NeedsScaleHelper() const;
std::vector<vk::ImageView>& StorageViewsFor(vk::Image Image::*image);
bool& InitializationFor(vk::Image Image::*image) noexcept;
Scheduler* scheduler{}; Scheduler* scheduler{};
TextureCacheRuntime* runtime{}; TextureCacheRuntime* runtime{};
@@ -394,10 +406,8 @@ private:
vk::Image Image::*current_image{}; vk::Image Image::*current_image{};
std::vector<vk::ImageView> storage_image_views; std::vector<vk::ImageView> storage_image_views;
std::vector<vk::ImageView> scaled_storage_image_views;
VkImageAspectFlags aspect_mask = 0; VkImageAspectFlags aspect_mask = 0;
bool original_initialized = false; bool initialized = false;
bool scaled_initialized = false;
std::optional<Framebuffer> scale_framebuffer; std::optional<Framebuffer> scale_framebuffer;
std::optional<Framebuffer> normal_framebuffer; std::optional<Framebuffer> normal_framebuffer;
@@ -595,6 +605,7 @@ struct TextureCacheParams {
static constexpr bool HAS_DEVICE_MEMORY_INFO = true; static constexpr bool HAS_DEVICE_MEMORY_INFO = true;
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true; static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
static constexpr bool HAS_MSAA_DOWNLOADS = true; static constexpr bool HAS_MSAA_DOWNLOADS = true;
static constexpr bool USE_UNIFIED_MEMORY = true;
using Runtime = Vulkan::TextureCacheRuntime; using Runtime = Vulkan::TextureCacheRuntime;
using Image = Vulkan::Image; using Image = Vulkan::Image;
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -26,8 +23,8 @@ struct BlockLinearSwizzle2DParams {
}; };
struct BlockLinearSwizzle3DParams { struct BlockLinearSwizzle3DParams {
alignas(16) std::array<u32, 3> origin; std::array<u32, 3> origin;
alignas(16) std::array<s32, 3> destination; std::array<s32, 3> destination;
u32 bytes_per_block_log2; u32 bytes_per_block_log2;
u32 slice_size; u32 slice_size;
u32 block_size; u32 block_size;
@@ -95,6 +95,8 @@ struct ImageBase {
u32 scale_rating = 0; u32 scale_rating = 0;
u64 scale_tick = 0; u64 scale_tick = 0;
bool has_scaled = false; bool has_scaled = false;
u64 direct_upload_tick = 0;
bool direct_upload_blocked = false;
size_t channel = 0; size_t channel = 0;
+102 -64
View File
@@ -20,7 +20,6 @@
#include "video_core/engines/kepler_compute.h" #include "video_core/engines/kepler_compute.h"
#include "video_core/guest_memory.h" #include "video_core/guest_memory.h"
#include "video_core/host1x/gpu_device_memory_manager.h" #include "video_core/host1x/gpu_device_memory_manager.h"
#include "video_core/texture_cache/accelerated_swizzle.h"
#include "video_core/texture_cache/image_view_base.h" #include "video_core/texture_cache/image_view_base.h"
#include "video_core/texture_cache/samples_helper.h" #include "video_core/texture_cache/samples_helper.h"
#include "video_core/texture_cache/texture_cache_base.h" #include "video_core/texture_cache/texture_cache_base.h"
@@ -120,13 +119,16 @@ void TextureCache<P>::RunGarbageCollector() {
bool aggressive_mode = false; bool aggressive_mode = false;
u64 ticks_to_destroy = 0; u64 ticks_to_destroy = 0;
size_t num_iterations = 0; size_t num_iterations = 0;
size_t num_downloads = 0;
const auto Configure = [&](bool allow_aggressive) { const auto Configure = [&](bool allow_aggressive) {
high_priority_mode = total_used_memory >= expected_memory; high_priority_mode = total_used_memory >= expected_memory;
aggressive_mode = allow_aggressive && total_used_memory >= critical_memory; aggressive_mode = allow_aggressive && total_used_memory >= critical_memory;
ticks_to_destroy = aggressive_mode ? 10ULL : high_priority_mode ? 25ULL : 50ULL; ticks_to_destroy = aggressive_mode ? 10ULL : high_priority_mode ? 25ULL : 50ULL;
num_iterations = aggressive_mode ? 40 : (high_priority_mode ? 20 : 10); num_iterations = aggressive_mode ? 40 : (high_priority_mode ? 20 : 10);
num_downloads = MAX_GC_DOWNLOADS_PER_PASS;
}; };
const auto Cleanup = [this, &num_iterations, &high_priority_mode, &aggressive_mode](ImageId image_id) { const auto Cleanup = [this, &num_iterations, &num_downloads, &high_priority_mode,
&aggressive_mode](ImageId image_id) {
if (num_iterations == 0) { if (num_iterations == 0) {
return true; return true;
} }
@@ -140,6 +142,10 @@ void TextureCache<P>::RunGarbageCollector() {
return false; return false;
} }
if (must_download) { if (must_download) {
if (num_downloads == 0) {
return false;
}
--num_downloads;
auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes); auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes);
const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info)); const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info));
image.DownloadMemory(map, copies); image.DownloadMemory(map, copies);
@@ -280,11 +286,11 @@ void TextureCache<P>::CheckFeedbackLoop(std::span<const ImageViewInOut> views) {
const ImageId view_image_id = slot_image_views[view.id].image_id; const ImageId view_image_id = slot_image_views[view.id].image_id;
{ {
bool is_feedback = false; bool is_continue = false;
for (size_t i = 0; i < 8; ++i) for (size_t i = 0; i < 8; ++i)
is_feedback |= (rt_active_mask & (1u << i)) && view_image_id == rt_image_id[i]; is_continue |= (rt_active_mask & (1u << i)) && view_image_id == rt_image_id[i];
if (is_feedback) if (is_continue)
return true; continue;
} }
if (depth_active && view_image_id == rt_depth_image_id) { if (depth_active && view_image_id == rt_depth_image_id) {
return true; return true;
@@ -586,6 +592,15 @@ FramebufferId TextureCache<P>::GetFramebufferId(const RenderTargets& key) {
template <class P> template <class P>
void TextureCache<P>::WriteMemory(DAddr cpu_addr, size_t size) { void TextureCache<P>::WriteMemory(DAddr cpu_addr, size_t size) {
ForEachImageInRegion(cpu_addr, size, [this](ImageId image_id, Image& image) { ForEachImageInRegion(cpu_addr, size, [this](ImageId image_id, Image& image) {
if constexpr (USE_UNIFIED_MEMORY) {
if (image.direct_upload_tick != 0) {
const u64 upload_tick = image.direct_upload_tick;
image.direct_upload_tick = 0;
if (!runtime.IsDirectUploadRetired(upload_tick)) {
image.direct_upload_blocked = true;
}
}
}
if (True(image.flags & ImageFlagBits::CpuModified)) { if (True(image.flags & ImageFlagBits::CpuModified)) {
return; return;
} }
@@ -627,25 +642,14 @@ void TextureCache<P>::DownloadMemory(DAddr cpu_addr, size_t size) {
std::ranges::sort(images, [this](ImageId lhs, ImageId rhs) { std::ranges::sort(images, [this](ImageId lhs, ImageId rhs) {
return slot_images[lhs].modification_tick < slot_images[rhs].modification_tick; return slot_images[lhs].modification_tick < slot_images[rhs].modification_tick;
}); });
size_t total_size_bytes = 0;
for (const ImageId image_id : images) {
total_size_bytes += slot_images[image_id].unswizzled_size_bytes;
}
auto download_map = runtime.DownloadStagingBuffer(total_size_bytes);
for (const ImageId image_id : images) { for (const ImageId image_id : images) {
Image& image = slot_images[image_id]; Image& image = slot_images[image_id];
auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes);
const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info)); const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info));
image.DownloadMemory(download_map, copies); image.DownloadMemory(map, copies);
download_map.offset += image.unswizzled_size_bytes; runtime.Finish();
} SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span,
runtime.Finish();
std::span<u8> download_span = download_map.mapped_span;
for (const ImageId image_id : images) {
const ImageBase& image = slot_images[image_id];
const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info));
SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, download_span,
swizzle_data_buffer); swizzle_data_buffer);
download_span = download_span.subspan(image.unswizzled_size_bytes);
} }
} }
@@ -1134,12 +1138,6 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
TrackImage(image, image_id); TrackImage(image, image_id);
if (image.info.rescaleable &&
IsRegionGpuModified(image.cpu_addr, image.guest_size_bytes)) {
runtime.TransitionImageLayout(image);
return;
}
if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) { if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) {
LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented"); LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
runtime.TransitionImageLayout(image); runtime.TransitionImageLayout(image);
@@ -1163,11 +1161,59 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
QueueAsyncUnswizzle(image, image_id); QueueAsyncUnswizzle(image, image_id);
return; return;
} }
if (True(image.flags & ImageFlagBits::AcceleratedUpload) &&
TryUploadFromUnifiedMemory(image)) {
runtime.InsertUploadMemoryBarrier();
return;
}
auto staging = runtime.UploadStagingBuffer(MapSizeBytes(image)); auto staging = runtime.UploadStagingBuffer(MapSizeBytes(image));
UploadImageContents(image, staging); UploadImageContents(image, staging);
runtime.InsertUploadMemoryBarrier(); runtime.InsertUploadMemoryBarrier();
} }
template <class P>
bool TextureCache<P>::TryUploadFromUnifiedMemory([[maybe_unused]] Image& image) {
if constexpr (USE_UNIFIED_MEMORY) {
if (image.direct_upload_blocked || image.guest_size_bytes == 0) {
return false;
}
if (!runtime.IsUnifiedMemoryBindable() || !runtime.CanUploadImageDirectly(image.info)) {
return false;
}
const u64 window_size = runtime.UnifiedMemoryWindowSize();
if (window_size == 0) {
return false;
}
const u8* const first = gpu_memory->GetSpan(image.gpu_addr, image.guest_size_bytes);
if (first == nullptr) {
return false;
}
const u64 phys_offset = static_cast<u64>(first - device_memory.GetPhysicalBase());
const u64 unified_base = runtime.UnifiedMemoryBase();
if (phys_offset < unified_base) {
return false;
}
const u64 relative = phys_offset - unified_base;
const u64 unified_size = runtime.UnifiedMemorySize();
if (relative >= unified_size || unified_size - relative < image.guest_size_bytes) {
return false;
}
const u64 local_offset = relative % window_size;
if (window_size - local_offset < image.guest_size_bytes) {
return false;
}
const auto swizzles = FullUploadSwizzles(image.info);
if (!runtime.UploadImageDirectly(image, static_cast<size_t>(relative / window_size),
local_offset, FixSmallVectorADL(swizzles))) {
return false;
}
image.direct_upload_tick = runtime.CurrentTick();
return true;
} else {
return false;
}
}
template <class P> template <class P>
template <typename StagingBuffer> template <typename StagingBuffer>
void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging) { void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging) {
@@ -1175,7 +1221,8 @@ void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging)
const GPUVAddr gpu_addr = image.gpu_addr; const GPUVAddr gpu_addr = image.gpu_addr;
if (True(image.flags & ImageFlagBits::AcceleratedUpload)) { if (True(image.flags & ImageFlagBits::AcceleratedUpload)) {
gpu_memory->ReadBlockUnsafe(gpu_addr, mapped_span.data(), image.guest_size_bytes); gpu_memory->ReadBlock(gpu_addr, mapped_span.data(), mapped_span.size_bytes(),
VideoCommon::CacheType::NoTextureCache);
const auto uploads = FullUploadSwizzles(image.info); const auto uploads = FullUploadSwizzles(image.info);
runtime.AccelerateImageUpload(image, staging, FixSmallVectorADL(uploads), 0, 0); runtime.AccelerateImageUpload(image, staging, FixSmallVectorADL(uploads), 0, 0);
return; return;
@@ -1282,9 +1329,6 @@ ImageId TextureCache<P>::FindImage(const ImageInfo& info, GPUVAddr gpu_addr,
template <class P> template <class P>
bool TextureCache<P>::ImageCanRescale(ImageBase& image) { bool TextureCache<P>::ImageCanRescale(ImageBase& image) {
if (!Settings::values.resolution_info.active) {
return false;
}
if (!image.info.rescaleable) { if (!image.info.rescaleable) {
return false; return false;
} }
@@ -1372,12 +1416,12 @@ void TextureCache<P>::QueueAsyncDecode(Image& image, ImageId image_id) {
decode->image_id = image_id; decode->image_id = image_id;
async_decodes.push_back(std::move(decode)); async_decodes.push_back(std::move(decode));
Common::ScratchBuffer<u8> local_unswizzle_data_buffer(image.unswizzled_size_bytes); std::vector<u8> local_unswizzle_data_buffer(image.unswizzled_size_bytes, 0);
Tegra::Memory::GpuGuestMemory<u8, Tegra::Memory::GuestMemoryFlags::UnsafeRead> swizzle_data(*gpu_memory, image.gpu_addr, image.guest_size_bytes, &swizzle_data_buffer); Tegra::Memory::GpuGuestMemory<u8, Tegra::Memory::GuestMemoryFlags::UnsafeRead> swizzle_data(*gpu_memory, image.gpu_addr, image.guest_size_bytes, &swizzle_data_buffer);
auto copies = UnswizzleImage(*gpu_memory, image.gpu_addr, image.info, swizzle_data, local_unswizzle_data_buffer); auto copies = UnswizzleImage(*gpu_memory, image.gpu_addr, image.info, swizzle_data, local_unswizzle_data_buffer);
const size_t out_size = MapSizeBytes(image); const size_t out_size = MapSizeBytes(image);
auto func = [out_size, copies = std::move(copies), info = image.info, auto func = [out_size, copies, info = image.info,
input = std::move(local_unswizzle_data_buffer), input = std::move(local_unswizzle_data_buffer),
async_decode = decode_ptr]() mutable { async_decode = decode_ptr]() mutable {
async_decode->decoded_data.resize_destructive(out_size); async_decode->decoded_data.resize_destructive(out_size);
@@ -1446,16 +1490,18 @@ void TextureCache<P>::TickAsyncUnswizzle() {
Image& image = slot_images[task.image_id]; Image& image = slot_images[task.image_id];
if (!task.initialized) { if (!task.initialized) {
task.total_size = image.guest_size_bytes; task.total_size = MapSizeBytes(image);
task.staging_buffer = runtime.UploadStagingBuffer(task.total_size, true); task.staging_buffer = runtime.UploadStagingBuffer(task.total_size, true);
const auto layout = FullUploadSwizzles(task.info); const auto& info = image.info;
const auto params = const u32 bytes_per_block = BytesPerBlock(info.format);
VideoCommon::Accelerated::MakeBlockLinearSwizzle3DParams(layout.front(), task.info); const u32 width_blocks = Common::DivCeil(info.size.width, 4u);
task.bytes_per_slice = params.slice_size; const u32 height_blocks = Common::DivCeil(info.size.height, 4u);
task.chunked = task.info.block.depth == 0;
const u32 stride = width_blocks * bytes_per_block;
const u32 aligned_height = height_blocks;
task.bytes_per_slice = static_cast<size_t>(stride) * aligned_height;
task.last_submitted_offset = 0; task.last_submitted_offset = 0;
task.slices_submitted = 0;
task.initialized = true; task.initialized = true;
} }
@@ -1470,39 +1516,31 @@ void TextureCache<P>::TickAsyncUnswizzle() {
if (copy_amount == 0) copy_amount = task.bytes_per_slice; if (copy_amount == 0) copy_amount = task.bytes_per_slice;
} }
gpu_memory->ReadBlockUnsafe(image.gpu_addr + task.current_offset, gpu_memory->ReadBlock(image.gpu_addr + task.current_offset,
task.staging_buffer.mapped_span.data() + task.current_offset, task.staging_buffer.mapped_span.data() + task.current_offset,
copy_amount); copy_amount);
task.current_offset += copy_amount; task.current_offset += copy_amount;
} }
const bool is_final_batch = task.current_offset >= task.total_size; const bool is_final_batch = task.current_offset >= task.total_size;
const size_t bytes_ready = task.current_offset - task.last_submitted_offset;
const u32 complete_slices = static_cast<u32>(bytes_ready / task.bytes_per_slice);
if (task.chunked) { if (complete_slices >= swizzle_slices_per_batch || (is_final_batch && complete_slices > 0)) {
const size_t bytes_ready = task.current_offset - task.last_submitted_offset; const u32 z_start = static_cast<u32>(task.last_submitted_offset / task.bytes_per_slice);
const u32 complete_slices = static_cast<u32>(bytes_ready / task.bytes_per_slice); const u32 slices_to_process = (std::min)(complete_slices, swizzle_slices_per_batch);
const u32 z_count = (std::min)(slices_to_process, image.info.size.depth - z_start);
if (complete_slices >= swizzle_slices_per_batch || (is_final_batch && complete_slices > 0)) { if (z_count > 0) {
const u32 z_start = task.slices_submitted; const auto uploads = FullUploadSwizzles(task.info);
const u32 slices_to_process = (std::min)(complete_slices, swizzle_slices_per_batch); runtime.AccelerateImageUpload(image, task.staging_buffer, FixSmallVectorADL(uploads), z_start, z_count);
const u32 z_count = (std::min)(slices_to_process, image.info.size.depth - z_start); task.last_submitted_offset += (static_cast<size_t>(z_count) * task.bytes_per_slice);
if (z_count > 0) {
const auto uploads = FullUploadSwizzles(task.info);
runtime.AccelerateImageUpload(image, task.staging_buffer,
FixSmallVectorADL(uploads), z_start, z_count);
task.last_submitted_offset += static_cast<size_t>(z_count) * task.bytes_per_slice;
task.slices_submitted += z_count;
}
} }
} else if (is_final_batch && task.slices_submitted == 0) {
const auto uploads = FullUploadSwizzles(task.info);
runtime.AccelerateImageUpload(image, task.staging_buffer, FixSmallVectorADL(uploads), 0,
image.info.size.depth);
task.slices_submitted = image.info.size.depth;
} }
const bool all_slices_submitted = task.slices_submitted >= image.info.size.depth; // Check if complete
const u32 slices_submitted = static_cast<u32>(task.last_submitted_offset / task.bytes_per_slice);
const bool all_slices_submitted = slices_submitted >= image.info.size.depth;
if (is_final_batch && all_slices_submitted) { if (is_final_batch && all_slices_submitted) {
runtime.FreeDeferredStagingBuffer(task.staging_buffer); runtime.FreeDeferredStagingBuffer(task.staging_buffer);
@@ -108,6 +108,7 @@ class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelI
static constexpr bool HAS_DEVICE_MEMORY_INFO = P::HAS_DEVICE_MEMORY_INFO; static constexpr bool HAS_DEVICE_MEMORY_INFO = P::HAS_DEVICE_MEMORY_INFO;
/// True when the API can do asynchronous texture downloads. /// True when the API can do asynchronous texture downloads.
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = P::IMPLEMENTS_ASYNC_DOWNLOADS; static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = P::IMPLEMENTS_ASYNC_DOWNLOADS;
static constexpr bool USE_UNIFIED_MEMORY = P::USE_UNIFIED_MEMORY;
static constexpr size_t UNSET_CHANNEL{(std::numeric_limits<size_t>::max)()}; static constexpr size_t UNSET_CHANNEL{(std::numeric_limits<size_t>::max)()};
@@ -120,6 +121,7 @@ class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelI
static constexpr s64 DEFAULT_EXPECTED_MEMORY = 1_GiB + 125_MiB; static constexpr s64 DEFAULT_EXPECTED_MEMORY = 1_GiB + 125_MiB;
static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB + 625_MiB; static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB + 625_MiB;
static constexpr size_t GC_EMERGENCY_COUNTS = 2; static constexpr size_t GC_EMERGENCY_COUNTS = 2;
static constexpr size_t MAX_GC_DOWNLOADS_PER_PASS = 4;
using Runtime = typename P::Runtime; using Runtime = typename P::Runtime;
using Image = typename P::Image; using Image = typename P::Image;
@@ -138,8 +140,6 @@ class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelI
AsyncBuffer staging_buffer; AsyncBuffer staging_buffer;
size_t last_submitted_offset = 0; size_t last_submitted_offset = 0;
size_t bytes_per_slice; size_t bytes_per_slice;
u32 slices_submitted = 0;
bool chunked = false;
bool initialized = false; bool initialized = false;
}; };
@@ -309,6 +309,8 @@ private:
void RefreshContents(Image& image, ImageId image_id); void RefreshContents(Image& image, ImageId image_id);
bool TryUploadFromUnifiedMemory(Image& image);
/// Upload data from guest to an image /// Upload data from guest to an image
template <typename StagingBuffer> template <typename StagingBuffer>
void UploadImageContents(Image& image, StagingBuffer& staging_buffer); void UploadImageContents(Image& image, StagingBuffer& staging_buffer);
+46 -5
View File
@@ -16,6 +16,7 @@
#include <fmt/format.h> #include <fmt/format.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/host_memory.h"
#include "common/literals.h" #include "common/literals.h"
#include <ranges> #include <ranges>
#include "common/settings.h" #include "common/settings.h"
@@ -944,6 +945,7 @@ bool Device::GetSuitability(bool requires_swapchain) {
FOR_EACH_VK_FEATURE_EXT(FEATURE_EXTENSION); FOR_EACH_VK_FEATURE_EXT(FEATURE_EXTENSION);
FOR_EACH_VK_EXTENSION(EXTENSION); FOR_EACH_VK_EXTENSION(EXTENSION);
FOR_EACH_VK_PLATFORM_EXTENSION(EXTENSION);
extensions.depth_stencil_resolve = extensions.depth_stencil_resolve =
extensions.depth_stencil_resolve && extensions.depth_stencil_resolve &&
@@ -962,6 +964,13 @@ bool Device::GetSuitability(bool requires_swapchain) {
extensions.robustness_2 = false; extensions.robustness_2 = false;
} }
#ifdef __ANDROID__
if (extensions.external_memory_ahb && !extensions.queue_family_foreign) {
loaded_extensions.erase(VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME);
extensions.external_memory_ahb = false;
}
#endif
#undef FEATURE_EXTENSION #undef FEATURE_EXTENSION
#undef EXTENSION #undef EXTENSION
@@ -1118,11 +1127,6 @@ bool Device::GetSuitability(bool requires_swapchain) {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_PROPERTIES_EXT; VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_PROPERTIES_EXT;
SetNext(next, properties.transform_feedback); SetNext(next, properties.transform_feedback);
} }
if (extensions.vertex_attribute_divisor) {
properties.vertex_attribute_divisor.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_PROPERTIES_EXT;
SetNext(next, properties.vertex_attribute_divisor);
}
if (extensions.maintenance5) { if (extensions.maintenance5) {
properties.maintenance5.sType = properties.maintenance5.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_PROPERTIES_KHR; VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_PROPERTIES_KHR;
@@ -1133,6 +1137,21 @@ bool Device::GetSuitability(bool requires_swapchain) {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_PROPERTIES_EXT; VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_PROPERTIES_EXT;
SetNext(next, properties.custom_border_color); SetNext(next, properties.custom_border_color);
} }
if (extensions.external_memory_host) {
properties.external_memory_host.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT;
SetNext(next, properties.external_memory_host);
}
if (extensions.maintenance3 || instance_version >= VK_API_VERSION_1_1) {
properties.maintenance3.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES;
SetNext(next, properties.maintenance3);
}
if (extensions.maintenance4 || features.maintenance4.maintenance4) {
properties.maintenance4.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_4_PROPERTIES;
SetNext(next, properties.maintenance4);
}
// Perform the property fetch. // Perform the property fetch.
physical.GetProperties2(properties2); physical.GetProperties2(properties2);
@@ -1520,12 +1539,27 @@ void Device::CollectPhysicalMemoryInfo() {
device_access_memory = 0; device_access_memory = 0;
u64 device_initial_usage = 0; u64 device_initial_usage = 0;
u64 local_memory = 0; u64 local_memory = 0;
const auto heap_has_usable_type = [&mem_properties](size_t heap) {
for (u32 index = 0; index < mem_properties.memoryTypeCount; ++index) {
if (mem_properties.memoryTypes[index].heapIndex != heap) {
continue;
}
if ((mem_properties.memoryTypes[index].propertyFlags &
VK_MEMORY_PROPERTY_PROTECTED_BIT) == 0) {
return true;
}
}
return false;
};
for (size_t element = 0; element < num_properties; ++element) { for (size_t element = 0; element < num_properties; ++element) {
const bool is_heap_local = const bool is_heap_local =
(mem_properties.memoryHeaps[element].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0; (mem_properties.memoryHeaps[element].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0;
if (!is_integrated && !is_heap_local) { if (!is_integrated && !is_heap_local) {
continue; continue;
} }
if (!heap_has_usable_type(element)) {
continue;
}
valid_heap_memory.push_back(element); valid_heap_memory.push_back(element);
if (is_heap_local) { if (is_heap_local) {
local_memory += mem_properties.memoryHeaps[element].size; local_memory += mem_properties.memoryHeaps[element].size;
@@ -1537,6 +1571,12 @@ void Device::CollectPhysicalMemoryInfo() {
} }
device_access_memory += mem_properties.memoryHeaps[element].size; device_access_memory += mem_properties.memoryHeaps[element].size;
} }
const u64 committed_backing = Common::GetCommittedBackingSize();
if (committed_backing != 0) {
LOG_INFO(Render_Vulkan, "Discounting {} MiB of guest memory committed by the host",
committed_backing >> 20);
local_memory -= (std::min)(local_memory, committed_backing);
}
if (is_integrated) { if (is_integrated) {
const s64 available_memory = static_cast<s64>(device_access_memory - device_initial_usage); const s64 available_memory = static_cast<s64>(device_access_memory - device_initial_usage);
const u64 memory_size = Settings::values.vram_usage_mode.GetValue() == Settings::VramUsageMode::Aggressive ? 6_GiB : 4_GiB; const u64 memory_size = Settings::values.vram_usage_mode.GetValue() == Settings::VramUsageMode::Aggressive ? 6_GiB : 4_GiB;
@@ -1551,6 +1591,7 @@ void Device::CollectPhysicalMemoryInfo() {
device_access_memory = std::min<u64>(device_access_memory, normal_memory + scaler_memory); device_access_memory = std::min<u64>(device_access_memory, normal_memory + scaler_memory);
} }
} }
device_access_memory -= (std::min)(device_access_memory, committed_backing);
} }
void Device::CollectToolingInfo() { void Device::CollectToolingInfo() {
+38 -29
View File
@@ -70,7 +70,6 @@ VK_DEFINE_HANDLE(VmaAllocator)
FEATURE(EXT, ProvokingVertex, PROVOKING_VERTEX, provoking_vertex) \ FEATURE(EXT, ProvokingVertex, PROVOKING_VERTEX, provoking_vertex) \
FEATURE(EXT, Robustness2, ROBUSTNESS_2, robustness2) \ FEATURE(EXT, Robustness2, ROBUSTNESS_2, robustness2) \
FEATURE(EXT, TransformFeedback, TRANSFORM_FEEDBACK, transform_feedback) \ FEATURE(EXT, TransformFeedback, TRANSFORM_FEEDBACK, transform_feedback) \
FEATURE(EXT, VertexAttributeDivisor, VERTEX_ATTRIBUTE_DIVISOR, vertex_attribute_divisor) \
FEATURE(EXT, VertexInputDynamicState, VERTEX_INPUT_DYNAMIC_STATE, vertex_input_dynamic_state) \ FEATURE(EXT, VertexInputDynamicState, VERTEX_INPUT_DYNAMIC_STATE, vertex_input_dynamic_state) \
FEATURE(KHR, Maintenance5, MAINTENANCE_5, maintenance5) \ FEATURE(KHR, Maintenance5, MAINTENANCE_5, maintenance5) \
FEATURE(KHR, Maintenance6, MAINTENANCE_6, maintenance6) \ FEATURE(KHR, Maintenance6, MAINTENANCE_6, maintenance6) \
@@ -86,12 +85,14 @@ VK_DEFINE_HANDLE(VmaAllocator)
EXTENSION(EXT, CONDITIONAL_RENDERING, conditional_rendering) \ EXTENSION(EXT, CONDITIONAL_RENDERING, conditional_rendering) \
EXTENSION(EXT, CONSERVATIVE_RASTERIZATION, conservative_rasterization) \ EXTENSION(EXT, CONSERVATIVE_RASTERIZATION, conservative_rasterization) \
EXTENSION(EXT, DEPTH_RANGE_UNRESTRICTED, depth_range_unrestricted) \ EXTENSION(EXT, DEPTH_RANGE_UNRESTRICTED, depth_range_unrestricted) \
EXTENSION(EXT, EXTERNAL_MEMORY_HOST, external_memory_host) \
EXTENSION(EXT, MEMORY_BUDGET, memory_budget) \ EXTENSION(EXT, MEMORY_BUDGET, memory_budget) \
EXTENSION(EXT, ROBUSTNESS_2, robustness_2) \ EXTENSION(EXT, ROBUSTNESS_2, robustness_2) \
EXTENSION(EXT, SAMPLER_FILTER_MINMAX, sampler_filter_minmax) \ EXTENSION(EXT, SAMPLER_FILTER_MINMAX, sampler_filter_minmax) \
EXTENSION(EXT, SHADER_STENCIL_EXPORT, shader_stencil_export) \ EXTENSION(EXT, SHADER_STENCIL_EXPORT, shader_stencil_export) \
EXTENSION(EXT, SHADER_VIEWPORT_INDEX_LAYER, shader_viewport_index_layer) \ EXTENSION(EXT, SHADER_VIEWPORT_INDEX_LAYER, shader_viewport_index_layer) \
EXTENSION(EXT, TOOLING_INFO, tooling_info) \ EXTENSION(EXT, TOOLING_INFO, tooling_info) \
EXTENSION(EXT, VERTEX_ATTRIBUTE_DIVISOR, vertex_attribute_divisor) \
EXTENSION(KHR, CREATE_RENDERPASS_2, create_renderpass2) \ EXTENSION(KHR, CREATE_RENDERPASS_2, create_renderpass2) \
EXTENSION(KHR, DEPTH_STENCIL_RESOLVE, depth_stencil_resolve) \ EXTENSION(KHR, DEPTH_STENCIL_RESOLVE, depth_stencil_resolve) \
EXTENSION(KHR, DRAW_INDIRECT_COUNT, draw_indirect_count) \ EXTENSION(KHR, DRAW_INDIRECT_COUNT, draw_indirect_count) \
@@ -116,6 +117,14 @@ VK_DEFINE_HANDLE(VmaAllocator)
EXTENSION(IMG, FILTER_CUBIC, filter_cubic_img) \ EXTENSION(IMG, FILTER_CUBIC, filter_cubic_img) \
EXTENSION(QCOM, FILTER_CUBIC_WEIGHTS, filter_cubic_weights) EXTENSION(QCOM, FILTER_CUBIC_WEIGHTS, filter_cubic_weights)
#ifdef __ANDROID__
#define FOR_EACH_VK_PLATFORM_EXTENSION(EXTENSION) \
EXTENSION(EXT, QUEUE_FAMILY_FOREIGN, queue_family_foreign) \
EXTENSION(ANDROID, EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER, external_memory_ahb)
#else
#define FOR_EACH_VK_PLATFORM_EXTENSION(EXTENSION)
#endif
// Define extensions which must be supported. // Define extensions which must be supported.
#define FOR_EACH_VK_MANDATORY_EXTENSION(EXTENSION_NAME) \ #define FOR_EACH_VK_MANDATORY_EXTENSION(EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME) \ EXTENSION_NAME(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME) \
@@ -354,7 +363,6 @@ public:
#define FN_MAX_LIMIT_LIST \ #define FN_MAX_LIMIT_LIST \
FN_MAX_LIMIT_ELEM(ComputeSharedMemorySize) \ FN_MAX_LIMIT_ELEM(ComputeSharedMemorySize) \
FN_MAX_LIMIT_ELEM(ComputeWorkGroupInvocations) \
FN_MAX_LIMIT_ELEM(PerStageDescriptorSampledImages) \ FN_MAX_LIMIT_ELEM(PerStageDescriptorSampledImages) \
FN_MAX_LIMIT_ELEM(PerStageResources) \ FN_MAX_LIMIT_ELEM(PerStageResources) \
FN_MAX_LIMIT_ELEM(DescriptorSetSamplers) \ FN_MAX_LIMIT_ELEM(DescriptorSetSamplers) \
@@ -680,32 +688,6 @@ FN_MAX_LIMIT_LIST
return features.host_query_reset.hostQueryReset != VK_FALSE; return features.host_query_reset.hostQueryReset != VK_FALSE;
} }
u32 GetMaxVertexAttribDivisor() const {
const u32 reported = properties.vertex_attribute_divisor.maxVertexAttribDivisor;
if (reported == 0) {
return 1;
}
return reported;
}
bool IsVertexAttributeInstanceRateZeroDivisorSupported() const {
return features.vertex_attribute_divisor.vertexAttributeInstanceRateZeroDivisor == VK_TRUE;
}
u32 GetVertexAttribDivisor(u32 frequency) const {
const u32 max_divisor = GetMaxVertexAttribDivisor();
if (frequency == 0) {
if (IsVertexAttributeInstanceRateZeroDivisorSupported()) {
return 0;
}
return max_divisor;
}
if (frequency > max_divisor) {
return max_divisor;
}
return frequency;
}
/// Returns true if the device supports VK_EXT_transform_feedback. /// Returns true if the device supports VK_EXT_transform_feedback.
bool IsExtTransformFeedbackSupported() const { bool IsExtTransformFeedbackSupported() const {
return extensions.transform_feedback; return extensions.transform_feedback;
@@ -904,6 +886,30 @@ FN_MAX_LIMIT_LIST
return extensions.conditional_rendering; return extensions.conditional_rendering;
} }
bool IsExtExternalMemoryHostSupported() const {
return extensions.external_memory_host;
}
bool IsExtExternalMemoryAhbSupported() const {
#ifdef __ANDROID__
return extensions.external_memory_ahb && extensions.queue_family_foreign;
#else
return false;
#endif
}
u64 GetMinImportedHostPointerAlignment() const {
return properties.external_memory_host.minImportedHostPointerAlignment;
}
u64 GetMaxBufferSize() const {
return properties.maintenance4.maxBufferSize;
}
u64 GetMaxMemoryAllocationSize() const {
return properties.maintenance3.maxMemoryAllocationSize;
}
bool IsExtAstcDecodeModeSupported() const { bool IsExtAstcDecodeModeSupported() const {
return extensions.astc_decode_mode; return extensions.astc_decode_mode;
} }
@@ -1170,6 +1176,7 @@ private:
FOR_EACH_VK_FEATURE_1_4(FEATURE); FOR_EACH_VK_FEATURE_1_4(FEATURE);
FOR_EACH_VK_FEATURE_EXT(FEATURE); FOR_EACH_VK_FEATURE_EXT(FEATURE);
FOR_EACH_VK_EXTENSION(EXTENSION); FOR_EACH_VK_EXTENSION(EXTENSION);
FOR_EACH_VK_PLATFORM_EXTENSION(EXTENSION);
#undef EXTENSION #undef EXTENSION
#undef FEATURE #undef FEATURE
@@ -1201,10 +1208,12 @@ private:
VkPhysicalDeviceDescriptorBufferPropertiesEXT descriptor_buffer{}; VkPhysicalDeviceDescriptorBufferPropertiesEXT descriptor_buffer{};
VkPhysicalDeviceSubgroupSizeControlProperties subgroup_size_control{}; VkPhysicalDeviceSubgroupSizeControlProperties subgroup_size_control{};
VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{}; VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{};
VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT vertex_attribute_divisor{}; VkPhysicalDeviceMaintenance3Properties maintenance3{};
VkPhysicalDeviceMaintenance4Properties maintenance4{};
VkPhysicalDeviceMaintenance5PropertiesKHR maintenance5{}; VkPhysicalDeviceMaintenance5PropertiesKHR maintenance5{};
VkPhysicalDeviceDepthStencilResolveProperties depth_stencil_resolve{}; VkPhysicalDeviceDepthStencilResolveProperties depth_stencil_resolve{};
VkPhysicalDeviceCustomBorderColorPropertiesEXT custom_border_color{}; VkPhysicalDeviceCustomBorderColorPropertiesEXT custom_border_color{};
VkPhysicalDeviceExternalMemoryHostPropertiesEXT external_memory_host{};
VkPhysicalDeviceProperties properties{}; VkPhysicalDeviceProperties properties{};
}; };
@@ -25,9 +25,34 @@
#include "video_core/gpu_logging/gpu_logging.h" #include "video_core/gpu_logging/gpu_logging.h"
#include "common/settings.h" #include "common/settings.h"
#ifdef __ANDROID__
#include <android/hardware_buffer.h>
#endif
namespace Vulkan { namespace Vulkan {
namespace { namespace {
[[nodiscard]] std::optional<u32> FindImportMemoryType(
const VkPhysicalDeviceMemoryProperties &props, u32 type_mask) {
const auto find = [&](VkMemoryPropertyFlags wanted) -> std::optional<u32> {
for (u32 i = 0; i < props.memoryTypeCount; ++i) {
if (((type_mask >> i) & 1u) != 0 &&
(props.memoryTypes[i].propertyFlags & wanted) == wanted) {
return i;
}
}
return std::nullopt;
};
auto type_index = find(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
if (!type_index) {
type_index = find(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
}
return type_index;
}
// Helpers translating MemoryUsage to flags/usage // Helpers translating MemoryUsage to flags/usage
[[maybe_unused]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) { [[maybe_unused]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) {
@@ -200,6 +225,316 @@ namespace Vulkan {
size = 0; size = 0;
} }
HostMemoryImport::HostMemoryImport(const Device &device_, void *base, size_t size,
std::span<AHardwareBuffer *const> hardware_buffers,
size_t hardware_buffer_window, size_t hardware_buffer_base)
: device{device_} {
if (ImportHardwareBuffers(hardware_buffers, hardware_buffer_window, hardware_buffer_base,
size)) {
return;
}
if (device.IsTiler()) {
return;
}
if (!hardware_buffers.empty()) {
return;
}
if (ImportHostPointer(base, size)) {
return;
}
LOG_INFO(Render_Vulkan, "Unified memory disabled, no host memory import path");
}
bool HostMemoryImport::ImportHostPointer(void *base, size_t size) {
if (!device.IsExtExternalMemoryHostSupported()) {
return false;
}
const u64 alignment = device.GetMinImportedHostPointerAlignment();
if (alignment == 0 || !Common::IsAligned(reinterpret_cast<uintptr_t>(base), alignment) ||
!Common::IsAligned(size, alignment)) {
return false;
}
using namespace Common::Literals;
constexpr VkDeviceSize DesktopWindowSize = 4_GiB;
VkDeviceSize candidate_window = DesktopWindowSize;
const u64 max_buffer_size = device.GetMaxBufferSize();
if (max_buffer_size != 0 && max_buffer_size < candidate_window) {
candidate_window = max_buffer_size;
}
const u64 max_allocation_size = device.GetMaxMemoryAllocationSize();
if (max_allocation_size != 0 && max_allocation_size < candidate_window) {
candidate_window = max_allocation_size;
}
candidate_window = Common::AlignDown(candidate_window, alignment);
if (candidate_window == 0) {
return false;
}
window_size = candidate_window;
const auto &logical = device.GetLogical();
const auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
for (size_t offset = 0; offset < size; offset += window_size) {
u8 *const window_base = static_cast<u8 *>(base) + offset;
const VkDeviceSize window_len =
(std::min)(static_cast<VkDeviceSize>(size - offset), window_size);
VkMemoryHostPointerPropertiesEXT host_props{
.sType = VK_STRUCTURE_TYPE_MEMORY_HOST_POINTER_PROPERTIES_EXT,
.pNext = nullptr,
.memoryTypeBits = 0,
};
if (logical.GetMemoryHostPointerPropertiesEXT(
VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, window_base,
&host_props) != VK_SUCCESS ||
host_props.memoryTypeBits == 0) {
break;
}
const VkExternalMemoryBufferCreateInfo external_info{
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO,
.pNext = nullptr,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT,
};
const VkBufferCreateInfo buffer_ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = &external_info,
.flags = 0,
.size = window_len,
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
VkBuffer new_buffer{};
if (logical.CreateBufferRaw(buffer_ci, &new_buffer) != VK_SUCCESS) {
break;
}
const VkMemoryRequirements requirements =
logical.GetBufferMemoryRequirements(new_buffer);
const u32 type_mask = requirements.memoryTypeBits & host_props.memoryTypeBits;
if (type_mask == 0 || requirements.size > window_len) {
logical.DestroyBufferRaw(new_buffer);
break;
}
const auto type_index = FindImportMemoryType(memory_props, type_mask);
if (!type_index) {
logical.DestroyBufferRaw(new_buffer);
break;
}
const u32 heap_index = memory_props.memoryTypes[*type_index].heapIndex;
const VkDeviceSize heap_size = memory_props.memoryHeaps[heap_index].size;
if (imported_size + window_len > heap_size / 2) {
logical.DestroyBufferRaw(new_buffer);
break;
}
const VkImportMemoryHostPointerInfoEXT import_info{
.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT,
.pNext = nullptr,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT,
.pHostPointer = window_base,
};
const VkMemoryAllocateInfo alloc_info{
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &import_info,
.allocationSize = window_len,
.memoryTypeIndex = *type_index,
};
vk::DeviceMemory memory = logical.TryAllocateMemory(alloc_info);
if (!memory) {
logical.DestroyBufferRaw(new_buffer);
break;
}
if (logical.BindBufferMemory(new_buffer, *memory, 0) != VK_SUCCESS) {
logical.DestroyBufferRaw(new_buffer);
break;
}
windows.push_back(Window{
.memory = std::move(memory),
.buffer = new_buffer,
});
imported_size += static_cast<size_t>(window_len);
}
if (windows.empty()) {
return false;
}
return true;
}
bool HostMemoryImport::ImportHardwareBuffers(
[[maybe_unused]] std::span<AHardwareBuffer *const> hardware_buffers,
[[maybe_unused]] size_t hardware_buffer_window,
[[maybe_unused]] size_t hardware_buffer_base, [[maybe_unused]] size_t size) {
#ifdef __ANDROID__
if (hardware_buffers.empty() || hardware_buffer_window == 0 ||
!device.IsExtExternalMemoryAhbSupported()) {
return false;
}
const u64 max_allocation_size = device.GetMaxMemoryAllocationSize();
if (max_allocation_size != 0 && hardware_buffer_window > max_allocation_size) {
return false;
}
if (hardware_buffer_base >= size) {
return false;
}
const auto &logical = device.GetLogical();
const auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
window_size = hardware_buffer_window;
base_offset = hardware_buffer_base;
const auto import_all = [&](VkBufferUsageFlags usage, bool want_address) {
for (size_t i = 0; i < hardware_buffers.size(); ++i) {
const size_t offset = hardware_buffer_base + i * hardware_buffer_window;
if (offset >= size) {
break;
}
const VkDeviceSize window_len = (std::min)(
static_cast<VkDeviceSize>(size - offset),
static_cast<VkDeviceSize>(hardware_buffer_window));
VkAndroidHardwareBufferPropertiesANDROID ahb_props{
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
.pNext = nullptr,
.allocationSize = 0,
.memoryTypeBits = 0,
};
if (logical.GetAndroidHardwareBufferPropertiesANDROID(hardware_buffers[i],
&ahb_props) != VK_SUCCESS ||
ahb_props.memoryTypeBits == 0 || ahb_props.allocationSize < window_len) {
break;
}
const VkExternalMemoryBufferCreateInfo external_info{
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO,
.pNext = nullptr,
.handleTypes =
VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
};
const VkBufferCreateInfo buffer_ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = &external_info,
.flags = 0,
.size = window_len,
.usage = usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
VkBuffer new_buffer{};
if (logical.CreateBufferRaw(buffer_ci, &new_buffer) != VK_SUCCESS) {
break;
}
const VkMemoryRequirements requirements =
logical.GetBufferMemoryRequirements(new_buffer);
const u32 type_mask = requirements.memoryTypeBits & ahb_props.memoryTypeBits;
if (type_mask == 0 || requirements.size > ahb_props.allocationSize) {
logical.DestroyBufferRaw(new_buffer);
break;
}
const auto type_index = FindImportMemoryType(memory_props, type_mask);
if (!type_index) {
logical.DestroyBufferRaw(new_buffer);
break;
}
const VkImportAndroidHardwareBufferInfoANDROID import_info{
.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID,
.pNext = nullptr,
.buffer = hardware_buffers[i],
};
const VkMemoryDedicatedAllocateInfo dedicated_info{
.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
.pNext = &import_info,
.image = VK_NULL_HANDLE,
.buffer = new_buffer,
};
const VkMemoryAllocateFlagsInfo flags_info{
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO,
.pNext = &dedicated_info,
.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT,
.deviceMask = 0,
};
const void *alloc_next = &dedicated_info;
if (want_address) {
alloc_next = &flags_info;
}
const VkMemoryAllocateInfo alloc_info{
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = alloc_next,
.allocationSize = ahb_props.allocationSize,
.memoryTypeIndex = *type_index,
};
vk::DeviceMemory memory = logical.TryAllocateMemory(alloc_info);
if (!memory) {
logical.DestroyBufferRaw(new_buffer);
break;
}
if (logical.BindBufferMemory(new_buffer, *memory, 0) != VK_SUCCESS) {
logical.DestroyBufferRaw(new_buffer);
break;
}
VkDeviceAddress address = 0;
if (want_address) {
address = logical.GetBufferDeviceAddress(new_buffer);
}
windows.push_back(Window{
.memory = std::move(memory),
.buffer = new_buffer,
.address = address,
});
imported_size += static_cast<size_t>(window_len);
}
return !windows.empty();
};
constexpr VkBufferUsageFlags TransferUsage =
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VkBufferUsageFlags shader_usage = TransferUsage |
VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
const bool want_address = device.IsBufferDeviceAddressSupported();
VkBufferUsageFlags minimal_usage = TransferUsage | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
if (want_address) {
shader_usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
minimal_usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
}
const auto reset_windows = [&] {
for (Window &window : windows) {
if (window.buffer != VK_NULL_HANDLE) {
logical.DestroyBufferRaw(window.buffer);
}
}
windows.clear();
imported_size = 0;
};
bindable = import_all(shader_usage, want_address);
if (!bindable) {
reset_windows();
bindable = import_all(minimal_usage, want_address);
}
if (!bindable) {
reset_windows();
import_all(TransferUsage, false);
}
if (windows.empty()) {
window_size = 0;
base_offset = 0;
return false;
}
foreign_ownership = true;
return true;
#else
return false;
#endif
}
HostMemoryImport::~HostMemoryImport() {
for (Window &window : windows) {
if (window.buffer != VK_NULL_HANDLE) {
device.GetLogical().DestroyBufferRaw(window.buffer);
}
}
}
MemoryAllocator::MemoryAllocator(const Device &device_) MemoryAllocator::MemoryAllocator(const Device &device_)
: device{device_}, allocator{device.GetAllocator()}, : device{device_}, allocator{device.GetAllocator()},
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties}, properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
@@ -332,6 +667,17 @@ namespace Vulkan {
return MemoryCommit(allocator, a, info); return MemoryCommit(allocator, a, info);
} }
HostMemoryImport *MemoryAllocator::CreateHostMemoryImport(
void *base, size_t size, std::span<AHardwareBuffer *const> hardware_buffers,
size_t hardware_buffer_window, size_t hardware_buffer_base) {
unified_memory = std::make_unique<HostMemoryImport>(
device, base, size, hardware_buffers, hardware_buffer_window, hardware_buffer_base);
if (!unified_memory->IsValid()) {
unified_memory.reset();
}
return unified_memory.get();
}
MemoryCommit MemoryAllocator::Commit(const vk::Buffer &buffer, MemoryUsage usage) { MemoryCommit MemoryAllocator::Commit(const vk::Buffer &buffer, MemoryUsage usage) {
// Allocate memory appropriate for this buffer automatically // Allocate memory appropriate for this buffer automatically
const auto vma_usage = MemoryUsageVma(usage); const auto vma_usage = MemoryUsageVma(usage);
@@ -15,6 +15,8 @@
#include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/vulkan_common/vulkan_wrapper.h"
#include "video_core/vulkan_common/vma.h" #include "video_core/vulkan_common/vma.h"
struct AHardwareBuffer;
namespace Vulkan { namespace Vulkan {
class Device; class Device;
@@ -84,6 +86,76 @@ namespace Vulkan {
void *mapped_ptr{}; ///< Optional persistent mapped pointer void *mapped_ptr{}; ///< Optional persistent mapped pointer
}; };
class HostMemoryImport {
public:
explicit HostMemoryImport(const Device &device_, void *base, size_t size,
std::span<AHardwareBuffer *const> hardware_buffers,
size_t hardware_buffer_window, size_t hardware_buffer_base);
~HostMemoryImport();
HostMemoryImport(const HostMemoryImport &) = delete;
HostMemoryImport &operator=(const HostMemoryImport &) = delete;
[[nodiscard]] bool IsValid() const noexcept {
return !windows.empty();
}
[[nodiscard]] size_t GetSize() const noexcept {
return imported_size;
}
[[nodiscard]] size_t GetBaseOffset() const noexcept {
return base_offset;
}
[[nodiscard]] bool NeedsForeignOwnershipTransfer() const noexcept {
return foreign_ownership;
}
[[nodiscard]] VkDeviceSize GetWindowSize() const noexcept {
return window_size;
}
[[nodiscard]] VkBuffer GetWindowBuffer(size_t index) const noexcept {
return windows[index].buffer;
}
[[nodiscard]] VkDeviceAddress GetWindowAddress(size_t index) const noexcept {
return windows[index].address;
}
[[nodiscard]] size_t GetWindowCount() const noexcept {
return windows.size();
}
[[nodiscard]] bool IsBindable() const noexcept {
return bindable;
}
private:
struct Window {
vk::DeviceMemory memory;
VkBuffer buffer{};
VkDeviceAddress address{};
};
bool ImportHostPointer(void *base, size_t size);
bool ImportHardwareBuffers(std::span<AHardwareBuffer *const> hardware_buffers,
size_t hardware_buffer_window, size_t hardware_buffer_base,
size_t size);
const Device &device;
std::vector<Window> windows;
VkDeviceSize window_size{};
size_t imported_size{};
size_t base_offset{};
bool foreign_ownership{};
bool bindable{};
};
/// Memory allocator container. /// Memory allocator container.
/// Allocates and releases memory allocations on demand. /// Allocates and releases memory allocations on demand.
class MemoryAllocator { class MemoryAllocator {
@@ -120,6 +192,15 @@ namespace Vulkan {
/// Commits memory required by the buffer and binds it (for buffers created outside VMA). /// Commits memory required by the buffer and binds it (for buffers created outside VMA).
MemoryCommit Commit(const vk::Buffer &buffer, MemoryUsage usage); MemoryCommit Commit(const vk::Buffer &buffer, MemoryUsage usage);
HostMemoryImport *CreateHostMemoryImport(void *base, size_t size,
std::span<AHardwareBuffer *const> hardware_buffers,
size_t hardware_buffer_window,
size_t hardware_buffer_base);
[[nodiscard]] HostMemoryImport *GetHostMemoryImport() const noexcept {
return unified_memory.get();
}
private: private:
static bool IsAutoUsage(VmaMemoryUsage u) noexcept { static bool IsAutoUsage(VmaMemoryUsage u) noexcept {
switch (u) { switch (u) {
@@ -137,6 +218,7 @@ namespace Vulkan {
const VkPhysicalDeviceMemoryProperties properties; ///< Physical device memory properties. const VkPhysicalDeviceMemoryProperties properties; ///< Physical device memory properties.
VkDeviceSize buffer_image_granularity; ///< Adjacent buffer/image granularity VkDeviceSize buffer_image_granularity; ///< Adjacent buffer/image granularity
u32 valid_memory_types{~0u}; u32 valid_memory_types{~0u};
std::unique_ptr<HostMemoryImport> unified_memory;
}; };
} // namespace Vulkan } // namespace Vulkan
@@ -217,12 +217,16 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkGetBufferMemoryRequirements2); X(vkGetBufferMemoryRequirements2);
X(vkGetDeviceQueue); X(vkGetDeviceQueue);
X(vkGetEventStatus); X(vkGetEventStatus);
X(vkGetMemoryHostPointerPropertiesEXT);
X(vkGetFenceStatus); X(vkGetFenceStatus);
X(vkGetImageMemoryRequirements); X(vkGetImageMemoryRequirements);
X(vkGetPipelineCacheData); X(vkGetPipelineCacheData);
X(vkGetMemoryFdKHR); X(vkGetMemoryFdKHR);
#ifdef _WIN32 #ifdef _WIN32
X(vkGetMemoryWin32HandleKHR); X(vkGetMemoryWin32HandleKHR);
#endif
#ifdef __ANDROID__
X(vkGetAndroidHardwareBufferPropertiesANDROID);
#endif #endif
X(vkGetQueryPoolResults); X(vkGetQueryPoolResults);
X(vkGetPipelineExecutablePropertiesKHR); X(vkGetPipelineExecutablePropertiesKHR);
@@ -333,12 +333,16 @@ struct DeviceDispatch : InstanceDispatch {
PFN_vkGetBufferMemoryRequirements2 vkGetBufferMemoryRequirements2{}; PFN_vkGetBufferMemoryRequirements2 vkGetBufferMemoryRequirements2{};
PFN_vkGetDeviceQueue vkGetDeviceQueue{}; PFN_vkGetDeviceQueue vkGetDeviceQueue{};
PFN_vkGetEventStatus vkGetEventStatus{}; PFN_vkGetEventStatus vkGetEventStatus{};
PFN_vkGetMemoryHostPointerPropertiesEXT vkGetMemoryHostPointerPropertiesEXT{};
PFN_vkGetFenceStatus vkGetFenceStatus{}; PFN_vkGetFenceStatus vkGetFenceStatus{};
PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements{}; PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements{};
PFN_vkGetPipelineCacheData vkGetPipelineCacheData{}; PFN_vkGetPipelineCacheData vkGetPipelineCacheData{};
PFN_vkGetMemoryFdKHR vkGetMemoryFdKHR{}; PFN_vkGetMemoryFdKHR vkGetMemoryFdKHR{};
#ifdef _WIN32 #ifdef _WIN32
PFN_vkGetMemoryWin32HandleKHR vkGetMemoryWin32HandleKHR{}; PFN_vkGetMemoryWin32HandleKHR vkGetMemoryWin32HandleKHR{};
#endif
#ifdef __ANDROID__
PFN_vkGetAndroidHardwareBufferPropertiesANDROID vkGetAndroidHardwareBufferPropertiesANDROID{};
#endif #endif
PFN_vkGetPipelineExecutablePropertiesKHR vkGetPipelineExecutablePropertiesKHR{}; PFN_vkGetPipelineExecutablePropertiesKHR vkGetPipelineExecutablePropertiesKHR{};
PFN_vkGetPipelineExecutableStatisticsKHR vkGetPipelineExecutableStatisticsKHR{}; PFN_vkGetPipelineExecutableStatisticsKHR vkGetPipelineExecutableStatisticsKHR{};
@@ -1085,6 +1089,34 @@ public:
VkMemoryRequirements GetBufferMemoryRequirements(VkBuffer buffer, VkMemoryRequirements GetBufferMemoryRequirements(VkBuffer buffer,
void* pnext = nullptr) const noexcept; void* pnext = nullptr) const noexcept;
VkResult GetMemoryHostPointerPropertiesEXT(
VkExternalMemoryHandleTypeFlagBits handle_type, const void* host_pointer,
VkMemoryHostPointerPropertiesEXT* out_properties) const noexcept {
return dld->vkGetMemoryHostPointerPropertiesEXT(handle, handle_type, host_pointer,
out_properties);
}
#ifdef __ANDROID__
VkResult GetAndroidHardwareBufferPropertiesANDROID(
const struct AHardwareBuffer* buffer,
VkAndroidHardwareBufferPropertiesANDROID* out_properties) const noexcept {
return dld->vkGetAndroidHardwareBufferPropertiesANDROID(handle, buffer, out_properties);
}
#endif
VkResult CreateBufferRaw(const VkBufferCreateInfo& ci, VkBuffer* out_buffer) const noexcept {
return dld->vkCreateBuffer(handle, &ci, nullptr, out_buffer);
}
void DestroyBufferRaw(VkBuffer buffer) const noexcept {
dld->vkDestroyBuffer(handle, buffer, nullptr);
}
VkResult BindBufferMemory(VkBuffer buffer, VkDeviceMemory memory,
VkDeviceSize offset) const noexcept {
return dld->vkBindBufferMemory(handle, buffer, memory, offset);
}
VkMemoryRequirements GetImageMemoryRequirements(VkImage image) const noexcept; VkMemoryRequirements GetImageMemoryRequirements(VkImage image) const noexcept;
std::vector<VkPipelineExecutablePropertiesKHR> GetPipelineExecutablePropertiesKHR( std::vector<VkPipelineExecutablePropertiesKHR> GetPipelineExecutablePropertiesKHR(