Implement VK_KHR_depth_stencil_resolve

This extension allows users to pass depthstencil attachments that
vkCmdResolveImage will resolve to in addition to its other resolve
operations. Only the mandatory resolve modes
"VK_RESOLVE_MODE_SAMPLE_ZERO_BIT" and "VK_RESOLVE_MODE_NONE" are
supported.

It's trivial to support both independent resolve modes since we must
resolve depth and stencil attachments separately due to how depth and
stencil attachments are stored internally.

Change-Id: I0f8ff7cddca5f5acbac1d991b11f0a4447956784
Bug: b/167558951
Test: dEQP-VK.renderpass2.depth_stencil_resolve.*
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/48808
Tested-by: Sean Risser <srisser@google.com>
Commit-Queue: Sean Risser <srisser@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
diff --git a/src/Device/Blitter.cpp b/src/Device/Blitter.cpp
index f709c31..4926469 100644
--- a/src/Device/Blitter.cpp
+++ b/src/Device/Blitter.cpp
@@ -22,6 +22,7 @@
 #include "System/Memory.hpp"
 #include "Vulkan/VkBuffer.hpp"
 #include "Vulkan/VkImage.hpp"
+#include "Vulkan/VkImageView.hpp"
 
 #include <utility>
 
@@ -1880,6 +1881,88 @@
 	dst->contentsChanged(dstSubresRange);
 }
 
+static void resolveDepth(const vk::ImageView *src, vk::ImageView *dst, const VkSubpassDescriptionDepthStencilResolve &dsrDesc)
+{
+	if(dsrDesc.depthResolveMode == VK_RESOLVE_MODE_NONE)
+	{
+		return;
+	}
+
+	vk::Format format = src->getFormat(VK_IMAGE_ASPECT_DEPTH_BIT);
+	VkExtent2D extent = src->getMipLevelExtent(0, VK_IMAGE_ASPECT_DEPTH_BIT);
+	int width = extent.width;
+	int height = extent.height;
+	int pitch = src->rowPitchBytes(VK_IMAGE_ASPECT_DEPTH_BIT, 0);
+
+	// To support other resolve modes, get the slice bytes and get a pointer to each sample plane.
+	// Then modify the loop below to include logic for handling each new mode.
+	uint8_t *source = (uint8_t *)src->getOffsetPointer({ 0, 0, 0 }, VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0);
+	uint8_t *dest = (uint8_t *)dst->getOffsetPointer({ 0, 0, 0 }, VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0);
+
+	size_t formatSize = format.bytes();
+	// TODO(b/167558951) support other resolve modes.
+	ASSERT(dsrDesc.depthResolveMode == VK_RESOLVE_MODE_SAMPLE_ZERO_BIT);
+	for(int y = 0; y < height; y++)
+	{
+		memcpy(dest, source, formatSize * width);
+
+		source += pitch;
+		dest += pitch;
+	}
+
+	dst->contentsChanged();
+}
+
+static void resolveStencil(const vk::ImageView *src, vk::ImageView *dst, const VkSubpassDescriptionDepthStencilResolve &dsrDesc)
+{
+	if(dsrDesc.stencilResolveMode == VK_RESOLVE_MODE_NONE)
+	{
+		return;
+	}
+
+	VkExtent2D extent = src->getMipLevelExtent(0, VK_IMAGE_ASPECT_STENCIL_BIT);
+	int width = extent.width;
+	int height = extent.height;
+	int pitch = src->rowPitchBytes(VK_IMAGE_ASPECT_STENCIL_BIT, 0);
+
+	// To support other resolve modes, use src->slicePitchBytes() and get a pointer to each sample's slice.
+	// Then modify the loop below to include logic for handling each new mode.
+	uint8_t *source = reinterpret_cast<uint8_t *>(src->getOffsetPointer({ 0, 0, 0 }, VK_IMAGE_ASPECT_STENCIL_BIT, 0, 0));
+	uint8_t *dest = reinterpret_cast<uint8_t *>(dst->getOffsetPointer({ 0, 0, 0 }, VK_IMAGE_ASPECT_STENCIL_BIT, 0, 0));
+
+	// TODO(b/167558951) support other resolve modes.
+	ASSERT(dsrDesc.stencilResolveMode == VK_RESOLVE_MODE_SAMPLE_ZERO_BIT);
+	for(int y = 0; y < height; y++)
+	{
+		// Stencil is always 8 bits, so the width of the resource we're resolving is
+		// the number of bytes in each row we need to copy during for SAMPLE_ZERO
+		memcpy(dest, source, width);
+
+		source += pitch;
+		dest += pitch;
+	}
+
+	dst->contentsChanged();
+}
+
+void Blitter::resolveDepthStencil(const vk::ImageView *src, vk::ImageView *dst, const VkSubpassDescriptionDepthStencilResolve &dsrDesc)
+{
+	VkImageSubresourceRange srcRange = src->getSubresourceRange();
+	VkImageSubresourceRange dstRange = src->getSubresourceRange();
+	ASSERT(src->getFormat() == dst->getFormat());
+	ASSERT(srcRange.layerCount == 1 && dstRange.layerCount == 1);
+	ASSERT(srcRange.aspectMask == dstRange.aspectMask);
+
+	if(srcRange.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT)
+	{
+		resolveDepth(src, dst, dsrDesc);
+	}
+	if(srcRange.aspectMask & VK_IMAGE_ASPECT_STENCIL_BIT)
+	{
+		resolveStencil(src, dst, dsrDesc);
+	}
+}
+
 void Blitter::resolve(const vk::Image *src, vk::Image *dst, VkImageResolve region)
 {
 	// "The aspectMask member of srcSubresource and dstSubresource must only contain VK_IMAGE_ASPECT_COLOR_BIT"
diff --git a/src/Device/Blitter.hpp b/src/Device/Blitter.hpp
index 51b280d..b9fc7c7 100644
--- a/src/Device/Blitter.hpp
+++ b/src/Device/Blitter.hpp
@@ -28,6 +28,7 @@
 namespace vk {
 
 class Image;
+class ImageView;
 class Buffer;
 
 }  // namespace vk
@@ -145,6 +146,7 @@
 
 	void blit(const vk::Image *src, vk::Image *dst, VkImageBlit region, VkFilter filter);
 	void resolve(const vk::Image *src, vk::Image *dst, VkImageResolve region);
+	void resolveDepthStencil(const vk::ImageView *src, vk::ImageView *dst, const VkSubpassDescriptionDepthStencilResolve &dsrDesc);
 	void copy(const vk::Image *src, uint8_t *dst, unsigned int dstPitch);
 
 	void updateBorders(vk::Image *image, const VkImageSubresource &subresource);
diff --git a/src/Vulkan/VkFramebuffer.cpp b/src/Vulkan/VkFramebuffer.cpp
index 1ed2642..4f6a088 100644
--- a/src/Vulkan/VkFramebuffer.cpp
+++ b/src/Vulkan/VkFramebuffer.cpp
@@ -183,6 +183,17 @@
 			}
 		}
 	}
+
+	if(renderPass->hasDepthStencilResolve() && subpass.pDepthStencilAttachment != nullptr)
+	{
+		VkSubpassDescriptionDepthStencilResolve dsResolve = renderPass->getSubpassDepthStencilResolve(subpassIndex);
+		uint32_t depthStencilAttachment = subpass.pDepthStencilAttachment->attachment;
+		if(depthStencilAttachment != VK_ATTACHMENT_UNUSED)
+		{
+			ImageView *imageView = attachments[depthStencilAttachment];
+			imageView->resolveDepthStencil(attachments[dsResolve.pDepthStencilResolveAttachment->attachment], dsResolve);
+		}
+	}
 }
 
 size_t Framebuffer::ComputeRequiredAllocationSize(const VkFramebufferCreateInfo *pCreateInfo)
diff --git a/src/Vulkan/VkImage.cpp b/src/Vulkan/VkImage.cpp
index 53150d5..a0287cc 100644
--- a/src/Vulkan/VkImage.cpp
+++ b/src/Vulkan/VkImage.cpp
@@ -17,6 +17,7 @@
 #include "VkBuffer.hpp"
 #include "VkDevice.hpp"
 #include "VkDeviceMemory.hpp"
+#include "VkImageView.hpp"
 #include "VkStringify.hpp"
 #include "Device/ASTC_Decoder.hpp"
 #include "Device/BC_Decoder.hpp"
@@ -992,6 +993,11 @@
 	device->getBlitter()->resolve(this, dstImage, region);
 }
 
+void Image::resolveDepthStencilTo(const ImageView *src, ImageView *dst, const VkSubpassDescriptionDepthStencilResolve &dsResolve) const
+{
+	device->getBlitter()->resolveDepthStencil(src, dst, dsResolve);
+}
+
 VkFormat Image::getClearFormat() const
 {
 	// Set the proper format for the clear value, as described here:
diff --git a/src/Vulkan/VkImage.hpp b/src/Vulkan/VkImage.hpp
index 6869d53..9274191 100644
--- a/src/Vulkan/VkImage.hpp
+++ b/src/Vulkan/VkImage.hpp
@@ -31,6 +31,7 @@
 class Buffer;
 class Device;
 class DeviceMemory;
+class ImageView;
 
 #ifdef __ANDROID__
 struct BackingMemory
@@ -65,6 +66,7 @@
 	void blitTo(Image *dstImage, const VkImageBlit &region, VkFilter filter) const;
 	void copyTo(uint8_t *dst, unsigned int dstPitch) const;
 	void resolveTo(Image *dstImage, const VkImageResolve &region) const;
+	void resolveDepthStencilTo(const ImageView *src, ImageView *dst, const VkSubpassDescriptionDepthStencilResolve &depthStencilResolve) const;
 	void clear(const VkClearValue &clearValue, const vk::Format &viewFormat, const VkRect2D &renderArea, const VkImageSubresourceRange &subresourceRange);
 	void clear(const VkClearColorValue &color, const VkImageSubresourceRange &subresourceRange);
 	void clear(const VkClearDepthStencilValue &color, const VkImageSubresourceRange &subresourceRange);
diff --git a/src/Vulkan/VkImageView.cpp b/src/Vulkan/VkImageView.cpp
index b58b35d..e629c77 100644
--- a/src/Vulkan/VkImageView.cpp
+++ b/src/Vulkan/VkImageView.cpp
@@ -262,6 +262,17 @@
 	}
 }
 
+void ImageView::resolveDepthStencil(ImageView *resolveAttachment, const VkSubpassDescriptionDepthStencilResolve &dsResolve)
+{
+	ASSERT(subresourceRange.levelCount == 1 && resolveAttachment->subresourceRange.levelCount == 1);
+	if((subresourceRange.layerCount != 1) || (resolveAttachment->subresourceRange.layerCount != 1))
+	{
+		UNIMPLEMENTED("b/148242443: layerCount != 1");  // FIXME(b/148242443)
+	}
+
+	image->resolveDepthStencilTo(this, resolveAttachment, dsResolve);
+}
+
 const Image *ImageView::getImage(Usage usage) const
 {
 	switch(usage)
@@ -310,6 +321,13 @@
 	return { extent.width, extent.height };
 }
 
+VkExtent2D ImageView::getMipLevelExtent(uint32_t mipLevel, VkImageAspectFlagBits aspect) const
+{
+	VkExtent3D extent = image->getMipLevelExtent(aspect, subresourceRange.baseMipLevel + mipLevel);
+
+	return { extent.width, extent.height };
+}
+
 int ImageView::getDepthOrLayerCount(uint32_t mipLevel) const
 {
 	VkExtent3D extent = image->getMipLevelExtent(static_cast<VkImageAspectFlagBits>(subresourceRange.aspectMask),
diff --git a/src/Vulkan/VkImageView.hpp b/src/Vulkan/VkImageView.hpp
index bba9a37..5bf6b50 100644
--- a/src/Vulkan/VkImageView.hpp
+++ b/src/Vulkan/VkImageView.hpp
@@ -79,6 +79,7 @@
 	void resolve(ImageView *resolveAttachment);
 	void resolve(ImageView *resolveAttachment, int layer);
 	void resolveWithLayerMask(ImageView *resolveAttachment, uint32_t layerMask);
+	void resolveDepthStencil(ImageView *resolveAttachment, const VkSubpassDescriptionDepthStencilResolve &dsResolve);
 
 	VkImageViewType getType() const { return viewType; }
 	Format getFormat(Usage usage = RAW) const;
@@ -88,6 +89,7 @@
 	int getMipLevelSize(VkImageAspectFlagBits aspect, uint32_t mipLevel, Usage usage = RAW) const;
 	int layerPitchBytes(VkImageAspectFlagBits aspect, Usage usage = RAW) const;
 	VkExtent2D getMipLevelExtent(uint32_t mipLevel) const;
+	VkExtent2D getMipLevelExtent(uint32_t mipLevel, VkImageAspectFlagBits aspect) const;
 	int getDepthOrLayerCount(uint32_t mipLevel) const;
 
 	int getSampleCount() const
diff --git a/src/Vulkan/VkPhysicalDevice.cpp b/src/Vulkan/VkPhysicalDevice.cpp
index cacc782..9b76407 100644
--- a/src/Vulkan/VkPhysicalDevice.cpp
+++ b/src/Vulkan/VkPhysicalDevice.cpp
@@ -838,8 +838,8 @@
 {
 	properties->supportedDepthResolveModes = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT | VK_RESOLVE_MODE_NONE;
 	properties->supportedStencilResolveModes = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT | VK_RESOLVE_MODE_NONE;
-	properties->independentResolveNone = VK_FALSE;
-	properties->independentResolve = VK_FALSE;
+	properties->independentResolveNone = VK_TRUE;
+	properties->independentResolve = VK_TRUE;
 }
 
 void PhysicalDevice::getProperties(VkPhysicalDeviceDepthStencilResolveProperties *properties) const
diff --git a/src/Vulkan/VkRenderPass.cpp b/src/Vulkan/VkRenderPass.cpp
index 0830709..dab3d51 100644
--- a/src/Vulkan/VkRenderPass.cpp
+++ b/src/Vulkan/VkRenderPass.cpp
@@ -129,7 +129,7 @@
     , subpassCount(pCreateInfo->subpassCount)
     , dependencyCount(pCreateInfo->dependencyCount)
 {
-	init(pCreateInfo, mem);
+	init(pCreateInfo, &mem);
 }
 
 RenderPass::RenderPass(const VkRenderPassCreateInfo2KHR *pCreateInfo, void *mem)
@@ -137,18 +137,76 @@
     , subpassCount(pCreateInfo->subpassCount)
     , dependencyCount(pCreateInfo->dependencyCount)
 {
-	init(pCreateInfo, mem);
+	init(pCreateInfo, &mem);
 	// Note: the init function above ignores:
 	// - pCorrelatedViewMasks: This provides a potential performance optimization
 	// - VkAttachmentReference2::aspectMask : This specifies which aspects may be used
 	// - VkSubpassDependency2::viewOffset : This is the same as VkRenderPassMultiviewCreateInfo::pViewOffsets, which is currently ignored
 	// - Any pNext pointer in VkRenderPassCreateInfo2KHR's internal structures
+
+	char *hostMemory = reinterpret_cast<char *>(mem);
+
+	// Handle the extensions in each subpass
+	for(uint32_t i = 0; i < subpassCount; i++)
+	{
+		auto const &subpass = pCreateInfo->pSubpasses[i];
+		const VkBaseInStructure *extension = reinterpret_cast<const VkBaseInStructure *>(subpass.pNext);
+		while(extension)
+		{
+			switch(extension->sType)
+			{
+				case VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE:
+				{
+					const auto *ext = reinterpret_cast<const VkSubpassDescriptionDepthStencilResolve *>(extension);
+					// If any subpass includes depthStencilResolve, allocate a DSR struct for each subpass
+					// This allows us to index into subpassDepthStencilResolves using the subpass index.
+					if(ext->pDepthStencilResolveAttachment != nullptr && ext->pDepthStencilResolveAttachment->attachment != VK_ATTACHMENT_UNUSED)
+					{
+						if(subpassDepthStencilResolves == nullptr)
+						{
+							subpassDepthStencilResolves = reinterpret_cast<VkSubpassDescriptionDepthStencilResolve *>(hostMemory);
+							hostMemory += subpassCount * sizeof(VkSubpassDescriptionDepthStencilResolve);
+							for(uint32_t subpass = 0; subpass < subpassCount; subpass++)
+							{
+								subpassDepthStencilResolves[subpass].sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE;
+								subpassDepthStencilResolves[subpass].pNext = nullptr;
+								subpassDepthStencilResolves[subpass].depthResolveMode = VK_RESOLVE_MODE_NONE;
+								subpassDepthStencilResolves[subpass].stencilResolveMode = VK_RESOLVE_MODE_NONE;
+								subpassDepthStencilResolves[subpass].pDepthStencilResolveAttachment = nullptr;
+							}
+						}
+
+						VkAttachmentReference2 *reference = reinterpret_cast<VkAttachmentReference2 *>(hostMemory);
+						hostMemory += sizeof(VkAttachmentReference2);
+
+						subpassDepthStencilResolves[i].depthResolveMode = ext->depthResolveMode;
+						subpassDepthStencilResolves[i].stencilResolveMode = ext->stencilResolveMode;
+						reference->pNext = nullptr;
+						reference->sType = ext->pDepthStencilResolveAttachment->sType;
+						reference->attachment = ext->pDepthStencilResolveAttachment->attachment;
+						reference->layout = ext->pDepthStencilResolveAttachment->layout;
+						reference->aspectMask = ext->pDepthStencilResolveAttachment->aspectMask;
+						subpassDepthStencilResolves[i].pDepthStencilResolveAttachment = reinterpret_cast<const VkAttachmentReference2 *>(reference);
+
+						MarkFirstUse(reference->attachment, i);
+					}
+				}
+				break;
+				default:
+					LOG_TRAP("VkRenderPassCreateInfo2KHR->subpass[%d]->pNext sType: %s",
+					         i, vk::Stringify(extension->sType).c_str());
+					break;
+			}
+
+			extension = extension->pNext;
+		}
+	}
 }
 
 template<class T>
-void RenderPass::init(const T *pCreateInfo, void *mem)
+void RenderPass::init(const T *pCreateInfo, void **mem)
 {
-	char *hostMemory = reinterpret_cast<char *>(mem);
+	char *hostMemory = reinterpret_cast<char *>(*mem);
 
 	// subpassCount must be greater than 0
 	ASSERT(pCreateInfo->subpassCount > 0);
@@ -300,7 +358,9 @@
 	{
 		dependencies = reinterpret_cast<VkSubpassDependency *>(hostMemory);
 		CopySubpassDependencies(dependencies, pCreateInfo->pDependencies, pCreateInfo->dependencyCount);
+		hostMemory += dependencyCount * sizeof(VkSubpassDependency);
 	}
+	*mem = hostMemory;
 }
 
 void RenderPass::destroy(const VkAllocationCallbacks *pAllocator)
@@ -315,7 +375,46 @@
 
 size_t RenderPass::ComputeRequiredAllocationSize(const VkRenderPassCreateInfo2KHR *pCreateInfo)
 {
-	return ComputeRequiredAllocationSizeT(pCreateInfo);
+	size_t requiredMemory = ComputeRequiredAllocationSizeT(pCreateInfo);
+
+	// Calculate the memory required to handle depth stencil resolves
+	bool usesDSR = false;
+	for(uint32_t i = 0; i < pCreateInfo->subpassCount; i++)
+	{
+		auto const &subpass = pCreateInfo->pSubpasses[i];
+		const VkBaseInStructure *extension = reinterpret_cast<const VkBaseInStructure *>(subpass.pNext);
+		while(extension)
+		{
+			switch(extension->sType)
+			{
+				case VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE:
+				{
+					const auto *ext = reinterpret_cast<const VkSubpassDescriptionDepthStencilResolve *>(extension);
+					if(ext->pDepthStencilResolveAttachment != nullptr && ext->pDepthStencilResolveAttachment->attachment != VK_ATTACHMENT_UNUSED)
+					{
+						if(!usesDSR)
+						{
+							// If any subpass uses DSR, then allocate a VkSubpassDescriptionDepthStencilResolve
+							// for all subpasses. This allows us to index into our DSR structs using the subpass index.
+							requiredMemory += sizeof(VkSubpassDescriptionDepthStencilResolve) * pCreateInfo->subpassCount;
+							usesDSR = true;
+						}
+						// For each subpass that actually uses DSR, allocate a VkAttachmentReference2.
+						requiredMemory += sizeof(VkAttachmentReference2);
+					}
+				}
+				break;
+				default:
+					LOG_TRAP("VkRenderPassCreateInfo2KHR->subpass[%d]->pNext sType: %s",
+					         i, vk::Stringify(extension->sType).c_str());
+					break;
+			}
+
+			extension = extension->pNext;
+		}
+	}
+
+	return requiredMemory;
 }
 
 void RenderPass::getRenderAreaGranularity(VkExtent2D *pGranularity) const
diff --git a/src/Vulkan/VkRenderPass.hpp b/src/Vulkan/VkRenderPass.hpp
index 3f02f6b..23a3ffa 100644
--- a/src/Vulkan/VkRenderPass.hpp
+++ b/src/Vulkan/VkRenderPass.hpp
@@ -53,6 +53,16 @@
 		return subpasses[subpassIndex];
 	}
 
+	bool hasDepthStencilResolve() const
+	{
+		return subpassDepthStencilResolves != nullptr;
+	}
+
+	VkSubpassDescriptionDepthStencilResolve getSubpassDepthStencilResolve(uint32_t subpassIndex) const
+	{
+		return subpassDepthStencilResolves[subpassIndex];
+	}
+
 	uint32_t getDependencyCount() const
 	{
 		return dependencyCount;
@@ -88,6 +98,7 @@
 	VkAttachmentDescription *attachments = nullptr;
 	uint32_t subpassCount = 0;
 	VkSubpassDescription *subpasses = nullptr;
+	VkSubpassDescriptionDepthStencilResolve *subpassDepthStencilResolves = nullptr;
 	uint32_t dependencyCount = 0;
 	VkSubpassDependency *dependencies = nullptr;
 	int *attachmentFirstUse = nullptr;
@@ -96,7 +107,7 @@
 
 	void MarkFirstUse(int attachment, int subpass);
 	template<class T>
-	void init(const T *pCreateInfo, void *mem);
+	void init(const T *pCreateInfo, void **mem);
 };
 
 static inline RenderPass *Cast(VkRenderPass object)
@@ -106,4 +117,4 @@
 
 }  // namespace vk
 
-#endif  // VK_RENDER_PASS_HPP_
\ No newline at end of file
+#endif  // VK_RENDER_PASS_HPP_
diff --git a/src/Vulkan/libVulkan.cpp b/src/Vulkan/libVulkan.cpp
index 36d8a21..e98d8d0 100644
--- a/src/Vulkan/libVulkan.cpp
+++ b/src/Vulkan/libVulkan.cpp
@@ -408,6 +408,7 @@
 	{ VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME, VK_EXT_HOST_QUERY_RESET_SPEC_VERSION },
 	{ VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME, VK_EXT_SCALAR_BLOCK_LAYOUT_SPEC_VERSION },
 	{ VK_EXT_SEPARATE_STENCIL_USAGE_EXTENSION_NAME, VK_EXT_SEPARATE_STENCIL_USAGE_SPEC_VERSION },
+	{ VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME, VK_KHR_DEPTH_STENCIL_RESOLVE_SPEC_VERSION },
 	{ VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME, VK_KHR_IMAGE_FORMAT_LIST_SPEC_VERSION },
 	{ VK_KHR_IMAGELESS_FRAMEBUFFER_EXTENSION_NAME, VK_KHR_IMAGELESS_FRAMEBUFFER_SPEC_VERSION },
 	{ VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME, VK_KHR_SHADER_FLOAT_CONTROLS_SPEC_VERSION },