NT-handle import: consume on SUCCESS only — PUNKTFUNK LOCAL PATCH.

Not upstream. Granite's DeviceAllocator::internal_allocate closed the imported
by-reference NT handle (OPAQUE_WIN32 / D3D11_TEXTURE / D3D12_RESOURCE — the
predicate excludes fd and KMT types) UNCONDITIONALLY after the FIRST
vkAllocateMemory — on success AND on failure — while every failure before the
allocator (c-shim validation, vkCreateImage, find_memory_type) left the handle
open. Both classes surface to pyrowave_image_create's caller as the same error
code, so the caller could not know whether to close: punktfunk's import_plane
closed on every failure and double-closed whenever the failure was at or after
the allocate — and a recycled handle value makes that close an unrelated live
handle's. The unconditional consume also broke the allocator's own
block-recycling retry loop, which re-ran vkAllocateMemory with import_info
still pointing at the just-closed handle.

Fixed by moving the consume to the API commit point: pyrowave_image_create
closes the by-reference handle exactly on its success return. This pins
pyrowave.h's documented contract ("take ownership and close the HANDLE on
import") to mean on SUCCESS, matching pyrowave_sync_object_create (Granite's
semaphore import already closes only after a successful vkImportSemaphore*).
NT-handle memory imports never transfer ownership at the Vulkan level (the
implementation holds its own reference), so the post-create close is legal.

Behavior note: Granite code that imports by-reference memory WITHOUT going
through pyrowave_image_create — in this vendored subset only wsi_dxgi.cpp's
interop swapchain, which the pyrowave C API never reaches — now keeps the
caller's handle open on allocate-stage failure instead of consuming it. That
matches the semaphore contract; those callers own their close-on-failure.

Correctness re-validated by pyrowave_win_smoke on real hardware after the
change.

diff --git a/crates/pyrowave-sys/vendor/pyrowave/Granite/vulkan/memory_allocator.cpp b/crates/pyrowave-sys/vendor/pyrowave/Granite/vulkan/memory_allocator.cpp
index f33bdac3..fd035b66 100644
--- a/crates/pyrowave-sys/vendor/pyrowave/Granite/vulkan/memory_allocator.cpp
+++ b/crates/pyrowave-sys/vendor/pyrowave/Granite/vulkan/memory_allocator.cpp
@@ -732,16 +732,15 @@ bool DeviceAllocator::internal_allocate(
 		res = table->vkAllocateMemory(device->get_device(), &info, nullptr, &device_memory);
 	}
 
-	// If we're importing, make sure we consume the native handle.
-	if (external && bool(*external) &&
-	    ExternalHandle::memory_handle_type_imports_by_reference(external->memory_handle_type))
-	{
-#ifdef _WIN32
-		::CloseHandle(external->handle);
-#else
-		::close(external->handle);
-#endif
-	}
+	// PUNKTFUNK (patch 0006): the consume of by-reference native handles used to happen HERE,
+	// unconditionally — success AND failure of the first vkAllocateMemory. That made the caller's
+	// failure contract ambiguous (an allocate-stage failure had already closed the handle while a
+	// create/find_memory_type failure had not), and the block-recycling retry loop below re-ran
+	// vkAllocateMemory with import_info still pointing at the just-closed handle. The consume now
+	// lives at the API commit point (pyrowave_c.cpp: pyrowave_image_create's success return), so
+	// the allocator never closes a caller's handle and retries import a still-open handle.
+	// (fd-type imports were never affected: memory_handle_type_imports_by_reference excludes
+	// OPAQUE_FD/DMA_BUF, whose ownership vkAllocateMemory itself transfers on success.)
 
 	if (res == VK_SUCCESS)
 	{
diff --git a/crates/pyrowave-sys/vendor/pyrowave/pyrowave_c.cpp b/crates/pyrowave-sys/vendor/pyrowave/pyrowave_c.cpp
index eb917e64..985cd0a9 100644
--- a/crates/pyrowave-sys/vendor/pyrowave/pyrowave_c.cpp
+++ b/crates/pyrowave-sys/vendor/pyrowave/pyrowave_c.cpp
@@ -592,6 +592,21 @@ pyrowave_result pyrowave_image_create(const pyrowave_image_create_info *info, py
 	if (!img)
 		return PYROWAVE_ERROR_FAILED_EXTERNAL_HANDLE;
 
+	// PUNKTFUNK (patch 0006): consume the imported by-reference NT handle exactly here, at the
+	// API's success boundary — the allocator no longer closes it (see memory_allocator.cpp).
+	// This pins pyrowave.h's documented contract ("take ownership and close the HANDLE on
+	// import") to mean ON SUCCESS, matching pyrowave_sync_object_create's semantics: on ANY
+	// failure return the caller still owns the handle and can close it unconditionally.
+	// By-reference NT-handle imports never transfer ownership at the Vulkan level (the
+	// implementation keeps its own reference), so a post-create close here is always legal.
+#ifdef _WIN32
+	if (info->external_handle &&
+	    ExternalHandle::memory_handle_type_imports_by_reference(info->handle_type))
+	{
+		::CloseHandle(reinterpret_cast<HANDLE>(info->external_handle));
+	}
+#endif
+
 	auto *image = new pyrowave_image_opaque();
 	image->device = &device;
 	image->img = std::move(img);
