Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add VK_EXT_image_2d_view_of_3d support #2442

Merged
merged 4 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Docs/MoltenVK_Runtime_UserGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ In addition to core *Vulkan* functionality, **MoltenVK** also supports the foll
- `VK_KHR_get_physical_device_properties2`
- `VK_KHR_get_surface_capabilities2`
- `VK_KHR_imageless_framebuffer`
- `VK_EXT_image_2d_view_of_3d`
- *Requires MVK_CONFIG_USE_MTLHEAP to be enabled.*
- `VK_KHR_image_format_list`
- `VK_KHR_incremental_present`
- `VK_KHR_maintenance1`
Expand Down
13 changes: 12 additions & 1 deletion MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@
portabilityFeatures->imageViewFormatReinterpretation = true;
portabilityFeatures->imageViewFormatSwizzle = (_metalFeatures.nativeTextureSwizzle ||
getMVKConfig().fullImageViewSwizzle);
portabilityFeatures->imageView2DOn3DImage = false;
portabilityFeatures->imageView2DOn3DImage = getMVKConfig().useMTLHeap;
portabilityFeatures->multisampleArrayImage = _metalFeatures.multisampleArrayTextures;
portabilityFeatures->mutableComparisonSamplers = _metalFeatures.depthSampleCompare;
portabilityFeatures->pointPolygons = false;
Expand Down Expand Up @@ -537,6 +537,14 @@
hostImageCopyFeatures->hostImageCopy = true;
break;
}
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_2D_VIEW_OF_3D_FEATURES_EXT: {
if (getMVKConfig().useMTLHeap) {
auto* extFeatures = (VkPhysicalDeviceImage2DViewOf3DFeaturesEXT*)next;
extFeatures->image2DViewOf3D = true;
extFeatures->sampler2DViewOf3D = true;
}
break;
}
case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_CREATION_CACHE_CONTROL_FEATURES_EXT: {
auto* pipelineCreationCacheControlFeatures = (VkPhysicalDevicePipelineCreationCacheControlFeaturesEXT*)next;
pipelineCreationCacheControlFeatures->pipelineCreationCacheControl = true;
Expand Down Expand Up @@ -3392,6 +3400,9 @@ static uint32_t mvkGetEntryProperty(io_registry_entry_t entry, CFStringRef prope
if (!_metalFeatures.arrayOfTextures || !_metalFeatures.arrayOfSamplers) {
pWritableExtns->vk_EXT_descriptor_indexing.enabled = false;
}
if (!getMVKConfig().useMTLHeap) {
pWritableExtns->vk_EXT_image_2d_view_of_3d.enabled = false;
}

// The relevant functions are not available if not built with Xcode 14.
#if MVK_XCODE_14
Expand Down
1 change: 1 addition & 0 deletions MoltenVK/MoltenVK/GPUObjects/MVKDeviceFeatureStructs.def
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ MVK_DEVICE_FEATURE_EXTN(ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2,
MVK_DEVICE_FEATURE_EXTN(ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, EXT, 31)
MVK_DEVICE_FEATURE_EXTN(FragmentShaderInterlock, FRAGMENT_SHADER_INTERLOCK, EXT, 3)
MVK_DEVICE_FEATURE_EXTN(HostImageCopy, HOST_IMAGE_COPY, EXT, 1)
MVK_DEVICE_FEATURE_EXTN(Image2DViewOf3D, IMAGE_2D_VIEW_OF_3D, EXT, 2)
MVK_DEVICE_FEATURE_EXTN(PipelineCreationCacheControl, PIPELINE_CREATION_CACHE_CONTROL, EXT, 1)
MVK_DEVICE_FEATURE_EXTN(Robustness2, ROBUSTNESS_2, EXT, 3)
MVK_DEVICE_FEATURE_EXTN(ShaderAtomicFloat, SHADER_ATOMIC_FLOAT, EXT, 12)
Expand Down
10 changes: 10 additions & 0 deletions MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ typedef struct MVKMappedMemoryRange {
VkDeviceSize size = 0;
} MVKMappedMemoryRange;

struct HeapAllocation {
id<MTLHeap> heap = nil; // Reference to the heap containing this allocation
size_t offset = 0; // Offset into the heap
size_t size = 0; // Total size of this allocation
size_t align = 0; // Allocation alignment requirement

bool isValid() const {
return (heap != nil) && (size != 0);
}
};

/** Represents a Vulkan device-space memory allocation. */
class MVKDeviceMemory : public MVKVulkanAPIDeviceObject {
Expand Down
17 changes: 8 additions & 9 deletions MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.mm
Original file line number Diff line number Diff line change
Expand Up @@ -188,15 +188,14 @@
// Can't create MTLHeaps of zero size.
if (_allocationSize == 0) { return true; }

#if MVK_MACOS
// MTLHeaps on macOS must use private storage for now.
if (_mtlStorageMode != MTLStorageModePrivate) { return true; }
#endif
#if MVK_IOS
// MTLHeaps on iOS must use private or shared storage for now.
if ( !(_mtlStorageMode == MTLStorageModePrivate ||
_mtlStorageMode == MTLStorageModeShared) ) { return true; }
#endif
if (getPhysicalDevice()->getMTLDeviceCapabilities().isAppleGPU) {
// MTLHeaps on Apple silicon must use private or shared storage for now.
if ( !(_mtlStorageMode == MTLStorageModePrivate ||
_mtlStorageMode == MTLStorageModeShared) ) { return true; }
} else {
// MTLHeaps with immediate-mode GPUs must use private storage for now.
if (_mtlStorageMode != MTLStorageModePrivate) { return true; }
}

MTLHeapDescriptor* heapDesc = [MTLHeapDescriptor new];
heapDesc.type = MTLHeapTypePlacement;
Expand Down
5 changes: 5 additions & 0 deletions MoltenVK/MoltenVK/GPUObjects/MVKImage.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class MVKImagePlane : public MVKBaseObject {
id<MTLTexture> _mtlTexture;
std::unordered_map<NSUInteger, id<MTLTexture>> _mtlTextureViews;
MVKSmallVector<MVKImageSubresource, 1> _subresources;
HeapAllocation _heapAllocation;
};


Expand Down Expand Up @@ -233,6 +234,7 @@ class MVKImage : public MVKVulkanAPIDeviceObject {
/** Populates the specified transfer image descriptor data structure. */
void getTransferDescriptorData(MVKImageDescriptorData& imgData);

MTLTextureDescriptor* newMTLTextureDescriptor(uint32_t planeIndex);

#pragma mark Resource memory

Expand Down Expand Up @@ -334,6 +336,8 @@ class MVKImage : public MVKVulkanAPIDeviceObject {
/** Returns the Metal CPU cache mode used by this image. */
MTLCPUCacheMode getMTLCPUCacheMode();

HeapAllocation* getHeapAllocation(uint32_t planeIndex);


#pragma mark Construction

Expand Down Expand Up @@ -395,6 +399,7 @@ class MVKImage : public MVKVulkanAPIDeviceObject {
bool _hasMutableFormat;
bool _shouldSupportAtomics;
bool _isLinearForAtomics;
bool _is2DViewOn3DImageCompatible = false;
};


Expand Down
89 changes: 63 additions & 26 deletions MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
MVKImageMemoryBinding* memoryBinding = getMemoryBinding();
MVKDeviceMemory* dvcMem = memoryBinding->_deviceMemory;

if (_image->_is2DViewOn3DImageCompatible && !dvcMem->ensureMTLHeap()) {
MVKAssert(0, "Creating a 2D view of a 3D texture currently requires a placement heap, which is not available.");
}

if (_image->_ioSurface) {
_mtlTexture = [_image->getMTLDevice()
newTextureWithDescriptor: mtlTexDesc
Expand All @@ -60,6 +64,11 @@
bytesPerRow: _subresources[0].layout.rowPitch];
} else if (dvcMem && dvcMem->getMTLHeap() && !_image->getIsDepthStencil()) {
// Metal support for depth/stencil from heaps is flaky
_heapAllocation.heap = dvcMem->getMTLHeap();
_heapAllocation.offset = memoryBinding->getDeviceMemoryOffset() + _subresources[0].layout.offset;
const auto texSizeAlign = [dvcMem->getMTLDevice() heapTextureSizeAndAlignWithDescriptor:mtlTexDesc];
_heapAllocation.size = texSizeAlign.size;
_heapAllocation.align = texSizeAlign.align;
_mtlTexture = [dvcMem->getMTLHeap()
newTextureWithDescriptor: mtlTexDesc
offset: memoryBinding->getDeviceMemoryOffset() + _subresources[0].layout.offset];
Expand Down Expand Up @@ -827,6 +836,10 @@ static MTLRegion getMTLRegion(const ImgRgn& imgRgn) {
imgData.usage = getCombinedUsage();
}

MTLTextureDescriptor* MVKImage::newMTLTextureDescriptor(uint32_t planeIndex) {
return _planes[planeIndex]->newMTLTextureDescriptor();
}

// Returns whether an MVKImageView can have the specified format.
// If the list of pre-declared view formats is not empty,
// and the format is not on that list, the view format is not valid.
Expand Down Expand Up @@ -1073,6 +1086,11 @@ static MTLRegion getMTLRegion(const ImgRgn& imgRgn) {
return _memoryBindings[0]->_deviceMemory ? _memoryBindings[0]->_deviceMemory->getMTLCPUCacheMode() : MTLCPUCacheModeDefaultCache;
}

HeapAllocation* MVKImage::getHeapAllocation(uint32_t planeIndex) {
auto& heapAllocation = _planes[planeIndex]->_heapAllocation;
return (heapAllocation.isValid()) ? &heapAllocation : nullptr;
}

MTLTextureUsage MVKImage::getMTLTextureUsage(MTLPixelFormat mtlPixFmt) {

// In the special case of a dedicated aliasable image, we must presume the texture can be used for anything.
Expand All @@ -1094,7 +1112,9 @@ static MTLRegion getMTLRegion(const ImgRgn& imgRgn) {

// Metal before 3.0 doesn't support 3D compressed textures, so we'll
// decompress the texture ourselves, and we need to be able to write to it.
bool makeWritable = MVK_MACOS && _is3DCompressed;
// Additionally, the ability to create 2D alias over 3D image is dependent
// on write capability to synchronize correctly.
bool makeWritable = (MVK_MACOS && _is3DCompressed) || _is2DViewOn3DImageCompatible;
if (makeWritable) {
mvkEnableFlags(mtlUsage, MTLTextureUsageShaderWrite);
}
Expand Down Expand Up @@ -1258,6 +1278,8 @@ static MTLRegion getMTLRegion(const ImgRgn& imgRgn) {
if (pExportInfo && pExportInfo->exportObjectType == VK_EXPORT_METAL_OBJECT_TYPE_METAL_IOSURFACE_BIT_EXT && !_ioSurface) {
setConfigurationResult(useIOSurface(nil));
}

_is2DViewOn3DImageCompatible = mvkIsAnyFlagEnabled(pCreateInfo->flags, VK_IMAGE_CREATE_2D_VIEW_COMPATIBLE_BIT_EXT);
}

VkSampleCountFlagBits MVKImage::validateSamples(const VkImageCreateInfo* pCreateInfo, bool isAttachment) {
Expand Down Expand Up @@ -1805,24 +1827,51 @@ static void signalAndUntrack(const MVKSwapchainSignaler& signaler) {
MTLTextureType mtlTextureType = _imageView->_mtlTextureType;
NSRange sliceRange = NSMakeRange(_imageView->_subresourceRange.baseArrayLayer, _imageView->_subresourceRange.layerCount);
// Fake support for 2D views of 3D textures.
if (_imageView->_image->getImageType() == VK_IMAGE_TYPE_3D &&
id<MTLTexture> aliasTex = nil;
auto* image = _imageView->_image;
id<MTLTexture> mtlTex = image->getMTLTexture(_planeIndex);
if (image->getImageType() == VK_IMAGE_TYPE_3D &&
(mtlTextureType == MTLTextureType2D || mtlTextureType == MTLTextureType2DArray)) {
mtlTextureType = MTLTextureType3D;
sliceRange = NSMakeRange(0, 1);
if (image->_is2DViewOn3DImageCompatible) {
const auto heapAllocation = image->getHeapAllocation(_planeIndex);
MVKAssert(heapAllocation, "Attempting to create a 2D view of a 3D texture without a placement heap");

const auto relativeSliceOffset = _imageView->_subresourceRange.baseArrayLayer * (heapAllocation->size / image->_extent.depth);
MTLTextureDescriptor* mtlTexDesc = image->newMTLTextureDescriptor(_planeIndex); // temp retain

mtlTexDesc.depth = 1;
mtlTexDesc.arrayLength = _imageView->_subresourceRange.layerCount;
mtlTexDesc.textureType = mtlTextureType;

// Create a temporary texture that is backed by the 3D texture's memory
aliasTex = [heapAllocation->heap
newTextureWithDescriptor: mtlTexDesc
offset: heapAllocation->offset + relativeSliceOffset];

[mtlTexDesc release]; // temp release

mtlTex = aliasTex;
sliceRange = NSMakeRange(0, _imageView->_subresourceRange.layerCount);
} else {
mtlTextureType = MTLTextureType3D;
sliceRange = NSMakeRange(0, 1);
}
}
id<MTLTexture> mtlTex = _imageView->_image->getMTLTexture(_planeIndex);
id<MTLTexture> texView = nil;
if (_useNativeSwizzle) {
return [mtlTex newTextureViewWithPixelFormat: _mtlPixFmt
textureType: mtlTextureType
levels: NSMakeRange(_imageView->_subresourceRange.baseMipLevel, _imageView->_subresourceRange.levelCount)
slices: sliceRange
swizzle: mvkMTLTextureSwizzleChannelsFromVkComponentMapping(_componentSwizzle)]; // retained
texView = [mtlTex newTextureViewWithPixelFormat: _mtlPixFmt
textureType: mtlTextureType
levels: NSMakeRange(_imageView->_subresourceRange.baseMipLevel, _imageView->_subresourceRange.levelCount)
slices: sliceRange
swizzle: mvkMTLTextureSwizzleChannelsFromVkComponentMapping(_componentSwizzle)]; // retained
} else {
return [mtlTex newTextureViewWithPixelFormat: _mtlPixFmt
textureType: mtlTextureType
levels: NSMakeRange(_imageView->_subresourceRange.baseMipLevel, _imageView->_subresourceRange.levelCount)
slices: sliceRange]; // retained
texView = [mtlTex newTextureViewWithPixelFormat: _mtlPixFmt
textureType: mtlTextureType
levels: NSMakeRange(_imageView->_subresourceRange.baseMipLevel, _imageView->_subresourceRange.levelCount)
slices: sliceRange]; // retained
}
[aliasTex release];
return texView;
}


Expand Down Expand Up @@ -2242,18 +2291,6 @@ static void signalAndUntrack(const MVKSwapchainSignaler& signaler) {
}
}

VkImageType imgType = _image->getImageType();
VkImageViewType viewType = pCreateInfo->viewType;

// VK_KHR_maintenance1 supports taking 2D image views of 3D slices for sampling.
// No dice in Metal. But we are able to fake out a 3D render attachment by making the Metal view
// itself a 3D texture (when we create it), and setting the rendering depthPlane appropriately.
if ((viewType == VK_IMAGE_VIEW_TYPE_2D || viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY) && (imgType == VK_IMAGE_TYPE_3D)) {
if (!mvkIsOnlyAnyFlagEnabled(_usage, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) {
setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImageView(): 2D views on 3D images can only be used as color attachments."));
}
}

// If a 2D array view on a 2D image with layerCount 1, and the only usages are
// attachment usages, then force the use of a 2D non-arrayed view. This is important for
// input attachments, or they won't match the types declared in the fragment shader.
Expand Down
1 change: 1 addition & 0 deletions MoltenVK/MoltenVK/Layers/MVKExtensions.def
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ MVK_EXTENSION(EXT_hdr_metadata, EXT_HDR_METADATA,
MVK_EXTENSION(EXT_headless_surface, EXT_HEADLESS_SURFACE, INSTANCE, 10.11, 8.0, 1.0)
MVK_EXTENSION(EXT_host_image_copy, EXT_HOST_IMAGE_COPY, DEVICE, 10.11, 8.0, 1.0)
MVK_EXTENSION(EXT_host_query_reset, EXT_HOST_QUERY_RESET, DEVICE, 10.11, 8.0, 1.0)
MVK_EXTENSION(EXT_image_2d_view_of_3d, EXT_IMAGE_2D_VIEW_OF_3D, DEVICE, 10.15, 13.0, 1.0)
MVK_EXTENSION(EXT_image_robustness, EXT_IMAGE_ROBUSTNESS, DEVICE, 10.11, 8.0, 1.0)
MVK_EXTENSION(EXT_inline_uniform_block, EXT_INLINE_UNIFORM_BLOCK, DEVICE, 10.11, 8.0, 1.0)
MVK_EXTENSION(EXT_layer_settings, EXT_LAYER_SETTINGS, INSTANCE, 10.11, 8.0, 1.0)
Expand Down