Compare commits

..

19 Commits

Author SHA1 Message Date
CamilleLaVey d0640101da Remove opts per tick frames 2026-09-06 18:46:35 -04:00
CamilleLaVey 5f758e6d6d Quick adjustments on the tickframe buffer destruction 2026-09-06 17:56:46 -04:00
CamilleLaVey 76b1d2b517 Tests some last minute changes 2026-09-06 17:08:14 -04:00
CamilleLaVey a609d9d1c5 Fix build 2026-09-06 16:35:16 -04:00
CamilleLaVey 25c5056706 [vulkan] Changes to lru eviction and virtual buffer policy adjustments 2026-09-06 16:26:30 -04:00
CamilleLaVey 6634646824 Remove phi test toggle 2026-09-06 15:40:15 -04:00
CamilleLaVey 89c781ae83 Another tests on phi 2026-09-06 04:29:02 -04:00
CamilleLaVey b858e52f0a Remove some toggles for tests + phi testing changes 2026-09-06 03:34:34 -04:00
CamilleLaVey 6c87c031a2 Another test toggle for phi tracking 2026-09-06 01:41:41 -04:00
CamilleLaVey a77c18fbbf license fix 2026-09-06 01:08:03 -04:00
CamilleLaVey 203ac3cb51 Magic toggles for tests 2026-09-06 01:05:09 -04:00
CamilleLaVey cf4e6ea901 New resolution on the multirange resolve 2026-09-06 00:10:14 -04:00
CamilleLaVey 49e96351b1 Just a quick test 2026-09-05 21:51:37 -04:00
CamilleLaVey 29b2b04a2d [shader_recompiler] Trackers on phi operands 2026-09-05 19:44:21 -04:00
CamilleLaVey 76b7a561ba [vulkan, buffer_cache] Enable sparse binding on buffer cache 2026-09-05 19:35:37 -04:00
lizzie 11de264541 [cmake] Fix some macOS compilation errors (#4350)
Don't compile GLSL/GLASM backends without OpenGL enabled. These codepaths are unused on non-OpenGL platforms.

Fixes other miscellaneous errors that pop up on Clang 23 as well.

Signed-off-by: Lizzie lizzie@eden-emu.dev
Signed-off-by: crueter crueter@eden-emu.dev

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Co-authored-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4350
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-09-05 23:43:46 +02:00
lizzie f6e7686038 [ci] Fix macOS "no type named 'free' in namespace 'std' (#4347)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4347
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-09-04 13:32:36 +02:00
xbzk 1dcc574591 [gdb] fix conn state to use same pipe (instead of a copy) (#4339)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

I was trying to use GDB stub to investigate MHR and it was not connecting.
Error was suggesting it was not available for connection although i saw it booting on logs.
Debugged and noticed signal_pipe_ was not the correct one, and followed the example of client_socket_.
GDB stub is now working,  on windows, at least.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4339
Reviewed-by: Lizzie and Samuel <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-09-02 14:04:35 +02:00
lizzie 5c20f244c9 [cmake] stop building OpenGL on Android (#4342)
Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

we don't even support OpenGL on android

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4342
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-09-02 04:07:24 +02:00
72 changed files with 1714 additions and 1298 deletions
@@ -0,0 +1,112 @@
diff --git a/include/vk_mem_alloc.h b/include/vk_mem_alloc.h
index 8df0364..4856064 100644
--- a/include/vk_mem_alloc.h
+++ b/include/vk_mem_alloc.h
@@ -3017,7 +3017,7 @@ remove them if not needed.
#if defined(__ANDROID_API__) && (__ANDROID_API__ < 16)
#include <cstdlib>
-static void* vma_aligned_alloc(size_t alignment, size_t size)
+static inline void* vma_aligned_alloc(size_t alignment, size_t size)
{
// alignment must be >= sizeof(void*)
if(alignment < sizeof(void*))
@@ -3860,7 +3860,7 @@ Returned value is the found element, if present in the collection or place where
new element with value (key) should be inserted.
*/
template <typename CmpLess, typename IterT, typename KeyT>
-static IterT VmaBinaryFindFirstNotLess(IterT beg, IterT end, const KeyT& key, const CmpLess& cmp)
+static inline IterT VmaBinaryFindFirstNotLess(IterT beg, IterT end, const KeyT& key, const CmpLess& cmp)
{
size_t down = 0;
size_t up = size_t(end - beg);
@@ -3898,7 +3898,7 @@ Warning! O(n^2) complexity. Use only inside VMA_HEAVY_ASSERT.
T must be pointer type, e.g. VmaAllocation, VmaPool.
*/
template<typename T>
-static bool VmaValidatePointerArray(uint32_t count, const T* arr)
+static inline bool VmaValidatePointerArray(uint32_t count, const T* arr)
{
for (uint32_t i = 0; i < count; ++i)
{
@@ -4188,13 +4188,13 @@ static void VmaFree(const VkAllocationCallbacks* pAllocationCallbacks, void* ptr
}
template<typename T>
-static T* VmaAllocate(const VkAllocationCallbacks* pAllocationCallbacks)
+static inline T* VmaAllocate(const VkAllocationCallbacks* pAllocationCallbacks)
{
return (T*)VmaMalloc(pAllocationCallbacks, sizeof(T), VMA_ALIGN_OF(T));
}
template<typename T>
-static T* VmaAllocateArray(const VkAllocationCallbacks* pAllocationCallbacks, size_t count)
+static inline T* VmaAllocateArray(const VkAllocationCallbacks* pAllocationCallbacks, size_t count)
{
return (T*)VmaMalloc(pAllocationCallbacks, sizeof(T) * count, VMA_ALIGN_OF(T));
}
@@ -4204,14 +4204,14 @@ static T* VmaAllocateArray(const VkAllocationCallbacks* pAllocationCallbacks, si
#define vma_new_array(allocator, type, count) new(VmaAllocateArray<type>((allocator), (count)))(type)
template<typename T>
-static void vma_delete(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr)
+static inline void vma_delete(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr)
{
ptr->~T();
VmaFree(pAllocationCallbacks, ptr);
}
template<typename T>
-static void vma_delete_array(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr, size_t count)
+static inline void vma_delete_array(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr, size_t count)
{
if (ptr != VMA_NULL)
{
@@ -4658,13 +4658,13 @@ void VmaVector<T, AllocatorT>::remove(size_t index)
#endif // _VMA_VECTOR_FUNCTIONS
template<typename T, typename allocatorT>
-static void VmaVectorInsert(VmaVector<T, allocatorT>& vec, size_t index, const T& item)
+static inline void VmaVectorInsert(VmaVector<T, allocatorT>& vec, size_t index, const T& item)
{
vec.insert(index, item);
}
template<typename T, typename allocatorT>
-static void VmaVectorRemove(VmaVector<T, allocatorT>& vec, size_t index)
+static inline void VmaVectorRemove(VmaVector<T, allocatorT>& vec, size_t index)
{
vec.remove(index);
}
@@ -10620,19 +10620,19 @@ static void VmaFree(VmaAllocator hAllocator, void* ptr)
}
template<typename T>
-static T* VmaAllocate(VmaAllocator hAllocator)
+static inline T* VmaAllocate(VmaAllocator hAllocator)
{
return (T*)VmaMalloc(hAllocator, sizeof(T), VMA_ALIGN_OF(T));
}
template<typename T>
-static T* VmaAllocateArray(VmaAllocator hAllocator, size_t count)
+static inline T* VmaAllocateArray(VmaAllocator hAllocator, size_t count)
{
return (T*)VmaMalloc(hAllocator, sizeof(T) * count, VMA_ALIGN_OF(T));
}
template<typename T>
-static void vma_delete(VmaAllocator hAllocator, T* ptr)
+static inline void vma_delete(VmaAllocator hAllocator, T* ptr)
{
if(ptr != VMA_NULL)
{
@@ -10642,7 +10642,7 @@ static void vma_delete(VmaAllocator hAllocator, T* ptr)
}
template<typename T>
-static void vma_delete_array(VmaAllocator hAllocator, T* ptr, size_t count)
+static inline void vma_delete_array(VmaAllocator hAllocator, T* ptr, size_t count)
{
if(ptr != VMA_NULL)
{
+3 -2
View File
@@ -3,6 +3,7 @@
cmake_minimum_required(VERSION 3.31)
set(CMAKE_OSX_DEPLOYMENT_TARGET "15.0" CACHE STRING "macOS deployment target")
project(yuzu)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
@@ -238,7 +239,7 @@ option(YUZU_USE_BUNDLED_SIRIT "Download bundled sirit" ${BUNDLED_SIRIT_DEFAULT})
# FreeBSD 15+ has libusb, versions below should disable it
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR LINUX OR FREEBSD OR APPLE" OFF)
cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT (WIN32 AND ARCHITECTURE_arm64) AND NOT APPLE" OFF)
cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT (WIN32 AND ARCHITECTURE_arm64) AND NOT APPLE AND NOT ANDROID" OFF)
mark_as_advanced(FORCE ENABLE_OPENGL)
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
@@ -512,7 +513,7 @@ endfunction()
# =============================================
if (APPLE)
foreach(fw Carbon Metal Cocoa IOKit CoreVideo CoreMedia Security UniformTypeIdentifiers)
foreach(fw Carbon Metal Cocoa IOKit CoreVideo CoreMedia Security UniformTypeIdentifiers Foundation)
find_library(${fw}_LIBRARY ${fw} REQUIRED)
list(APPEND PLATFORM_LIBRARIES ${${fw}_LIBRARY})
endforeach()
+5 -10
View File
@@ -1,12 +1,4 @@
{
"": {
"ci": true,
"hash": "9f50d993c39529e022ad456163de91ac5934e16faf8fc348f305355fc0a643c534f87ded707e11bd03bcbc0a1760bd20852b6533a67ac0558a078385181cb184",
"name": "SDL3",
"package": "SDL3",
"repo": "crueter-ci/SDL3",
"version": "3.4.14-1788231389-147a8ee32d"
},
"biscuit": {
"hash": "1229f345b014f7ca544dedb4edb3311e41ba736f9aa9a67f88b5f26f3c983288c6bb6cdedcfb0b8a02c63088a37e6a0d7ba97d9c2a4d721b213916327cffe28a",
"min_version": "0.9.1",
@@ -68,10 +60,10 @@
},
"discord-rpc": {
"find_args": "MODULE",
"hash": "8213c43dcb0f7d479f5861091d111ed12fbdec1e62e6d729d65a4bc181d82f48a35d5fd3cd5c291f2393ac7c9681eabc6b76609755f55376284c8a8d67e148f3",
"hash": "8d680b3a16d6f6bf292ad823cf8635595ff986f2a49f758e3009f505b540a9d75194a8cf44cddea75736a8c3e3b5160166e716a0939ce3f18cc463cf648753fe",
"package": "DiscordRPC",
"repo": "eden-emulator/discord-rpc",
"version": "0d8b2d6a37"
"version": "76616d8675"
},
"enet": {
"find_args": "MODULE",
@@ -311,6 +303,9 @@
"find_args": "CONFIG",
"hash": "deb5902ef8db0e329fbd5f3f4385eb0e26bdd9f14f3a2334823fb3fe18f36bc5d235d620d6e5f6fe3551ec3ea7038638899db8778c09f6d5c278f5ff95c3344b",
"package": "VulkanMemoryAllocator",
"patches": [
"0001-macos-clang.patch"
],
"repo": "GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator",
"version": "v3.3.0"
},
+1 -1
View File
@@ -76,7 +76,7 @@ The following options are desktop only.
- `ENABLE_LIBUSB` (ON) Enable the use of the libusb input backend (HIGHLY RECOMMENDED)
- `ENABLE_OPENGL` (ON) Enable the OpenGL graphics backend
- Unavailable on Windows/ARM64
- Unavailable on Windows/ARM64 and on Android
- You probably shouldn't turn this off.
### Qt
+1
View File
@@ -11,6 +11,7 @@
#include <limits>
#include <span>
#include <array>
#include <algorithm>
#include <time.h>
namespace Tz {
+1
View File
@@ -5,6 +5,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
#include <cstdlib>
#include <string_view>
#ifdef _WIN32
#include <llvm/Demangle/Demangle.h>
+5 -1
View File
@@ -1,9 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <iterator>
#include <cstring>
#include "common/make_unique_for_overwrite.h"
@@ -61,7 +65,7 @@ public:
void resize(size_type size) {
if (size > buffer_capacity) {
auto new_buffer = Common::make_unique_for_overwrite<T[]>(size);
std::move(buffer.get(), buffer.get() + buffer_capacity, new_buffer.get());
std::memcpy(new_buffer.get(), buffer.get(), buffer_capacity * sizeof(T));
buffer = std::move(new_buffer);
buffer_capacity = size;
}
+1
View File
@@ -10,6 +10,7 @@
#include <functional>
#include <span>
#include <string>
#include <type_traits>
#include "common/common_types.h"
+3 -3
View File
@@ -6,13 +6,13 @@
#include <mutex>
#include <utility>
#include <type_traits>
#include <boost/asio.hpp>
#include <boost/version.hpp>
#if BOOST_VERSION > 108400 && (!defined(_WINDOWS) && !defined(__ANDROID__)) || defined(YUZU_BOOST_v1)
#define USE_BOOST_v1
#endif
#ifdef USE_BOOST_v1
#include <boost/process/v1/async_pipe.hpp>
#else
@@ -358,7 +358,7 @@ private:
ConnectionState(boost::asio::ip::tcp::socket&& client_socket_, async_pipe signal_pipe_, Kernel::KernelCore& kernel)
: client_socket{std::move(client_socket_)}
, signal_pipe{signal_pipe_}
, signal_pipe{std::move(signal_pipe_)}
, active_thread{kernel, nullptr}
{}
+2 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -6,6 +6,7 @@
#pragma once
#include <type_traits>
#include "common/common_funcs.h"
namespace FileSys {
@@ -7,6 +7,7 @@
#pragma once
#include <optional>
#include <type_traits>
#include "common/literals.h"
#include "core/file_sys/fssystem/fs_i_storage.h"
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <type_traits>
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h"
@@ -1,9 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <mutex>
#include <type_traits>
#include "common/alignment.h"
#include "common/common_funcs.h"
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -6,6 +6,7 @@
#pragma once
#include <type_traits>
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h"
@@ -6,6 +6,8 @@
#pragma once
#include <type_traits>
#include <cstddef>
#include "common/literals.h"
#include "core/file_sys/errors.h"
@@ -1,8 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <type_traits>
#include "common/alignment.h"
#include "core/file_sys/fssystem/fs_i_storage.h"
#include "core/file_sys/fssystem/fs_types.h"
@@ -6,6 +6,9 @@
#pragma once
#include <type_traits>
#include <array>
#include <cstddef>
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fs_i_storage.h"
#include "core/file_sys/fssystem/fssystem_bucket_tree.h"
@@ -1,9 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <optional>
#include <array>
#include <cstddef>
#include <type_traits>
#include "core/file_sys/fssystem/fs_i_storage.h"
#include "core/file_sys/fssystem/fs_types.h"
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -6,6 +6,8 @@
#pragma once
#include <type_traits>
#include <cstddef>
#include "core/file_sys/fssystem/fssystem_compression_common.h"
#include "core/file_sys/fssystem/fssystem_nca_header.h"
#include "core/file_sys/vfs/vfs.h"
@@ -1,8 +1,14 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <type_traits>
#include <array>
#include <cstddef>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/literals.h"
+1
View File
@@ -7,6 +7,7 @@
#pragma once
#include <memory>
#include <type_traits>
#include "common/common_funcs.h"
#include "common/page_table.h"
+3
View File
@@ -1021,6 +1021,9 @@ Result KProcess::Run(KernelCore& kernel, s32 priority, size_t stack_size) {
// Suspend for debug, if we should.
if (kernel.System().DebuggerEnabled()) {
LOG_INFO(Debug_GDBStub,
"GDB stub enabled; suspending guest process until a debugger continues execution on port {}",
Settings::values.gdbstub_port.GetValue());
main_thread->RequestSuspend(kernel, SuspendType::Debug);
}
+1
View File
@@ -6,6 +6,7 @@
#pragma once
#include <type_traits>
#include "common/assert.h"
#include "common/bit_field.h"
#include "common/common_funcs.h"
+4
View File
@@ -1,9 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <type_traits>
#include <functional>
#include "common/common_funcs.h"
+8 -26
View File
@@ -175,19 +175,10 @@ Result AlbumManager::LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& ou
return ResultIsNotMounted;
}
out_image_output = {
.width = 1280,
.height = 720,
.attribute =
{
.unknown_0{},
.orientation = AlbumImageOrientation::None,
.unknown_1{},
.unknown_2{},
.pad163{},
},
.pad179{},
};
out_image_output = {};
out_image_output.width = 1280;
out_image_output.height = 720;
out_image_output.attribute.orientation = AlbumImageOrientation::None;
std::filesystem::path path;
const auto result = GetFile(path, file_id);
@@ -211,19 +202,10 @@ Result AlbumManager::LoadAlbumScreenShotThumbnail(
return ResultIsNotMounted;
}
out_image_output = {
.width = 320,
.height = 180,
.attribute =
{
.unknown_0{},
.orientation = AlbumImageOrientation::None,
.unknown_1{},
.unknown_2{},
.pad163{},
},
.pad179{},
};
out_image_output = {};
out_image_output.width = 320;
out_image_output.height = 180;
out_image_output.attribute.orientation = AlbumImageOrientation::None;
std::filesystem::path path;
const auto result = GetFile(path, file_id);
+2 -7
View File
@@ -73,13 +73,8 @@ void IScreenShotApplicationService::CaptureAndSaveScreenshot(AlbumReportOption r
Layout::FramebufferLayout layout =
Layout::DefaultFrameLayout(screenshot_width, screenshot_height);
const Capture::ScreenShotAttribute attribute{
.unknown_0{},
.orientation = Capture::AlbumImageOrientation::None,
.unknown_1{},
.unknown_2{},
.pad163{},
};
Capture::ScreenShotAttribute attribute{};
attribute.orientation = Capture::AlbumImageOrientation::None;
renderer.RequestScreenshot(
image_data.data(),
+4
View File
@@ -1,8 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <type_traits>
#include "common/common_funcs.h"
#include "common/common_types.h"
+2 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
@@ -6,6 +6,7 @@
#pragma once
#include <type_traits>
#include <fmt/ranges.h>
#include "common/common_funcs.h"
+1
View File
@@ -8,6 +8,7 @@
#include <array>
#include <chrono>
#include <type_traits>
#include <fmt/ranges.h>
#include "common/common_types.h"
@@ -1,10 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <type_traits>
#include "common/common_types.h"
#include "core/hle/service/psc/time/common.h"
@@ -7,6 +7,7 @@
#pragma once
#include <array>
#include <type_traits>
#include "common/bit_field.h"
#include "common/common_funcs.h"
+7
View File
@@ -1,8 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstddef>
#include <array>
#include <type_traits>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
+53 -51
View File
@@ -6,55 +6,6 @@
add_library(shader_recompiler STATIC
backend/bindings.h
backend/glasm/emit_glasm.cpp
backend/glasm/emit_glasm.h
backend/glasm/emit_glasm_barriers.cpp
backend/glasm/emit_glasm_bitwise_conversion.cpp
backend/glasm/emit_glasm_composite.cpp
backend/glasm/emit_glasm_context_get_set.cpp
backend/glasm/emit_glasm_control_flow.cpp
backend/glasm/emit_glasm_convert.cpp
backend/glasm/emit_glasm_floating_point.cpp
backend/glasm/emit_glasm_image.cpp
backend/glasm/emit_glasm_instructions.h
backend/glasm/emit_glasm_integer.cpp
backend/glasm/emit_glasm_logical.cpp
backend/glasm/emit_glasm_memory.cpp
backend/glasm/emit_glasm_not_implemented.cpp
backend/glasm/emit_glasm_select.cpp
backend/glasm/emit_glasm_shared_memory.cpp
backend/glasm/emit_glasm_special.cpp
backend/glasm/emit_glasm_undefined.cpp
backend/glasm/emit_glasm_warp.cpp
backend/glasm/glasm_emit_context.cpp
backend/glasm/glasm_emit_context.h
backend/glasm/reg_alloc.cpp
backend/glasm/reg_alloc.h
backend/glsl/emit_glsl.cpp
backend/glsl/emit_glsl.h
backend/glsl/emit_glsl_atomic.cpp
backend/glsl/emit_glsl_barriers.cpp
backend/glsl/emit_glsl_bitwise_conversion.cpp
backend/glsl/emit_glsl_composite.cpp
backend/glsl/emit_glsl_context_get_set.cpp
backend/glsl/emit_glsl_control_flow.cpp
backend/glsl/emit_glsl_convert.cpp
backend/glsl/emit_glsl_floating_point.cpp
backend/glsl/emit_glsl_image.cpp
backend/glsl/emit_glsl_instructions.h
backend/glsl/emit_glsl_integer.cpp
backend/glsl/emit_glsl_logical.cpp
backend/glsl/emit_glsl_memory.cpp
backend/glsl/emit_glsl_not_implemented.cpp
backend/glsl/emit_glsl_select.cpp
backend/glsl/emit_glsl_shared_memory.cpp
backend/glsl/emit_glsl_special.cpp
backend/glsl/emit_glsl_undefined.cpp
backend/glsl/emit_glsl_warp.cpp
backend/glsl/glsl_emit_context.cpp
backend/glsl/glsl_emit_context.h
backend/glsl/var_alloc.cpp
backend/glsl/var_alloc.h
backend/spirv/emit_spirv.cpp
backend/spirv/emit_spirv.h
backend/spirv/emit_spirv_atomic.cpp
@@ -239,9 +190,60 @@ add_library(shader_recompiler STATIC
program_header.h
runtime_info.h
shader_info.h
varying_state.h
varying_state.h)
)
if (ENABLE_OPENGL)
target_sources(shader_recompiler PRIVATE
backend/glasm/emit_glasm.cpp
backend/glasm/emit_glasm.h
backend/glasm/emit_glasm_barriers.cpp
backend/glasm/emit_glasm_bitwise_conversion.cpp
backend/glasm/emit_glasm_composite.cpp
backend/glasm/emit_glasm_context_get_set.cpp
backend/glasm/emit_glasm_control_flow.cpp
backend/glasm/emit_glasm_convert.cpp
backend/glasm/emit_glasm_floating_point.cpp
backend/glasm/emit_glasm_image.cpp
backend/glasm/emit_glasm_instructions.h
backend/glasm/emit_glasm_integer.cpp
backend/glasm/emit_glasm_logical.cpp
backend/glasm/emit_glasm_memory.cpp
backend/glasm/emit_glasm_not_implemented.cpp
backend/glasm/emit_glasm_select.cpp
backend/glasm/emit_glasm_shared_memory.cpp
backend/glasm/emit_glasm_special.cpp
backend/glasm/emit_glasm_undefined.cpp
backend/glasm/emit_glasm_warp.cpp
backend/glasm/glasm_emit_context.cpp
backend/glasm/glasm_emit_context.h
backend/glasm/reg_alloc.cpp
backend/glasm/reg_alloc.h
backend/glsl/emit_glsl.cpp
backend/glsl/emit_glsl.h
backend/glsl/emit_glsl_atomic.cpp
backend/glsl/emit_glsl_barriers.cpp
backend/glsl/emit_glsl_bitwise_conversion.cpp
backend/glsl/emit_glsl_composite.cpp
backend/glsl/emit_glsl_context_get_set.cpp
backend/glsl/emit_glsl_control_flow.cpp
backend/glsl/emit_glsl_convert.cpp
backend/glsl/emit_glsl_floating_point.cpp
backend/glsl/emit_glsl_image.cpp
backend/glsl/emit_glsl_instructions.h
backend/glsl/emit_glsl_integer.cpp
backend/glsl/emit_glsl_logical.cpp
backend/glsl/emit_glsl_memory.cpp
backend/glsl/emit_glsl_not_implemented.cpp
backend/glsl/emit_glsl_select.cpp
backend/glsl/emit_glsl_shared_memory.cpp
backend/glsl/emit_glsl_special.cpp
backend/glsl/emit_glsl_undefined.cpp
backend/glsl/emit_glsl_warp.cpp
backend/glsl/glsl_emit_context.cpp
backend/glsl/glsl_emit_context.h
backend/glsl/var_alloc.cpp
backend/glsl/var_alloc.h)
endif()
target_link_libraries(shader_recompiler PUBLIC common fmt::fmt sirit::sirit)
@@ -553,17 +553,6 @@ void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateIn
}
}
template <typename Descriptors, typename Descriptor, typename Func>
static u32 Add(Descriptors& descriptors, const Descriptor& desc, Func&& pred) {
// TODO: Handle arrays
const auto it{std::ranges::find_if(descriptors, pred)};
if (it != descriptors.end()) {
return static_cast<u32>(std::distance(descriptors.begin(), it));
}
descriptors.push_back(desc);
return static_cast<u32>(descriptors.size()) - 1;
}
void JoinStorageInfo(Info& base, Info& source) {
auto& descriptors = base.storage_buffers_descriptors;
for (auto& desc : source.storage_buffers_descriptors) {
+3
View File
@@ -20,6 +20,7 @@ add_library(video_core STATIC
buffer_cache/buffer_cache.h
buffer_cache/memory_tracker_base.h
buffer_cache/usage_tracker.h
buffer_cache/virtual_range_cache.h
buffer_cache/word_manager.h
cache_types.h
capture.h
@@ -166,6 +167,8 @@ add_library(video_core STATIC
renderer_vulkan/vk_fence_manager.h
renderer_vulkan/vk_graphics_pipeline.cpp
renderer_vulkan/vk_graphics_pipeline.h
renderer_vulkan/vk_multi_range_buffer.cpp
renderer_vulkan/vk_multi_range_buffer.h
renderer_vulkan/vk_master_semaphore.cpp
renderer_vulkan/vk_master_semaphore.h
renderer_vulkan/vk_pipeline_cache.cpp
+20
View File
@@ -20,6 +20,8 @@ namespace VideoCommon {
enum class BufferFlagBits {
Picked = 1 << 0,
CachedWrites = 1 << 1,
PreemtiveDownload = 1 << 2,
};
DECLARE_ENUM_FLAG_OPERATORS(BufferFlagBits)
@@ -56,6 +58,15 @@ public:
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
void IncreaseStreamScore(int score) noexcept {
stream_score += score;
@@ -76,6 +87,15 @@ public:
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
[[nodiscard]] VAddr CpuAddr() const noexcept {
return cpu_addr;
+180 -187
View File
@@ -7,7 +7,6 @@
#pragma once
#include <algorithm>
#include <limits>
#include <memory>
#include <numeric>
@@ -113,6 +112,13 @@ void BufferCache<P>::TickFrame() {
async_buffers_death_ring.clear();
}
template <class P>
void BufferCache<P>::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t size) {
if constexpr (requires { runtime.BindMultiRangeStorageBuffer(u64{}, bool{}); }) {
virtual_ranges.Unmap(as_id, gpu_addr, size);
}
}
template <class P>
void BufferCache<P>::WriteMemory(DAddr device_addr, u64 size) {
if (memory_tracker.IsRegionGpuModified(device_addr, size)) {
@@ -122,6 +128,25 @@ void BufferCache<P>::WriteMemory(DAddr device_addr, u64 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>
bool BufferCache<P>::OnCPUWrite(DAddr device_addr, u64 size) {
const bool is_dirty = IsRegionRegistered(device_addr, size);
@@ -190,8 +215,8 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
BufferId buffer_b;
do {
channel_state->has_deleted_buffers = false;
buffer_a = FindBuffer(*cpu_src_address, static_cast<u32>(amount));
buffer_b = FindBuffer(*cpu_dest_address, static_cast<u32>(amount));
buffer_a = FindBuffer(*cpu_src_address, static_cast<u32>(amount), false);
buffer_b = FindBuffer(*cpu_dest_address, static_cast<u32>(amount), false);
} while (channel_state->has_deleted_buffers);
auto& src_buffer = slot_buffers[buffer_a];
auto& dest_buffer = slot_buffers[buffer_b];
@@ -247,7 +272,7 @@ bool BufferCache<P>::DMAClear(GPUVAddr dst_address, u64 amount, u32 value) {
ClearDownload(*cpu_dst_address, size);
gpu_modified_ranges.Subtract(*cpu_dst_address, size);
const BufferId buffer = FindBuffer(*cpu_dst_address, static_cast<u32>(size));
const BufferId buffer = FindBuffer(*cpu_dst_address, static_cast<u32>(size), false);
Buffer& dest_buffer = slot_buffers[buffer];
const u32 offset = dest_buffer.Offset(*cpu_dst_address);
runtime.ClearBuffer(dest_buffer, offset, size, value);
@@ -269,7 +294,7 @@ std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainBuffer(GPUVAddr gpu_ad
template <class P>
std::pair<typename P::Buffer*, u32> BufferCache<P>::ObtainCPUBuffer(
DAddr device_addr, u32 size, ObtainBufferSynchronize sync_info, ObtainBufferOperation post_op) {
const BufferId buffer_id = FindBuffer(device_addr, size);
const BufferId buffer_id = FindBuffer(device_addr, size, false);
Buffer& buffer = slot_buffers[buffer_id];
// synchronize op
@@ -404,7 +429,7 @@ void BufferCache<P>::UnbindGraphicsStorageBuffers(size_t stage) {
}
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) {
const bool already_enabled =
((channel_state->enabled_storage_buffers[stage] >> ssbo_index) & 1U) != 0;
@@ -415,7 +440,7 @@ void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index,
LOG_WARNING(HW_GPU,
"Skipping graphics storage buffer {} due to driver limit {}",
ssbo_index, max_bindings);
return;
return false;
}
}
}
@@ -431,6 +456,7 @@ void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index,
const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset;
channel_state->storage_buffers[stage][ssbo_index] =
StorageBufferBinding(ssbo_addr, cbuf_index, is_written);
return (channel_state->storage_buffers[stage][ssbo_index].buffer_id != NULL_BUFFER_ID);
}
template <class P>
@@ -743,6 +769,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>
Binding& BufferCache<P>::VertexBufferSlot(u32 index) {
ASSERT(index < NUM_VERTEX_BUFFERS);
@@ -969,11 +1005,85 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
channel_state->fast_bound_uniform_buffers[stage] &= ~(1u << binding_index);
}
template <class P>
void BufferCache<P>::ResolveMultiRangeStorage(Binding& binding, bool is_written,
std::vector<MultiRangeSegment>& pool) {
binding.segment_first = 0;
binding.segment_count = 0;
if constexpr (requires { runtime.BindMultiRangeStorageBuffer(u64{}, bool{}); }) {
if (binding.gpu_addr == 0 || binding.size == 0) {
return;
}
if (is_written && !runtime.PrefersSparseSources()) {
return;
}
const VirtualSegments* found =
virtual_ranges.Query(*gpu_memory, binding.gpu_addr, binding.size);
if (!found || found->size() < 2) {
return;
}
const VirtualSegments segments = *found;
const u32 first = static_cast<u32>(pool.size());
const bool prefer_sparse = runtime.PrefersSparseSources();
for (const VirtualSegment& segment : segments) {
const BufferId buffer_id =
FindBuffer(segment.device_addr, segment.size, prefer_sparse);
if (!buffer_id) {
pool.resize(first);
return;
}
pool.push_back(MultiRangeSegment{
.buffer_id = buffer_id,
.device_addr = segment.device_addr,
.size = segment.size,
});
}
binding.segment_first = first;
binding.segment_count = static_cast<u32>(segments.size());
}
}
template <class P>
bool BufferCache<P>::BindMultiRangeStorage(const Binding& binding, bool is_written,
std::span<const MultiRangeSegment> pool) {
if constexpr (requires { runtime.BindMultiRangeStorageBuffer(u64{}, bool{}); }) {
if (binding.segment_count < 2) {
return false;
}
if (binding.segment_first + binding.segment_count > pool.size()) {
return false;
}
const u64 key = (static_cast<u64>(gpu_memory->GetID()) << 48) ^ binding.gpu_addr;
runtime.ResetMultiRange();
for (u32 index = 0; index < binding.segment_count; ++index) {
const MultiRangeSegment& segment = pool[binding.segment_first + index];
Buffer& buffer = slot_buffers[segment.buffer_id];
TouchBuffer(buffer, segment.buffer_id);
if (SynchronizeBuffer(buffer, segment.device_addr, segment.size)) {
runtime.InvalidateMultiRange(key);
}
const u32 offset = buffer.Offset(segment.device_addr);
buffer.MarkUsage(offset, segment.size);
if (is_written) {
MarkWrittenBuffer(segment.buffer_id, segment.device_addr, segment.size);
}
runtime.PushMultiRangeSource(buffer, offset, segment.size);
}
return runtime.BindMultiRangeStorageBuffer(key, is_written);
} else {
return false;
}
}
template <class P>
void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
u32 binding_index = 0;
ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
const Binding& binding = channel_state->storage_buffers[stage][index];
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
if (BindMultiRangeStorage(binding, is_written, graphics_segments)) {
return;
}
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
const u32 size = binding.size;
@@ -981,7 +1091,6 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
const u32 offset = buffer.Offset(binding.device_addr);
buffer.MarkUsage(offset, size);
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
if (is_written) {
MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size);
@@ -1110,6 +1219,11 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
u32 binding_index = 0;
ForEachEnabledBit(channel_state->enabled_compute_storage_buffers, [&](u32 index) {
const Binding& binding = channel_state->compute_storage_buffers[index];
const bool is_written =
((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
if (BindMultiRangeStorage(binding, is_written, compute_segments)) {
return;
}
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
const u32 size = binding.size;
@@ -1117,8 +1231,6 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
const u32 offset = buffer.Offset(binding.device_addr);
buffer.MarkUsage(offset, size);
const bool is_written =
((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
if (is_written) {
MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size);
@@ -1164,11 +1276,12 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
template <class P>
void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
graphics_segments.clear();
BufferOperations([&]() {
if (is_indexed) {
UpdateIndexBuffer();
}
UpdateVertexBuffers(is_indexed);
UpdateVertexBuffers();
UpdateTransformFeedbackBuffers();
for (size_t stage = 0; stage < NUM_STAGES; ++stage) {
UpdateUniformBuffers(stage);
@@ -1183,6 +1296,7 @@ void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
template <class P>
void BufferCache<P>::DoUpdateComputeBuffers() {
compute_segments.clear();
BufferOperations([&]() {
UpdateComputeUniformBuffers();
UpdateComputeStorageBuffers();
@@ -1205,11 +1319,11 @@ void BufferCache<P>::UpdateIndexBuffer() {
auto inline_index_size = static_cast<u32>(draw_state.inline_index_draw_indexes.size());
u32 buffer_size = Common::AlignUp(inline_index_size, CACHING_PAGESIZE);
if (inline_buffer_id == NULL_BUFFER_ID) [[unlikely]] {
inline_buffer_id = CreateBuffer(0, buffer_size);
inline_buffer_id = CreateBuffer(0, buffer_size, false);
}
if (slot_buffers[inline_buffer_id].SizeBytes() < buffer_size) [[unlikely]] {
slot_buffers.erase(inline_buffer_id);
inline_buffer_id = CreateBuffer(0, buffer_size);
inline_buffer_id = CreateBuffer(0, buffer_size, false);
}
channel_state->index_buffer = Binding{
.device_addr = 0,
@@ -1222,14 +1336,9 @@ void BufferCache<P>::UpdateIndexBuffer() {
const GPUVAddr gpu_addr_begin = index_buffer_ref.StartAddress();
const GPUVAddr gpu_addr_end = index_buffer_ref.EndAddress();
const std::optional<DAddr> device_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
u64 address_size = 0;
if (gpu_addr_end > gpu_addr_begin) {
address_size = (std::min)(gpu_addr_end - gpu_addr_begin,
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));
const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
const u32 draw_size = (index_buffer_ref.count + index_buffer_ref.first) * u32(index_buffer_ref.FormatSizeInBytes());
const u32 size = (std::min)(address_size, draw_size);
if (size == 0 || !device_addr) {
channel_state->index_buffer = NULL_BINDING;
return;
@@ -1237,147 +1346,25 @@ void BufferCache<P>::UpdateIndexBuffer() {
channel_state->index_buffer = Binding{
.device_addr = *device_addr,
.size = size,
.buffer_id = FindBuffer(*device_addr, size),
.buffer_id = FindBuffer(*device_addr, size, false),
};
}
template <class P>
u64 BufferCache<P>::DrawMaxIndex() {
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) {
void BufferCache<P>::UpdateVertexBuffers() {
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]) {
return;
}
flags[Dirty::VertexBuffers] = false;
for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
UpdateVertexBuffer(index, is_indexed);
UpdateVertexBuffer(index);
}
}
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]) {
return;
}
@@ -1386,33 +1373,17 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index, bool is_indexed) {
const GPUVAddr gpu_addr_begin = array.Address();
const GPUVAddr gpu_addr_end = limit.Address() + 1;
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;
UpdateVertexBufferSlot(index, NULL_BINDING);
return;
}
// TODO: Analyze stride and number of vertices
constexpr u64 implausible_size = IMPLAUSIBLE_VERTEX_SIZE;
u64 address_size = gpu_addr_end - gpu_addr_begin;
if (address_size > u64{(std::numeric_limits<u32>::max)()}) {
address_size = implausible_size;
if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || size >= 64_MiB) {
size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, 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, false);
const Binding binding{
.device_addr = *device_addr,
.size = size,
@@ -1433,7 +1404,7 @@ void BufferCache<P>::UpdateDrawIndirect() {
binding = Binding{
.device_addr = *device_addr,
.size = static_cast<u32>(size),
.buffer_id = FindBuffer(*device_addr, static_cast<u32>(size)),
.buffer_id = FindBuffer(*device_addr, static_cast<u32>(size), false),
};
};
if (current_draw_indirect->include_count) {
@@ -1457,7 +1428,7 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) {
channel_state->dirty_uniform_buffers[stage] |= 1U << index;
}
// Resolve buffer
binding.buffer_id = FindBuffer(binding.device_addr, binding.size);
binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false);
});
}
@@ -1466,8 +1437,10 @@ void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
// Resolve buffer
Binding& binding = channel_state->storage_buffers[stage][index];
const BufferId buffer_id = FindBuffer(binding.device_addr, binding.size);
const BufferId buffer_id = FindBuffer(binding.device_addr, binding.size, false);
binding.buffer_id = buffer_id;
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
ResolveMultiRangeStorage(binding, is_written, graphics_segments);
});
}
@@ -1475,7 +1448,7 @@ template <class P>
void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
Binding& binding = channel_state->texture_buffers[stage][index];
binding.buffer_id = FindBuffer(binding.device_addr, binding.size);
binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false);
});
}
@@ -1499,7 +1472,7 @@ void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
channel_state->transform_feedback_buffers[index] = NULL_BINDING;
return;
}
const BufferId buffer_id = FindBuffer(*device_addr, size);
const BufferId buffer_id = FindBuffer(*device_addr, size, false);
channel_state->transform_feedback_buffers[index] = Binding{
.device_addr = *device_addr,
.size = size,
@@ -1521,7 +1494,7 @@ void BufferCache<P>::UpdateComputeUniformBuffers() {
binding.size = cbuf.size;
}
}
binding.buffer_id = FindBuffer(binding.device_addr, binding.size);
binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false);
});
}
@@ -1530,7 +1503,10 @@ void BufferCache<P>::UpdateComputeStorageBuffers() {
ForEachEnabledBit(channel_state->enabled_compute_storage_buffers, [&](u32 index) {
// Resolve buffer
Binding& binding = channel_state->compute_storage_buffers[index];
binding.buffer_id = FindBuffer(binding.device_addr, binding.size);
binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false);
const bool is_written =
((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
ResolveMultiRangeStorage(binding, is_written, compute_segments);
});
}
@@ -1538,7 +1514,7 @@ template <class P>
void BufferCache<P>::UpdateComputeTextureBuffers() {
ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) {
Binding& binding = channel_state->compute_texture_buffers[index];
binding.buffer_id = FindBuffer(binding.device_addr, binding.size);
binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false);
});
}
@@ -1554,7 +1530,7 @@ void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u3
}
template <class P>
BufferId BufferCache<P>::FindBuffer(DAddr device_addr, u32 size) {
BufferId BufferCache<P>::FindBuffer(DAddr device_addr, u32 size, bool sparse_compatible) {
if (device_addr == 0) {
return NULL_BUFFER_ID;
}
@@ -1564,10 +1540,18 @@ BufferId BufferCache<P>::FindBuffer(DAddr device_addr, u32 size) {
Buffer& buffer = slot_buffers[buffer_id];
WaitForGpuFenceIfNeeded(buffer);
if (buffer.IsInBounds(device_addr, size)) {
return buffer_id;
bool usable = true;
if constexpr (requires { buffer.IsSparseCompatible(); }) {
if (sparse_compatible && !buffer.IsSparseCompatible()) {
usable = false;
}
}
if (usable) {
return buffer_id;
}
}
}
return CreateBuffer(device_addr, size);
return CreateBuffer(device_addr, size, sparse_compatible);
}
template <class P>
@@ -1689,14 +1673,15 @@ void BufferCache<P>::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id,
}
template <class P>
BufferId BufferCache<P>::CreateBuffer(DAddr device_addr, u32 wanted_size) {
BufferId BufferCache<P>::CreateBuffer(DAddr device_addr, u32 wanted_size,
bool sparse_compatible) {
DAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, 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>((std::min)(device_addr_end - device_addr, max_buffer_size));
wanted_size = static_cast<u32>(device_addr_end - device_addr);
const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size);
const u32 size = static_cast<u32>((std::min)(overlap.end - overlap.begin, max_buffer_size));
const BufferId new_buffer_id = slot_buffers.insert(runtime, overlap.begin, size);
const u32 size = static_cast<u32>(overlap.end - overlap.begin);
const BufferId new_buffer_id =
slot_buffers.insert(runtime, overlap.begin, size, sparse_compatible);
auto& new_buffer = slot_buffers[new_buffer_id];
const size_t size_bytes = new_buffer.SizeBytes();
runtime.ClearBuffer(new_buffer, 0, size_bytes, 0);
@@ -1860,7 +1845,7 @@ void BufferCache<P>::InlineMemoryImplementation(DAddr dest_address, size_t copy_
ClearDownload(dest_address, copy_size);
gpu_modified_ranges.Subtract(dest_address, copy_size);
BufferId buffer_id = FindBuffer(dest_address, static_cast<u32>(copy_size));
BufferId buffer_id = FindBuffer(dest_address, static_cast<u32>(copy_size), false);
auto& buffer = slot_buffers[buffer_id];
SynchronizeBuffer(buffer, dest_address, static_cast<u32>(copy_size));
@@ -1946,6 +1931,9 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64
template <class P>
void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
if constexpr (requires { runtime.OnBufferDeleted(slot_buffers[buffer_id]); }) {
runtime.OnBufferDeleted(slot_buffers[buffer_id]);
}
bool dirty_index{false};
boost::container::small_vector<u64, NUM_VERTEX_BUFFERS> dirty_vertex_buffers;
const auto scalar_replace = [buffer_id](Binding& binding) {
@@ -2049,9 +2037,14 @@ Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
// The end address used for size calculation does not need to be aligned
const DAddr cpu_end = Common::AlignUp(*device_addr + size, Core::DEVICE_PAGESIZE);
u32 binding_size = static_cast<u32>(cpu_end - *aligned_device_addr);
if (is_written) {
binding_size = aligned_size;
}
const Binding binding{
.device_addr = *aligned_device_addr,
.size = is_written ? aligned_size : static_cast<u32>(cpu_end - *aligned_device_addr),
.gpu_addr = aligned_gpu_addr,
.size = binding_size,
.buffer_id = BufferId{},
};
return binding;
+31 -24
View File
@@ -29,6 +29,7 @@
#include "common/settings.h"
#include "common/slot_vector.h"
#include "video_core/buffer_cache/buffer_base.h"
#include "video_core/buffer_cache/virtual_range_cache.h"
#include "video_core/control/channel_state_cache.h"
#include "video_core/delayed_destruction_ring.h"
#include "video_core/dirty_flags.h"
@@ -51,7 +52,6 @@ constexpr u32 NUM_VERTEX_BUFFERS = 16;
#else
constexpr u32 NUM_VERTEX_BUFFERS = 32;
#endif
constexpr u64 IMPLAUSIBLE_VERTEX_SIZE = 64_MiB;
constexpr u32 NUM_TRANSFORM_FEEDBACK_BUFFERS = 4;
constexpr u32 NUM_GRAPHICS_UNIFORM_BUFFERS = 18;
constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8;
@@ -82,8 +82,17 @@ static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = static_cast<u32>(4_KiB);
struct Binding {
DAddr device_addr{};
GPUVAddr gpu_addr{};
u32 size{};
BufferId buffer_id;
u32 segment_first{};
u32 segment_count{};
};
struct MultiRangeSegment {
BufferId buffer_id;
DAddr device_addr{};
u32 size{};
};
struct TextureBufferBinding : Binding {
@@ -216,8 +225,18 @@ public:
void TickFrame();
bool BindMultiRangeStorage(const Binding& binding, bool is_written,
std::span<const MultiRangeSegment> pool);
void ResolveMultiRangeStorage(Binding& binding, bool is_written,
std::vector<MultiRangeSegment>& pool);
void UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t size);
void WriteMemory(DAddr device_addr, u64 size);
void CachedWriteMemory(DAddr device_addr, u64 size);
bool OnCPUWrite(DAddr device_addr, u64 size);
void DownloadMemory(DAddr device_addr, u64 size);
@@ -247,7 +266,7 @@ public:
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);
void UnbindGraphicsTextureBuffers(size_t stage);
@@ -308,10 +327,6 @@ public:
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> GetDrawIndirectBuffer();
@@ -379,6 +394,8 @@ private:
void BindHostTransformFeedbackBuffers();
void BindHostVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride);
void BindHostComputeUniformBuffers();
void BindHostComputeStorageBuffers();
@@ -391,15 +408,9 @@ private:
void UpdateIndexBuffer();
void UpdateVertexBuffers(bool is_indexed);
void UpdateVertexBuffers();
void UpdateVertexBuffer(u32 index, bool is_indexed);
[[nodiscard]] u64 DrawVertexBound(u32 index, bool is_indexed);
[[nodiscard]] u64 DrawMaxIndex();
[[nodiscard]] u64 StreamAttributeExtent(u32 index);
void UpdateVertexBuffer(u32 index);
void UpdateDrawIndirect();
@@ -421,7 +432,7 @@ private:
void MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u32 size);
[[nodiscard]] BufferId FindBuffer(DAddr device_addr, u32 size);
[[nodiscard]] BufferId FindBuffer(DAddr device_addr, u32 size, bool sparse_compatible);
void WaitForGpuFenceIfNeeded(Buffer& buffer);
@@ -429,7 +440,8 @@ private:
void JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, bool accumulate_stream_score);
[[nodiscard]] BufferId CreateBuffer(DAddr device_addr, u32 wanted_size);
[[nodiscard]] BufferId CreateBuffer(DAddr device_addr, u32 wanted_size,
bool sparse_compatible);
void Register(BufferId buffer_id);
@@ -491,14 +503,6 @@ private:
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 enabled_vertex_buffers_mask = 0;
@@ -528,6 +532,9 @@ private:
using TickType = u64;
};
Common::LeastRecentlyUsedCache<LRUItemParams> lru_cache;
VirtualRangeCache virtual_ranges;
std::vector<MultiRangeSegment> graphics_segments;
std::vector<MultiRangeSegment> compute_segments;
u64 frame_tick = 0;
u64 total_used_memory = 0;
u64 minimum_memory = 0;
@@ -0,0 +1,173 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <atomic>
#include <limits>
#include <mutex>
#include <optional>
#include <vector>
#include <boost/container/small_vector.hpp>
#include "common/common_types.h"
#include "common/container/unordered_map.h"
#include "video_core/memory_manager.h"
namespace VideoCommon {
struct VirtualSegment {
GPUVAddr gpu_addr;
DAddr device_addr;
u32 size;
};
using VirtualSegments = boost::container::small_vector<VirtualSegment, 8>;
class VirtualRangeCache {
public:
static constexpr size_t MAX_ENTRIES = 8192;
static constexpr size_t MAX_DEFERRED = 4096;
const VirtualSegments* Query(Tegra::MemoryManager& memory, GPUVAddr gpu_addr, u32 size) {
if (has_deferred.load(std::memory_order_acquire)) {
ApplyDeferred();
}
if (entries.size() > MAX_ENTRIES) {
entries.clear();
}
const size_t as_id = memory.GetID();
const u64 key = MakeKey(as_id, gpu_addr);
const auto it = entries.find(key);
if (it != entries.end() && it->second.as_id == as_id &&
it->second.gpu_addr == gpu_addr && it->second.size == size) {
return &it->second.segments;
}
Entry entry{};
entry.as_id = as_id;
entry.gpu_addr = gpu_addr;
entry.size = size;
const auto ranges = memory.GetSubmappedRange(gpu_addr, size);
GPUVAddr expected = gpu_addr;
bool contiguous = true;
for (const auto& [range_addr, range_size] : ranges) {
if (range_addr != expected || range_size == 0) {
contiguous = false;
break;
}
const std::optional<DAddr> device_addr = memory.GpuToCpuAddress(range_addr);
if (!device_addr || *device_addr == 0) {
contiguous = false;
break;
}
if (range_size > static_cast<size_t>((std::numeric_limits<u32>::max)())) {
contiguous = false;
break;
}
entry.segments.push_back(VirtualSegment{
.gpu_addr = range_addr,
.device_addr = *device_addr,
.size = static_cast<u32>(range_size),
});
expected += range_size;
}
if (!contiguous || expected != gpu_addr + size) {
entry.segments.clear();
}
const auto result = entries.insert_or_assign(key, std::move(entry));
return &result.first->second.segments;
}
void Unmap(size_t as_id, GPUVAddr gpu_addr, u64 size) {
if (size == 0) {
return;
}
{
std::scoped_lock lock{deferred_mutex};
if (!deferred.empty()) {
DeferredUnmap& last = deferred.back();
if (last.as_id == as_id && last.gpu_addr + last.size == gpu_addr) {
last.size += size;
has_deferred.store(true, std::memory_order_release);
return;
}
}
if (deferred.size() >= MAX_DEFERRED) {
deferred.clear();
deferred_overflow = true;
} else {
deferred.push_back(DeferredUnmap{
.as_id = as_id,
.gpu_addr = gpu_addr,
.size = size,
});
}
}
has_deferred.store(true, std::memory_order_release);
}
private:
struct Entry {
VirtualSegments segments;
size_t as_id{};
GPUVAddr gpu_addr{};
u32 size{};
};
struct DeferredUnmap {
size_t as_id;
GPUVAddr gpu_addr;
u64 size;
};
static u64 MakeKey(size_t as_id, GPUVAddr gpu_addr) {
return (static_cast<u64>(as_id) << 48) ^ gpu_addr;
}
void ApplyDeferred() {
std::vector<DeferredUnmap> pending;
bool overflow = false;
{
std::scoped_lock lock{deferred_mutex};
has_deferred.store(false, std::memory_order_release);
pending.swap(deferred);
overflow = deferred_overflow;
deferred_overflow = false;
}
if (overflow) {
entries.clear();
return;
}
if (pending.empty() || entries.empty()) {
return;
}
for (auto it = entries.begin(); it != entries.end();) {
const Entry& entry = it->second;
const GPUVAddr entry_end = entry.gpu_addr + entry.size;
bool overlaps = false;
for (const DeferredUnmap& unmap : pending) {
if (unmap.as_id != entry.as_id) {
continue;
}
if (entry.gpu_addr < unmap.gpu_addr + unmap.size && unmap.gpu_addr < entry_end) {
overlaps = true;
break;
}
}
if (overlaps) {
it = entries.erase(it);
} else {
++it;
}
}
}
::Common::unordered_map<u64, Entry> entries;
std::vector<DeferredUnmap> deferred;
std::mutex deferred_mutex;
std::atomic<bool> has_deferred{false};
bool deferred_overflow{};
};
} // namespace VideoCommon
+1
View File
@@ -8,6 +8,7 @@
#include <array>
#include <vector>
#include <type_traits>
#include "common/bit_field.h"
#include "common/common_funcs.h"
+7
View File
@@ -1,8 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <cstddef>
#include <type_traits>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
+1
View File
@@ -7,6 +7,7 @@
#pragma once
#include <memory>
#include <type_traits>
#include "common/common_types.h"
#include "common/scratch_buffer.h"
@@ -206,40 +206,6 @@ foreach(VARIANT IN ITEMS ${SHADER_TYPE_VARIANTS})
set(SHADER_HEADERS ${SHADER_HEADERS} ${VARIANT_HEADER_FILE})
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})
get_filename_component(FILENAME ${FILEPATH} NAME)
string(REPLACE "." "_" HEADER_NAME ${FILENAME})
@@ -5,13 +5,9 @@
#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_8bit_storage : require
#endif
#define HAS_EXTENDED_TYPES 1
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
#define END_PUSH_CONSTANTS };
#define UNIFORM(n)
@@ -5,13 +5,9 @@
#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_8bit_storage : require
#endif
#define HAS_EXTENDED_TYPES 1
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
#define END_PUSH_CONSTANTS };
#define UNIFORM(n)
@@ -5,13 +5,9 @@
#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_8bit_storage : require
#endif
#define HAS_EXTENDED_TYPES 1
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
#define END_PUSH_CONSTANTS };
#define UNIFORM(n)
@@ -52,7 +52,7 @@ constexpr std::array PROGRAM_LUT{
Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params)
: VideoCommon::BufferBase(null_params) {}
Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_)
Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_, bool)
: VideoCommon::BufferBase(cpu_addr_, size_bytes_) {
buffer.Create();
if (runtime.device.HasDebuggingToolAttached()) {
@@ -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) {
// TODO: Should HostBindings provide the correct runtime types to avoid these transforms?
std::array<GLuint, 32> buffer_handles;
@@ -23,7 +23,8 @@ class BufferCacheRuntime;
class Buffer : public VideoCommon::BufferBase {
public:
explicit Buffer(BufferCacheRuntime&, DAddr cpu_addr, u64 size_bytes);
explicit Buffer(BufferCacheRuntime&, DAddr cpu_addr, u64 size_bytes,
bool sparse_compatible);
explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams);
void ImmediateUpload(size_t offset, std::span<const u8> data) noexcept;
@@ -99,6 +100,8 @@ public:
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 BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size);
@@ -259,7 +259,6 @@ void RasterizerOpenGL::PrepareDraw(bool is_indexed, Func&& draw_func) {
}
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) {
const auto& draw_state = maxwell3d->draw_manager.draw_state;
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() {
const auto& params = maxwell3d->draw_manager.indirect_state;
buffer_cache.SetDrawIndirect(&params);
buffer_cache.SetDrawInstanceCount(0);
PrepareDraw(params.is_indexed, [this, &params](GLenum primitive_mode) {
if (params.is_byte_count) {
const GPUVAddr tfb_object_base_addr = params.indirect_start_address - 4U;
@@ -56,7 +56,8 @@ size_t BytesPerIndex(VkIndexType index_type) {
}
}
vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allocator, u64 size) {
vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allocator, u64 size,
VkDeviceSize sparse_alignment) {
VkBufferUsageFlags flags =
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT |
@@ -82,6 +83,9 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
if (sparse_alignment > 1) {
return memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal, sparse_alignment);
}
return memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
}
} // Anonymous namespace
@@ -99,10 +103,14 @@ Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_p
}
}
Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_)
Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_,
bool sparse_compatible_)
: VideoCommon::BufferBase(cpu_addr_, size_bytes_), device{&runtime.device},
scheduler{&runtime.scheduler},
buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes())}, tracker{SizeBytes()} {
buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes(),
runtime.SparseAlignmentFor(sparse_compatible_))},
tracker{SizeBytes()} {
sparse_compatible = sparse_compatible_;
if (runtime.device.HasDebuggingToolAttached()) {
buffer.SetObjectNameEXT(fmt::format("Buffer {:#x}", CpuAddr()).c_str());
}
@@ -348,7 +356,8 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
staging_pool{staging_pool_}, guest_descriptor_queue{guest_descriptor_queue_},
quad_index_pass(device, scheduler, descriptor_pool, staging_pool,
compute_pass_descriptor_queue) {
compute_pass_descriptor_queue),
multi_range_buffers(device_) {
const VkDriverIdKHR driver_id = device.GetDriverID();
limit_dynamic_storage_buffers = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
@@ -536,6 +545,37 @@ void BufferCacheRuntime::ClearBuffer(VkBuffer dest_buffer, u32 offset, size_t si
});
}
bool BufferCacheRuntime::BindMultiRangeStorageBuffer(u64 key, bool is_written) {
if (multi_range_sources.empty() || multi_range_total == 0) {
return false;
}
const MultiRangeRef ref = multi_range_buffers.Get(device, scheduler, memory_allocator, key,
multi_range_sources, multi_range_total);
if (ref.handle == VK_NULL_HANDLE) {
return false;
}
if (is_written && !ref.sparse) {
return false;
}
if (ref.needs_gather) {
PreCopyBarrier();
VkDeviceSize dst_offset = 0;
for (const MultiRangeSource& source : multi_range_sources) {
const std::array<VideoCommon::BufferCopy, 1> copy{VideoCommon::BufferCopy{
.src_offset = u64(source.offset),
.dst_offset = u64(dst_offset),
.size = size_t(source.size),
}};
CopyBuffer(ref.handle, source.handle, copy, false);
dst_offset += source.size;
}
PostCopyBarrier();
multi_range_buffers.MarkGathered(key);
}
guest_descriptor_queue.AddBuffer(ref.handle, ref.address, 0, ref.size);
return true;
}
void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format,
u32 base_vertex, u32 num_indices, VkBuffer buffer,
u32 offset, [[maybe_unused]] u32 size) {
@@ -585,6 +625,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) {
boost::container::static_vector<VkBuffer, VideoCommon::NUM_VERTEX_BUFFERS> buffer_handles(bindings.buffers.size());
for (u32 i = 0; i < bindings.buffers.size(); ++i) {
@@ -8,11 +8,14 @@
#include <limits>
#include <boost/container/small_vector.hpp>
#include "video_core/buffer_cache/buffer_cache_base.h"
#include "video_core/buffer_cache/memory_tracker_base.h"
#include "video_core/buffer_cache/usage_tracker.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/renderer_vulkan/vk_multi_range_buffer.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/surface.h"
@@ -31,7 +34,8 @@ class BufferCacheRuntime;
class Buffer : public VideoCommon::BufferBase {
public:
explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params);
explicit Buffer(BufferCacheRuntime& runtime, VAddr cpu_addr_, u64 size_bytes_);
explicit Buffer(BufferCacheRuntime& runtime, VAddr cpu_addr_, u64 size_bytes_,
bool sparse_compatible_);
[[nodiscard]] VkBufferView View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format);
@@ -43,6 +47,14 @@ public:
return device_address;
}
[[nodiscard]] bool IsSparseCompatible() const noexcept {
return sparse_compatible;
}
[[nodiscard]] vk::MemoryLocation Location() const noexcept {
return buffer.Location();
}
[[nodiscard]] bool IsRegionUsed(u64 offset, u64 size) const noexcept {
return tracker.IsUsed(offset, size);
}
@@ -77,6 +89,7 @@ private:
VkDeviceAddress device_address{};
u64 last_usage_tick{};
bool is_null{};
bool sparse_compatible{};
};
class QuadArrayIndexBuffer;
@@ -125,7 +138,7 @@ public:
void PreCopyBarrier();
void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer,
void CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer,
std::span<const VideoCommon::BufferCopy> copies, bool barrier,
bool can_reorder_upload = false);
@@ -138,6 +151,8 @@ public:
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 BindTransformFeedbackBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size);
@@ -153,6 +168,46 @@ public:
return ref.mapped_span;
}
[[nodiscard]] VkDeviceSize SparseAlignmentFor(bool sparse_compatible) const noexcept {
if (!sparse_compatible || !multi_range_buffers.use_sparse) {
return 0;
}
return multi_range_buffers.block_size;
}
[[nodiscard]] bool PrefersSparseSources() const noexcept {
return multi_range_buffers.use_sparse;
}
void ResetMultiRange() noexcept {
multi_range_sources.clear();
multi_range_total = 0;
}
void PushMultiRangeSource(const Buffer& buffer, u32 offset, u32 size) {
const vk::MemoryLocation location = buffer.Location();
multi_range_sources.push_back(MultiRangeSource{
.handle = buffer.Handle(),
.memory = location.memory,
.memory_offset = location.offset,
.offset = offset,
.size = size,
.write_tick = buffer.getWriteTick(),
.memory_type = location.memory_type,
});
multi_range_total += size;
}
bool BindMultiRangeStorageBuffer(u64 key, bool is_written);
void InvalidateMultiRange(u64 key) {
multi_range_buffers.Invalidate(key);
}
void OnBufferDeleted(const Buffer& buffer) {
multi_range_buffers.DropOwner(scheduler, buffer.Handle());
}
void BindUniformBuffer(const Buffer& buffer, u32 offset, u32 size) {
BindBuffer(buffer, offset, size);
}
@@ -206,6 +261,10 @@ private:
std::unique_ptr<Uint8Pass> uint8_pass;
QuadIndexedPass quad_index_pass;
MultiRangeBufferCache multi_range_buffers;
boost::container::small_vector<MultiRangeSource, 16> multi_range_sources;
VkDeviceSize multi_range_total{};
bool limit_dynamic_storage_buffers = false;
u32 max_dynamic_storage_buffers = (std::numeric_limits<u32>::max)();
};
@@ -22,13 +22,7 @@
#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_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_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_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/surface.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
@@ -878,291 +872,4 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk(
});
}
namespace {
constexpr u32 UNSWIZZLE_BINDING_INPUT_BUFFER = 0;
constexpr u32 UNSWIZZLE_BINDING_OUTPUT_IMAGE = 1;
constexpr size_t UNSWIZZLE_NUM_BINDINGS = 2;
constexpr std::array<VkDescriptorSetLayoutBinding, UNSWIZZLE_NUM_BINDINGS>
UNSWIZZLE_DESCRIPTOR_SET_BINDINGS{{
{
.binding = UNSWIZZLE_BINDING_INPUT_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
{
.binding = UNSWIZZLE_BINDING_OUTPUT_IMAGE,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
}};
constexpr std::array<VkDescriptorUpdateTemplateEntry, UNSWIZZLE_NUM_BINDINGS>
UNSWIZZLE_DESCRIPTOR_UPDATE_TEMPLATE{{
{
.dstBinding = UNSWIZZLE_BINDING_INPUT_BUFFER,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.offset = UNSWIZZLE_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry),
.stride = sizeof(DescriptorUpdateEntry),
},
{
.dstBinding = UNSWIZZLE_BINDING_OUTPUT_IMAGE,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.offset = UNSWIZZLE_BINDING_OUTPUT_IMAGE * sizeof(DescriptorUpdateEntry),
.stride = sizeof(DescriptorUpdateEntry),
},
}};
constexpr DescriptorBankInfo UNSWIZZLE_BANK_INFO{
.uniform_buffers = 0,
.storage_buffers = 1,
.texture_buffers = 0,
.image_buffers = 0,
.textures = 0,
.images = 1,
.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
BlockLinearUnswizzle2DPass::BlockLinearUnswizzle2DPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
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(
VideoCommon::Accelerated::BlockLinearSwizzle2DParams)>,
UnswizzleSpv(device_, BLOCK_LINEAR_UNSWIZZLE_2D_COMP_SPV,
BLOCK_LINEAR_UNSWIZZLE_2D_NONARROW_COMP_SPV)),
scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
BlockLinearUnswizzle2DPass::~BlockLinearUnswizzle2DPass() = default;
void BlockLinearUnswizzle2DPass::Unswizzle(
Image& image, const StagingBufferRef& map,
std::span<const VideoCommon::SwizzleParameters> swizzles) {
using namespace VideoCommon::Accelerated;
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(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
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) {
using namespace VideoCommon::Accelerated;
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);
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);
}
PitchUnswizzlePass::PitchUnswizzlePass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
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(PitchUnswizzlePushConstants)>,
UnswizzleSpv(device_, PITCH_UNSWIZZLE_COMP_SPV,
PITCH_UNSWIZZLE_NONARROW_COMP_SPV)),
scheduler{scheduler_}, compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
PitchUnswizzlePass::~PitchUnswizzlePass() = default;
void PitchUnswizzlePass::Unswizzle(Image& image, const StagingBufferRef& map,
std::span<const VideoCommon::SwizzleParameters> 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 bytes_per_block = VideoCore::Surface::BytesPerBlock(image.info.format);
const u32 pitch = image.info.pitch;
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 PitchUnswizzlePushConstants params{
.origin{0, 0},
.destination{0, 0},
.bytes_per_block = bytes_per_block,
.pitch = pitch,
};
scheduler.Record([this, num_dispatches_x, num_dispatches_y, 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, 1);
});
}
RecordUnswizzleExitBarrier(scheduler, vk_image, aspect_mask);
}
} // namespace Vulkan
@@ -164,49 +164,4 @@ private:
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class BlockLinearUnswizzle2DPass final : public ComputePass {
public:
explicit BlockLinearUnswizzle2DPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
~BlockLinearUnswizzle2DPass();
void Unswizzle(Image& image, const StagingBufferRef& map,
std::span<const VideoCommon::SwizzleParameters> swizzles);
private:
Scheduler& scheduler;
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class BlockLinearUnswizzleImage3DPass final : public ComputePass {
public:
explicit BlockLinearUnswizzleImage3DPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
~BlockLinearUnswizzleImage3DPass();
void Unswizzle(Image& image, const StagingBufferRef& map,
std::span<const VideoCommon::SwizzleParameters> swizzles);
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);
private:
Scheduler& scheduler;
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
} // namespace Vulkan
@@ -694,11 +694,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
const size_t num_vertex_arrays = (std::min)(
Maxwell::NumVertexArrays, static_cast<size_t>(device.GetMaxVertexInputBindings()));
for (size_t index = 0; index < num_vertex_arrays; ++index) {
const bool instanced = ((key.state.enabled_divisors >> index) & 1) != 0;
auto rate = VK_VERTEX_INPUT_RATE_VERTEX;
if (instanced) {
rate = VK_VERTEX_INPUT_RATE_INSTANCE;
}
const bool instanced = key.state.binding_divisors[index] != 0;
const auto rate =
instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
vertex_bindings.push_back({
.binding = static_cast<u32>(index),
.stride = key.state.vertex_strides[index],
@@ -707,7 +705,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
if (instanced) {
vertex_binding_divisors.push_back({
.binding = static_cast<u32>(index),
.divisor = device.GetVertexAttribDivisor(key.state.binding_divisors[index]),
.divisor = key.state.binding_divisors[index],
});
}
}
@@ -0,0 +1,338 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <mutex>
#include <utility>
#include "video_core/renderer_vulkan/vk_multi_range_buffer.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/vulkan_common/vulkan_device.h"
namespace Vulkan {
MultiRangeBufferCache::MultiRangeBufferCache(const Device& device) {
sparse_usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
if (device.IsBufferDeviceAddressSupported()) {
sparse_usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
}
if (!device.IsSparseBindingSupported()) {
return;
}
u32 memory_type_bits = 0;
const VkDeviceSize queried = QueryBlockSize(device, memory_type_bits);
if (queried == 0 || memory_type_bits == 0) {
return;
}
block_size = queried;
sparse_memory_type_bits = memory_type_bits;
use_sparse = true;
}
VkDeviceSize MultiRangeBufferCache::QueryBlockSize(const Device& device,
u32& memory_type_bits) const {
const VkDevice logical = *device.GetLogical();
const auto& dld = device.GetDispatchLoader();
const VkBufferCreateInfo probe_ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = VK_BUFFER_CREATE_SPARSE_BINDING_BIT | VK_BUFFER_CREATE_SPARSE_ALIASED_BIT,
.size = DEFAULT_BLOCK_SIZE,
.usage = sparse_usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
VkBuffer probe{};
if (dld.vkCreateBuffer(logical, &probe_ci, nullptr, &probe) != VK_SUCCESS) {
return 0;
}
const SparseBuffer owned{probe, logical, dld};
const VkBufferMemoryRequirementsInfo2 reqs_info{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2,
.pNext = nullptr,
.buffer = probe,
};
VkMemoryRequirements2 reqs2{
.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2,
.pNext = nullptr,
.memoryRequirements = {},
};
dld.vkGetBufferMemoryRequirements2(logical, &reqs_info, &reqs2);
memory_type_bits = reqs2.memoryRequirements.memoryTypeBits;
return reqs2.memoryRequirements.alignment;
}
u64 MultiRangeBufferCache::HashSources(std::span<const MultiRangeSource> sources) const {
u64 hash = 0xcbf29ce484222325ULL;
const auto mix = [&hash](u64 value) {
hash ^= value;
hash *= 0x100000001b3ULL;
};
for (const MultiRangeSource& source : sources) {
mix(u64(source.handle));
mix(u64(source.offset));
mix(u64(source.size));
}
return hash;
}
u64 MultiRangeBufferCache::HashContent(std::span<const MultiRangeSource> sources) const {
u64 hash = 0xcbf29ce484222325ULL;
for (const MultiRangeSource& source : sources) {
hash ^= source.write_tick;
hash *= 0x100000001b3ULL;
}
return hash;
}
bool MultiRangeBufferCache::CanBindSparse(std::span<const MultiRangeSource> sources) const {
return use_sparse &&
std::none_of(sources.begin(), sources.end(),
[block = block_size, bits = sparse_memory_type_bits](auto const& e) {
const VkDeviceSize memory_offset = e.memory_offset + e.offset;
return e.memory == VK_NULL_HANDLE || e.memory_type >= 32 ||
((bits >> e.memory_type) & 1) == 0 ||
(memory_offset % block) != 0 || (e.size % block) != 0;
});
}
SparseBuffer MultiRangeBufferCache::CreateSparse(const Device& device, Scheduler& scheduler,
std::span<const MultiRangeSource> sources,
VkDeviceSize total) {
const VkDevice logical = *device.GetLogical();
const auto& dld = device.GetDispatchLoader();
const VkBufferCreateInfo buffer_ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = VK_BUFFER_CREATE_SPARSE_BINDING_BIT | VK_BUFFER_CREATE_SPARSE_ALIASED_BIT,
.size = total,
.usage = sparse_usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
VkBuffer raw{};
if (dld.vkCreateBuffer(logical, &buffer_ci, nullptr, &raw) != VK_SUCCESS) {
return SparseBuffer{};
}
SparseBuffer handle{raw, logical, dld};
std::vector<VkSparseMemoryBind> binds;
binds.reserve(sources.size());
VkDeviceSize resource_offset = 0;
for (const MultiRangeSource& source : sources) {
binds.push_back(VkSparseMemoryBind{
.resourceOffset = resource_offset,
.size = source.size,
.memory = source.memory,
.memoryOffset = source.memory_offset + source.offset,
.flags = 0,
});
resource_offset += source.size;
}
const VkSparseBufferMemoryBindInfo buffer_bind{
.buffer = raw,
.bindCount = static_cast<u32>(binds.size()),
.pBinds = binds.data(),
};
const VkBindSparseInfo bind_info{
.sType = VK_STRUCTURE_TYPE_BIND_SPARSE_INFO,
.pNext = nullptr,
.waitSemaphoreCount = 0,
.pWaitSemaphores = nullptr,
.bufferBindCount = 1,
.pBufferBinds = &buffer_bind,
.imageOpaqueBindCount = 0,
.pImageOpaqueBinds = nullptr,
.imageBindCount = 0,
.pImageBinds = nullptr,
.signalSemaphoreCount = 0,
.pSignalSemaphores = nullptr,
};
const VkFenceCreateInfo fence_ci{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
};
vk::Fence fence = device.GetLogical().CreateFence(fence_ci);
VkResult bind_result = VK_ERROR_UNKNOWN;
{
std::scoped_lock lock{scheduler.submit_mutex};
bind_result = device.GetGraphicsQueue().BindSparse(bind_info, *fence);
}
if (bind_result != VK_SUCCESS) {
return SparseBuffer{};
}
fence.Wait();
return handle;
}
void MultiRangeBufferCache::RetireEntry(Scheduler& scheduler, Entry& entry) {
if (!entry.sparse_handle && !entry.gathered) {
return;
}
if (retired.size() == retired.capacity()) {
DrainRetired(scheduler);
}
if (retired.size() == retired.capacity()) {
u64 oldest = retired.front().tick;
for (const Retired& item : retired) {
if (item.tick < oldest) {
oldest = item.tick;
}
}
scheduler.Wait(oldest);
DrainRetired(scheduler);
}
retired.push_back(Retired{
.handle = std::move(entry.sparse_handle),
.gathered = std::move(entry.gathered),
.tick = scheduler.CurrentTick(),
});
}
void MultiRangeBufferCache::DrainRetired(Scheduler& scheduler) {
size_t index = 0;
while (index < retired.size()) {
if (scheduler.IsFree(retired[index].tick)) {
if (index + 1 != retired.size()) {
retired[index] = std::move(retired.back());
}
retired.pop_back();
} else {
++index;
}
}
}
MultiRangeRef MultiRangeBufferCache::Get(const Device& device, Scheduler& scheduler,
MemoryAllocator& memory_allocator, u64 key,
std::span<const MultiRangeSource> sources,
VkDeviceSize total) {
if (sources.empty() || total == 0) {
return MultiRangeRef{};
}
if (!retired.empty()) {
DrainRetired(scheduler);
}
const u64 geometry = HashSources(sources);
const u64 content = HashContent(sources);
const auto it = entries.find(key);
if (it != entries.end() && it->second.geometry == geometry && it->second.size == total) {
Entry& entry = it->second;
if (entry.content != content) {
entry.content = content;
entry.dirty = true;
}
MultiRangeRef ref{
.handle = *entry.sparse_handle,
.address = entry.address,
.size = entry.size,
.sparse = true,
.needs_gather = false,
};
if (!entry.sparse_handle) {
ref.handle = *entry.gathered;
ref.sparse = false;
ref.needs_gather = entry.dirty;
}
return ref;
}
if (it != entries.end()) {
RetireEntry(scheduler, it->second);
entries.erase(it);
}
Entry entry{};
entry.geometry = geometry;
entry.content = content;
entry.size = total;
if (CanBindSparse(sources)) {
entry.sparse_handle = CreateSparse(device, scheduler, sources, total);
if (entry.sparse_handle) {
entry.owners.reserve(sources.size());
for (const MultiRangeSource& source : sources) {
entry.owners.push_back(source.handle);
}
}
}
if (!entry.sparse_handle) {
VkBufferUsageFlags flags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
if (device.IsBufferDeviceAddressSupported()) {
flags |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
}
const VkBufferCreateInfo gather_ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = total,
.usage = flags,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
entry.gathered = memory_allocator.CreateBuffer(gather_ci, MemoryUsage::DeviceLocal);
entry.dirty = true;
}
if (device.IsBufferDeviceAddressSupported()) {
VkBuffer address_handle = *entry.sparse_handle;
if (!entry.sparse_handle) {
address_handle = *entry.gathered;
}
entry.address = device.GetLogical().GetBufferDeviceAddress(address_handle);
}
MultiRangeRef ref{
.handle = *entry.sparse_handle,
.address = entry.address,
.size = entry.size,
.sparse = true,
.needs_gather = false,
};
if (!entry.sparse_handle) {
ref.handle = *entry.gathered;
ref.sparse = false;
ref.needs_gather = true;
}
entries.emplace(key, std::move(entry));
return ref;
}
void MultiRangeBufferCache::MarkGathered(u64 key) {
if (auto const it = entries.find(key); it != entries.end()) {
it->second.dirty = false;
}
}
void MultiRangeBufferCache::DropOwner(Scheduler& scheduler, VkBuffer owner) {
if (owner == VK_NULL_HANDLE) {
return;
}
for (auto it = entries.begin(); it != entries.end();) {
Entry& entry = it->second;
bool owned = false;
for (const VkBuffer handle : entry.owners) {
if (handle == owner) {
owned = true;
break;
}
}
if (!owned) {
++it;
continue;
}
RetireEntry(scheduler, entry);
it = entries.erase(it);
}
}
void MultiRangeBufferCache::Invalidate(u64 key) {
if (auto const it = entries.find(key); it != entries.end()) {
it->second.dirty = true;
}
}
} // namespace Vulkan
@@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <span>
#include <vector>
#include <boost/container/static_vector.hpp>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/container/unordered_map.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
namespace Vulkan {
using SparseBuffer = vk::Handle<VkBuffer, VkDevice, vk::DeviceDispatch>;
class Device;
class Scheduler;
struct MultiRangeSource {
VkBuffer handle{};
VkDeviceMemory memory{};
VkDeviceSize memory_offset{};
VkDeviceSize offset{};
VkDeviceSize size{};
u64 write_tick{};
u32 memory_type{};
};
struct MultiRangeRef {
VkBuffer handle{};
VkDeviceAddress address{};
VkDeviceSize size{};
bool sparse{};
bool needs_gather{};
};
class MultiRangeBufferCache final {
public:
static constexpr VkDeviceSize DEFAULT_BLOCK_SIZE = 64 * 1024;
static constexpr size_t MAX_RETIRED = 256;
explicit MultiRangeBufferCache(const Device& device);
YUZU_NON_COPYABLE(MultiRangeBufferCache);
[[nodiscard]] MultiRangeRef Get(const Device& device, Scheduler& scheduler,
MemoryAllocator& memory_allocator, u64 key,
std::span<const MultiRangeSource> sources,
VkDeviceSize total);
void MarkGathered(u64 key);
void Invalidate(u64 key);
void DropOwner(Scheduler& scheduler, VkBuffer owner);
VkDeviceSize block_size{DEFAULT_BLOCK_SIZE};
bool use_sparse{};
private:
struct Retired {
SparseBuffer handle;
vk::Buffer gathered;
u64 tick{};
};
struct Entry {
vk::Buffer gathered;
SparseBuffer sparse_handle;
std::vector<VkBuffer> owners;
VkDeviceAddress address{};
VkDeviceSize size{};
u64 geometry{};
u64 content{};
bool dirty{true};
};
[[nodiscard]] u64 HashSources(std::span<const MultiRangeSource> sources) const;
[[nodiscard]] u64 HashContent(std::span<const MultiRangeSource> sources) const;
[[nodiscard]] bool CanBindSparse(std::span<const MultiRangeSource> sources) const;
[[nodiscard]] SparseBuffer CreateSparse(const Device& device, Scheduler& scheduler,
std::span<const MultiRangeSource> sources,
VkDeviceSize total);
[[nodiscard]] VkDeviceSize QueryBlockSize(const Device& device, u32& memory_type_bits) const;
void RetireEntry(Scheduler& scheduler, Entry& entry);
void DrainRetired(Scheduler& scheduler);
::Common::unordered_map<u64, Entry> entries;
boost::container::static_vector<Retired, MAX_RETIRED> retired;
u32 sparse_memory_type_bits{};
VkBufferUsageFlags sparse_usage{};
};
} // namespace Vulkan
@@ -338,7 +338,7 @@ void PresentManager::PresentThread(std::stop_token token) {
// By exchanging the lock ownership we take the swapchain lock
// before the queue lock goes out of scope. This way the swapchain
// lock in WaitPresent is guaranteed to occur after here.
std::exchange(lock, std::unique_lock{swapchain_mutex});
void(std::exchange(lock, std::unique_lock{swapchain_mutex}));
CopyToSwapchain(frame);
// Free the frame for reuse
@@ -137,12 +137,10 @@ VkRect2D GetScissorState(const Maxwell& regs, size_t index, u32 up_scale = 1, u3
max_y = (std::max)(max_y, 0);
if (src.enable) {
const s32 min_x = static_cast<s32>(src.min_x.Value());
const s32 max_x = static_cast<s32>(src.max_x.Value());
scissor.offset.x = scale_up(min_x);
scissor.offset.x = scale_up(src.min_x);
scissor.offset.y = scale_up(min_y);
scissor.extent.width = scale_up((std::max)(max_x - min_x, 0));
scissor.extent.height = scale_up((std::max)(max_y - min_y, 0));
scissor.extent.width = scale_up(src.max_x - src.min_x);
scissor.extent.height = scale_up(max_y - min_y);
} else {
scissor.offset.x = 0;
scissor.offset.y = 0;
@@ -262,7 +260,6 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) {
}
void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
buffer_cache.SetDrawInstanceCount(instance_count);
PrepareDraw(is_indexed, [this, is_indexed, instance_count] {
const auto& draw_state = maxwell3d->draw_manager.draw_state;
const u32 num_instances{instance_count};
@@ -298,7 +295,6 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
void RasterizerVulkan::DrawIndirect() {
const auto& params = maxwell3d->draw_manager.indirect_state;
buffer_cache.SetDrawIndirect(&params);
buffer_cache.SetDrawInstanceCount(0);
PrepareDraw(params.is_indexed, [this, &params] {
const auto indirect_buffer = buffer_cache.GetDrawIndirectBuffer();
const auto& buffer = indirect_buffer.first;
@@ -823,6 +819,7 @@ void RasterizerVulkan::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) {
std::scoped_lock lock{texture_cache.mutex};
texture_cache.UnmapGPUMemory(as_id, addr, size);
}
buffer_cache.UnmapGPUMemory(as_id, addr, size);
}
void RasterizerVulkan::SignalFence(std::function<void()>&& func) {
@@ -1921,19 +1918,13 @@ void RasterizerVulkan::UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs)
for (u32 binding = 0; binding < max_bindings; ++binding) {
const auto& input_binding{regs.vertex_streams[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({
.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT,
.pNext = nullptr,
.binding = binding,
.stride = input_binding.stride,
.inputRate = input_rate,
.divisor = divisor,
.inputRate = is_instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX,
.divisor = is_instanced ? input_binding.frequency : 1,
});
}
@@ -300,7 +300,7 @@ void Scheduler::WorkerThread(std::stop_token stop_token) {
// Exchange lock ownership so that we take the execution lock before
// the queue lock goes out of scope. This allows us to force execution
// to complete in the next step.
std::exchange(lk, std::unique_lock{execution_mutex});
void(std::exchange(lk, std::unique_lock{execution_mutex}));
// Perform the work, tracking whether the chunk was a submission
// before executing.
@@ -449,9 +449,7 @@ void Scheduler::EndRenderPass()
| VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT
| VK_ACCESS_TRANSFER_READ_BIT
| VK_ACCESS_TRANSFER_WRITE_BIT,
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
@@ -462,7 +460,7 @@ void Scheduler::EndRenderPass()
}
cmdbuf.EndRenderPass();
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));
if (has_transform_feedback) {
static constexpr VkMemoryBarrier XFB_OUTPUT_BARRIER{
@@ -160,55 +160,6 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
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,
std::optional<VkFormat> format_override = {}) {
auto format_info =
@@ -297,18 +248,8 @@ constexpr u32 UNSWIZZLE_WORKGROUP_INVOCATIONS = 32 * 32;
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,
VkFormat format, VkImageViewType view_type) {
VkFormat format) {
static constexpr VkImageViewUsageCreateInfo storage_image_view_usage_create_info{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
.pNext = nullptr,
@@ -319,7 +260,7 @@ constexpr u32 UNSWIZZLE_WORKGROUP_INVOCATIONS = 32 * 32;
.pNext = &storage_image_view_usage_create_info,
.flags = 0,
.image = image,
.viewType = view_type,
.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY,
.format = format,
.components{
.r = VK_COMPONENT_SWIZZLE_IDENTITY,
@@ -717,11 +658,18 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im
.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);
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,
nullptr, nullptr, write_barrier);
// TODO: Move this to another API
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,
@@ -1020,14 +968,6 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
bl3d_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
staging_buffer_pool, compute_pass_descriptor_queue);
}
if (SupportsAcceleratedUnswizzleDevice(device)) {
bl_unswizzle_2d_pass.emplace(device, scheduler, descriptor_pool,
compute_pass_descriptor_queue);
bl_unswizzle_image_3d_pass.emplace(device, scheduler, descriptor_pool,
compute_pass_descriptor_queue);
pitch_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
compute_pass_descriptor_queue);
}
}
void TextureCacheRuntime::Finish() {
@@ -1962,12 +1902,16 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
if (runtime->device.HasDebuggingToolAttached()) {
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;
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} {}
@@ -2091,7 +2035,7 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset,
temp_vk_image, info.format, info.num_samples,
{image_copies.data(), image_copies.size()}, false);
}
InitializationFor(current_image) = true;
initialized = true;
runtime->ReleaseMsaaScratchImage(temp_vk_image);
if (is_rescaled) {
@@ -2112,7 +2056,7 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset,
const VkBuffer src_buffer = buffer;
const VkImage vk_image = *original_image;
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,
vk_copies](vk::CommandBuffer cmdbuf) {
@@ -2377,46 +2321,16 @@ void Image::DownloadMemory(const StagingBufferRef& map, std::span<const BufferIm
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 {
const bool astc_decode = WillUseAcceleratedAstcDecode(runtime->device, info);
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];
auto& view = storage_image_views[level];
if (!view) {
auto format_info =
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;
}
if (unswizzle_upload) {
const PixelFormat view_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));
view = MakeStorageView(runtime->device.GetLogical(), level, *(this->*current_image),
format_info.format);
}
return *view;
}
@@ -2456,7 +2370,6 @@ bool Image::ScaleUp(bool ignore) {
}
if (NeedsScaleHelper()) {
if (!BlitScaleHelper(true)) {
flags &= ~ImageFlagBits::Rescaled;
current_image = &Image::original_image;
return false;
}
@@ -2646,10 +2559,6 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
if (device->IsExtAstcDecodeModeSupported() && IsLdrAstcFormat(format_info.format)) {
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{
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.pNext = view_next,
@@ -2658,7 +2567,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
.viewType = VkImageViewType{},
.format = format_info.format,
.components = swizzle_mapping,
.subresourceRange = subresource_range,
.subresourceRange = MakeSubresourceRange(aspect_mask, info.range),
};
const auto create = [&](TextureType tex_type, std::optional<u32> num_layers) {
VkImageViewCreateInfo ci{create_info};
@@ -3232,19 +3141,6 @@ void TextureCacheRuntime::AccelerateImageUpload(
return astc_decoder_pass->Assemble(image, map, swizzles);
}
if (bl_unswizzle_2d_pass && image.info.type == ImageType::e2D) {
return bl_unswizzle_2d_pass->Unswizzle(image, map, swizzles);
}
if (bl_unswizzle_image_3d_pass && image.info.type == ImageType::e3D &&
!IsPixelFormatBCn(image.info.format)) {
return bl_unswizzle_image_3d_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 (IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D) {
ASSERT(false && "GPU unswizzle is disabled for BCn 3D texture");
@@ -159,9 +159,6 @@ public:
std::optional<ASTCDecoderPass> astc_decoder_pass;
std::optional<BlockLinearUnswizzle3DPass> bl3d_unswizzle_pass;
std::optional<BlockLinearUnswizzle2DPass> bl_unswizzle_2d_pass;
std::optional<BlockLinearUnswizzleImage3DPass> bl_unswizzle_image_3d_pass;
std::optional<PitchUnswizzlePass> pitch_unswizzle_pass;
const Settings::ResolutionScalingInfo& resolution;
std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
@@ -353,7 +350,7 @@ public:
/// Returns true when the image is already initialized and mark it as initialized
[[nodiscard]] bool ExchangeInitialization() noexcept {
return std::exchange(InitializationFor(current_image), true);
return std::exchange(initialized, true);
}
VkImageView StorageImageView(s32 level) noexcept;
@@ -373,10 +370,6 @@ private:
bool NeedsScaleHelper() const;
std::vector<vk::ImageView>& StorageViewsFor(vk::Image Image::*image);
bool& InitializationFor(vk::Image Image::*image) noexcept;
Scheduler* scheduler{};
TextureCacheRuntime* runtime{};
@@ -394,10 +387,8 @@ private:
vk::Image Image::*current_image{};
std::vector<vk::ImageView> storage_image_views;
std::vector<vk::ImageView> scaled_storage_image_views;
VkImageAspectFlags aspect_mask = 0;
bool original_initialized = false;
bool scaled_initialized = false;
bool initialized = false;
std::optional<Framebuffer> scale_framebuffer;
std::optional<Framebuffer> normal_framebuffer;
@@ -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-License-Identifier: GPL-2.0-or-later
@@ -26,8 +23,8 @@ struct BlockLinearSwizzle2DParams {
};
struct BlockLinearSwizzle3DParams {
alignas(16) std::array<u32, 3> origin;
alignas(16) std::array<s32, 3> destination;
std::array<u32, 3> origin;
std::array<s32, 3> destination;
u32 bytes_per_block_log2;
u32 slice_size;
u32 block_size;
+37 -63
View File
@@ -20,7 +20,6 @@
#include "video_core/engines/kepler_compute.h"
#include "video_core/guest_memory.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/samples_helper.h"
#include "video_core/texture_cache/texture_cache_base.h"
@@ -280,11 +279,11 @@ void TextureCache<P>::CheckFeedbackLoop(std::span<const ImageViewInOut> views) {
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)
is_feedback |= (rt_active_mask & (1u << i)) && view_image_id == rt_image_id[i];
if (is_feedback)
return true;
is_continue |= (rt_active_mask & (1u << i)) && view_image_id == rt_image_id[i];
if (is_continue)
continue;
}
if (depth_active && view_image_id == rt_depth_image_id) {
return true;
@@ -627,25 +626,14 @@ void TextureCache<P>::DownloadMemory(DAddr cpu_addr, size_t size) {
std::ranges::sort(images, [this](ImageId lhs, ImageId rhs) {
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) {
Image& image = slot_images[image_id];
auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes);
const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info));
image.DownloadMemory(download_map, copies);
download_map.offset += image.unswizzled_size_bytes;
}
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,
image.DownloadMemory(map, copies);
runtime.Finish();
SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span,
swizzle_data_buffer);
download_span = download_span.subspan(image.unswizzled_size_bytes);
}
}
@@ -1134,12 +1122,6 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId 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()) {
LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
runtime.TransitionImageLayout(image);
@@ -1175,7 +1157,8 @@ void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging)
const GPUVAddr gpu_addr = image.gpu_addr;
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);
runtime.AccelerateImageUpload(image, staging, FixSmallVectorADL(uploads), 0, 0);
return;
@@ -1282,9 +1265,6 @@ ImageId TextureCache<P>::FindImage(const ImageInfo& info, GPUVAddr gpu_addr,
template <class P>
bool TextureCache<P>::ImageCanRescale(ImageBase& image) {
if (!Settings::values.resolution_info.active) {
return false;
}
if (!image.info.rescaleable) {
return false;
}
@@ -1372,12 +1352,12 @@ void TextureCache<P>::QueueAsyncDecode(Image& image, ImageId image_id) {
decode->image_id = image_id;
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);
auto copies = UnswizzleImage(*gpu_memory, image.gpu_addr, image.info, swizzle_data, local_unswizzle_data_buffer);
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),
async_decode = decode_ptr]() mutable {
async_decode->decoded_data.resize_destructive(out_size);
@@ -1446,16 +1426,18 @@ void TextureCache<P>::TickAsyncUnswizzle() {
Image& image = slot_images[task.image_id];
if (!task.initialized) {
task.total_size = image.guest_size_bytes;
task.total_size = MapSizeBytes(image);
task.staging_buffer = runtime.UploadStagingBuffer(task.total_size, true);
const auto layout = FullUploadSwizzles(task.info);
const auto params =
VideoCommon::Accelerated::MakeBlockLinearSwizzle3DParams(layout.front(), task.info);
task.bytes_per_slice = params.slice_size;
task.chunked = task.info.block.depth == 0;
const auto& info = image.info;
const u32 bytes_per_block = BytesPerBlock(info.format);
const u32 width_blocks = Common::DivCeil(info.size.width, 4u);
const u32 height_blocks = Common::DivCeil(info.size.height, 4u);
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.slices_submitted = 0;
task.initialized = true;
}
@@ -1470,39 +1452,31 @@ void TextureCache<P>::TickAsyncUnswizzle() {
if (copy_amount == 0) copy_amount = task.bytes_per_slice;
}
gpu_memory->ReadBlockUnsafe(image.gpu_addr + task.current_offset,
task.staging_buffer.mapped_span.data() + task.current_offset,
copy_amount);
gpu_memory->ReadBlock(image.gpu_addr + task.current_offset,
task.staging_buffer.mapped_span.data() + task.current_offset,
copy_amount);
task.current_offset += copy_amount;
}
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) {
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 (complete_slices >= swizzle_slices_per_batch || (is_final_batch && complete_slices > 0)) {
const u32 z_start = static_cast<u32>(task.last_submitted_offset / 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)) {
const u32 z_start = task.slices_submitted;
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 (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;
}
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);
}
} 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) {
runtime.FreeDeferredStagingBuffer(task.staging_buffer);
@@ -139,8 +139,6 @@ class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelI
AsyncBuffer staging_buffer;
size_t last_submitted_offset = 0;
size_t bytes_per_slice;
u32 slices_submitted = 0;
bool chunked = false;
bool initialized = false;
};
@@ -1219,11 +1219,6 @@ bool Device::GetSuitability(bool requires_swapchain) {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_PROPERTIES_EXT;
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) {
properties.maintenance5.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_PROPERTIES_KHR;
@@ -1575,6 +1570,8 @@ void Device::SetupFamilies(VkSurfaceKHR surface) {
}
if (graphics) {
graphics_family = *graphics;
graphics_family_sparse_binding =
(queue_family_properties[*graphics].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0;
}
if (present) {
present_family = *present;
+6 -29
View File
@@ -70,7 +70,6 @@ VK_DEFINE_HANDLE(VmaAllocator)
FEATURE(EXT, ProvokingVertex, PROVOKING_VERTEX, provoking_vertex) \
FEATURE(EXT, Robustness2, ROBUSTNESS_2, robustness2) \
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(KHR, Maintenance5, MAINTENANCE_5, maintenance5) \
FEATURE(KHR, Maintenance6, MAINTENANCE_6, maintenance6) \
@@ -93,6 +92,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
EXTENSION(EXT, SHADER_STENCIL_EXPORT, shader_stencil_export) \
EXTENSION(EXT, SHADER_VIEWPORT_INDEX_LAYER, shader_viewport_index_layer) \
EXTENSION(EXT, TOOLING_INFO, tooling_info) \
EXTENSION(EXT, VERTEX_ATTRIBUTE_DIVISOR, vertex_attribute_divisor) \
EXTENSION(KHR, CREATE_RENDERPASS_2, create_renderpass2) \
EXTENSION(KHR, DEPTH_STENCIL_RESOLVE, depth_stencil_resolve) \
EXTENSION(KHR, DRAW_INDIRECT_COUNT, draw_indirect_count) \
@@ -317,6 +317,10 @@ public:
return properties.driver.driverID;
}
bool IsSparseBindingSupported() const {
return features.features.sparseBinding && graphics_family_sparse_binding;
}
/// Returns true for tile-based deferred renderers.
bool IsTiler() const {
switch (GetDriverID()) {
@@ -359,7 +363,6 @@ public:
#define FN_MAX_LIMIT_LIST \
FN_MAX_LIMIT_ELEM(ComputeSharedMemorySize) \
FN_MAX_LIMIT_ELEM(ComputeWorkGroupInvocations) \
FN_MAX_LIMIT_ELEM(PerStageDescriptorSampledImages) \
FN_MAX_LIMIT_ELEM(PerStageResources) \
FN_MAX_LIMIT_ELEM(DescriptorSetSamplers) \
@@ -690,32 +693,6 @@ FN_MAX_LIMIT_LIST
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.
bool IsExtTransformFeedbackSupported() const {
return extensions.transform_feedback;
@@ -1174,6 +1151,7 @@ private:
u32 instance_version{}; ///< Vulkan instance version.
u32 graphics_family{}; ///< Main graphics queue family index.
u32 present_family{}; ///< Main present queue family index.
bool graphics_family_sparse_binding{};
struct Extensions {
#define EXTENSION(prefix, macro_name, var_name) bool var_name{};
@@ -1216,7 +1194,6 @@ private:
VkPhysicalDeviceDescriptorBufferPropertiesEXT descriptor_buffer{};
VkPhysicalDeviceSubgroupSizeControlProperties subgroup_size_control{};
VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{};
VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT vertex_attribute_divisor{};
VkPhysicalDeviceMaintenance5PropertiesKHR maintenance5{};
VkPhysicalDeviceDepthStencilResolveProperties depth_stencil_resolve{};
VkPhysicalDeviceCustomBorderColorPropertiesEXT custom_border_color{};
@@ -26,350 +26,383 @@
#include "common/settings.h"
namespace Vulkan {
namespace {
namespace {
// Helpers translating MemoryUsage to flags/usage
[[maybe_unused]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::DeviceLocal:
return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
case MemoryUsage::Upload:
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
case MemoryUsage::Download:
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
case MemoryUsage::Stream:
return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
ASSERT_MSG(false, "Invalid memory usage={}", usage);
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
[[maybe_unused]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::DeviceLocal:
return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
case MemoryUsage::Upload:
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
case MemoryUsage::Download:
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
case MemoryUsage::Stream:
return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
ASSERT_MSG(false, "Invalid memory usage={}", usage);
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferredVmaFlags(MemoryUsage usage) {
if (usage == MemoryUsage::Download) {
return VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
return usage != MemoryUsage::DeviceLocal ? VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
: VkMemoryPropertyFlagBits{};
[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferredVmaFlags(MemoryUsage usage) {
if (usage == MemoryUsage::Download) {
return VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
return usage != MemoryUsage::DeviceLocal ? VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
: VkMemoryPropertyFlagBits{};
}
[[nodiscard]] VmaAllocationCreateFlags MemoryUsageVmaFlags(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::Upload:
case MemoryUsage::Stream:
return VMA_ALLOCATION_CREATE_MAPPED_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
case MemoryUsage::Download:
return VMA_ALLOCATION_CREATE_MAPPED_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
case MemoryUsage::DeviceLocal:
return {};
}
return {};
[[nodiscard]] VmaAllocationCreateFlags MemoryUsageVmaFlags(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::Upload:
case MemoryUsage::Stream:
return VMA_ALLOCATION_CREATE_MAPPED_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
case MemoryUsage::Download:
return VMA_ALLOCATION_CREATE_MAPPED_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
case MemoryUsage::DeviceLocal:
return {};
}
return {};
}
[[nodiscard]] VmaMemoryUsage MemoryUsageVma(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::DeviceLocal:
case MemoryUsage::Stream:
return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
case MemoryUsage::Upload:
case MemoryUsage::Download:
return VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
}
return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
[[nodiscard]] VmaMemoryUsage MemoryUsageVma(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::DeviceLocal:
case MemoryUsage::Stream:
return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
case MemoryUsage::Upload:
case MemoryUsage::Download:
return VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
}
// This avoids calling vkGetBufferMemoryRequirements* directly.
template<typename T>
static VkBuffer GetVkHandleFromBuffer(const T &buf) {
if constexpr (requires { static_cast<VkBuffer>(buf); }) {
return static_cast<VkBuffer>(buf);
} else if constexpr (requires {{ buf.GetHandle() } -> std::convertible_to<VkBuffer>; }) {
return buf.GetHandle();
} else if constexpr (requires {{ buf.Handle() } -> std::convertible_to<VkBuffer>; }) {
return buf.Handle();
} else if constexpr (requires {{ buf.vk_handle() } -> std::convertible_to<VkBuffer>; }) {
return buf.vk_handle();
} else {
static_assert(sizeof(T) == 0, "Cannot extract VkBuffer handle from vk::Buffer");
return VK_NULL_HANDLE;
}
}
} // namespace
return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
}
} // namespace
//MemoryCommit is now VMA-backed
MemoryCommit::MemoryCommit(VmaAllocator alloc, VmaAllocation a,
const VmaAllocationInfo &info) noexcept
: allocator{alloc}, allocation{a}, memory{info.deviceMemory},
offset{info.offset}, size{info.size}, mapped_ptr{info.pMappedData} {
// Log GPU memory allocation
MemoryCommit::MemoryCommit(VmaAllocator alloc, VmaAllocation a,
const VmaAllocationInfo &info) noexcept
: allocator{alloc}, allocation{a}, memory{info.deviceMemory},
offset{info.offset}, size{info.size}, mapped_ptr{info.pMappedData} {
// Log GPU memory allocation
if (GPU::Logging::IsActive() &&
Settings::values.gpu_log_memory_tracking.GetValue()) {
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
reinterpret_cast<uintptr_t>(memory),
static_cast<u64>(size),
0 // Memory property flags (not easily available from VMA)
);
}
}
MemoryCommit::~MemoryCommit() { Release(); }
MemoryCommit::MemoryCommit(MemoryCommit &&rhs) noexcept
: allocator{std::exchange(rhs.allocator, nullptr)},
allocation{std::exchange(rhs.allocation, nullptr)},
memory{std::exchange(rhs.memory, VK_NULL_HANDLE)},
offset{std::exchange(rhs.offset, 0)},
size{std::exchange(rhs.size, 0)},
mapped_ptr{std::exchange(rhs.mapped_ptr, nullptr)} {}
MemoryCommit &MemoryCommit::operator=(MemoryCommit &&rhs) noexcept {
if (this != &rhs) {
Release();
allocator = std::exchange(rhs.allocator, nullptr);
allocation = std::exchange(rhs.allocation, nullptr);
memory = std::exchange(rhs.memory, VK_NULL_HANDLE);
offset = std::exchange(rhs.offset, 0);
size = std::exchange(rhs.size, 0);
mapped_ptr = std::exchange(rhs.mapped_ptr, nullptr);
}
return *this;
}
std::span<u8> MemoryCommit::Map()
{
if (!allocation) return {};
if (!mapped_ptr) {
if (vmaMapMemory(allocator, allocation, &mapped_ptr) != VK_SUCCESS) return {};
}
const size_t n = static_cast<size_t>(std::min<VkDeviceSize>(size,
(std::numeric_limits<size_t>::max)()));
return std::span<u8>{static_cast<u8 *>(mapped_ptr), n};
}
std::span<const u8> MemoryCommit::Map() const
{
if (!allocation) return {};
if (!mapped_ptr) {
void *p = nullptr;
if (vmaMapMemory(allocator, allocation, &p) != VK_SUCCESS) return {};
const_cast<MemoryCommit *>(this)->mapped_ptr = p;
}
const size_t n = static_cast<size_t>(std::min<VkDeviceSize>(size,
(std::numeric_limits<size_t>::max)()));
return std::span<const u8>{static_cast<const u8 *>(mapped_ptr), n};
}
void MemoryCommit::Unmap()
{
if (allocation && mapped_ptr) {
vmaUnmapMemory(allocator, allocation);
mapped_ptr = nullptr;
}
}
void MemoryCommit::Release() {
if (allocation && allocator) {
// Log GPU memory deallocation
if (GPU::Logging::IsActive() &&
Settings::values.gpu_log_memory_tracking.GetValue()) {
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
reinterpret_cast<uintptr_t>(memory),
static_cast<u64>(size),
0 // Memory property flags (not easily available from VMA)
Settings::values.gpu_log_memory_tracking.GetValue() &&
memory != VK_NULL_HANDLE) {
GPU::Logging::GPULogger::GetInstance().LogMemoryDeallocation(
reinterpret_cast<uintptr_t>(memory)
);
}
}
MemoryCommit::~MemoryCommit() { Release(); }
MemoryCommit::MemoryCommit(MemoryCommit &&rhs) noexcept
: allocator{std::exchange(rhs.allocator, nullptr)},
allocation{std::exchange(rhs.allocation, nullptr)},
memory{std::exchange(rhs.memory, VK_NULL_HANDLE)},
offset{std::exchange(rhs.offset, 0)},
size{std::exchange(rhs.size, 0)},
mapped_ptr{std::exchange(rhs.mapped_ptr, nullptr)} {}
MemoryCommit &MemoryCommit::operator=(MemoryCommit &&rhs) noexcept {
if (this != &rhs) {
Release();
allocator = std::exchange(rhs.allocator, nullptr);
allocation = std::exchange(rhs.allocation, nullptr);
memory = std::exchange(rhs.memory, VK_NULL_HANDLE);
offset = std::exchange(rhs.offset, 0);
size = std::exchange(rhs.size, 0);
mapped_ptr = std::exchange(rhs.mapped_ptr, nullptr);
}
return *this;
}
std::span<u8> MemoryCommit::Map()
{
if (!allocation) return {};
if (!mapped_ptr) {
if (vmaMapMemory(allocator, allocation, &mapped_ptr) != VK_SUCCESS) return {};
}
const size_t n = static_cast<size_t>(std::min<VkDeviceSize>(size,
(std::numeric_limits<size_t>::max)()));
return std::span<u8>{static_cast<u8 *>(mapped_ptr), n};
}
std::span<const u8> MemoryCommit::Map() const
{
if (!allocation) return {};
if (!mapped_ptr) {
void *p = nullptr;
if (vmaMapMemory(allocator, allocation, &p) != VK_SUCCESS) return {};
const_cast<MemoryCommit *>(this)->mapped_ptr = p;
}
const size_t n = static_cast<size_t>(std::min<VkDeviceSize>(size,
(std::numeric_limits<size_t>::max)()));
return std::span<const u8>{static_cast<const u8 *>(mapped_ptr), n};
}
void MemoryCommit::Unmap()
{
if (allocation && mapped_ptr) {
if (mapped_ptr) {
vmaUnmapMemory(allocator, allocation);
mapped_ptr = nullptr;
}
vmaFreeMemory(allocator, allocation);
}
allocation = nullptr;
allocator = nullptr;
memory = VK_NULL_HANDLE;
offset = 0;
size = 0;
}
void MemoryCommit::Release() {
if (allocation && allocator) {
// Log GPU memory deallocation
if (GPU::Logging::IsActive() &&
Settings::values.gpu_log_memory_tracking.GetValue() &&
memory != VK_NULL_HANDLE) {
GPU::Logging::GPULogger::GetInstance().LogMemoryDeallocation(
reinterpret_cast<uintptr_t>(memory)
);
}
MemoryAllocator::MemoryAllocator(const Device &device_)
: device{device_}, allocator{device.GetAllocator()},
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
buffer_image_granularity{
device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {
if (mapped_ptr) {
vmaUnmapMemory(allocator, allocation);
mapped_ptr = nullptr;
}
vmaFreeMemory(allocator, allocation);
}
allocation = nullptr;
allocator = nullptr;
memory = VK_NULL_HANDLE;
offset = 0;
size = 0;
}
MemoryAllocator::MemoryAllocator(const Device &device_)
: device{device_}, allocator{device.GetAllocator()},
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
buffer_image_granularity{
device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {
// Preserve the previous "RenderDoc small heap" trimming behavior that we had in original vma minus the heap bug
if (device.HasDebuggingToolAttached())
{
using namespace Common::Literals;
ForEachDeviceLocalHostVisibleHeap(device, [this](size_t heap_idx, VkMemoryHeap &heap) {
if (heap.size <= 256_MiB) {
for (u32 t = 0; t < properties.memoryTypeCount; ++t) {
if (properties.memoryTypes[t].heapIndex == heap_idx) {
valid_memory_types &= ~(1u << t);
}
// Preserve the previous "RenderDoc small heap" trimming behavior that we had in original vma minus the heap bug
if (device.HasDebuggingToolAttached())
{
using namespace Common::Literals;
ForEachDeviceLocalHostVisibleHeap(device, [this](size_t heap_idx, VkMemoryHeap &heap) {
if (heap.size <= 256_MiB) {
for (u32 t = 0; t < properties.memoryTypeCount; ++t) {
if (properties.memoryTypes[t].heapIndex == heap_idx) {
valid_memory_types &= ~(1u << t);
}
}
});
}
}
});
}
}
MemoryAllocator::~MemoryAllocator() = default;
MemoryAllocator::~MemoryAllocator() = default;
vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo &ci) const
{
const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
.requiredFlags = 0,
.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
.memoryTypeBits = 0,
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
.priority = 0.f,
};
VkImage handle{};
VmaAllocation allocation{};
VmaAllocationInfo alloc_info{};
vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
// Log GPU memory allocation for images
if (GPU::Logging::IsActive() &&
Settings::values.gpu_log_memory_tracking.GetValue()) {
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
reinterpret_cast<uintptr_t>(alloc_info.deviceMemory),
static_cast<u64>(alloc_info.size),
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
);
}
return vk::Image(handle, ci.usage, *device.GetLogical(), allocator, allocation,
device.GetDispatchLoader());
}
vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const {
// MESA will do memcpy() if not marked as host cached, so just force mark it for most buffers
auto const anv_flags = (usage == MemoryUsage::Stream
&& device.GetDriverID() == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA)
? VK_MEMORY_PROPERTY_HOST_CACHED_BIT : 0;
const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
.usage = MemoryUsageVma(usage),
vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo &ci) const
{
const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
.requiredFlags = 0,
.preferredFlags = MemoryUsagePreferredVmaFlags(usage) | anv_flags,
.memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types,
.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
.memoryTypeBits = 0,
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
.priority = 0.f,
};
};
VkBuffer handle{};
VmaAllocationInfo alloc_info{};
VmaAllocation allocation{};
VkMemoryPropertyFlags property_flags{};
VkImage handle{};
VmaAllocation allocation{};
VmaAllocationInfo alloc_info{};
vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags);
// Log GPU memory allocation for buffers
if (GPU::Logging::IsActive() &&
Settings::values.gpu_log_memory_tracking.GetValue()) {
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
reinterpret_cast<uintptr_t>(alloc_info.deviceMemory),
static_cast<u64>(alloc_info.size),
property_flags
);
}
u8 *data = reinterpret_cast<u8 *>(alloc_info.pMappedData);
const std::span<u8> mapped_data = data ? std::span<u8>{data, ci.size} : std::span<u8>{};
const bool is_coherent = (property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data,
is_coherent,
device.GetDispatchLoader());
// Log GPU memory allocation for images
if (GPU::Logging::IsActive() &&
Settings::values.gpu_log_memory_tracking.GetValue()) {
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
reinterpret_cast<uintptr_t>(alloc_info.deviceMemory),
static_cast<u64>(alloc_info.size),
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
);
}
MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements &reqs, MemoryUsage usage)
{
const auto vma_usage = MemoryUsageVma(usage);
VmaAllocationCreateInfo ci{};
ci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage);
ci.usage = vma_usage;
ci.memoryTypeBits = reqs.memoryTypeBits & valid_memory_types;
ci.requiredFlags = 0;
ci.preferredFlags = MemoryUsagePreferredVmaFlags(usage);
return vk::Image(handle, ci.usage, *device.GetLogical(), allocator, allocation,
device.GetDispatchLoader());
}
VmaAllocation a{};
VmaAllocationInfo info{};
vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const {
// MESA will do memcpy() if not marked as host cached, so just force mark it for most buffers
auto const anv_flags = (usage == MemoryUsage::Stream
&& device.GetDriverID() == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA)
? VK_MEMORY_PROPERTY_HOST_CACHED_BIT : 0;
const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
.usage = MemoryUsageVma(usage),
.requiredFlags = 0,
.preferredFlags = MemoryUsagePreferredVmaFlags(usage) | anv_flags,
.memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types,
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
.priority = 0.f,
};
VkResult res = vmaAllocateMemory(allocator, &reqs, &ci, &a, &info);
VkBuffer handle{};
VmaAllocationInfo alloc_info{};
VmaAllocation allocation{};
VkMemoryPropertyFlags property_flags{};
if (res != VK_SUCCESS) {
// Relax 1: drop budget constraint
auto ci2 = ci;
ci2.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
res = vmaAllocateMemory(allocator, &reqs, &ci2, &a, &info);
vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags);
// Relax 2: if we preferred DEVICE_LOCAL, drop that preference
if (res != VK_SUCCESS && (ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
auto ci3 = ci2;
ci3.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
res = vmaAllocateMemory(allocator, &reqs, &ci3, &a, &info);
}
}
vk::Check(res);
return MemoryCommit(allocator, a, info);
// Log GPU memory allocation for buffers
if (GPU::Logging::IsActive() &&
Settings::values.gpu_log_memory_tracking.GetValue()) {
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
reinterpret_cast<uintptr_t>(alloc_info.deviceMemory),
static_cast<u64>(alloc_info.size),
property_flags
);
}
MemoryCommit MemoryAllocator::Commit(const vk::Buffer &buffer, MemoryUsage usage) {
// Allocate memory appropriate for this buffer automatically
const auto vma_usage = MemoryUsageVma(usage);
u8 *data = reinterpret_cast<u8 *>(alloc_info.pMappedData);
const std::span<u8> mapped_data = data ? std::span<u8>{data, ci.size} : std::span<u8>{};
const bool is_coherent = (property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
VmaAllocationCreateInfo ci{};
ci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage);
ci.usage = vma_usage;
ci.requiredFlags = 0;
ci.preferredFlags = MemoryUsagePreferredVmaFlags(usage);
ci.pool = VK_NULL_HANDLE;
ci.pUserData = nullptr;
ci.priority = 0.0f;
const vk::MemoryLocation location{
.memory = alloc_info.deviceMemory,
.offset = alloc_info.offset,
.memory_type = alloc_info.memoryType,
};
return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data, is_coherent,
location, device.GetDispatchLoader());
}
const VkBuffer raw = *buffer;
vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage,
VkDeviceSize min_alignment) const {
if (min_alignment <= 1) {
return CreateBuffer(ci, usage);
}
VkMemoryPropertyFlags anv_flags = 0;
if (usage == MemoryUsage::Stream &&
device.GetDriverID() == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA) {
anv_flags = VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
}
u32 memory_type_bits = valid_memory_types;
if (usage == MemoryUsage::Stream) {
memory_type_bits = 0u;
}
const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
.usage = MemoryUsageVma(usage),
.requiredFlags = 0,
.preferredFlags = MemoryUsagePreferredVmaFlags(usage) | anv_flags,
.memoryTypeBits = memory_type_bits,
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
.priority = 0.f,
};
VmaAllocation a{};
VmaAllocationInfo info{};
VkBuffer handle{};
VmaAllocationInfo alloc_info{};
VmaAllocation allocation{};
VkMemoryPropertyFlags property_flags{};
// Let VMA infer memory requirements from the buffer
VkResult res = vmaAllocateMemoryForBuffer(allocator, raw, &ci, &a, &info);
vk::Check(vmaCreateBufferWithAlignment(allocator, &ci, &alloc_ci, min_alignment, &handle,
&allocation, &alloc_info));
vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags);
if (res != VK_SUCCESS) {
auto ci2 = ci;
ci2.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
res = vmaAllocateMemoryForBuffer(allocator, raw, &ci2, &a, &info);
u8 *data = reinterpret_cast<u8 *>(alloc_info.pMappedData);
std::span<u8> mapped_data{};
if (data) {
mapped_data = std::span<u8>{data, ci.size};
}
const bool is_coherent = (property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
if (res != VK_SUCCESS && (ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
auto ci3 = ci2;
ci3.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
res = vmaAllocateMemoryForBuffer(allocator, raw, &ci3, &a, &info);
}
const vk::MemoryLocation location{
.memory = alloc_info.deviceMemory,
.offset = alloc_info.offset,
.memory_type = alloc_info.memoryType,
};
return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data, is_coherent,
location, device.GetDispatchLoader());
}
MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements &reqs, MemoryUsage usage)
{
const auto vma_usage = MemoryUsageVma(usage);
VmaAllocationCreateInfo ci{};
ci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage);
ci.usage = vma_usage;
ci.memoryTypeBits = reqs.memoryTypeBits & valid_memory_types;
ci.requiredFlags = 0;
ci.preferredFlags = MemoryUsagePreferredVmaFlags(usage);
VmaAllocation a{};
VmaAllocationInfo info{};
VkResult res = vmaAllocateMemory(allocator, &reqs, &ci, &a, &info);
if (res != VK_SUCCESS) {
// Relax 1: drop budget constraint
auto ci2 = ci;
ci2.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
res = vmaAllocateMemory(allocator, &reqs, &ci2, &a, &info);
// Relax 2: if we preferred DEVICE_LOCAL, drop that preference
if (res != VK_SUCCESS && (ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
auto ci3 = ci2;
ci3.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
res = vmaAllocateMemory(allocator, &reqs, &ci3, &a, &info);
}
vk::Check(res);
vk::Check(vmaBindBufferMemory2(allocator, a, 0, raw, nullptr));
return MemoryCommit(allocator, a, info);
}
vk::Check(res);
return MemoryCommit(allocator, a, info);
}
MemoryCommit MemoryAllocator::Commit(const vk::Buffer &buffer, MemoryUsage usage) {
// Allocate memory appropriate for this buffer automatically
const auto vma_usage = MemoryUsageVma(usage);
VmaAllocationCreateInfo ci{};
ci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage);
ci.usage = vma_usage;
ci.requiredFlags = 0;
ci.preferredFlags = MemoryUsagePreferredVmaFlags(usage);
ci.pool = VK_NULL_HANDLE;
ci.pUserData = nullptr;
ci.priority = 0.0f;
const VkBuffer raw = *buffer;
VmaAllocation a{};
VmaAllocationInfo info{};
// Let VMA infer memory requirements from the buffer
VkResult res = vmaAllocateMemoryForBuffer(allocator, raw, &ci, &a, &info);
if (res != VK_SUCCESS) {
auto ci2 = ci;
ci2.flags &= ~VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
res = vmaAllocateMemoryForBuffer(allocator, raw, &ci2, &a, &info);
if (res != VK_SUCCESS && (ci.preferredFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
auto ci3 = ci2;
ci3.preferredFlags &= ~VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
res = vmaAllocateMemoryForBuffer(allocator, raw, &ci3, &a, &info);
}
}
vk::Check(res);
vk::Check(vmaBindBufferMemory2(allocator, a, 0, raw, nullptr));
return MemoryCommit(allocator, a, info);
}
} // namespace Vulkan
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -107,6 +107,9 @@ namespace Vulkan {
vk::Buffer CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const;
vk::Buffer CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage,
VkDeviceSize min_alignment) const;
/**
* Commits a memory with the specified requirements.
*
@@ -229,6 +229,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkGetPipelineExecutableStatisticsKHR);
X(vkGetSemaphoreCounterValue);
X(vkMapMemory);
X(vkQueueBindSparse);
X(vkQueueSubmit);
X(vkQueueSubmit2);
X(vkResetFences);
+22 -3
View File
@@ -345,6 +345,7 @@ struct DeviceDispatch : InstanceDispatch {
PFN_vkGetQueryPoolResults vkGetQueryPoolResults{};
PFN_vkGetSemaphoreCounterValue vkGetSemaphoreCounterValue{};
PFN_vkMapMemory vkMapMemory{};
PFN_vkQueueBindSparse vkQueueBindSparse{};
PFN_vkQueueSubmit vkQueueSubmit{};
PFN_vkQueueSubmit2 vkQueueSubmit2{};
PFN_vkResetFences vkResetFences{};
@@ -740,13 +741,20 @@ private:
const DeviceDispatch* dld = nullptr;
};
struct MemoryLocation {
VkDeviceMemory memory{};
VkDeviceSize offset{};
u32 memory_type{};
};
class Buffer {
public:
explicit Buffer(VkBuffer handle_, VkDevice owner_, VmaAllocator allocator_,
VmaAllocation allocation_, std::span<u8> mapped_, bool is_coherent_,
const DeviceDispatch& dld_) noexcept
MemoryLocation location_, const DeviceDispatch& dld_) noexcept
: handle{handle_}, owner{owner_}, allocator{allocator_},
allocation{allocation_}, mapped{mapped_}, is_coherent{is_coherent_}, dld{&dld_} {}
allocation{allocation_}, mapped{mapped_}, location{location_},
is_coherent{is_coherent_}, dld{&dld_} {}
Buffer() = default;
Buffer(const Buffer&) = delete;
@@ -754,7 +762,7 @@ public:
Buffer(Buffer&& rhs) noexcept
: handle{std::exchange(rhs.handle, VkBuffer{})}, owner{rhs.owner}, allocator{rhs.allocator},
allocation{rhs.allocation}, mapped{rhs.mapped},
allocation{rhs.allocation}, mapped{rhs.mapped}, location{rhs.location},
is_coherent{rhs.is_coherent}, dld{rhs.dld} {}
Buffer& operator=(Buffer&& rhs) noexcept {
@@ -764,6 +772,7 @@ public:
allocator = rhs.allocator;
allocation = rhs.allocation;
mapped = rhs.mapped;
location = rhs.location;
is_coherent = rhs.is_coherent;
dld = rhs.dld;
return *this;
@@ -811,6 +820,10 @@ public:
void SetObjectNameEXT(const char* name) const;
MemoryLocation Location() const noexcept {
return location;
}
private:
void Release() const noexcept;
@@ -819,6 +832,7 @@ private:
VmaAllocator allocator = nullptr;
VmaAllocation allocation = nullptr;
std::span<u8> mapped = {};
MemoryLocation location{};
bool is_coherent = false;
const DeviceDispatch* dld = nullptr;
};
@@ -843,6 +857,11 @@ public:
return dld->vkQueueSubmit2(queue, submit_infos.size(), submit_infos.data(), fence);
}
VkResult BindSparse(Span<VkBindSparseInfo> bind_infos,
VkFence fence = VK_NULL_HANDLE) const noexcept {
return dld->vkQueueBindSparse(queue, bind_infos.size(), bind_infos.data(), fence);
}
VkResult Present(const VkPresentInfoKHR& present_info) const noexcept {
return dld->vkQueuePresentKHR(queue, &present_info);
}
+1 -1
View File
@@ -4796,6 +4796,6 @@ void VolumeButton::ResetMultiplier() {
#endif
#if !defined(QT_STATICPLUGIN) || defined(__APPLE__)
#define VMA_IMPLEMENTATION
#define VMA_IMPLEMENTATION 1
#include "video_core/vulkan_common/vma.h"
#endif
+1 -1
View File
@@ -65,7 +65,7 @@ else()
endif()
# update cached cpmfile content
string(JSON cpmfile SET "${cpmfile}" "${key}" "${new_object}")
string(JSON cpmfile SET "${cpmfile}" "${KEY}" "${new_object}")
# write cached cpmfile
get_cpmfile_path(file)