Vulkan: wrap external buffers as regions#9110
Conversation
|
This description sounds written by AI. Per our contribution guidelines, please disclose any AI tool usage or assert that none was used. |
|
Indeed. I did not review yet. Please don't until I do and I update description. Those things work on their own now |
|
@xFile3160 -- when ready for review, change the status away from Draft. |
halide_vulkan_wrap_vk_buffer stored the raw VkBuffer in halide_buffer_t::device, but the Vulkan runtime expects that field to point at MemoryRegion metadata. Wrap external VkBuffer handles in small runtime-owned MemoryRegion metadata instead. Detach/free release only that metadata, not the external buffer. Add an explicit-offset wrap helper for views into larger buffers, and include that offset in descriptor updates and copy paths. Refs halide#8715
1398255 to
d7ca1b1
Compare
derek-gerstmann
left a comment
There was a problem hiding this comment.
Thanks for the contribution! I suggested a few minor changes, but otherwise looks great.
@alexreinking FYI ... this PR adds new methods to the runtime api, so we should flag this for the release notes.
| int32_t offset = 0; //< indexing offset from start of region (used to adjust indices in compute shader to avoid alignment constraints for arbitrary crops) | ||
| }; | ||
|
|
||
| enum class MemoryRegionKind { |
There was a problem hiding this comment.
'Kind' is pretty vague. Perhaps rename this enum to something like MemoryOwnership.
There was a problem hiding this comment.
I'd change AllocatorOwned to just Owned, and ExternalWrapped to Wrapped.
| * owned. The device field of the halide_buffer_t must be 0 when this routine | ||
| * is called. | ||
| */ | ||
| extern int halide_vulkan_wrap_vk_buffer_with_offset(void *user_context, |
There was a problem hiding this comment.
We don't need two methods for this ... just keep this one with the offset parameter and rename it halide_vulkan_wrap_vk_buffer and delete the other one.
There was a problem hiding this comment.
yea I agree. I did the split because I wanted to preserve the zero-offset call.
| (void *)&halide_vulkan_initialize_kernels, | ||
| (void *)&halide_vulkan_release_context, | ||
| (void *)&halide_vulkan_run, | ||
| (void *)&halide_vulkan_wrap_vk_buffer, |
There was a problem hiding this comment.
We only need one of these, remove the second.
| namespace Internal { | ||
| namespace Vulkan { | ||
|
|
||
| ALWAYS_INLINE const MemoryRegion *vk_region_root(const MemoryRegion *region) { |
There was a problem hiding this comment.
This isn't returning the root ... it's returning the owner. Rename it vk_region_owner
There was a problem hiding this comment.
How is this different than ctx.allocator->owner_of()?
There was a problem hiding this comment.
So I needed an helper to follow crop aliases back to the owning region. A cropped buffer gets a MemoryRegion that is just metadata pointing back to the owning region. When we wrap buffers, we need to walk through any crop aliases to find the actual external wrapper region that owns the vkBuffer metadata and base offset. I think I encountered this specifically when dealing with nv21/nv12.
| return current; | ||
| } | ||
|
|
||
| ALWAYS_INLINE MemoryRegion *vk_region_root(MemoryRegion *region) { |
There was a problem hiding this comment.
Remove this ... overloading the same method is error prone, and the const casting is bad.
| } | ||
|
|
||
| if (region != root && region->kind == MemoryRegionKind::CropAlias) { | ||
| free(region); |
There was a problem hiding this comment.
You shouldn't be calling free() directly ... use vk_host_free() and pass on the allocation callbacks for the context.
There was a problem hiding this comment.
yep agreed, you're right. This was host metadata tho, but I will use vk_host_malloc and vk_host_free
| return nullptr; | ||
| } | ||
|
|
||
| MemoryRegion *region = reinterpret_cast<MemoryRegion *>(malloc(sizeof(MemoryRegion))); |
There was a problem hiding this comment.
You shouldn't be calling malloc() or free() directly ... use vk_host_malloc() and vk_host_free() and pass on the allocation callbacks for the context.
| // get the allocated region for the device | ||
| MemoryRegion *device_region = reinterpret_cast<MemoryRegion *>(halide_buffer->device); | ||
| #ifdef DEBUG_RUNTIME | ||
| const uint64_t device_region_size = device_region->allocation.size; |
There was a problem hiding this comment.
If this is only for printing a debug message, then move the declaration and the ifdef down below and wrap the debug() call.
| return halide_error_code_success; | ||
| } | ||
|
|
||
| WEAK int halide_vulkan_wrap_vk_buffer(void *user_context, struct halide_buffer_t *buf, uint64_t vk_buffer) { |
There was a problem hiding this comment.
We don't need two methods for this. Remove this one, and rename the other one to halide_vulkan_wrap_vk_buffer
| } | ||
|
|
||
| MemoryRegion *owner = owner_of(user_context, region); | ||
| if (owner != nullptr && owner->kind == MemoryRegionKind::ExternalWrapped) { |
There was a problem hiding this comment.
What's wrong with mapping an externally wrapped buffer()?
There was a problem hiding this comment.
so the halide allocator map() expects allovcator owned BlockResource and VkDeviceMemory. For external wrapped buffer, Halide does not know memory handle, bind offset, memory properties, host visibility, coherency and ownership. Wouldn't be unsafe to allow this?
This fixes the Vulkan native-buffer wrapper path.
The issue in #8715 started from trying to pass an application-owned
VkBufferto Halide. The only way to make that work reliably was to mirror Halide's
private
MemoryRegionlayout in application code and pass that throughhalide_vulkan_wrap_vk_buffer. That is not a good API boundary.halide_vulkan_wrap_vk_buffershould accept a Vulkan buffer handle and letHalide build whatever runtime metadata it needs internally.
This PR changes the wrapper path to do that. External
VkBufferhandles arewrapped in small runtime-owned
MemoryRegionmetadata. The Vulkan bufferremains owned by the caller; Halide only owns the metadata needed to pass the
buffer through descriptor updates, copies, crops, and detach/free.
The intended flow is:
VkBuffer.halide_buffer_t.VkBuffer.This also adds
halide_vulkan_wrap_vk_buffer_with_offsetfor views into largerVulkan buffers. The explicit external offset is included in descriptor updates
and copy paths. Regular Halide crops continue to use the existing indexing
offset path.
The GPU object lifetime test now covers Vulkan native buffer wrapping. It
rewraps a Vulkan buffer handle, then wraps the same buffer with an explicit byte
offset and verifies that the shifted view copies back the expected values.
Refs #8715