mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
Prefer VRAM for GPU-only Metal memory and mark the GL import dedicated
Indium and QuartzCore took the first memory type a resource allowed. The Vulkan spec orders a type with no flags before every DEVICE_LOCAL type, and NVIDIA exposes such system-RAM types, so on NVIDIA private buffers, textures and framebufferOnly drawables could land in system RAM. They now take the first allowed DEVICE_LOCAL type and fall back to the old choice; shared and managed resources are unchanged. The drawable's exported image is a dedicated Vulkan allocation, but the OpenGL import never set GL_DEDICATED_MEMORY_OBJECT_EXT, which EXT_external_objects requires. Mesa ignores it; it is the suspected cause of the black screen on NVIDIA in issue #3. The import's GL errors were never checked (reportGLErrors is compiled out), so a failure was silent. /tmp/metal-device.log now lists the selected device's memory heaps and types, the memory type chosen per resource kind (with the old choice and changed=yes/no), and the result of the first drawable and semaphore imports into OpenGL (later ones only on failure). Practice match on the RX 6600: all choices changed=no, import dedicated=yes with no GL errors, semaphore ok, match=yes. Not run on NVIDIA. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
6d6159b594
commit
1c30c99e51
3 files changed
+443
-54
No files matched your search
+20
-1
@@ -158,7 +158,7 @@ On Hyprland, also check that direct scanout is off (`hyprctl getoption render:di
|
||||
|
||||
## Hybrid graphics laptops (AMD/Intel iGPU + NVIDIA)
|
||||
|
||||
Metal renders with Vulkan, and each finished frame is handed to an OpenGL (EGL) context that presents it. The hand-off uses opaque file descriptors, which only work when Vulkan and OpenGL run on the same GPU. Indium takes the first Vulkan device the loader lists, and on a hybrid laptop both that device and the EGL context are normally the integrated GPU, so the discrete GPU stays idle. A report in [issue #3](https://github.com/lucannez64/darling-lol/issues/3) (Ryzen 9 5900HS, RTX 3060 Laptop) measured about 70 FPS in ARAM; which GPU rendered that match is not known. **Running on an NVIDIA GPU is not validated.**
|
||||
Metal renders with Vulkan, and each finished frame is handed to an OpenGL (EGL) context that presents it. The hand-off uses opaque file descriptors, which only work when Vulkan and OpenGL run on the same GPU. Indium takes the first Vulkan device the loader lists, and on a hybrid laptop both that device and the EGL context are normally the integrated GPU, so the discrete GPU stays idle. A report in [issue #3](https://github.com/lucannez64/darling-lol/issues/3) (Ryzen 9 5900HS, RTX 3060 Laptop) measured 60-70 FPS in ARAM on the integrated GPU; on the NVIDIA GPU the game showed a black screen. **Running on an NVIDIA GPU is not validated.**
|
||||
|
||||
### Run on the discrete GPU
|
||||
|
||||
@@ -185,6 +185,25 @@ Each process that uses Metal appends to guest `/tmp/metal-device.log` (host `~/.
|
||||
|
||||
`MISMATCH` on the presenting line means Vulkan and OpenGL are on different GPUs. `match=unknown` means the OpenGL driver reports no device UUID. Include this file with performance reports.
|
||||
|
||||
The log also lists the selected device's memory heaps and types, and one line the first time each kind of resource (`buffer`, `texture`, `drawable`, `drawable-internal`) gets a storage mode and memory type:
|
||||
|
||||
```
|
||||
[MetalDevice] pid=… memory heap=0 size=6144MiB deviceLocal=yes
|
||||
[MetalDevice] pid=… memory type=0 flags=0x0(none) heap=1
|
||||
[MetalDevice] pid=… memory alloc kind=texture storage=private typeBits=0x… type=1 flags=0x1(DEVICE_LOCAL) heap=0 heapDeviceLocal=yes firstFit=0 flags=0x0(none) heap=1 heapDeviceLocal=no changed=yes
|
||||
```
|
||||
|
||||
Resources only the GPU uses (`private`, `memoryless`, and `framebufferOnly` drawables) take the first allowed `DEVICE_LOCAL` type. Before 2026-10-16 they took the first allowed type (`firstFit`), which on NVIDIA can be system RAM; `changed=yes` marks where the two differ. `shared` and `managed` resources keep the first host-visible type. On the RX 6600 (RADV) type 0 is `DEVICE_LOCAL`, so nothing changes there.
|
||||
|
||||
The first import of a drawable into the presenting OpenGL context, and the first semaphore import, are logged too; later ones only when they fail (at most five):
|
||||
|
||||
```
|
||||
[MetalDevice] pid=… drawable GL import (first) dedicated=yes size=… 1920x1080 memoryType=1 tiling=linear glTiling=linear dedicated_error=0x0 import_error=0x0 setup_error=0x0 storage_error=0x0 query_error=0x0 ok
|
||||
[MetalDevice] pid=… drawable GL semaphore (first) import_error=0x0 wait_error=0x0 ok (checked for the first 600 imports)
|
||||
```
|
||||
|
||||
A non-zero error or `FAILED` means the frames cannot reach the window. Before 2026-10-16 the import did not mark the memory as dedicated, which the extension requires; Mesa ignores it, and it is the suspected cause of the NVIDIA black screen (not yet confirmed). If the game shows a black screen on NVIDIA, report it with this file.
|
||||
|
||||
### Host-side checks
|
||||
|
||||
These are host settings, not game settings. Run on AC power, and use a performance power profile (`powerprofilesctl set performance`, or `asusctl profile -P Performance` on ASUS laptops). On the tested desktop the game is limited mostly by its own main thread on the CPU, not by the GPU (see [status](STATUS.md#game-fps-2026-10-1314)), so laptop CPU power limits matter as much as the GPU choice.
|
||||
|
||||
+216
-16
@@ -7105,13 +7105,14 @@ index 5ad375f8..c8e83814 100644
|
||||
+- (BOOL) _treeHasAnimations;
|
||||
@end
|
||||
diff --git a/QuartzCore/CAMetalDrawable.mm b/QuartzCore/CAMetalDrawable.mm
|
||||
index d6f86b31..4d08ce26 100644
|
||||
index d6f86b31..2bdc198d 100644
|
||||
--- a/QuartzCore/CAMetalDrawable.mm
|
||||
+++ b/QuartzCore/CAMetalDrawable.mm
|
||||
@@ -31,6 +31,12 @@
|
||||
@@ -31,6 +31,13 @@
|
||||
#include <OpenGL/glext.h>
|
||||
#include <indium/indium.private.hpp>
|
||||
|
||||
+#include <atomic>
|
||||
+#include <cstdio>
|
||||
+#include <cstring>
|
||||
+#include <mutex>
|
||||
@@ -7121,7 +7122,7 @@ index d6f86b31..4d08ce26 100644
|
||||
namespace DynamicVK = Indium::DynamicVK;
|
||||
|
||||
static void reportGLErrors(void) {
|
||||
@@ -50,8 +56,86 @@ static void reportGLErrors(void) {
|
||||
@@ -50,8 +57,98 @@ static void reportGLErrors(void) {
|
||||
namespace Indium::DynamicVK {
|
||||
static DynamicFunction<PFN_vkGetSemaphoreFdKHR> vkGetSemaphoreFdKHR("vkGetSemaphoreFdKHR");
|
||||
static DynamicFunction<PFN_vkGetMemoryFdKHR> vkGetMemoryFdKHR("vkGetMemoryFdKHR");
|
||||
@@ -7138,6 +7139,21 @@ index d6f86b31..4d08ce26 100644
|
||||
+// silently break. Record which GL renderer actually presents and whether its
|
||||
+// device UUID matches the Vulkan device's, once per process, in
|
||||
+// /tmp/metal-device.log (alongside the device list Indium logs at startup).
|
||||
+// Clears pending GL errors (bounded: without a current context glGetError may never
|
||||
+// report GL_NO_ERROR).
|
||||
+static void drainGLErrors() {
|
||||
+ for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+// Same line format as Indium's device log.
|
||||
+static void appendDeviceLog(const std::string& line) {
|
||||
+ if (FILE* log = fopen("/tmp/metal-device.log", "a")) {
|
||||
+ fprintf(log, "[MetalDevice] pid=%d %s\n", getpid(), line.c_str());
|
||||
+ fclose(log);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static std::string darlingUuidString(const GLubyte* uuid) {
|
||||
+ static const char* digits = "0123456789abcdef";
|
||||
+ std::string out;
|
||||
@@ -7198,17 +7214,104 @@ index d6f86b31..4d08ce26 100644
|
||||
+ line += " match=unknown";
|
||||
+ }
|
||||
+
|
||||
+ if (FILE* log = fopen("/tmp/metal-device.log", "a")) {
|
||||
+ fprintf(log, "[MetalDevice] pid=%d %s\n", getpid(), line.c_str());
|
||||
+ fclose(log);
|
||||
+ }
|
||||
+ appendDeviceLog(line);
|
||||
+ });
|
||||
+}
|
||||
+
|
||||
//
|
||||
// helper classes
|
||||
//
|
||||
@@ -161,6 +245,11 @@ CAMetalDrawableTexture::CAMetalDrawableTexture(CGSize size, Indium::PixelFormat
|
||||
@@ -129,13 +226,39 @@ public:
|
||||
// texture
|
||||
//
|
||||
|
||||
-static size_t findSharedMemory(const VkMemoryRequirements& reqs, std::shared_ptr<Indium::PrivateDevice> device, bool framebufferOnly) {
|
||||
+static std::string memoryFlagsString(VkMemoryPropertyFlags flags) {
|
||||
+ char buffer[16];
|
||||
+ snprintf(buffer, sizeof(buffer), "0x%x(", (unsigned) flags);
|
||||
+ std::string out = buffer;
|
||||
+ if (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) out += "DEVICE_LOCAL|";
|
||||
+ if (flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) out += "HOST_VISIBLE|";
|
||||
+ if (flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) out += "HOST_COHERENT|";
|
||||
+ if (flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) out += "HOST_CACHED|";
|
||||
+ if (flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) out += "LAZILY_ALLOCATED|";
|
||||
+ if (flags & VK_MEMORY_PROPERTY_PROTECTED_BIT) out += "PROTECTED|";
|
||||
+ if (flags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD) out += "DEVICE_COHERENT_AMD|";
|
||||
+ if (flags & VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD) out += "DEVICE_UNCACHED_AMD|";
|
||||
+ if (out.back() == '|') out.back() = ')';
|
||||
+ else out += "none)";
|
||||
+ return out;
|
||||
+}
|
||||
+
|
||||
+// Vulkan orders memory types so a flag-less type (system RAM, which NVIDIA
|
||||
+// exposes) precedes every DEVICE_LOCAL type, and first fit put framebufferOnly
|
||||
+// drawables there. Both images are only touched by the GPU then -- the internal
|
||||
+// one is imported by the presenting OpenGL context, which runs on the same GPU
|
||||
+// (see reportGpuDeviceMatch) -- so they take the first allowed DEVICE_LOCAL type,
|
||||
+// falling back to first fit. Without framebufferOnly they still need host-visible,
|
||||
+// coherent memory and keep first fit. On RADV type 0 is DEVICE_LOCAL: unchanged.
|
||||
+static size_t findSharedMemory(const VkMemoryRequirements& reqs, std::shared_ptr<Indium::PrivateDevice> device, bool framebufferOnly, bool internal) {
|
||||
+ const auto& memory = device->memoryProperties();
|
||||
+ size_t firstFit = SIZE_MAX;
|
||||
size_t targetIndex = SIZE_MAX;
|
||||
|
||||
- for (size_t i = 0; i < device->memoryProperties().memoryTypeCount; ++i) {
|
||||
- const auto& type = device->memoryProperties().memoryTypes[i];
|
||||
+ for (size_t i = 0; i < memory.memoryTypeCount; ++i) {
|
||||
+ const auto& type = memory.memoryTypes[i];
|
||||
|
||||
- if ((reqs.memoryTypeBits & (1 << i)) == 0) {
|
||||
+ if ((reqs.memoryTypeBits & (1u << i)) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -147,9 +270,42 @@ static size_t findSharedMemory(const VkMemoryRequirements& reqs, std::shared_ptr
|
||||
continue;
|
||||
}
|
||||
|
||||
- // okay, this is good enough
|
||||
- targetIndex = i;
|
||||
- break;
|
||||
+ if (firstFit == SIZE_MAX) {
|
||||
+ firstFit = i;
|
||||
+ }
|
||||
+
|
||||
+ if (!framebufferOnly || (type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0) {
|
||||
+ targetIndex = i;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (targetIndex == SIZE_MAX) {
|
||||
+ targetIndex = firstFit;
|
||||
+ }
|
||||
+ if (targetIndex == SIZE_MAX) {
|
||||
+ return targetIndex;
|
||||
+ }
|
||||
+
|
||||
+ // Once per (image, framebufferOnly, type) in the process, in Indium's device log.
|
||||
+ static std::atomic<uint64_t> logged[2] {};
|
||||
+ const size_t bit = ((internal ? 2u : 0u) + (framebufferOnly ? 1u : 0u)) * 32 + targetIndex;
|
||||
+ const uint64_t mask = uint64_t(1) << (bit & 63);
|
||||
+ if ((logged[bit >> 6].load(std::memory_order_relaxed) & mask) == 0 && (logged[bit >> 6].fetch_or(mask) & mask) == 0) {
|
||||
+ auto describe = [&](size_t index) {
|
||||
+ const auto& type = memory.memoryTypes[index];
|
||||
+ return std::to_string(index) + " flags=" + memoryFlagsString(type.propertyFlags)
|
||||
+ + " heap=" + std::to_string(type.heapIndex)
|
||||
+ + " heapDeviceLocal=" + ((memory.memoryHeaps[type.heapIndex].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) ? "yes" : "no");
|
||||
+ };
|
||||
+ char typeBits[16];
|
||||
+ snprintf(typeBits, sizeof(typeBits), "0x%08x", (unsigned) reqs.memoryTypeBits);
|
||||
+ appendDeviceLog(std::string("memory alloc kind=") + (internal ? "drawable-internal" : "drawable")
|
||||
+ + " storage=" + (framebufferOnly ? "framebufferOnly" : "hostVisible")
|
||||
+ + " typeBits=" + typeBits
|
||||
+ + " type=" + describe(targetIndex)
|
||||
+ + " firstFit=" + describe(firstFit)
|
||||
+ + (targetIndex != firstFit ? " changed=yes" : " changed=no"));
|
||||
}
|
||||
|
||||
return targetIndex;
|
||||
@@ -161,6 +317,11 @@ CAMetalDrawableTexture::CAMetalDrawableTexture(CGSize size, Indium::PixelFormat
|
||||
_pixelFormat(pixelFormat),
|
||||
_framebufferOnly(framebufferOnly)
|
||||
{
|
||||
@@ -7220,18 +7323,93 @@ index d6f86b31..4d08ce26 100644
|
||||
//
|
||||
// create the images
|
||||
//
|
||||
@@ -401,6 +490,10 @@ CAMetalDrawableTexture::CAMetalDrawableTexture(CGSize size, Indium::PixelFormat
|
||||
@@ -219,7 +380,7 @@ CAMetalDrawableTexture::CAMetalDrawableTexture(CGSize size, Indium::PixelFormat
|
||||
|
||||
DynamicVK::vkGetImageMemoryRequirements(_device->device(), _image, &reqs);
|
||||
|
||||
- size_t targetIndex = findSharedMemory(reqs, _device, _framebufferOnly);
|
||||
+ size_t targetIndex = findSharedMemory(reqs, _device, _framebufferOnly, false);
|
||||
|
||||
if (targetIndex == SIZE_MAX) {
|
||||
throw std::runtime_error("No suitable memory region found for image");
|
||||
@@ -242,7 +403,7 @@ CAMetalDrawableTexture::CAMetalDrawableTexture(CGSize size, Indium::PixelFormat
|
||||
|
||||
DynamicVK::vkGetImageMemoryRequirements(_device->device(), _internalImage, &reqs);
|
||||
|
||||
- targetIndex = findSharedMemory(reqs, _device, _framebufferOnly);
|
||||
+ targetIndex = findSharedMemory(reqs, _device, _framebufferOnly, true);
|
||||
|
||||
if (targetIndex == SIZE_MAX) {
|
||||
throw std::runtime_error("No suitable memory region found for internal image");
|
||||
@@ -380,16 +541,26 @@ CAMetalDrawableTexture::CAMetalDrawableTexture(CGSize size, Indium::PixelFormat
|
||||
abort();
|
||||
}
|
||||
|
||||
- reportGLErrors();
|
||||
+ // reportGLErrors() is compiled out, so a failed import used to be silent (a black
|
||||
+ // window). Clear errors left by earlier calls; the import is checked below.
|
||||
+ drainGLErrors();
|
||||
|
||||
// import the memory into OpenGL
|
||||
glCreateMemoryObjectsEXT(1, &_memoryObject);
|
||||
reportGLErrors();
|
||||
|
||||
+ // _internalMemory is a dedicated allocation (VkMemoryDedicatedAllocateInfo above),
|
||||
+ // and EXT_external_objects requires DEDICATED_MEMORY_OBJECT_EXT to be TRUE when the
|
||||
+ // memory was created as one. Mesa ignores the flag; NVIDIA enforces it. It has to be
|
||||
+ // set before the import, after which the memory object is immutable.
|
||||
+ GLint dedicated = GL_TRUE;
|
||||
+ glMemoryObjectParameterivEXT(_memoryObject, GL_DEDICATED_MEMORY_OBJECT_EXT, &dedicated);
|
||||
+ const GLenum dedicatedError = glGetError();
|
||||
+
|
||||
// this transfers ownership of the FD to OpenGL
|
||||
glImportMemoryFdEXT(_memoryObject, reqs.size, GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd);
|
||||
fd = -1;
|
||||
- reportGLErrors();
|
||||
+ const GLenum importError = glGetError();
|
||||
|
||||
GLint prevTex = 0;
|
||||
glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevTex);
|
||||
@@ -401,10 +572,36 @@ CAMetalDrawableTexture::CAMetalDrawableTexture(CGSize size, Indium::PixelFormat
|
||||
glTextureParameteri(_textureID, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTextureParameteri(_textureID, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTextureParameteri(_textureID, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
- reportGLErrors();
|
||||
+ // A Metal layer is opaque: macOS ignores the drawable's alpha channel. The game's blended effects lower destination
|
||||
+ // alpha as a side effect, and the layer compositor (CARenderer: blending plus an alpha test against 0, over a
|
||||
+ // cleared-to-black window) then showed those regions as black quads. Always present alpha = 1.
|
||||
+ glTextureParameteri(_textureID, GL_TEXTURE_SWIZZLE_A, GL_ONE);
|
||||
reportGLErrors();
|
||||
+ const GLenum setupError = glGetError();
|
||||
|
||||
glTextureStorageMem2DEXT(_textureID, 1, GL_RGBA8, _size.width, _size.height, _memoryObject, 0);
|
||||
@@ -605,35 +698,16 @@ void CAMetalDrawableActual::present() {
|
||||
- reportGLErrors();
|
||||
+ const GLenum storageError = glGetError();
|
||||
+
|
||||
+ // The first import in the process is always logged, so a working run on a new
|
||||
+ // driver shows up too; after that only failed imports, at most five.
|
||||
+ static std::atomic<unsigned> importCount {0};
|
||||
+ static std::atomic<unsigned> importFailures {0};
|
||||
+ const bool failed = dedicatedError != GL_NO_ERROR || importError != GL_NO_ERROR || setupError != GL_NO_ERROR || storageError != GL_NO_ERROR;
|
||||
+ const bool first = importCount.fetch_add(1, std::memory_order_relaxed) == 0;
|
||||
+ if (first || (failed && importFailures.fetch_add(1, std::memory_order_relaxed) < 5)) {
|
||||
+ GLint dedicatedReadBack = -1;
|
||||
+ GLint tiling = 0;
|
||||
+ glGetMemoryObjectParameterivEXT(_memoryObject, GL_DEDICATED_MEMORY_OBJECT_EXT, &dedicatedReadBack);
|
||||
+ glGetTextureParameteriv(_textureID, GL_TEXTURE_TILING_EXT, &tiling);
|
||||
+ const GLenum queryError = glGetError();
|
||||
+ char line[512];
|
||||
+ snprintf(line, sizeof(line), "drawable GL import%s dedicated=%s size=%llu %dx%d memoryType=%zu tiling=linear glTiling=%s"
|
||||
+ " dedicated_error=0x%x import_error=0x%x setup_error=0x%x storage_error=0x%x query_error=0x%x%s",
|
||||
+ first ? " (first)" : "", dedicatedReadBack == GL_TRUE ? "yes" : (dedicatedReadBack == -1 ? "unknown" : "no"),
|
||||
+ (unsigned long long) reqs.size, (int) _size.width, (int) _size.height, targetIndex,
|
||||
+ tiling == GL_LINEAR_TILING_EXT ? "linear" : (tiling == GL_OPTIMAL_TILING_EXT ? "optimal" : "unknown"),
|
||||
+ dedicatedError, importError, setupError, storageError, queryError, failed ? " FAILED" : " ok");
|
||||
+ appendDeviceLog(line);
|
||||
+ }
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, prevTex);
|
||||
};
|
||||
@@ -605,35 +802,16 @@ void CAMetalDrawableActual::present() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7242,16 +7420,16 @@ index d6f86b31..4d08ce26 100644
|
||||
- if (_semaphore) {
|
||||
- int fd = -1;
|
||||
- VkSemaphoreGetFdInfoKHR info {};
|
||||
-
|
||||
- info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
|
||||
- info.semaphore = _semaphore->semaphore;
|
||||
- info.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
|
||||
+ // present() may run on the deferred encoding worker. The window's
|
||||
+ // EGL context remains current on the main thread, so attempting to
|
||||
+ // import a semaphore here fails and leaks the exported syncobj FD.
|
||||
+ // Import it in synchronizeRender(), with the compositor context
|
||||
+ // current, immediately before the GPU-side wait.
|
||||
|
||||
- info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
|
||||
- info.semaphore = _semaphore->semaphore;
|
||||
- info.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
|
||||
-
|
||||
- if (DynamicVK::vkGetSemaphoreFdKHR(_semaphore->device->device(), &info, &fd) != VK_SUCCESS) {
|
||||
- // TODO
|
||||
- abort();
|
||||
@@ -7273,7 +7451,7 @@ index d6f86b31..4d08ce26 100644
|
||||
}
|
||||
}
|
||||
|
||||
@@ -702,9 +776,24 @@ void CAMetalDrawableActual::reset() {
|
||||
@@ -702,9 +880,46 @@ void CAMetalDrawableActual::reset() {
|
||||
};
|
||||
|
||||
void CAMetalDrawableActual::synchronizeRender() {
|
||||
@@ -7281,6 +7459,11 @@ index d6f86b31..4d08ce26 100644
|
||||
- GLenum layout = GL_LAYOUT_GENERAL_EXT;
|
||||
- glWaitSemaphoreEXT(_glSemaphore, 0, NULL, 1, &tex, &layout);
|
||||
+ if (!_semaphore) return;
|
||||
+ // GL errors of the import and wait are checked for the first imports of the
|
||||
+ // process only: glGetError can stall a threaded GL driver, and this runs every frame.
|
||||
+ static std::atomic<unsigned> checkedImports {0};
|
||||
+ bool check = false;
|
||||
+ GLenum importError = GL_NO_ERROR;
|
||||
+ if (_glSemaphore == 0) {
|
||||
+ // prepareRender() has made the layer's GL context current on this
|
||||
+ // thread. Never try to steal it from the submission worker.
|
||||
@@ -7292,12 +7475,29 @@ index d6f86b31..4d08ce26 100644
|
||||
+ info.semaphore = _semaphore->semaphore;
|
||||
+ info.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
|
||||
+ if (DynamicVK::vkGetSemaphoreFdKHR(_semaphore->device->device(), &info, &fd) != VK_SUCCESS) abort();
|
||||
+ check = checkedImports.fetch_add(1, std::memory_order_relaxed) < 600;
|
||||
+ if (check) drainGLErrors();
|
||||
+ glImportSemaphoreFdEXT(_glSemaphore, GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd);
|
||||
+ // A successful import transfers the native FD to the GL driver.
|
||||
+ if (check) importError = glGetError();
|
||||
+ }
|
||||
+ GLuint tex = _texture->glTexture();
|
||||
+ GLenum layout = GL_LAYOUT_GENERAL_EXT;
|
||||
+ glWaitSemaphoreEXT(_glSemaphore, 0, NULL, 1, &tex, &layout);
|
||||
+ if (check) {
|
||||
+ // One line on first use, then only failures (at most five).
|
||||
+ const GLenum waitError = glGetError();
|
||||
+ const bool failed = importError != GL_NO_ERROR || waitError != GL_NO_ERROR;
|
||||
+ static std::atomic<bool> loggedFirst {false};
|
||||
+ static std::atomic<unsigned> failures {0};
|
||||
+ const bool first = !loggedFirst.exchange(true, std::memory_order_relaxed);
|
||||
+ if (first || (failed && failures.fetch_add(1, std::memory_order_relaxed) < 5)) {
|
||||
+ char line[192];
|
||||
+ snprintf(line, sizeof(line), "drawable GL semaphore%s import_error=0x%x wait_error=0x%x%s (checked for the first 600 imports)",
|
||||
+ first ? " (first)" : "", importError, waitError, failed ? " FAILED" : " ok");
|
||||
+ appendDeviceLog(line);
|
||||
+ }
|
||||
+ }
|
||||
};
|
||||
|
||||
static void glDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* context) {
|
||||
|
||||
@@ -781,7 +781,7 @@ index 8546daa..7e4993c 100644
|
||||
void updateBindings();
|
||||
|
||||
diff --git a/private-include/indium/device.private.hpp b/private-include/indium/device.private.hpp
|
||||
index 58f1794..75cdeb3 100644
|
||||
index 58f1794..be7dbdc 100644
|
||||
--- a/private-include/indium/device.private.hpp
|
||||
+++ b/private-include/indium/device.private.hpp
|
||||
@@ -13,6 +13,8 @@
|
||||
@@ -836,7 +836,7 @@ index 58f1794..75cdeb3 100644
|
||||
std::shared_ptr<TimelineSemaphore> getWrappedTimelineSemaphore();
|
||||
|
||||
BinarySemaphore getBinarySemaphore(bool exportable = false);
|
||||
@@ -111,5 +128,31 @@ namespace Indium {
|
||||
@@ -111,5 +128,34 @@ namespace Indium {
|
||||
|
||||
INDIUM_PROPERTY(VkPhysicalDeviceMemoryProperties, m, M,emoryProperties);
|
||||
INDIUM_PROPERTY_READONLY(Feature, f, F,eatures);
|
||||
@@ -859,6 +859,9 @@ index 58f1794..75cdeb3 100644
|
||||
+ void releaseDescriptorPool(VkDescriptorPool pool);
|
||||
+ // Cached handles belong to the device and remain alive through all GPU uses.
|
||||
+ std::pair<VkRenderPass, bool> acquireRenderPass(const VkRenderPassCreateInfo& info);
|
||||
+ // Memory type index for a buffer or image of the given storage mode, or SIZE_MAX. Non-virtual and without
|
||||
+ // state in the class, so the layout above is unchanged.
|
||||
+ size_t findMemoryType(uint32_t memoryTypeBits, StorageMode storageMode, bool image);
|
||||
+ private:
|
||||
+ std::mutex _descriptorPoolMutex;
|
||||
+ std::vector<VkDescriptorPool> _availableDescriptorPools;
|
||||
@@ -1789,7 +1792,7 @@ index e58f0ba..616d177 100644
|
||||
+ finishTrackedEncoder(_privateCommandBuffer.lock()->commandBuffer());
|
||||
};
|
||||
diff --git a/src/indium/buffer.cpp b/src/indium/buffer.cpp
|
||||
index 23fd2d3..e73df2a 100644
|
||||
index 23fd2d3..1a48b81 100644
|
||||
--- a/src/indium/buffer.cpp
|
||||
+++ b/src/indium/buffer.cpp
|
||||
@@ -1,6 +1,8 @@
|
||||
@@ -1801,7 +1804,34 @@ index 23fd2d3..e73df2a 100644
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@@ -69,23 +71,11 @@ Indium::PrivateBuffer::PrivateBuffer(std::shared_ptr<PrivateDevice> device, size
|
||||
@@ -43,49 +45,17 @@ Indium::PrivateBuffer::PrivateBuffer(std::shared_ptr<PrivateDevice> device, size
|
||||
|
||||
DynamicVK::vkGetBufferMemoryRequirements(_privateDevice->device(), _buffer, &requirements);
|
||||
|
||||
- size_t targetIndex = SIZE_MAX;
|
||||
-
|
||||
- for (size_t i = 0; i < _privateDevice->memoryProperties().memoryTypeCount; ++i) {
|
||||
- const auto& type = _privateDevice->memoryProperties().memoryTypes[i];
|
||||
-
|
||||
- if ((requirements.memoryTypeBits & (1 << i)) == 0) {
|
||||
- continue;
|
||||
- }
|
||||
-
|
||||
- if ((_storageMode == StorageMode::Managed || _storageMode == StorageMode::Shared) && (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) {
|
||||
- continue;
|
||||
- }
|
||||
-
|
||||
- if (_storageMode == StorageMode::Shared && (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0) {
|
||||
- continue;
|
||||
- }
|
||||
-
|
||||
- // okay, this is good enough
|
||||
- targetIndex = i;
|
||||
- break;
|
||||
- }
|
||||
+ size_t targetIndex = _privateDevice->findMemoryType(requirements.memoryTypeBits, _storageMode, false);
|
||||
|
||||
if (targetIndex == SIZE_MAX) {
|
||||
throw std::runtime_error("No suitable memory region found for buffer with requested storage mode");
|
||||
}
|
||||
|
||||
@@ -1829,7 +1859,7 @@ index 23fd2d3..e73df2a 100644
|
||||
};
|
||||
|
||||
Indium::PrivateBuffer::PrivateBuffer(std::shared_ptr<PrivateDevice> device, const void* pointer, size_t length, ResourceOptions options):
|
||||
@@ -103,9 +93,11 @@ Indium::PrivateBuffer::PrivateBuffer(std::shared_ptr<PrivateDevice> device, cons
|
||||
@@ -103,9 +73,11 @@ Indium::PrivateBuffer::PrivateBuffer(std::shared_ptr<PrivateDevice> device, cons
|
||||
if (_storageMode == StorageMode::Managed) {
|
||||
VkMappedMemoryRange range {};
|
||||
range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
|
||||
@@ -1843,7 +1873,7 @@ index 23fd2d3..e73df2a 100644
|
||||
if (DynamicVK::vkFlushMappedMemoryRanges(_privateDevice->device(), 1, &range) != VK_SUCCESS) {
|
||||
// TODO
|
||||
abort();
|
||||
@@ -114,12 +106,10 @@ Indium::PrivateBuffer::PrivateBuffer(std::shared_ptr<PrivateDevice> device, cons
|
||||
@@ -114,12 +86,10 @@ Indium::PrivateBuffer::PrivateBuffer(std::shared_ptr<PrivateDevice> device, cons
|
||||
};
|
||||
|
||||
Indium::PrivateBuffer::~PrivateBuffer() {
|
||||
@@ -1859,7 +1889,7 @@ index 23fd2d3..e73df2a 100644
|
||||
};
|
||||
|
||||
std::shared_ptr<Indium::Device> Indium::PrivateBuffer::device() {
|
||||
@@ -136,7 +126,8 @@ void* Indium::PrivateBuffer::contents() {
|
||||
@@ -136,7 +106,8 @@ void* Indium::PrivateBuffer::contents() {
|
||||
}
|
||||
|
||||
if (!_mapped) {
|
||||
@@ -1869,7 +1899,7 @@ index 23fd2d3..e73df2a 100644
|
||||
// TODO
|
||||
abort();
|
||||
}
|
||||
@@ -148,9 +139,16 @@ void* Indium::PrivateBuffer::contents() {
|
||||
@@ -148,9 +119,16 @@ void* Indium::PrivateBuffer::contents() {
|
||||
void Indium::PrivateBuffer::didModifyRange(Range<size_t> range) {
|
||||
VkMappedMemoryRange vulkanRange {};
|
||||
vulkanRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
|
||||
@@ -1888,7 +1918,7 @@ index 23fd2d3..e73df2a 100644
|
||||
if (DynamicVK::vkFlushMappedMemoryRanges(_privateDevice->device(), 1, &vulkanRange) != VK_SUCCESS) {
|
||||
// TODO
|
||||
abort();
|
||||
@@ -158,8 +156,10 @@ void Indium::PrivateBuffer::didModifyRange(Range<size_t> range) {
|
||||
@@ -158,8 +136,10 @@ void Indium::PrivateBuffer::didModifyRange(Range<size_t> range) {
|
||||
};
|
||||
|
||||
uint64_t Indium::PrivateBuffer::gpuAddress() {
|
||||
@@ -2803,7 +2833,7 @@ index 5f62ad0..5957bec 100644
|
||||
DynamicVK::vkCmdBindDescriptorSets(buf->commandBuffer(), VK_PIPELINE_BIND_POINT_COMPUTE, _pso->layout(), 0, descriptorSets.size(), descriptorSets.data(), 0, nullptr);
|
||||
};
|
||||
diff --git a/src/indium/device.cpp b/src/indium/device.cpp
|
||||
index 322e186..2003684 100644
|
||||
index 322e186..78b77ae 100644
|
||||
--- a/src/indium/device.cpp
|
||||
+++ b/src/indium/device.cpp
|
||||
@@ -1,4 +1,6 @@
|
||||
@@ -2813,7 +2843,7 @@ index 322e186..2003684 100644
|
||||
#include <indium/instance.private.hpp>
|
||||
#include <indium/command-queue.private.hpp>
|
||||
#include <indium/render-pipeline.private.hpp>
|
||||
@@ -12,39 +14,167 @@
|
||||
@@ -12,39 +14,207 @@
|
||||
|
||||
#include <iridium/iridium.hpp>
|
||||
|
||||
@@ -2875,6 +2905,46 @@ index 322e186..2003684 100644
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ std::string memoryFlagsString(VkMemoryPropertyFlags flags) {
|
||||
+ static const std::pair<VkMemoryPropertyFlags, const char*> names[] = {
|
||||
+ { VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, "DEVICE_LOCAL" },
|
||||
+ { VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, "HOST_VISIBLE" },
|
||||
+ { VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, "HOST_COHERENT" },
|
||||
+ { VK_MEMORY_PROPERTY_HOST_CACHED_BIT, "HOST_CACHED" },
|
||||
+ { VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT, "LAZILY_ALLOCATED" },
|
||||
+ { VK_MEMORY_PROPERTY_PROTECTED_BIT, "PROTECTED" },
|
||||
+ { VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD, "DEVICE_COHERENT_AMD" },
|
||||
+ { VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD, "DEVICE_UNCACHED_AMD" },
|
||||
+ };
|
||||
+ std::string out = "0x" + hexValue(flags, 1) + "(";
|
||||
+ bool first = true;
|
||||
+ VkMemoryPropertyFlags rest = flags;
|
||||
+ for (const auto& name: names) {
|
||||
+ if ((flags & name.first) == 0) continue;
|
||||
+ out += (first ? "" : "|") + std::string(name.second);
|
||||
+ first = false;
|
||||
+ rest &= ~name.first;
|
||||
+ }
|
||||
+ if (rest) out += (first ? "0x" : "|0x") + hexValue(rest, 1);
|
||||
+ else if (first) out += "none";
|
||||
+ return out + ")";
|
||||
+ }
|
||||
+
|
||||
+ // The memory types and heaps of the device Metal renders on, logged once when it
|
||||
+ // is selected, so the per-allocation lines below can be read without vulkaninfo.
|
||||
+ void appendMemoryTable(const VkPhysicalDeviceMemoryProperties& memory) {
|
||||
+ for (uint32_t i = 0; i < memory.memoryHeapCount; ++i) {
|
||||
+ const auto& heap = memory.memoryHeaps[i];
|
||||
+ appendDeviceLog("memory heap=" + std::to_string(i) + " size=" + std::to_string(heap.size >> 20) + "MiB"
|
||||
+ + " deviceLocal=" + ((heap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) ? "yes" : "no"));
|
||||
+ }
|
||||
+ for (uint32_t i = 0; i < memory.memoryTypeCount; ++i) {
|
||||
+ const auto& type = memory.memoryTypes[i];
|
||||
+ appendDeviceLog("memory type=" + std::to_string(i) + " flags=" + memoryFlagsString(type.propertyFlags)
|
||||
+ + " heap=" + std::to_string(type.heapIndex));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ struct GpuSelector {
|
||||
+ std::string value;
|
||||
+ std::string source; // empty when the loader's default order should decide
|
||||
@@ -2987,7 +3057,7 @@ index 322e186..2003684 100644
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -67,12 +197,101 @@ void Indium::initGlobalDeviceList() {
|
||||
@@ -67,12 +237,105 @@ void Indium::initGlobalDeviceList() {
|
||||
DynamicVK::vkGetPhysicalDeviceFeatures2(device, &features);
|
||||
|
||||
if (!features12.timelineSemaphore) {
|
||||
@@ -3004,7 +3074,7 @@ index 322e186..2003684 100644
|
||||
+ memcpy(candidate.deviceUUID, idProperties.deviceUUID, 16);
|
||||
+ memcpy(candidate.driverUUID, idProperties.driverUUID, 16);
|
||||
+ candidates.push_back(candidate);
|
||||
}
|
||||
+ }
|
||||
+
|
||||
+ // Resolve the explicit selection against the supported list. An explicit choice
|
||||
+ // is honoured even when it names a software (CPU) device, because the user asked
|
||||
@@ -3061,6 +3131,10 @@ index 322e186..2003684 100644
|
||||
+ + " deviceUUID=" + uuidToString(selected.deviceUUID)
|
||||
+ + " reason=" + (selectedReason.empty() ? std::string("loader-first") : selectedReason));
|
||||
+
|
||||
+ VkPhysicalDeviceMemoryProperties selectedMemory {};
|
||||
+ DynamicVK::vkGetPhysicalDeviceMemoryProperties(selected.device, &selectedMemory);
|
||||
+ appendMemoryTable(selectedMemory);
|
||||
+
|
||||
+ // A VkDevice is created for the selected device only: its driver is initialized
|
||||
+ // and, on a hybrid laptop, the discrete GPU is not woken for a device the
|
||||
+ // rendering GPU cannot share memory with anyway. The old behaviour -- a VkDevice
|
||||
@@ -3083,7 +3157,7 @@ index 322e186..2003684 100644
|
||||
+
|
||||
+ for (size_t index: creationOrder) {
|
||||
+ globalDeviceList.push_back(std::make_shared<PrivateDevice>(candidates[index].device));
|
||||
+ }
|
||||
}
|
||||
+
|
||||
+ appendDeviceLog("created VkDevice for " + std::to_string(globalDeviceList.size()) + " of "
|
||||
+ + std::to_string(candidates.size()) + " supported device(s)"
|
||||
@@ -3091,7 +3165,7 @@ index 322e186..2003684 100644
|
||||
};
|
||||
|
||||
void Indium::finitGlobalDeviceList() {
|
||||
@@ -83,7 +302,8 @@ Indium::Device::~Device() {};
|
||||
@@ -83,7 +346,8 @@ Indium::Device::~Device() {};
|
||||
|
||||
Indium::PrivateDevice::PrivateDevice(VkPhysicalDevice physicalDevice):
|
||||
_physicalDevice(physicalDevice)
|
||||
@@ -3101,7 +3175,7 @@ index 322e186..2003684 100644
|
||||
DynamicVK::vkGetPhysicalDeviceProperties(_physicalDevice, &_properties);
|
||||
|
||||
std::vector<VkQueueFamilyProperties> queueFamilies;
|
||||
@@ -233,6 +453,36 @@ Indium::PrivateDevice::PrivateDevice(VkPhysicalDevice physicalDevice):
|
||||
@@ -233,6 +497,36 @@ Indium::PrivateDevice::PrivateDevice(VkPhysicalDevice physicalDevice):
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3138,7 +3212,7 @@ index 322e186..2003684 100644
|
||||
VkDeviceCreateInfo deviceCreateInfo {};
|
||||
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||
deviceCreateInfo.pQueueCreateInfos = queueCreateInfos.data();
|
||||
@@ -246,6 +496,7 @@ Indium::PrivateDevice::PrivateDevice(VkPhysicalDevice physicalDevice):
|
||||
@@ -246,6 +540,7 @@ Indium::PrivateDevice::PrivateDevice(VkPhysicalDevice physicalDevice):
|
||||
}
|
||||
|
||||
_features = indiumFeatures;
|
||||
@@ -3146,7 +3220,7 @@ index 322e186..2003684 100644
|
||||
|
||||
for (const auto& index: queueFamilyIndices) {
|
||||
VkQueue queue;
|
||||
@@ -297,6 +548,9 @@ Indium::PrivateDevice::PrivateDevice(VkPhysicalDevice physicalDevice):
|
||||
@@ -297,6 +592,9 @@ Indium::PrivateDevice::PrivateDevice(VkPhysicalDevice physicalDevice):
|
||||
};
|
||||
|
||||
Indium::PrivateDevice::~PrivateDevice() {
|
||||
@@ -3156,7 +3230,7 @@ index 322e186..2003684 100644
|
||||
if (_oneshotCommandPool) {
|
||||
DynamicVK::vkDestroyCommandPool(_device, _oneshotCommandPool, nullptr);
|
||||
}
|
||||
@@ -308,6 +562,23 @@ std::string Indium::PrivateDevice::name() const {
|
||||
@@ -308,6 +606,23 @@ std::string Indium::PrivateDevice::name() const {
|
||||
return _properties.deviceName;
|
||||
};
|
||||
|
||||
@@ -3180,7 +3254,7 @@ index 322e186..2003684 100644
|
||||
std::shared_ptr<Indium::CommandQueue> Indium::PrivateDevice::newCommandQueue() {
|
||||
return std::make_shared<PrivateCommandQueue>(shared_from_this());
|
||||
};
|
||||
@@ -317,9 +588,9 @@ std::shared_ptr<Indium::RenderPipelineState> Indium::PrivateDevice::newRenderPip
|
||||
@@ -317,9 +632,9 @@ std::shared_ptr<Indium::RenderPipelineState> Indium::PrivateDevice::newRenderPip
|
||||
};
|
||||
|
||||
std::shared_ptr<Indium::ComputePipelineState> Indium::PrivateDevice::newComputePipelineState(const ComputePipelineDescriptor& descriptor, PipelineOption options, std::shared_ptr<ComputePipelineReflection> reflection) {
|
||||
@@ -3193,7 +3267,7 @@ index 322e186..2003684 100644
|
||||
return std::make_shared<PrivateComputePipelineState>(shared_from_this(), descriptor);
|
||||
};
|
||||
|
||||
@@ -357,6 +628,7 @@ std::shared_ptr<Indium::Library> Indium::PrivateDevice::newLibrary(const void* d
|
||||
@@ -357,6 +672,7 @@ std::shared_ptr<Indium::Library> Indium::PrivateDevice::newLibrary(const void* d
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3201,7 +3275,7 @@ index 322e186..2003684 100644
|
||||
funcInfo.bindings.insert(funcInfo.bindings.end(), info.bindings.begin(), info.bindings.end());
|
||||
funcInfo.embeddedSamplers.insert(funcInfo.embeddedSamplers.end(), info.embeddedSamplers.begin(), info.embeddedSamplers.end());
|
||||
}
|
||||
@@ -480,9 +752,18 @@ void Indium::PrivateDevice::waitForSemaphore(VkSemaphore semaphore, uint64_t tar
|
||||
@@ -480,9 +796,18 @@ void Indium::PrivateDevice::waitForSemaphore(VkSemaphore semaphore, uint64_t tar
|
||||
wakeupEventLoop();
|
||||
};
|
||||
|
||||
@@ -3221,7 +3295,7 @@ index 322e186..2003684 100644
|
||||
VkSemaphoreTypeCreateInfo typeInfo {};
|
||||
typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO;
|
||||
typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE;
|
||||
@@ -505,7 +786,14 @@ Indium::TimelineSemaphore Indium::PrivateDevice::getTimelineSemaphore() {
|
||||
@@ -505,7 +830,14 @@ Indium::TimelineSemaphore Indium::PrivateDevice::getTimelineSemaphore() {
|
||||
};
|
||||
|
||||
void Indium::PrivateDevice::putTimelineSemaphore(const TimelineSemaphore& semaphore) {
|
||||
@@ -3237,7 +3311,7 @@ index 322e186..2003684 100644
|
||||
};
|
||||
|
||||
std::shared_ptr<Indium::TimelineSemaphore> Indium::PrivateDevice::getWrappedTimelineSemaphore() {
|
||||
@@ -551,3 +839,91 @@ std::shared_ptr<Indium::BinarySemaphore> Indium::PrivateDevice::getWrappedBinary
|
||||
@@ -551,3 +883,160 @@ std::shared_ptr<Indium::BinarySemaphore> Indium::PrivateDevice::getWrappedBinary
|
||||
delete ptr;
|
||||
});
|
||||
};
|
||||
@@ -3329,6 +3403,75 @@ index 322e186..2003684 100644
|
||||
+ }
|
||||
+ DynamicVK::vkDestroyDescriptorPool(_device, pool, nullptr);
|
||||
+}
|
||||
+
|
||||
+// The Vulkan spec orders memory types so that a type whose flags are a subset of
|
||||
+// another's comes first. A flag-less type (system RAM the GPU reads over PCIe,
|
||||
+// which NVIDIA exposes) therefore precedes every DEVICE_LOCAL type, and the first
|
||||
+// type allowed by memoryTypeBits put resources only the GPU touches in system RAM
|
||||
+// there. Private and Memoryless resources now take the first allowed DEVICE_LOCAL
|
||||
+// type and fall back to that first fit. Shared and Managed keep the first fit
|
||||
+// that is host visible (and coherent for Shared): moving them to DEVICE_LOCAL and
|
||||
+// HOST_VISIBLE BAR memory would make their CPU reads uncached. On RADV type 0 is
|
||||
+// DEVICE_LOCAL, so nothing changes there.
|
||||
+size_t Indium::PrivateDevice::findMemoryType(uint32_t memoryTypeBits, StorageMode storageMode, bool image) {
|
||||
+ const auto& memory = _memoryProperties;
|
||||
+ const bool gpuOnly = storageMode == StorageMode::Private || storageMode == StorageMode::Memoryless;
|
||||
+ size_t firstFit = SIZE_MAX;
|
||||
+ size_t deviceLocal = SIZE_MAX;
|
||||
+
|
||||
+ for (size_t i = 0; i < memory.memoryTypeCount; ++i) {
|
||||
+ const auto& type = memory.memoryTypes[i];
|
||||
+
|
||||
+ if ((memoryTypeBits & (1u << i)) == 0) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if ((storageMode == StorageMode::Managed || storageMode == StorageMode::Shared) && (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (storageMode == StorageMode::Shared && (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (firstFit == SIZE_MAX) {
|
||||
+ firstFit = i;
|
||||
+ }
|
||||
+
|
||||
+ if (!gpuOnly || (type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0) {
|
||||
+ deviceLocal = i;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ const size_t chosen = deviceLocal != SIZE_MAX ? deviceLocal : firstFit;
|
||||
+ if (chosen == SIZE_MAX) {
|
||||
+ return chosen;
|
||||
+ }
|
||||
+
|
||||
+ // Logged the first time each (kind, storage mode, type) occurs in the process;
|
||||
+ // afterwards this is one relaxed load. 2 kinds x 4 modes x 32 types = 256 bits.
|
||||
+ static std::atomic<uint64_t> logged[4] {};
|
||||
+ const size_t bit = ((image ? 4u : 0u) + (static_cast<size_t>(storageMode) & 3)) * 32 + chosen;
|
||||
+ const uint64_t mask = uint64_t(1) << (bit & 63);
|
||||
+ if ((logged[bit >> 6].load(std::memory_order_relaxed) & mask) == 0 && (logged[bit >> 6].fetch_or(mask) & mask) == 0) {
|
||||
+ static const char* modeNames[] = { "shared", "managed", "private", "memoryless" };
|
||||
+ auto describe = [&](size_t index) {
|
||||
+ const auto& type = memory.memoryTypes[index];
|
||||
+ return std::to_string(index) + " flags=" + memoryFlagsString(type.propertyFlags)
|
||||
+ + " heap=" + std::to_string(type.heapIndex)
|
||||
+ + " heapDeviceLocal=" + ((memory.memoryHeaps[type.heapIndex].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) ? "yes" : "no");
|
||||
+ };
|
||||
+ appendDeviceLog(std::string("memory alloc kind=") + (image ? "texture" : "buffer")
|
||||
+ + " storage=" + modeNames[static_cast<size_t>(storageMode) & 3]
|
||||
+ + " typeBits=0x" + hexValue(memoryTypeBits, 8)
|
||||
+ + " type=" + describe(chosen)
|
||||
+ + " firstFit=" + describe(firstFit)
|
||||
+ + (chosen != firstFit ? " changed=yes" : " changed=no"));
|
||||
+ }
|
||||
+
|
||||
+ return chosen;
|
||||
+};
|
||||
diff --git a/src/indium/dynamic-vk.cpp b/src/indium/dynamic-vk.cpp
|
||||
index a4677c3..69ef23f 100644
|
||||
--- a/src/indium/dynamic-vk.cpp
|
||||
@@ -4499,7 +4642,7 @@ index 71392df..d82e5f0 100644
|
||||
info.unnormalizedCoordinates = descriptor.normalizedCoordinates ? VK_FALSE : VK_TRUE;
|
||||
|
||||
diff --git a/src/indium/texture.cpp b/src/indium/texture.cpp
|
||||
index 1e3e971..4db87d4 100644
|
||||
index 1e3e971..cbdd7ff 100644
|
||||
--- a/src/indium/texture.cpp
|
||||
+++ b/src/indium/texture.cpp
|
||||
@@ -1,8 +1,90 @@
|
||||
@@ -4706,7 +4849,34 @@ index 1e3e971..4db87d4 100644
|
||||
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; // TODO: check if this should be "concurrent" instead. i think we're good, though.
|
||||
info.queueFamilyIndexCount = 0;
|
||||
info.pQueueFamilyIndices = nullptr;
|
||||
@@ -373,19 +495,18 @@ Indium::ConcreteTexture::ConcreteTexture(std::shared_ptr<PrivateDevice> device,
|
||||
@@ -347,45 +469,24 @@ Indium::ConcreteTexture::ConcreteTexture(std::shared_ptr<PrivateDevice> device,
|
||||
|
||||
DynamicVK::vkGetImageMemoryRequirements(_device->device(), _image, &reqs);
|
||||
|
||||
- size_t targetIndex = SIZE_MAX;
|
||||
-
|
||||
- for (size_t i = 0; i < _device->memoryProperties().memoryTypeCount; ++i) {
|
||||
- const auto& type = _device->memoryProperties().memoryTypes[i];
|
||||
-
|
||||
- if ((reqs.memoryTypeBits & (1 << i)) == 0) {
|
||||
- continue;
|
||||
- }
|
||||
-
|
||||
- if ((_storageMode == StorageMode::Managed || _storageMode == StorageMode::Shared) && (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) {
|
||||
- continue;
|
||||
- }
|
||||
-
|
||||
- if (_storageMode == StorageMode::Shared && (type.propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0) {
|
||||
- continue;
|
||||
- }
|
||||
-
|
||||
- // okay, this is good enough
|
||||
- targetIndex = i;
|
||||
- break;
|
||||
- }
|
||||
+ size_t targetIndex = _device->findMemoryType(reqs.memoryTypeBits, _storageMode, true);
|
||||
|
||||
if (targetIndex == SIZE_MAX) {
|
||||
throw std::runtime_error("No suitable memory region found for image with requested storage mode");
|
||||
}
|
||||
|
||||
@@ -4714,15 +4884,15 @@ index 1e3e971..4db87d4 100644
|
||||
- allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
- allocateInfo.allocationSize = reqs.size;
|
||||
- allocateInfo.memoryTypeIndex = targetIndex;
|
||||
-
|
||||
- if (DynamicVK::vkAllocateMemory(_device->device(), &allocateInfo, nullptr, &_memory) != VK_SUCCESS) {
|
||||
- // TODO
|
||||
- abort();
|
||||
- }
|
||||
+ Indium::count("textureAlloc", reqs.size);
|
||||
+ auto allocation = MemoryPool::allocate(_device->device(), reinterpret_cast<uint64_t>(_image), targetIndex, reqs.size, reqs.alignment, true, false);
|
||||
+ _memory = allocation.memory;
|
||||
|
||||
- if (DynamicVK::vkAllocateMemory(_device->device(), &allocateInfo, nullptr, &_memory) != VK_SUCCESS) {
|
||||
- // TODO
|
||||
- abort();
|
||||
- }
|
||||
-
|
||||
- DynamicVK::vkBindImageMemory(_device->device(), _image, _memory, 0);
|
||||
+ DynamicVK::vkBindImageMemory(_device->device(), _image, _memory, allocation.offset);
|
||||
|
||||
@@ -4735,7 +4905,7 @@ index 1e3e971..4db87d4 100644
|
||||
VkCommandBufferAllocateInfo cmdBufAllocInfo {};
|
||||
cmdBufAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
cmdBufAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
@@ -409,7 +530,7 @@ Indium::ConcreteTexture::ConcreteTexture(std::shared_ptr<PrivateDevice> device,
|
||||
@@ -409,7 +510,7 @@ Indium::ConcreteTexture::ConcreteTexture(std::shared_ptr<PrivateDevice> device,
|
||||
barrier.srcAccessMask = VK_ACCESS_NONE;
|
||||
barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
@@ -4744,7 +4914,7 @@ index 1e3e971..4db87d4 100644
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = _image;
|
||||
@@ -438,15 +559,13 @@ Indium::ConcreteTexture::ConcreteTexture(std::shared_ptr<PrivateDevice> device,
|
||||
@@ -438,15 +539,13 @@ Indium::ConcreteTexture::ConcreteTexture(std::shared_ptr<PrivateDevice> device,
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &cmdBuf;
|
||||
|
||||
@@ -4766,7 +4936,7 @@ index 1e3e971..4db87d4 100644
|
||||
|
||||
VkImageViewCreateInfo info2 {};
|
||||
info2.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
@@ -463,12 +582,24 @@ Indium::ConcreteTexture::ConcreteTexture(std::shared_ptr<PrivateDevice> device,
|
||||
@@ -463,12 +562,24 @@ Indium::ConcreteTexture::ConcreteTexture(std::shared_ptr<PrivateDevice> device,
|
||||
// TODO
|
||||
abort();
|
||||
}
|
||||
@@ -4792,7 +4962,7 @@ index 1e3e971..4db87d4 100644
|
||||
};
|
||||
|
||||
Indium::TextureType Indium::ConcreteTexture::textureType() const {
|
||||
@@ -537,7 +668,20 @@ VkImage Indium::ConcreteTexture::image() {
|
||||
@@ -537,7 +648,20 @@ VkImage Indium::ConcreteTexture::image() {
|
||||
return _image;
|
||||
};
|
||||
|
||||
@@ -4813,7 +4983,7 @@ index 1e3e971..4db87d4 100644
|
||||
return VK_IMAGE_LAYOUT_GENERAL;
|
||||
};
|
||||
|
||||
@@ -546,6 +690,16 @@ void Indium::ConcreteTexture::replaceRegion(Indium::Region region, size_t mipmap
|
||||
@@ -546,6 +670,16 @@ void Indium::ConcreteTexture::replaceRegion(Indium::Region region, size_t mipmap
|
||||
};
|
||||
|
||||
void Indium::ConcreteTexture::replaceRegion(Indium::Region region, size_t mipmapLevel, size_t slice, const void* bytes, size_t bytesPerRow, size_t bytesPerImage) {
|
||||
@@ -4830,7 +5000,7 @@ index 1e3e971..4db87d4 100644
|
||||
if (_storageMode != StorageMode::Managed && _storageMode != StorageMode::Shared) {
|
||||
throw std::runtime_error("Invalid usage of replaceRegion on non-managed and non-shared texture");
|
||||
}
|
||||
@@ -556,99 +710,109 @@ void Indium::ConcreteTexture::replaceRegion(Indium::Region region, size_t mipmap
|
||||
@@ -556,99 +690,109 @@ void Indium::ConcreteTexture::replaceRegion(Indium::Region region, size_t mipmap
|
||||
|
||||
auto aspect = pixelFormatToVkImageAspectFlags(_descriptor.pixelFormat);
|
||||
|
||||
|
||||
Reference in new issue
Block a user