shader_checker: validate fs outputs against cb
diff --git a/layers/shader_checker.cpp b/layers/shader_checker.cpp
index 4d1fe67..a15a0ee 100644
--- a/layers/shader_checker.cpp
+++ b/layers/shader_checker.cpp
@@ -295,6 +295,59 @@
 }
 
 
+enum FORMAT_TYPE {
+    FORMAT_TYPE_UNDEFINED,
+    FORMAT_TYPE_FLOAT,  /* UNORM, SNORM, FLOAT, USCALED, SSCALED, SRGB -- anything we consider float in the shader */
+    FORMAT_TYPE_SINT,
+    FORMAT_TYPE_UINT,
+};
+
+
+static unsigned
+get_format_type(VkFormat fmt) {
+    switch (fmt) {
+    case VK_FMT_UNDEFINED:
+        return FORMAT_TYPE_UNDEFINED;
+    case VK_FMT_R8_SINT:
+    case VK_FMT_R8G8_SINT:
+    case VK_FMT_R8G8B8_SINT:
+    case VK_FMT_R8G8B8A8_SINT:
+    case VK_FMT_R16_SINT:
+    case VK_FMT_R16G16_SINT:
+    case VK_FMT_R16G16B16_SINT:
+    case VK_FMT_R16G16B16A16_SINT:
+    case VK_FMT_R32_SINT:
+    case VK_FMT_R32G32_SINT:
+    case VK_FMT_R32G32B32_SINT:
+    case VK_FMT_R32G32B32A32_SINT:
+    case VK_FMT_B8G8R8_SINT:
+    case VK_FMT_B8G8R8A8_SINT:
+    case VK_FMT_R10G10B10A2_SINT:
+    case VK_FMT_B10G10R10A2_SINT:
+        return FORMAT_TYPE_SINT;
+    case VK_FMT_R8_UINT:
+    case VK_FMT_R8G8_UINT:
+    case VK_FMT_R8G8B8_UINT:
+    case VK_FMT_R8G8B8A8_UINT:
+    case VK_FMT_R16_UINT:
+    case VK_FMT_R16G16_UINT:
+    case VK_FMT_R16G16B16_UINT:
+    case VK_FMT_R16G16B16A16_UINT:
+    case VK_FMT_R32_UINT:
+    case VK_FMT_R32G32_UINT:
+    case VK_FMT_R32G32B32_UINT:
+    case VK_FMT_R32G32B32A32_UINT:
+    case VK_FMT_B8G8R8_UINT:
+    case VK_FMT_B8G8R8A8_UINT:
+    case VK_FMT_R10G10B10A2_UINT:
+    case VK_FMT_B10G10R10A2_UINT:
+        return FORMAT_TYPE_UINT;
+    default:
+        return FORMAT_TYPE_FLOAT;
+    }
+}
+
+
 static void
 validate_vi_against_vs_inputs(VkPipelineVertexInputCreateInfo const *vi, shader_source const *vs)
 {
@@ -340,12 +393,80 @@
 }
 
 
+static void
+validate_fs_outputs_against_cb(shader_source const *fs, VkPipelineCbStateCreateInfo const *cb)
+{
+    std::map<uint32_t, interface_var> outputs;
+    std::map<uint32_t, interface_var> builtin_outputs;
+
+    printf("Begin validate_fs_outputs_against_cb\n");
+
+    /* TODO: dual source blend index (spv::DecIndex, zero if not provided) */
+
+    collect_interface_by_location(fs, spv::StorageOutput, outputs, builtin_outputs);
+
+    /* Check for legacy gl_FragColor broadcast: In this case, we should have no user-defined outputs,
+     * and all color attachment should be UNORM/SNORM/FLOAT.
+     */
+    if (builtin_outputs.find(spv::BuiltInFragColor) != builtin_outputs.end()) {
+        bool broadcast_err = false;
+        if (outputs.size()) {
+            printf("  ERR: should not have user-defined FS outputs when using broadcast\n");
+            broadcast_err = true;
+        }
+
+        for (int i = 0; i < cb->attachmentCount; i++) {
+            unsigned attachmentType = get_format_type(cb->pAttachments[i].format);
+            if (attachmentType == FORMAT_TYPE_SINT || attachmentType == FORMAT_TYPE_UINT) {
+                printf("  ERR: CB fomat should not be SINT or UINT when using broadcast\n");
+                broadcast_err = true;
+            }
+        }
+
+        if (!broadcast_err)
+            printf("  OK: FS broadcast to all color attachments\n");
+
+        /* Skip the usual matching -- all attachments are considered written to. */
+        printf("End validate_fs_outputs_against_cb\n");
+        return;
+    }
+
+    auto it = outputs.begin();
+    uint32_t attachment = 0;
+
+    /* Walk attachment list and outputs together -- this is a little overpowered since attachments
+     * are currently dense, but the parallel with matching between shader stages is nice.
+     */
+
+    while (it != outputs.end() || attachment < cb->attachmentCount) {
+        if (attachment == cb->attachmentCount || it->first < attachment) {
+            printf("  ERR: fragment shader writes to output location %d with no matching attachment\n",
+                   it->first);
+            it++;
+        }
+        else if (it == outputs.end() || it->first > attachment) {
+            printf("  ERR: attachment %d not written by fragment shader\n",
+                   attachment);
+            attachment++;
+        }
+        else {
+            printf("  OK: match on attachment index %d\n",
+                   it->first);
+            /* TODO: typecheck */
+            it++;
+            attachment++;
+        }
+    }
+
+    printf("End validate_fs_outputs_against_cb\n");
+}
+
+
 VK_LAYER_EXPORT VkResult VKAPI vkCreateGraphicsPipeline(VkDevice device,
                                                              const VkGraphicsPipelineCreateInfo *pCreateInfo,
                                                              VkPipeline *pPipeline)
 {
     /* TODO: run cross-stage validation */
-    /* - Validate FS output -> CB */
     /* - Support GS, TCS, TES stages */
 
     /* We seem to allow pipeline stages to be specified out of order, so collect and identify them
@@ -386,6 +507,10 @@
                                           fs_source, "fragment shader");
     }
 
+    if (fs_source && cb) {
+        validate_fs_outputs_against_cb(fs_source, cb);
+    }
+
     VkLayerDispatchTable *pTable = tableMap[(VkBaseLayerObject *)device];
     VkResult res = pTable->CreateGraphicsPipeline(device, pCreateInfo, pPipeline);
     return res;