Bindless Image Experimental Extension#
API#
Enumerations
Structures
Functions
Bindless Images#
A bindless image is defined as one which provides access to the underlying data via image reference handles. At the application level, this allows the user to implement programs where the number of images is not known at compile-time, and store all handles to images irrespective of varying formats and layouts in some container such as a dynamic array.
Currently, in Level Zero, zeImageCreate performs the image memory allocation and image handle generation. This function only allows for the allocation of image memory in an implementation-specific layout.
- In this extension, we propose the following additions:
Provide a new image descriptor and flags for Bindless images.
Support for creation of images on linearly allocated memory backed by USM.
Extension API to create an image handle from pitched memory
Create Bindless sampled images
A “Bindless image” can be created by passing ze_image_bindless_exp_desc_t to pNext member of ze_image_desc_t and set the flags value as ZE_IMAGE_BINDLESS_EXP_FLAG_BINDLESS
A “Bindless sampled image” can be created by passing ze_image_bindless_exp_desc_t to pNext member of ze_image_desc_t and setting the flags to a combination of ZE_IMAGE_BINDLESS_EXP_FLAG_BINDLESS and ZE_IMAGE_BINDLESS_EXP_FLAG_SAMPLED_IMAGE When image view is created from bindless sampled image, sampling modes can be redefined by passing sampler descriptor in pNext field of ze_image_bindless_exp_desc_t struct. Image view created from bindless sampled image without setting ZE_IMAGE_BINDLESS_EXP_FLAG_SAMPLED_IMAGE is an unsampled image. Sampled image view can be created from bindless unsampled image by setting ZE_IMAGE_BINDLESS_EXP_FLAG_SAMPLED_IMAGE and passing sampler descriptor in pNext field of ze_image_bindless_exp_desc_t struct.
This extension is complimentary to and may be used in conjunction with the ZE_extension_image_view extension
Programming example with Bindless images#
// Assumed image data on host std::vector<float> imageDataHost; // 2D image dimensions size_t imageWidth = 1024; size_t imageHeight = 1024; // Single-precision float image format with one channel ze_image_format_t imageFormat = { ZE_IMAGE_FORMAT_LAYOUT_32, ZE_IMAGE_FORMAT_TYPE_FLOAT, ZE_IMAGE_FORMAT_SWIZZLE_R, ZE_IMAGE_FORMAT_SWIZZLE_X, ZE_IMAGE_FORMAT_SWIZZLE_R, ZE_IMAGE_FORMAT_SWIZZLE_X } // Create an image descriptor for bindless image ze_image_desc_t imageDesc = { ZE_STRUCTURE_TYPE_IMAGE_DESC, nullptr, 0, ZE_IMAGE_TYPE_2D, imageFormat, 128, 128, 0, 0, 0 }; ze_image_bindless_exp_desc_t bindlessImageDesc = {ZE_STRUCTURE_TYPE_BINDLESS_IMAGE_EXP_DESC}; bindlessImageDesc.flags = ZE_IMAGE_BINDLESS_EXP_FLAG_BINDLESS; imageDesc.pNext = &bindlessImageDesc; // A bindless image is valid on both host and device and can be passed into kernels // When passing ZE_IMAGE_BINDLESS_EXP_FLAG_BINDLESS to zeImageCreate, only the backing memory is allocated for Image ze_image_handle_t hImage; zeImageCreate(hContext, hDevice, &imageDesc, &hImage); //Copy To new bindless image memory zeCommandListAppendImageCopyFromMemory(hCommandlist, hImage, imageDataHost.data(), nullptr, nullptr, 0, nullptr); // Launch kernel and perform appropriate synchronizations // Copy back zeCommandListAppendImageCopyToMemory(hCommandlist, imageDataHost.data(), hImage, nullptr, nullptr, 0, nullptr); // Further image views can be created from the existing memory allocated using bindless flags ze_image_handle_t hImageView; zeImageViewCreateExt(hContext, hDevice, &imageDesc, hImage, &hImageView); // New image view can be separately used by users and destroyed // ... // Once all operations are complete we need destroy bindless image handle(s) zeImageDestroy(hImageView); zeImageDestroy(hImage);
Programming example with pitched memory usage#
// Retrieve pitched alloc properties specific to device ze_device_image_properties_t deviceImageProperties = {}; ze_device_pitched_alloc_exp_properties_t pitchedAllocProperties = {}; pitchedAllocProperties.stype = ZE_STRUCTURE_TYPE_PITCHED_ALLOC_DEVICE_EXP_PROPERTIES deviceImageProperties.pNext = &pitchedAllocProperties; zeDeviceGetImageProperties(hDevice, &deviceImageProperties); // Assumed image data on host std::vector<float> imageDataHost; // 2D image dimensions size_t imageWidth = 1024; size_t imageHeight = 1024; //Pitched memory in linear layout size_t rowPitch; unsigned int elementSize = 128; zeMemGetPitchFor2dImage(hContext, hDevice, imageWidth, imageHeight, elementSize, &rowPitch); size_t allocSize = rowPitch * imageHeight; ze_device_mem_alloc_desc_t allocDesc = {ZE_STRUCTURE_TYPE_DEVICE_MEM_ALLOC_DESC}; zeMemAllocDevice(hContext, &allocDesc, allocSize, allocSize, hDevice, &pitchedPtr); // Declare the copy region for copying ze_copy_region_t copyRegion = {0, 0, 0, imageWidth * sizeof(float), imageHeight, 0}; // Copy from host to device zeCommandListAppendMemoryCopyRegion(hCommandList, pitchedPtr, ©Region, rowPitch, 0, imageDataHost.data(), ©Region, imageWidth * sizeof(float), 0, nullptr, 0, nullptr); // Single-precision float image format with one channel ze_image_format_t imageFormat = { ZE_IMAGE_FORMAT_LAYOUT_32, ZE_IMAGE_FORMAT_TYPE_FLOAT, ZE_IMAGE_FORMAT_SWIZZLE_R, ZE_IMAGE_FORMAT_SWIZZLE_X, ZE_IMAGE_FORMAT_SWIZZLE_R, ZE_IMAGE_FORMAT_SWIZZLE_X } // Create an image descriptor for bindless image ze_image_desc_t imageDesc = { ZE_STRUCTURE_TYPE_IMAGE_DESC, nullptr, 0, ZE_IMAGE_TYPE_2D, imageFormat, 128, 128, 0, 0, 0 }; ze_image_pitched_exp_desc_t pitchedImageDesc = {ZE_STRUCTURE_TYPE_PITCHED_IMAGE_EXP_DESC}; pitchedImageDesc.ptr = pitchedPtr; imageDesc.pNext = &pitchedImageDesc; // A image created out of pitched memory is valid on both host and device and can be passed into kernels ze_image_handle_t hImage; zeImageCreate(hContext, hDevice, &imageDesc, &hImage); // Launch kernel and perform appropriate synchronizations // ... // Copy from device to host zeCommandListAppendMemoryCopyRegion(hCommandList, imageDataHost.data(), ©Region, imageWidth * sizeof(float), 0, pitchedPtr, ©Region, rowPitch, 0, nullptr, 0, nullptr); // Once all operations on the image are complete we need destroy image handle and free memory zeImageDestroy(hImage); zeMemFree(hContext, pitchedPtr);
Programming example with Bindless sampled images#
// 2D image dimensions size_t imageWidth = 1024; size_t imageHeight = 1024; // Single-precision float image format with one channel ze_image_format_t imageFormat = { ZE_IMAGE_FORMAT_LAYOUT_32, ZE_IMAGE_FORMAT_TYPE_FLOAT, ZE_IMAGE_FORMAT_SWIZZLE_R, ZE_IMAGE_FORMAT_SWIZZLE_X, ZE_IMAGE_FORMAT_SWIZZLE_R, ZE_IMAGE_FORMAT_SWIZZLE_X } // Define sampler descriptor ze_sampler_desc_t samplerDesc = { ZE_STRUCTURE_TYPE_SAMPLER_DESC, nullptr, ZE_SAMPLER_ADDRESS_MODE_CLAMP, ZE_SAMPLER_FILTER_MODE_LINEAR, true }; // Create an image descriptor for bindless image ze_image_desc_t imageDesc = { ZE_STRUCTURE_TYPE_IMAGE_DESC, nullptr, 0, ZE_IMAGE_TYPE_2D, imageFormat, imageWidth, imageHeight, 0, 0, 0 }; ze_image_bindless_exp_desc_t bindlessImageDesc = {ZE_STRUCTURE_TYPE_BINDLESS_IMAGE_EXP_DESC}; bindlessImageDesc.flags = ZE_IMAGE_BINDLESS_EXP_FLAG_BINDLESS | ZE_IMAGE_BINDLESS_EXP_FLAG_SAMPLED_IMAGE; imageDesc.pNext = &bindlessImageDesc; bindlessImageDesc.pNext = &samplerDesc; // Create bindless sampled image // pass ZE_IMAGE_BINDLESS_EXP_FLAG_BINDLESS and ZE_IMAGE_BINDLESS_EXP_FLAG_SAMPLED_IMAGE to zeImageCreate(), ze_image_handle_t hImage; zeImageCreate(hContext, hDevice, &imageDesc, &hImage); // Create an image view from bindless sampled image // define sampler descriptor for view ze_sampler_desc_t samplerDescForView = { ZE_STRUCTURE_TYPE_SAMPLER_DESC, nullptr, ZE_SAMPLER_ADDRESS_MODE_CLAMP, ZE_SAMPLER_FILTER_MODE_NEAREST, true }; ze_image_format_t imageViewFormat = { ZE_IMAGE_FORMAT_LAYOUT_32, ZE_IMAGE_FORMAT_TYPE_UINT, ZE_IMAGE_FORMAT_SWIZZLE_R, ZE_IMAGE_FORMAT_SWIZZLE_X, ZE_IMAGE_FORMAT_SWIZZLE_R, ZE_IMAGE_FORMAT_SWIZZLE_X } // image descriptor for bindless image view ze_image_desc_t imageViewDesc = { ZE_STRUCTURE_TYPE_IMAGE_DESC, nullptr, 0, ZE_IMAGE_TYPE_2D, imageViewFormat, 128, 128, 0, 0, 0 }; imageViewDesc.pNext = &bindlessImageDesc; bindlessImageDesc.pNext = &samplerDescForView; ze_image_handle_t hImageView; zeImageViewCreateExt(hContext, hDevice, &imageViewDesc, hImage, &hImageView); // If ZE_IMAGE_BINDLESS_EXP_FLAG_SAMPLED_IMAGE is not set, unsampled image is created ze_image_handle_t hUnsampledImageView; bindlessImageDesc.flags = ZE_IMAGE_BINDLESS_EXP_FLAG_BINDLESS; bindlessImageDesc.pNext = nullptr; zeImageViewCreateExt(hContext, hDevice, &imageViewDesc, hImage, &hUnsampledImageView); // Create an image view from bindless unsampled image ze_image_handle_t hUnsampledImage; ze_image_handle_t hSampledImageView; bindlessImageDesc.flags = ZE_IMAGE_BINDLESS_EXP_FLAG_BINDLESS; bindlessImageDesc.pNext = nullptr; imageDesc.pNext = &bindlessImageDesc; // create unsampled image zeImageCreate(hContext, hDevice, &imageDesc, &hUnsampledImage); bindlessImageDesc.flags = ZE_IMAGE_BINDLESS_EXP_FLAG_BINDLESS | ZE_IMAGE_BINDLESS_EXP_FLAG_SAMPLED_IMAGE; bindlessImageDesc.pNext = &samplerDescForView; zeImageViewCreateExt(hContext, hDevice, &imageDesc, hUnsampledImage, &hSampledImageView);