diff --git a/Docs/MoltenVK_Runtime_UserGuide.md b/Docs/MoltenVK_Runtime_UserGuide.md index 0148448b..3775a749 100644 --- a/Docs/MoltenVK_Runtime_UserGuide.md +++ b/Docs/MoltenVK_Runtime_UserGuide.md @@ -350,6 +350,7 @@ In addition to core *Vulkan* functionality, **MoltenVK** also supports the foll - `VK_KHR_timeline_semaphore` - `VK_KHR_uniform_buffer_standard_layout` - `VK_KHR_variable_pointers` +- `VK_EXT_4444_formats` *(requires 16-bit formats and either native texture swizzling or manual swizzling to be enabled)* - `VK_EXT_buffer_device_address` *(requires GPU Tier 2 argument buffers support)* - `VK_EXT_debug_marker` - `VK_EXT_debug_report` diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md index e96a4d08..1a1284e0 100644 --- a/Docs/Whats_New.md +++ b/Docs/Whats_New.md @@ -19,6 +19,7 @@ MoltenVK 1.2.5 Released TBD - Add support for extensions: + - `VK_EXT_4444_formats` - `VK_EXT_shader_demote_to_helper_invocation` - Ensure non-dispatch compute commands don't interfere with compute encoding state used by dispatch commands. - Support `VK_PRESENT_MODE_IMMEDIATE_KHR` if `VkPresentTimeGOOGLE::desiredPresentTime` is zero. diff --git a/MoltenVK/MoltenVK/API/mvk_datatypes.h b/MoltenVK/MoltenVK/API/mvk_datatypes.h index c5c2e182..f5c103b4 100644 --- a/MoltenVK/MoltenVK/API/mvk_datatypes.h +++ b/MoltenVK/MoltenVK/API/mvk_datatypes.h @@ -227,6 +227,15 @@ MTLTextureSwizzle mvkMTLTextureSwizzleFromVkComponentSwizzle(VkComponentSwizzle /** Returns all four Metal texture swizzles from the Vulkan component mapping. */ MTLTextureSwizzleChannels mvkMTLTextureSwizzleChannelsFromVkComponentMapping(VkComponentMapping vkMapping); +/** Maps a clear color according to the specified VkComponentSwizzle. */ +float mvkVkClearColorFloatValueFromVkComponentSwizzle(float *colors, uint32_t index, VkComponentSwizzle vkSwizzle); + +/** Maps a clear color according to the specified VkComponentSwizzle. */ +uint32_t mvkVkClearColorUIntValueFromVkComponentSwizzle(uint32_t *colors, uint32_t index, VkComponentSwizzle vkSwizzle); + +/** Maps a clear color according to the specified VkComponentSwizzle. */ +int32_t mvkVkClearColorIntValueFromVkComponentSwizzle(int32_t *colors, uint32_t index, VkComponentSwizzle vkSwizzle); + #pragma mark Mipmaps diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm index 816dbc3f..2c0ef546 100644 --- a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm +++ b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm @@ -374,6 +374,7 @@ bool MVKCmdBlitImage::canCopyFormats(const VkImageBlit2& region) { uint8_t srcPlaneIndex = MVKImage::getPlaneFromVkImageAspectFlags(region.srcSubresource.aspectMask); uint8_t dstPlaneIndex = MVKImage::getPlaneFromVkImageAspectFlags(region.dstSubresource.aspectMask); return ((_srcImage->getMTLPixelFormat(srcPlaneIndex) == _dstImage->getMTLPixelFormat(dstPlaneIndex)) && + !_srcImage->needsSwizzle() && (_dstImage->getSampleCount() == _srcImage->getSampleCount())); } @@ -456,7 +457,7 @@ void MVKCmdBlitImage::encode(MVKCommandEncoder* cmdEncoder, MVKCommandUse com uint32_t copyCnt = 0; uint32_t blitCnt = 0; - // Separate BLITs into those that are really just simple texure region copies, + // Separate BLITs into those that are really just simple texture region copies, // and those that require rendering for (auto& vkIB : _vkImageBlits) { if (canCopyFormats(vkIB) && canCopy(vkIB)) { @@ -500,6 +501,15 @@ void MVKCmdBlitImage::encode(MVKCommandEncoder* cmdEncoder, MVKCommandUse com id srcMTLTex = _srcImage->getMTLTexture(srcPlaneIndex); id dstMTLTex = _dstImage->getMTLTexture(dstPlaneIndex); if (blitCnt && srcMTLTex && dstMTLTex) { + if (cmdEncoder->getDevice()->_pMetalFeatures->nativeTextureSwizzle && + _srcImage->needsSwizzle()) { + // Use a view that has a swizzle on it. + srcMTLTex = [[srcMTLTex newTextureViewWithPixelFormat:srcMTLTex.pixelFormat + textureType:srcMTLTex.textureType + levels:NSMakeRange(0, srcMTLTex.mipmapLevelCount) + slices:NSMakeRange(0, srcMTLTex.arrayLength) + swizzle:_srcImage->getPixelFormats()->getMTLTextureSwizzleChannels(_srcImage->getVkFormat())] autorelease]; + } cmdEncoder->endCurrentMetalEncoding(); MTLRenderPassDescriptor* mtlRPD = [MTLRenderPassDescriptor renderPassDescriptor]; @@ -549,6 +559,14 @@ void MVKCmdBlitImage::encode(MVKCommandEncoder* cmdEncoder, MVKCommandUse com blitKey.srcFilter = mvkMTLSamplerMinMagFilterFromVkFilter(_filter); blitKey.srcAspect = mvkIBR.region.srcSubresource.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); blitKey.dstSampleCount = mvkSampleCountFromVkSampleCountFlagBits(_dstImage->getSampleCount()); + if (!cmdEncoder->getDevice()->_pMetalFeatures->nativeTextureSwizzle && + _srcImage->needsSwizzle()) { + VkComponentMapping vkMapping = _srcImage->getPixelFormats()->getVkComponentMapping(_srcImage->getVkFormat()); + blitKey.srcSwizzleR = vkMapping.r; + blitKey.srcSwizzleG = vkMapping.g; + blitKey.srcSwizzleB = vkMapping.b; + blitKey.srcSwizzleA = vkMapping.a; + } id mtlRPS = cmdEncoder->getCommandEncodingPool()->getCmdBlitImageMTLRenderPipelineState(blitKey); bool isBlittingDepth = mvkIsAnyFlagEnabled(blitKey.srcAspect, (VK_IMAGE_ASPECT_DEPTH_BIT)); bool isBlittingStencil = mvkIsAnyFlagEnabled(blitKey.srcAspect, (VK_IMAGE_ASPECT_STENCIL_BIT)); diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.h b/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.h index 1e37844f..b7f679c7 100644 --- a/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.h +++ b/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.h @@ -37,20 +37,31 @@ class MVKQueryPool; * This structure can be used as a key in a std::map and std::unordered_map. */ typedef struct MVKRPSKeyBlitImg { - uint16_t srcMTLPixelFormat = 0; /**< as MTLPixelFormat */ - uint16_t dstMTLPixelFormat = 0; /**< as MTLPixelFormat */ - uint8_t srcMTLTextureType = 0; /**< as MTLTextureType */ + uint16_t srcMTLPixelFormat : 12; /**< as MTLPixelFormat */ + uint16_t dstMTLPixelFormat : 12; /**< as MTLPixelFormat */ + uint8_t srcMTLTextureType : 4; /**< as MTLTextureType */ + uint8_t srcFilter : 4; /**< as MTLSamplerMinMagFilter */ uint8_t srcAspect = 0; /**< as VkImageAspectFlags */ - uint8_t srcFilter = 0; /**< as MTLSamplerMinMagFilter */ uint8_t dstSampleCount = 0; + uint8_t srcSwizzleR : 4; /**< as VkComponentSwizzle */ + uint8_t srcSwizzleG : 4; /**< as VkComponentSwizzle */ + uint8_t srcSwizzleB : 4; /**< as VkComponentSwizzle */ + uint8_t srcSwizzleA : 4; /**< as VkComponentSwizzle */ + + MVKRPSKeyBlitImg() : srcMTLPixelFormat(0), dstMTLPixelFormat(0), srcMTLTextureType(0), srcFilter(0), + srcSwizzleR(0), srcSwizzleG(0), srcSwizzleB(0), srcSwizzleA(0) {} bool operator==(const MVKRPSKeyBlitImg& rhs) const { if (srcMTLPixelFormat != rhs.srcMTLPixelFormat) { return false; } if (dstMTLPixelFormat != rhs.dstMTLPixelFormat) { return false; } if (srcMTLTextureType != rhs.srcMTLTextureType) { return false; } - if (srcAspect != rhs.srcAspect) { return false; } if (srcFilter != rhs.srcFilter) { return false; } + if (srcAspect != rhs.srcAspect) { return false; } if (dstSampleCount != rhs.dstSampleCount) { return false; } + if (srcSwizzleR != rhs.srcSwizzleR) { return false; } + if (srcSwizzleG != rhs.srcSwizzleG) { return false; } + if (srcSwizzleB != rhs.srcSwizzleB) { return false; } + if (srcSwizzleA != rhs.srcSwizzleA) { return false; } return true; } @@ -70,23 +81,40 @@ typedef struct MVKRPSKeyBlitImg { srcMTLTextureType == MTLTextureType1DArray); } + VkComponentMapping getSrcSwizzle() { + return { (VkComponentSwizzle)srcSwizzleR, (VkComponentSwizzle)srcSwizzleG, + (VkComponentSwizzle)srcSwizzleB, (VkComponentSwizzle)srcSwizzleA }; + } + std::size_t hash() const { std::size_t hash = srcMTLPixelFormat; - hash <<= 16; + hash <<= 12; hash |= dstMTLPixelFormat; - hash <<= 8; + hash <<= 4; hash |= srcMTLTextureType; + hash <<= 4; + hash |= srcFilter; + hash <<= 8; hash |= srcAspect; - hash <<= 8; - hash |= srcFilter; - hash <<= 8; hash |= dstSampleCount; + + hash <<= 4; + hash |= srcSwizzleR; + + hash <<= 4; + hash |= srcSwizzleG; + + hash <<= 4; + hash |= srcSwizzleB; + + hash <<= 4; + hash |= srcSwizzleA; return hash; } diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.mm b/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.mm index 36fb03b7..4e1ed93c 100644 --- a/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.mm +++ b/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.mm @@ -167,6 +167,25 @@ id MVKCommandResourceFactory::newCmdClearMTLRenderPipeli return rps; } +static char getSwizzleChar(char defaultChar, VkComponentSwizzle vkSwizzle) { + switch (vkSwizzle) { + case VK_COMPONENT_SWIZZLE_IDENTITY: return defaultChar; + // FIXME: 0 and 1 (currently not used in any default swizzles) + case VK_COMPONENT_SWIZZLE_R: return 'x'; + case VK_COMPONENT_SWIZZLE_G: return 'y'; + case VK_COMPONENT_SWIZZLE_B: return 'z'; + case VK_COMPONENT_SWIZZLE_A: return 'w'; + default: return defaultChar; + } +} + +static void getSwizzleString(char swizzleStr[4], VkComponentMapping vkMapping) { + swizzleStr[0] = getSwizzleChar('x', vkMapping.r); + swizzleStr[1] = getSwizzleChar('y', vkMapping.g); + swizzleStr[2] = getSwizzleChar('z', vkMapping.b); + swizzleStr[3] = getSwizzleChar('w', vkMapping.a); +} + id MVKCommandResourceFactory::newBlitFragFunction(MVKRPSKeyBlitImg& blitKey) { @autoreleasepool { bool isLayeredBlit = blitKey.dstSampleCount > 1 ? _device->_pMetalFeatures->multisampleLayeredRendering : _device->_pMetalFeatures->layeredRendering; @@ -177,6 +196,7 @@ id MVKCommandResourceFactory::newBlitFragFunction(MVKRPSKeyBlitImg& NSString* typePrefix = @"texture"; NSString* typeSuffix; NSString* coordArg; + char swizzleArg[4] = { 'x', 'y', 'z', 'w' }; if (mvkIsAnyFlagEnabled(blitKey.srcAspect, (VK_IMAGE_ASPECT_DEPTH_BIT))) { typePrefix = @"depth"; } @@ -208,6 +228,9 @@ id MVKCommandResourceFactory::newBlitFragFunction(MVKRPSKeyBlitImg& } NSString* sliceArg = isArrayType ? (isLayeredBlit ? @", subRez.slice + varyings.v_layer" : @", subRez.slice") : @""; NSString* srcFilter = isLinearFilter ? @"linear" : @"nearest"; + if (!getDevice()->_pMetalFeatures->nativeTextureSwizzle) { + getSwizzleString(swizzleArg, blitKey.getSrcSwizzle()); + } NSMutableString* msl = [NSMutableString stringWithCapacity: (2 * KIBI) ]; [msl appendLineMVK: @"#include "]; @@ -263,15 +286,15 @@ id MVKCommandResourceFactory::newBlitFragFunction(MVKRPSKeyBlitImg& [msl appendLineMVK: @" constant TexSubrez& subRez [[buffer(0)]]) {"]; [msl appendLineMVK: @" FragmentOutputs out;"]; if (mvkIsAnyFlagEnabled(blitKey.srcAspect, (VK_IMAGE_ASPECT_DEPTH_BIT))) { - [msl appendFormat: @" out.depth = tex.sample(ce_sampler, varyings.v_texCoord%@%@, level(subRez.lod));", coordArg, sliceArg]; + [msl appendFormat: @" out.depth = tex.sample(ce_sampler, varyings.v_texCoord%@%@, level(subRez.lod)).%c;", coordArg, sliceArg, swizzleArg[0]]; [msl appendLineMVK]; } if (mvkIsAnyFlagEnabled(blitKey.srcAspect, (VK_IMAGE_ASPECT_STENCIL_BIT))) { - [msl appendFormat: @" out.stencil = stencilTex.sample(ce_stencil_sampler, varyings.v_texCoord%@%@, level(subRez.lod)).x;", coordArg, sliceArg]; + [msl appendFormat: @" out.stencil = stencilTex.sample(ce_stencil_sampler, varyings.v_texCoord%@%@, level(subRez.lod)).%c;", coordArg, sliceArg, swizzleArg[0]]; [msl appendLineMVK]; } if (!mvkIsAnyFlagEnabled(blitKey.srcAspect, (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT))) { - [msl appendFormat: @" out.color = tex.sample(ce_sampler, varyings.v_texCoord%@%@, level(subRez.lod));", coordArg, sliceArg]; + [msl appendFormat: @" out.color = tex.sample(ce_sampler, varyings.v_texCoord%@%@, level(subRez.lod)).%.4s;", coordArg, sliceArg, swizzleArg]; [msl appendLineMVK]; } [msl appendLineMVK: @" return out;"]; diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm index 93adca53..12f1514c 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm @@ -371,6 +371,15 @@ void MVKPhysicalDevice::getFeatures(VkPhysicalDeviceFeatures2* features) { portabilityFeatures->vertexAttributeAccessBeyondStride = true; // Costs additional buffers. Should make configuration switch. break; } + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_4444_FORMATS_FEATURES_EXT: { + auto* formatFeatures = (VkPhysicalDevice4444FormatsFeaturesEXT*)next; + bool canSupport4444 = _metalFeatures.tileBasedDeferredRendering && + (_metalFeatures.nativeTextureSwizzle || + mvkConfig().fullImageViewSwizzle); + formatFeatures->formatA4R4G4B4 = canSupport4444; + formatFeatures->formatA4B4G4R4 = canSupport4444; + break; + } case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADER_INTERLOCK_FEATURES_EXT: { auto* interlockFeatures = (VkPhysicalDeviceFragmentShaderInterlockFeaturesEXT*)next; interlockFeatures->fragmentShaderSampleInterlock = _metalFeatures.rasterOrderGroups; diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDeviceFeatureStructs.def b/MoltenVK/MoltenVK/GPUObjects/MVKDeviceFeatureStructs.def index 0674e4c1..c0bbb481 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKDeviceFeatureStructs.def +++ b/MoltenVK/MoltenVK/GPUObjects/MVKDeviceFeatureStructs.def @@ -62,6 +62,7 @@ MVK_DEVICE_FEATURE(VariablePointer, VARIABLE_POINTER, MVK_DEVICE_FEATURE(VulkanMemoryModel, VULKAN_MEMORY_MODEL, 3) MVK_DEVICE_FEATURE_EXTN(FragmentShaderBarycentric, FRAGMENT_SHADER_BARYCENTRIC, KHR, 1) MVK_DEVICE_FEATURE_EXTN(PortabilitySubset, PORTABILITY_SUBSET, KHR, 15) +MVK_DEVICE_FEATURE_EXTN(4444Formats, 4444_FORMATS, EXT, 2) MVK_DEVICE_FEATURE_EXTN(FragmentShaderInterlock, FRAGMENT_SHADER_INTERLOCK, EXT, 3) MVK_DEVICE_FEATURE_EXTN(PipelineCreationCacheControl, PIPELINE_CREATION_CACHE_CONTROL, EXT, 1) MVK_DEVICE_FEATURE_EXTN(Robustness2, ROBUSTNESS_2, EXT, 3) diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h index db83c323..572e8f06 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h @@ -224,6 +224,9 @@ public: /** Returns the number of planes of this image view. */ uint8_t getPlaneCount() { return _planes.size(); } + /** Returns whether or not the image format requires swizzling. */ + bool needsSwizzle() { return getPixelFormats()->needsSwizzle(_vkFormat); } + /** Populates the specified layout for the specified sub-resource. */ VkResult getSubresourceLayout(const VkImageSubresource* pSubresource, VkSubresourceLayout* pLayout); diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm index cc8a1601..14870221 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm @@ -1575,6 +1575,26 @@ VkResult MVKImageViewPlane::initSwizzledMTLPixelFormat(const VkImageViewCreateIn VkImageAspectFlags aspectMask = pCreateInfo->subresourceRange.aspectMask; #define adjustComponentSwizzleValue(comp, currVal, newVal) if (_componentSwizzle.comp == VK_COMPONENT_SWIZZLE_ ##currVal) { _componentSwizzle.comp = VK_COMPONENT_SWIZZLE_ ##newVal; } +#define adjustAnyComponentSwizzleValue(comp, I, R, G, B, A) \ + switch (_componentSwizzle.comp) { \ + case VK_COMPONENT_SWIZZLE_IDENTITY: \ + _componentSwizzle.comp = VK_COMPONENT_SWIZZLE_##I; \ + break; \ + case VK_COMPONENT_SWIZZLE_R: \ + _componentSwizzle.comp = VK_COMPONENT_SWIZZLE_##R; \ + break; \ + case VK_COMPONENT_SWIZZLE_G: \ + _componentSwizzle.comp = VK_COMPONENT_SWIZZLE_##G; \ + break; \ + case VK_COMPONENT_SWIZZLE_B: \ + _componentSwizzle.comp = VK_COMPONENT_SWIZZLE_##B; \ + break; \ + case VK_COMPONENT_SWIZZLE_A: \ + _componentSwizzle.comp = VK_COMPONENT_SWIZZLE_##A; \ + break; \ + default: \ + break; \ + } // Use swizzle adjustment to bridge some differences between Vulkan and Metal pixel formats. // Do this ahead of other tests and adjustments so that swizzling will be enabled by tests below. @@ -1589,6 +1609,24 @@ VkResult MVKImageViewPlane::initSwizzledMTLPixelFormat(const VkImageViewCreateIn adjustComponentSwizzleValue(a, IDENTITY, ONE); break; + case VK_FORMAT_A4R4G4B4_UNORM_PACK16: + // Metal doesn't (publicly) support this directly, so use a swizzle to get the ordering right. + // n.b. **Do NOT use adjustComponentSwizzleValue if multiple values need substitution, + // and some of the substitutes are keys for other substitutions!** + adjustAnyComponentSwizzleValue(r, G, G, B, A, R); + adjustAnyComponentSwizzleValue(g, B, G, B, A, R); + adjustAnyComponentSwizzleValue(b, A, G, B, A, R); + adjustAnyComponentSwizzleValue(a, R, G, B, A, R); + break; + + case VK_FORMAT_A4B4G4R4_UNORM_PACK16: + // Metal doesn't support this directly, so use a swizzle to get the ordering right. + adjustAnyComponentSwizzleValue(r, A, A, B, G, R); + adjustAnyComponentSwizzleValue(g, B, A, B, G, R); + adjustAnyComponentSwizzleValue(b, G, A, B, G, R); + adjustAnyComponentSwizzleValue(a, R, A, B, G, R); + break; + default: break; } diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.h b/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.h index 03fc0f98..479965b4 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.h @@ -148,6 +148,7 @@ typedef struct MVKVkFormatDesc { uint32_t bytesPerBlock; MVKFormatType formatType; VkFormatProperties properties; + VkComponentMapping componentMapping; const char* name; bool hasReportedSubstitution; @@ -158,6 +159,13 @@ typedef struct MVKVkFormatDesc { inline bool vertexIsSupported() const { return (mtlVertexFormat != MTLVertexFormatInvalid); }; inline bool vertexIsSupportedOrSubstitutable() const { return vertexIsSupported() || (mtlVertexFormatSubstitute != MTLVertexFormatInvalid); }; + + bool needsSwizzle() const { + return componentMapping.r != VK_COMPONENT_SWIZZLE_IDENTITY || + componentMapping.g != VK_COMPONENT_SWIZZLE_IDENTITY || + componentMapping.b != VK_COMPONENT_SWIZZLE_IDENTITY || + componentMapping.a != VK_COMPONENT_SWIZZLE_IDENTITY; + } } MVKVkFormatDesc; /** Describes the properties of a MTLPixelFormat or MTLVertexFormat. */ @@ -320,6 +328,21 @@ public: */ size_t getBytesPerLayer(MTLPixelFormat mtlFormat, size_t bytesPerRow, uint32_t texelRowsPerLayer); + /** Returns whether or not the specified Vulkan format requires swizzling to use with Metal. */ + bool needsSwizzle(VkFormat vkFormat); + + /** Returns any VkComponentMapping needed to use the specified Vulkan format. */ + VkComponentMapping getVkComponentMapping(VkFormat vkFormat); + + /** + * Returns the inverse of the VkComponentMapping needed to use the specified Vulkan format. + * If the original mapping is not a one-to-one function, the behaviour is undefined. + */ + VkComponentMapping getInverseComponentMapping(VkFormat vkFormat); + + /** Returns any MTLTextureSwizzleChannels needed to use the specified Vulkan format. */ + MTLTextureSwizzleChannels getMTLTextureSwizzleChannels(VkFormat vkFormat); + /** Returns the default properties for the specified Vulkan format. */ VkFormatProperties& getVkFormatProperties(VkFormat vkFormat); diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm index 79f1dffa..3044f66d 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKPixelFormats.mm @@ -377,6 +377,44 @@ size_t MVKPixelFormats::getBytesPerLayer(MTLPixelFormat mtlFormat, size_t bytesP return mvkCeilingDivide(texelRowsPerLayer, getVkFormatDesc(mtlFormat).blockTexelSize.height) * bytesPerRow; } +bool MVKPixelFormats::needsSwizzle(VkFormat vkFormat) { + return getVkFormatDesc(vkFormat).needsSwizzle(); +} + +VkComponentMapping MVKPixelFormats::getVkComponentMapping(VkFormat vkFormat) { + return getVkFormatDesc(vkFormat).componentMapping; +} + +VkComponentMapping MVKPixelFormats::getInverseComponentMapping(VkFormat vkFormat) { +#define INVERT_SWIZZLE(x, X, Y) \ + case VK_COMPONENT_SWIZZLE_##X: \ + inverse.x = VK_COMPONENT_SWIZZLE_##Y; \ + break +#define INVERT_MAPPING(y, Y) \ + switch (mapping.y) { \ + case VK_COMPONENT_SWIZZLE_IDENTITY: \ + inverse.y = VK_COMPONENT_SWIZZLE_IDENTITY; \ + break; \ + INVERT_SWIZZLE(r, R, Y); \ + INVERT_SWIZZLE(g, G, Y); \ + INVERT_SWIZZLE(b, B, Y); \ + INVERT_SWIZZLE(a, A, Y); \ + default: break; \ + } + VkComponentMapping mapping = getVkComponentMapping(vkFormat), inverse; + INVERT_MAPPING(r, R) + INVERT_MAPPING(g, G) + INVERT_MAPPING(b, B) + INVERT_MAPPING(a, A) + return inverse; +#undef INVERT_MAPPING +#undef INVERT_SWIZZLE +} + +MTLTextureSwizzleChannels MVKPixelFormats::getMTLTextureSwizzleChannels(VkFormat vkFormat) { + return mvkMTLTextureSwizzleChannelsFromVkComponentMapping(getVkComponentMapping(vkFormat)); +} + VkFormatProperties& MVKPixelFormats::getVkFormatProperties(VkFormat vkFormat) { return getVkFormatDesc(vkFormat).properties; } @@ -464,13 +502,18 @@ MTLVertexFormat MVKPixelFormats::getMTLVertexFormat(VkFormat vkFormat) { MTLClearColor MVKPixelFormats::getMTLClearColor(VkClearValue vkClearValue, VkFormat vkFormat) { MTLClearColor mtlClr; + // The VkComponentMapping (and its MTLTextureSwizzleChannels equivalent) define the *sources* + // for the texture color components for reading. Since we're *writing* to the texture, + // we need to *invert* the mapping. + // n.b. Bad things might happen if the original swizzle isn't one-to-one! + VkComponentMapping inverseMap = getInverseComponentMapping(vkFormat); switch (getFormatType(vkFormat)) { case kMVKFormatColorHalf: - case kMVKFormatColorFloat: - mtlClr.red = vkClearValue.color.float32[0]; - mtlClr.green = vkClearValue.color.float32[1]; - mtlClr.blue = vkClearValue.color.float32[2]; - mtlClr.alpha = vkClearValue.color.float32[3]; + case kMVKFormatColorFloat: { + mtlClr.red = mvkVkClearColorFloatValueFromVkComponentSwizzle(vkClearValue.color.float32, 0, inverseMap.r); + mtlClr.green = mvkVkClearColorFloatValueFromVkComponentSwizzle(vkClearValue.color.float32, 1, inverseMap.g); + mtlClr.blue = mvkVkClearColorFloatValueFromVkComponentSwizzle(vkClearValue.color.float32, 2, inverseMap.b); + mtlClr.alpha = mvkVkClearColorFloatValueFromVkComponentSwizzle(vkClearValue.color.float32, 3, inverseMap.a); if (_physicalDevice && _physicalDevice->getMetalFeatures()->clearColorFloatRounding == MVK_FLOAT_ROUNDING_DOWN) { // For normalized formats, increment the clear value by half the ULP @@ -486,6 +529,8 @@ MTLClearColor MVKPixelFormats::getMTLClearColor(VkClearValue vkClearValue, VkFor #define OFFSET_SNORM(COLOR, BIT_WIDTH) OFFSET_NORM(-1.0, COLOR, BIT_WIDTH - 1) switch (vkFormat) { case VK_FORMAT_R4G4B4A4_UNORM_PACK16: + case VK_FORMAT_A4R4G4B4_UNORM_PACK16: + case VK_FORMAT_A4B4G4R4_UNORM_PACK16: OFFSET_UNORM(red, 4) OFFSET_UNORM(green, 4) OFFSET_UNORM(blue, 4) @@ -571,21 +616,22 @@ MTLClearColor MVKPixelFormats::getMTLClearColor(VkClearValue vkClearValue, VkFor #undef OFFSET_NORM } break; + } case kMVKFormatColorUInt8: case kMVKFormatColorUInt16: case kMVKFormatColorUInt32: - mtlClr.red = vkClearValue.color.uint32[0]; - mtlClr.green = vkClearValue.color.uint32[1]; - mtlClr.blue = vkClearValue.color.uint32[2]; - mtlClr.alpha = vkClearValue.color.uint32[3]; + mtlClr.red = mvkVkClearColorUIntValueFromVkComponentSwizzle(vkClearValue.color.uint32, 0, inverseMap.r); + mtlClr.green = mvkVkClearColorUIntValueFromVkComponentSwizzle(vkClearValue.color.uint32, 1, inverseMap.g); + mtlClr.blue = mvkVkClearColorUIntValueFromVkComponentSwizzle(vkClearValue.color.uint32, 2, inverseMap.b); + mtlClr.alpha = mvkVkClearColorUIntValueFromVkComponentSwizzle(vkClearValue.color.uint32, 3, inverseMap.a); break; case kMVKFormatColorInt8: case kMVKFormatColorInt16: case kMVKFormatColorInt32: - mtlClr.red = vkClearValue.color.int32[0]; - mtlClr.green = vkClearValue.color.int32[1]; - mtlClr.blue = vkClearValue.color.int32[2]; - mtlClr.alpha = vkClearValue.color.int32[3]; + mtlClr.red = mvkVkClearColorIntValueFromVkComponentSwizzle(vkClearValue.color.int32, 0, inverseMap.r); + mtlClr.green = mvkVkClearColorIntValueFromVkComponentSwizzle(vkClearValue.color.int32, 1, inverseMap.g); + mtlClr.blue = mvkVkClearColorIntValueFromVkComponentSwizzle(vkClearValue.color.int32, 2, inverseMap.b); + mtlClr.alpha = mvkVkClearColorIntValueFromVkComponentSwizzle(vkClearValue.color.int32, 3, inverseMap.a); break; default: mtlClr.red = 0.0; @@ -756,16 +802,21 @@ MVKPixelFormats::MVKPixelFormats(MVKPhysicalDevice* physicalDevice) : _physicalD buildVkFormatMaps(); } -#define addVkFormatDescFull(VK_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, CSPC, CSCB, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE) \ +#define addVkFormatDescFull(VK_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, CSPC, CSCB, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE, SWIZ_R, SWIZ_G, SWIZ_B, SWIZ_A) \ MVKAssert(fmtIdx < _vkFormatCount, "Attempting to describe %d VkFormats, but only have space for %d. Increase the value of _vkFormatCount", fmtIdx + 1, _vkFormatCount); \ _vkFormatDescriptions[fmtIdx++] = { VK_FORMAT_ ##VK_FMT, MTLPixelFormat ##MTL_FMT, MTLPixelFormat ##MTL_FMT_ALT, MTLVertexFormat ##MTL_VTX_FMT, MTLVertexFormat ##MTL_VTX_FMT_ALT, \ - CSPC, CSCB, { BLK_W, BLK_H }, BLK_BYTE_CNT, kMVKFormat ##MVK_FMT_TYPE, { 0, 0, 0 }, "VK_FORMAT_" #VK_FMT, false } + CSPC, CSCB, { BLK_W, BLK_H }, BLK_BYTE_CNT, kMVKFormat ##MVK_FMT_TYPE, { 0, 0, 0 }, \ + { VK_COMPONENT_SWIZZLE_ ##SWIZ_R, VK_COMPONENT_SWIZZLE_ ##SWIZ_G, VK_COMPONENT_SWIZZLE_ ##SWIZ_B, VK_COMPONENT_SWIZZLE_ ##SWIZ_A }, \ + "VK_FORMAT_" #VK_FMT, false } #define addVkFormatDesc(VK_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE) \ - addVkFormatDescFull(VK_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, 0, 0, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE) + addVkFormatDescFull(VK_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, 0, 0, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE, IDENTITY, IDENTITY, IDENTITY, IDENTITY) + +#define addVkFormatDescSwizzled(VK_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE, SWIZ_R, SWIZ_G, SWIZ_B, SWIZ_A) \ + addVkFormatDescFull(VK_FMT, MTL_FMT, MTL_FMT_ALT, MTL_VTX_FMT, MTL_VTX_FMT_ALT, 0, 0, BLK_W, BLK_H, BLK_BYTE_CNT, MVK_FMT_TYPE, SWIZ_R, SWIZ_G, SWIZ_B, SWIZ_A) #define addVkFormatDescChromaSubsampling(VK_FMT, MTL_FMT, CSPC, CSCB, BLK_W, BLK_H, BLK_BYTE_CNT) \ - addVkFormatDescFull(VK_FMT, MTL_FMT, Invalid, Invalid, Invalid, CSPC, CSCB, BLK_W, BLK_H, BLK_BYTE_CNT, ColorFloat) + addVkFormatDescFull(VK_FMT, MTL_FMT, Invalid, Invalid, Invalid, CSPC, CSCB, BLK_W, BLK_H, BLK_BYTE_CNT, ColorFloat, IDENTITY, IDENTITY, IDENTITY, IDENTITY) void MVKPixelFormats::initVkFormatCapabilities() { @@ -781,6 +832,8 @@ void MVKPixelFormats::initVkFormatCapabilities() { addVkFormatDesc( R4G4_UNORM_PACK8, Invalid, Invalid, Invalid, Invalid, 1, 1, 1, ColorFloat ); addVkFormatDesc( R4G4B4A4_UNORM_PACK16, ABGR4Unorm, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat ); addVkFormatDesc( B4G4R4A4_UNORM_PACK16, Invalid, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat ); + addVkFormatDescSwizzled( A4R4G4B4_UNORM_PACK16, ABGR4Unorm, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat, G, B, A, R ); + addVkFormatDescSwizzled( A4B4G4R4_UNORM_PACK16, ABGR4Unorm, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat, A, B, G, R ); addVkFormatDesc( R5G6B5_UNORM_PACK16, B5G6R5Unorm, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat ); addVkFormatDesc( B5G6R5_UNORM_PACK16, Invalid, Invalid, Invalid, Invalid, 1, 1, 2, ColorFloat ); @@ -1962,6 +2015,23 @@ void MVKPixelFormats::buildVkFormatMaps() { _vkFormatDescIndicesByVkFormatsExt[vkFmt] = fmtIdx; } + if (vkDesc.needsSwizzle()) { + if (_physicalDevice) { + id mtlDev = _physicalDevice->getMTLDevice(); +#if MVK_MACCAT + bool supportsNativeTextureSwizzle = [mtlDev supportsFamily: MTLGPUFamilyMacCatalyst2]; +#elif MVK_MACOS + bool supportsNativeTextureSwizzle = mvkOSVersionIsAtLeast(10.15) && [mtlDev supportsFeatureSet: MTLFeatureSet_macOS_GPUFamily2_v1]; +#endif +#if MVK_IOS || MVK_TVOS + bool supportsNativeTextureSwizzle = mtlDev && mvkOSVersionIsAtLeast(13.0); +#endif + if (!supportsNativeTextureSwizzle && !mvkConfig().fullImageViewSwizzle) { + vkDesc.mtlPixelFormat = vkDesc.mtlPixelFormatSubstitute = MTLPixelFormatInvalid; + } + } + } + // Populate the back reference from the Metal formats to the Vulkan format. // Validate the corresponding Metal formats for the platform, and clear them // in the Vulkan format if not supported. @@ -2078,6 +2148,13 @@ void MVKPixelFormats::setFormatProperties(MVKVkFormatDesc& vkDesc) { mvkDisableFlags(vkProps.optimalTilingFeatures, (VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT)); } + // These formats require swizzling. In order to support rendering, we'll have to swizzle + // in the fragment shader, but that hasn't been implemented yet. + if (vkDesc.needsSwizzle()) { + mvkDisableFlags(vkProps.optimalTilingFeatures, (kMVKVkFormatFeatureFlagsTexColorAtt | + kMVKVkFormatFeatureFlagsTexBlend)); + } + // Linear tiling is not available to depth/stencil or compressed formats. // GBGR and BGRG formats also do not support linear tiling in Metal. if ( !(vkDesc.formatType == kMVKFormatDepthStencil || vkDesc.formatType == kMVKFormatCompressed || diff --git a/MoltenVK/MoltenVK/Layers/MVKExtensions.def b/MoltenVK/MoltenVK/Layers/MVKExtensions.def index a50b3bb5..0a4bd964 100644 --- a/MoltenVK/MoltenVK/Layers/MVKExtensions.def +++ b/MoltenVK/MoltenVK/Layers/MVKExtensions.def @@ -92,6 +92,7 @@ MVK_EXTENSION(KHR_timeline_semaphore, KHR_TIMELINE_SEMAPHORE, MVK_EXTENSION(KHR_uniform_buffer_standard_layout, KHR_UNIFORM_BUFFER_STANDARD_LAYOUT, DEVICE, 10.11, 8.0) MVK_EXTENSION(KHR_variable_pointers, KHR_VARIABLE_POINTERS, DEVICE, 10.11, 8.0) MVK_EXTENSION(KHR_vulkan_memory_model, KHR_VULKAN_MEMORY_MODEL, DEVICE, MVK_NA, MVK_NA) +MVK_EXTENSION(EXT_4444_formats, EXT_4444_FORMATS, DEVICE, 11.0, 13.0) MVK_EXTENSION(EXT_buffer_device_address, EXT_BUFFER_DEVICE_ADDRESS, DEVICE, 13.0, 16.0) MVK_EXTENSION(EXT_debug_marker, EXT_DEBUG_MARKER, DEVICE, 10.11, 8.0) MVK_EXTENSION(EXT_debug_report, EXT_DEBUG_REPORT, INSTANCE, 10.11, 8.0) diff --git a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm index 1960dedd..1aab82eb 100644 --- a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm +++ b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm @@ -228,6 +228,45 @@ MVK_PUBLIC_SYMBOL MTLTextureSwizzleChannels mvkMTLTextureSwizzleChannelsFromVkCo #undef convert } +MVK_PUBLIC_SYMBOL float mvkVkClearColorFloatValueFromVkComponentSwizzle(float *colors, uint32_t index, VkComponentSwizzle vkSwizzle) { + switch (vkSwizzle) { + case VK_COMPONENT_SWIZZLE_IDENTITY: return colors[index]; + case VK_COMPONENT_SWIZZLE_ZERO: return 0.f; + case VK_COMPONENT_SWIZZLE_ONE: return 1.f; + case VK_COMPONENT_SWIZZLE_R: return colors[0]; + case VK_COMPONENT_SWIZZLE_G: return colors[1]; + case VK_COMPONENT_SWIZZLE_B: return colors[2]; + case VK_COMPONENT_SWIZZLE_A: return colors[3]; + default: return colors[index]; + } +} + +MVK_PUBLIC_SYMBOL uint32_t mvkVkClearColorUIntValueFromVkComponentSwizzle(uint32_t *colors, uint32_t index, VkComponentSwizzle vkSwizzle) { + switch (vkSwizzle) { + case VK_COMPONENT_SWIZZLE_IDENTITY: return colors[index]; + case VK_COMPONENT_SWIZZLE_ZERO: return 0U; + case VK_COMPONENT_SWIZZLE_ONE: return 1U; + case VK_COMPONENT_SWIZZLE_R: return colors[0]; + case VK_COMPONENT_SWIZZLE_G: return colors[1]; + case VK_COMPONENT_SWIZZLE_B: return colors[2]; + case VK_COMPONENT_SWIZZLE_A: return colors[3]; + default: return colors[index]; + } +} + +MVK_PUBLIC_SYMBOL int32_t mvkVkClearColorIntValueFromVkComponentSwizzle(int32_t *colors, uint32_t index, VkComponentSwizzle vkSwizzle) { + switch (vkSwizzle) { + case VK_COMPONENT_SWIZZLE_IDENTITY: return colors[index]; + case VK_COMPONENT_SWIZZLE_ZERO: return 0; + case VK_COMPONENT_SWIZZLE_ONE: return 1; + case VK_COMPONENT_SWIZZLE_R: return colors[0]; + case VK_COMPONENT_SWIZZLE_G: return colors[1]; + case VK_COMPONENT_SWIZZLE_B: return colors[2]; + case VK_COMPONENT_SWIZZLE_A: return colors[3]; + default: return colors[index]; + } +} + #pragma mark Mipmaps