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:
lucannez64andClaude Opus 5.5 committed 2026-10-16 02:04:43 +02:00
1 parent 6d6159b594
commit 1c30c99e51
3 files changed
+443 -54

No files matched your search

+20 -1
View File
@@ -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
View File
@@ -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) {
+207 -37
View File
@@ -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);