layers: validate capabilities against device features

V2: squashed in feedback.
V3: More careful about VkBool32

Signed-off-by: Chris Forbes <chrisforbes@google.com>
diff --git a/demos/tri.c b/demos/tri.c
index 9f3cfac..aa0fbce 100644
--- a/demos/tri.c
+++ b/demos/tri.c
@@ -2068,6 +2068,14 @@
                                              demo->queue_props);
     assert(demo->queue_count >= 1);
 
+    VkPhysicalDeviceFeatures features;
+    vkGetPhysicalDeviceFeatures(demo->gpu, &features);
+
+    if (!features.shaderClipDistance) {
+        ERR_EXIT("Required device feature `shaderClipDistance` not supported\n",
+                 "GetPhysicalDeviceFeatures failure");
+    }
+
     // Graphics queue and MemMgr queue can be separate.
     // TODO: Add support for separate queues, including synchronization,
     //       and appropriate tracking for QueueSubmit
@@ -2084,6 +2092,10 @@
         .queueCount = 1,
         .pQueuePriorities = queue_priorities};
 
+    VkPhysicalDeviceFeatures features = {
+        .shaderClipDistance = VK_TRUE,
+    };
+
     VkDeviceCreateInfo device = {
         .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
         .pNext = NULL,
@@ -2096,6 +2108,7 @@
                                       : NULL),
         .enabledExtensionCount = demo->enabled_extension_count,
         .ppEnabledExtensionNames = (const char *const *)demo->extension_names,
+        .pEnabledFeatures = &features,
     };
 
     err = vkCreateDevice(demo->gpu, &device, NULL, &demo->device);
diff --git a/layers/core_validation.cpp b/layers/core_validation.cpp
index b6a81e3..b0cb275 100644
--- a/layers/core_validation.cpp
+++ b/layers/core_validation.cpp
@@ -2519,6 +2519,161 @@
     }
 }
 
+static VkBool32 require_feature(layer_data *my_data, VkBool32 feature, char const *feature_name) {
+    if (!feature) {
+        if (log_msg(my_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT,
+                    /* dev */ 0, __LINE__, SHADER_CHECKER_FEATURE_NOT_ENABLED, "SC",
+                    "Shader requires VkPhysicalDeviceFeatures::%s but is not "
+                    "enabled on the device",
+                    feature_name)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static VkBool32 validate_shader_capabilities(layer_data *my_data, VkDevice dev, shader_module const *src)
+{
+    VkBool32 pass = VK_TRUE;
+
+    auto enabledFeatures = &my_data->physDevProperties.features;
+
+    for (auto insn : *src) {
+        if (insn.opcode() == spv::OpCapability) {
+            switch (insn.word(1)) {
+            case spv::CapabilityMatrix:
+            case spv::CapabilityShader:
+            case spv::CapabilityInputAttachment:
+            case spv::CapabilitySampled1D:
+            case spv::CapabilityImage1D:
+            case spv::CapabilitySampledBuffer:
+            case spv::CapabilityImageBuffer:
+            case spv::CapabilityImageQuery:
+            case spv::CapabilityDerivativeControl:
+                // Always supported by a Vulkan 1.0 implementation -- no feature bits.
+                break;
+
+            case spv::CapabilityGeometry:
+                pass &= require_feature(my_data, enabledFeatures->geometryShader, "geometryShader");
+                break;
+
+            case spv::CapabilityTessellation:
+                pass &= require_feature(my_data, enabledFeatures->tessellationShader, "tessellationShader");
+                break;
+
+            case spv::CapabilityFloat64:
+                pass &= require_feature(my_data, enabledFeatures->shaderFloat64, "shaderFloat64");
+                break;
+
+            case spv::CapabilityInt64:
+                pass &= require_feature(my_data, enabledFeatures->shaderInt64, "shaderInt64");
+                break;
+
+            case spv::CapabilityTessellationPointSize:
+            case spv::CapabilityGeometryPointSize:
+                pass &= require_feature(my_data, enabledFeatures->shaderTessellationAndGeometryPointSize,
+                                        "shaderTessellationAndGeometryPointSize");
+                break;
+
+            case spv::CapabilityImageGatherExtended:
+                pass &= require_feature(my_data, enabledFeatures->shaderImageGatherExtended, "shaderImageGatherExtended");
+                break;
+
+            case spv::CapabilityStorageImageMultisample:
+                pass &= require_feature(my_data, enabledFeatures->shaderStorageImageMultisample, "shaderStorageImageMultisample");
+                break;
+
+            case spv::CapabilityUniformBufferArrayDynamicIndexing:
+                pass &= require_feature(my_data, enabledFeatures->shaderUniformBufferArrayDynamicIndexing,
+                                        "shaderUniformBufferArrayDynamicIndexing");
+                break;
+
+            case spv::CapabilitySampledImageArrayDynamicIndexing:
+                pass &= require_feature(my_data, enabledFeatures->shaderSampledImageArrayDynamicIndexing,
+                                        "shaderSampledImageArrayDynamicIndexing");
+                break;
+
+            case spv::CapabilityStorageBufferArrayDynamicIndexing:
+                pass &= require_feature(my_data, enabledFeatures->shaderStorageBufferArrayDynamicIndexing,
+                                        "shaderStorageBufferArrayDynamicIndexing");
+                break;
+
+            case spv::CapabilityStorageImageArrayDynamicIndexing:
+                pass &= require_feature(my_data, enabledFeatures->shaderStorageImageArrayDynamicIndexing,
+                                        "shaderStorageImageArrayDynamicIndexing");
+                break;
+
+            case spv::CapabilityClipDistance:
+                pass &= require_feature(my_data, enabledFeatures->shaderClipDistance, "shaderClipDistance");
+                break;
+
+            case spv::CapabilityCullDistance:
+                pass &= require_feature(my_data, enabledFeatures->shaderCullDistance, "shaderCullDistance");
+                break;
+
+            case spv::CapabilityImageCubeArray:
+                pass &= require_feature(my_data, enabledFeatures->imageCubeArray, "imageCubeArray");
+                break;
+
+            case spv::CapabilitySampleRateShading:
+                pass &= require_feature(my_data, enabledFeatures->sampleRateShading, "sampleRateShading");
+                break;
+
+            case spv::CapabilitySparseResidency:
+                pass &= require_feature(my_data, enabledFeatures->shaderResourceResidency, "shaderResourceResidency");
+                break;
+
+            case spv::CapabilityMinLod:
+                pass &= require_feature(my_data, enabledFeatures->shaderResourceMinLod, "shaderResourceMinLod");
+                break;
+
+            case spv::CapabilitySampledCubeArray:
+                pass &= require_feature(my_data, enabledFeatures->imageCubeArray, "imageCubeArray");
+                break;
+
+            case spv::CapabilityImageMSArray:
+                pass &= require_feature(my_data, enabledFeatures->shaderStorageImageMultisample, "shaderStorageImageMultisample");
+                break;
+
+            case spv::CapabilityStorageImageExtendedFormats:
+                pass &= require_feature(my_data, enabledFeatures->shaderStorageImageExtendedFormats,
+                                        "shaderStorageImageExtendedFormats");
+                break;
+
+            case spv::CapabilityInterpolationFunction:
+                pass &= require_feature(my_data, enabledFeatures->sampleRateShading, "sampleRateShading");
+                break;
+
+            case spv::CapabilityStorageImageReadWithoutFormat:
+                pass &= require_feature(my_data, enabledFeatures->shaderStorageImageReadWithoutFormat,
+                                        "shaderStorageImageReadWithoutFormat");
+                break;
+
+            case spv::CapabilityStorageImageWriteWithoutFormat:
+                pass &= require_feature(my_data, enabledFeatures->shaderStorageImageWriteWithoutFormat,
+                                        "shaderStorageImageWriteWithoutFormat");
+                break;
+
+            case spv::CapabilityMultiViewport:
+                pass &= require_feature(my_data, enabledFeatures->multiViewport, "multiViewport");
+                break;
+
+            default:
+                if (log_msg(my_data->report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT, /* dev */0,
+                            __LINE__, SHADER_CHECKER_BAD_CAPABILITY, "SC",
+                            "Shader declares capability %u, not supported in Vulkan.",
+                            insn.word(1)))
+                    pass = VK_FALSE;
+                break;
+            }
+        }
+    }
+
+    return pass;
+}
+
+
 // Validate that the shaders used by the given pipeline
 //  As a side effect this function also records the sets that are actually used by the pipeline
 static VkBool32 validate_pipeline_shaders(layer_data *my_data, VkDevice dev, PIPELINE_NODE *pPipeline) {
@@ -2564,6 +2719,9 @@
                     }
                 }
 
+                /* validate shader capabilities against enabled device features */
+                pass = validate_shader_capabilities(my_data, dev, module) && pass;
+
                 /* mark accessible ids */
                 std::unordered_set<uint32_t> accessible_ids;
                 mark_accessible_ids(module, entrypoints[stage_id], accessible_ids);
diff --git a/layers/core_validation.h b/layers/core_validation.h
index 8d8941f..fea8ffd 100644
--- a/layers/core_validation.h
+++ b/layers/core_validation.h
@@ -404,6 +404,8 @@
     SHADER_CHECKER_PUSH_CONSTANT_NOT_ACCESSIBLE_FROM_STAGE, // Push constant range exists, but not accessible from stage
     SHADER_CHECKER_DESCRIPTOR_TYPE_MISMATCH,                // Descriptor type does not match shader resource type
     SHADER_CHECKER_DESCRIPTOR_NOT_ACCESSIBLE_FROM_STAGE,    // Descriptor used by shader, but not accessible from stage
+    SHADER_CHECKER_FEATURE_NOT_ENABLED,        // Shader uses capability requiring a feature not enabled on device
+    SHADER_CHECKER_BAD_CAPABILITY,             // Shader uses capability not supported by Vulkan (OpenCL features)
 } SHADER_CHECKER_ERROR;
 
 typedef enum _DRAW_TYPE {
diff --git a/layers/vk_validation_layer_details.md b/layers/vk_validation_layer_details.md
index ec36b3b..7bc554e 100644
--- a/layers/vk_validation_layer_details.md
+++ b/layers/vk_validation_layer_details.md
@@ -136,6 +136,8 @@
 | Push constant not accessible from stage | Flags error if the push constant range containing a push constant block member is not accessible from the current shader stage. | PUSH_CONSTANT_NOT_ACCESSIBLE_FROM_STAGE | vkCreateGraphicsPipelines | TBD | NA |
 | Descriptor not accessible from stage | Flags error if a descriptor used by a shader stage does not include that stage in its stageFlags | DESCRIPTOR_NOT_ACCESSIBLE_FROM_STAGE | vkCreateGraphicsPipelines | TBD | NA |
 | Descriptor type mismatch | Flags error if a descriptor type does not match the shader resource type. | DESCRIPTOR_TYPE_MISMATCH | vkCreateGraphicsPipelines | TBD | NA |
+| Feature not enabled | Flags error if a capability declared by the shader requires a feature not enabled on the device | FEATURE_NOT_ENABLED | vkCreateGraphicsPipelines | TBD | NA |
+| Bad capability | Flags error if a capability declared by the shader is not supported by Vulkan shaders | BAD_CAPABILITY | vkCreateGraphicsPipelines | TBD | NA |
 | NA | Enum used for informational messages | NONE | | NA | None |
 
 ### VK_LAYER_LUNARG_core_validation Shader Checker Pending Work