Validator: Support VK_EXT_scalar_block_layout

Adds validator option to specify scalar block layout rules.

Both VK_KHR_relax_block_layout and VK_EXT_scalar_block_layout can be
enabled at the same time.  But scalar block layout is as permissive
as relax block layout.

Also, scalar block layout does not require padding at the end of a
struct.

Add test for scalar layout testing ArrayStride 12 on array of vec3s

Cleanup: The internal getSize method does not need a round-up argument,
so remove it.
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index 050fecc..4fcd276 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -484,13 +484,35 @@
 SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetRelaxLogicalPointer(
     spv_validator_options options, bool val);
 
-// Records whether or not the validator should relax the rules on block layout.
+// Records whether the validator should use "relaxed" block layout rules.
+// Relaxed layout rules are described by Vulkan extension
+// VK_KHR_relaxed_block_layout, and they affect uniform blocks, storage blocks,
+// and push constants.
 //
-// When relaxed, it will enable VK_KHR_relaxed_block_layout when validating
-// standard uniform/storage block layout.
+// This is enabled by default when targeting Vulkan 1.1 or later.
+// Relaxed layout is more permissive than the default rules in Vulkan 1.0.
 SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetRelaxBlockLayout(
     spv_validator_options options, bool val);
 
+// Records whether the validator should use "scalar" block layout rules.
+// Scalar layout rules are more permissive than relaxed block layout.
+//
+// See Vulkan extnesion VK_EXT_scalar_block_layout.  The scalar alignment is
+// defined as follows:
+// - scalar alignment of a scalar is the scalar size
+// - scalar alignment of a vector is the scalar alignment of its component
+// - scalar alignment of a matrix is the scalar alignment of its component
+// - scalar alignment of an array is the scalar alignment of its element
+// - scalar alignment of a struct is the max scalar alignment among its
+//   members
+//
+// For a struct in Uniform, StorageClass, or PushConstant:
+// - a member Offset must be a multiple of the member's scalar alignment
+// - ArrayStride or MatrixStride must be a multiple of the array or matrix
+//   scalar alignment
+SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetScalarBlockLayout(
+    spv_validator_options options, bool val);
+
 // Records whether or not the validator should skip validating standard
 // uniform/storage block layout.
 SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetSkipBlockLayout(
diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp
index cfe5fc8..26c0989 100644
--- a/include/spirv-tools/libspirv.hpp
+++ b/include/spirv-tools/libspirv.hpp
@@ -82,12 +82,20 @@
   }
 
   // Enables VK_KHR_relaxed_block_layout when validating standard
-  // uniform/storage buffer layout.
+  // uniform/storage buffer/push-constant layout.  If true, disables
+  // scalar block layout rules.
   void SetRelaxBlockLayout(bool val) {
     spvValidatorOptionsSetRelaxBlockLayout(options_, val);
   }
 
-  // Skips validating standard uniform/storage buffer layout.
+  // Enables VK_EXT_scalar_block_layout when validating standard
+  // uniform/storage buffer/push-constant layout.  If true, disables
+  // relaxed block layout rules.
+  void SetScalarBlockLayout(bool val) {
+    spvValidatorOptionsSetScalarBlockLayout(options_, val);
+  }
+
+  // Skips validating standard uniform/storage buffer/push-constant layout.
   void SetSkipBlockLayout(bool val) {
     spvValidatorOptionsSetSkipBlockLayout(options_, val);
   }
diff --git a/source/spirv_validator_options.cpp b/source/spirv_validator_options.cpp
index 6d9afab..2e9cf26 100644
--- a/source/spirv_validator_options.cpp
+++ b/source/spirv_validator_options.cpp
@@ -95,6 +95,11 @@
   options->relax_block_layout = val;
 }
 
+void spvValidatorOptionsSetScalarBlockLayout(spv_validator_options options,
+                                             bool val) {
+  options->scalar_block_layout = val;
+}
+
 void spvValidatorOptionsSetSkipBlockLayout(spv_validator_options options,
                                            bool val) {
   options->skip_block_layout = val;
diff --git a/source/spirv_validator_options.h b/source/spirv_validator_options.h
index ba1c8da..f426ebf 100644
--- a/source/spirv_validator_options.h
+++ b/source/spirv_validator_options.h
@@ -43,12 +43,14 @@
         relax_struct_store(false),
         relax_logical_pointer(false),
         relax_block_layout(false),
+        scalar_block_layout(false),
         skip_block_layout(false) {}
 
   validator_universal_limits_t universal_limits_;
   bool relax_struct_store;
   bool relax_logical_pointer;
   bool relax_block_layout;
+  bool scalar_block_layout;
   bool skip_block_layout;
 };
 
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index 3e71581..a5c2b62 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -217,23 +217,57 @@
   return baseAlignment;
 }
 
+// Returns scalar alignment of a type.
+uint32_t getScalarAlignment(uint32_t type_id, ValidationState_t& vstate) {
+  const auto inst = vstate.FindDef(type_id);
+  const auto& words = inst->words();
+  switch (inst->opcode()) {
+    case SpvOpTypeInt:
+    case SpvOpTypeFloat:
+      return words[2] / 8;
+    case SpvOpTypeVector:
+    case SpvOpTypeMatrix:
+    case SpvOpTypeArray:
+    case SpvOpTypeRuntimeArray: {
+      const auto compositeMemberTypeId = words[2];
+      return getScalarAlignment(compositeMemberTypeId, vstate);
+    }
+    case SpvOpTypeStruct: {
+      const auto members = getStructMembers(type_id, vstate);
+      uint32_t max_member_alignment = 1;
+      for (uint32_t memberIdx = 0, numMembers = uint32_t(members.size());
+           memberIdx < numMembers; ++memberIdx) {
+        const auto id = members[memberIdx];
+        uint32_t member_alignment = getScalarAlignment(id, vstate);
+        if (member_alignment > max_member_alignment) {
+          max_member_alignment = member_alignment;
+        }
+      }
+      return max_member_alignment;
+    } break;
+    default:
+      assert(0);
+      break;
+  }
+
+  return 1;
+}
+
 // Returns size of a struct member. Doesn't include padding at the end of struct
 // or array.  Assumes that in the struct case, all members have offsets.
-uint32_t getSize(uint32_t member_id, bool roundUp,
-                 const LayoutConstraints& inherited,
+uint32_t getSize(uint32_t member_id, const LayoutConstraints& inherited,
                  MemberConstraints& constraints, ValidationState_t& vstate) {
   const auto inst = vstate.FindDef(member_id);
   const auto& words = inst->words();
   switch (inst->opcode()) {
     case SpvOpTypeInt:
     case SpvOpTypeFloat:
-      return getBaseAlignment(member_id, roundUp, inherited, constraints,
-                              vstate);
+      return words[2] / 8;
     case SpvOpTypeVector: {
       const auto componentId = words[2];
       const auto numComponents = words[3];
       const auto componentSize =
-          getSize(componentId, roundUp, inherited, constraints, vstate);
+          getSize(componentId, inherited, constraints, vstate);
       const auto size = componentSize * numComponents;
       return size;
     }
@@ -244,7 +278,7 @@
       const uint32_t num_elem = sizeInst->words()[3];
       const uint32_t elem_type = words[2];
       const uint32_t elem_size =
-          getSize(elem_type, roundUp, inherited, constraints, vstate);
+          getSize(elem_type, inherited, constraints, vstate);
       // Account for gaps due to alignments in the first N-1 elements,
       // then add the size of the last element.
       const auto size =
@@ -264,7 +298,7 @@
         const auto num_rows = component_inst->words()[3];
         const auto scalar_elem_type = component_inst->words()[2];
         const uint32_t scalar_elem_size =
-            getSize(scalar_elem_type, roundUp, inherited, constraints, vstate);
+            getSize(scalar_elem_type, inherited, constraints, vstate);
         return (num_rows - 1) * inherited.matrix_stride +
                num_columns * scalar_elem_size;
       }
@@ -286,8 +320,7 @@
       // has been checked earlier in the flow.
       assert(offset != 0xffffffff);
       const auto& constraint = constraints[std::make_pair(lastMember, lastIdx)];
-      return offset +
-             getSize(lastMember, roundUp, constraint, constraints, vstate);
+      return offset + getSize(lastMember, constraint, constraints, vstate);
     }
     default:
       assert(0);
@@ -306,7 +339,7 @@
                          const LayoutConstraints& inherited,
                          MemberConstraints& constraints,
                          ValidationState_t& vstate) {
-  const auto size = getSize(id, false, inherited, constraints, vstate);
+  const auto size = getSize(id, inherited, constraints, vstate);
   const auto F = offset;
   const auto L = offset + size - 1;
   if (size <= 16) {
@@ -334,19 +367,28 @@
                          ValidationState_t& vstate) {
   if (vstate.options()->skip_block_layout) return SPV_SUCCESS;
 
+  // Relaxed layout and scalar layout can both be in effect at the same time.
+  // For example, relaxed layout is implied by Vulkan 1.1.  But scalar layout
+  // is more permissive than relaxed layout.
+  const bool relaxed_block_layout = vstate.IsRelaxedBlockLayout();
+  const bool scalar_block_layout = vstate.options()->scalar_block_layout;
+
   auto fail = [&vstate, struct_id, storage_class_str, decoration_str,
-               blockRules](uint32_t member_idx) -> DiagnosticStream {
+               blockRules, relaxed_block_layout,
+               scalar_block_layout](uint32_t member_idx) -> DiagnosticStream {
     DiagnosticStream ds =
         std::move(vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(struct_id))
                   << "Structure id " << struct_id << " decorated as "
                   << decoration_str << " for variable in " << storage_class_str
-                  << " storage class must follow standard "
+                  << " storage class must follow "
+                  << (scalar_block_layout
+                          ? "scalar "
+                          : (relaxed_block_layout ? "relaxed " : "standard "))
                   << (blockRules ? "uniform buffer" : "storage buffer")
                   << " layout rules: member " << member_idx << " ");
     return ds;
   };
 
-  const bool relaxed_block_layout = vstate.IsRelaxedBlockLayout();
   const auto& members = getStructMembers(struct_id, vstate);
 
   // To check for member overlaps, we want to traverse the members in
@@ -389,20 +431,25 @@
     auto id = members[member_offset.member];
     const LayoutConstraints& constraint =
         constraints[std::make_pair(struct_id, uint32_t(memberIdx))];
+    // Scalar layout takes precedence because it's more permissive, and implying
+    // an alignment that divides evenly into the alignment that would otherwise
+    // be used.
     const auto alignment =
-        getBaseAlignment(id, blockRules, constraint, constraints, vstate);
+        scalar_block_layout
+            ? getScalarAlignment(id, vstate)
+            : getBaseAlignment(id, blockRules, constraint, constraints, vstate);
     const auto inst = vstate.FindDef(id);
     const auto opcode = inst->opcode();
-    const auto size = getSize(id, blockRules, constraint, constraints, vstate);
+    const auto size = getSize(id, constraint, constraints, vstate);
     // Check offset.
     if (offset == 0xffffffff)
       return fail(memberIdx) << "is missing an Offset decoration";
-    if (relaxed_block_layout && opcode == SpvOpTypeVector) {
+    if (!scalar_block_layout && relaxed_block_layout &&
+        opcode == SpvOpTypeVector) {
       // In relaxed block layout, the vector offset must be aligned to the
       // vector's scalar element type.
       const auto componentId = inst->words()[2];
-      const auto scalar_alignment = getBaseAlignment(
-          componentId, blockRules, constraint, constraints, vstate);
+      const auto scalar_alignment = getScalarAlignment(componentId, vstate);
       if (!IsAlignedTo(offset, scalar_alignment)) {
         return fail(memberIdx)
                << "at offset " << offset
@@ -410,7 +457,7 @@
       }
     } else {
       // Without relaxed block layout, the offset must be divisible by the
-      // base alignment.
+      // alignment requirement.
       if (!IsAlignedTo(offset, alignment)) {
         return fail(memberIdx)
                << "at offset " << offset << " is not aligned to " << alignment;
@@ -420,7 +467,7 @@
       return fail(memberIdx) << "at offset " << offset
                              << " overlaps previous member ending at offset "
                              << nextValidOffset - 1;
-    if (relaxed_block_layout) {
+    if (!scalar_block_layout && relaxed_block_layout) {
       // Check improper straddle of vectors.
       if (SpvOpTypeVector == opcode &&
           hasImproperStraddle(id, offset, constraint, constraints, vstate))
@@ -463,7 +510,8 @@
       }
     }
     nextValidOffset = offset + size;
-    if (blockRules && (SpvOpTypeArray == opcode || SpvOpTypeStruct == opcode)) {
+    if (!scalar_block_layout && blockRules &&
+        (SpvOpTypeArray == opcode || SpvOpTypeStruct == opcode)) {
       // Uniform block rules don't permit anything in the padding of a struct
       // or array.
       nextValidOffset = align(nextValidOffset, alignment);
diff --git a/source/val/validation_state.h b/source/val/validation_state.h
index 61b1699..1efd5b7 100644
--- a/source/val/validation_state.h
+++ b/source/val/validation_state.h
@@ -90,6 +90,19 @@
     // Allow an OpTypeInt with 8 bit width to be used in more than just int
     // conversion opcodes
     bool use_int8_type = false;
+
+    // Use scalar block layout. See VK_EXT_scalar_block_layout:
+    // Defines scalar alignment:
+    // - scalar alignment equals the scalar size in bytes
+    // - array alignment is same as its element alignment
+    // - array alignment is max alignment of any of its members
+    // - vector alignment is same as component alignment
+    // - matrix alignment is same as component alignment
+    // For struct in Uniform, StorageBuffer, PushConstant:
+    // - Offset of a member is multiple of scalar alignment of that member
+    // - ArrayStride and MatrixStride are multiples of scalar alignment
+    // Members need not be listed in offset order
+    bool scalar_block_layout = false;
   };
 
   ValidationState_t(const spv_const_context context,
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index 4726854..2d168cb 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -1559,7 +1559,7 @@
       getDiagnosticString(),
       HasSubstr(
           "Structure id 2 decorated as Block for variable in Uniform storage "
-          "class must follow standard uniform buffer layout rules: member 1 at "
+          "class must follow relaxed uniform buffer layout rules: member 1 at "
           "offset 5 is not aligned to scalar element size 4"));
 }
 
@@ -1596,6 +1596,263 @@
   EXPECT_THAT(getDiagnosticString(), Eq(""));
 }
 
+TEST_F(ValidateDecorations,
+       BlockLayoutPermitsTightScalarVec3PackingWithScalarLayoutGood) {
+  // Same as previous test, but with scalar block layout.
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 4
+               OpDecorate %S Block
+               OpDecorate %B DescriptorSet 0
+               OpDecorate %B Binding 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v3float = OpTypeVector %float 3
+          %S = OpTypeStruct %float %v3float
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %B = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  spvValidatorOptionsSetScalarBlockLayout(getValidatorOptions(), true);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations,
+       BlockLayoutPermitsScalarAlignedArrayWithScalarLayoutGood) {
+  // The array at offset 4 is ok with scalar block layout.
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 4
+               OpDecorate %S Block
+               OpDecorate %B DescriptorSet 0
+               OpDecorate %B Binding 0
+               OpDecorate %arr_float ArrayStride 4
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_3 = OpConstant %uint 3
+      %float = OpTypeFloat 32
+  %arr_float = OpTypeArray %float %uint_3
+          %S = OpTypeStruct %float %arr_float
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %B = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  spvValidatorOptionsSetScalarBlockLayout(getValidatorOptions(), true);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations,
+       BlockLayoutPermitsScalarAlignedArrayOfVec3WithScalarLayoutGood) {
+  // The array at offset 4 is ok with scalar block layout, even though
+  // its elements are vec3.
+  // This is the same as the previous case, but the array elements are vec3
+  // instead of float.
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 4
+               OpDecorate %S Block
+               OpDecorate %B DescriptorSet 0
+               OpDecorate %B Binding 0
+               OpDecorate %arr_vec3 ArrayStride 12
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_3 = OpConstant %uint 3
+      %float = OpTypeFloat 32
+       %vec3 = OpTypeVector %float 3
+   %arr_vec3 = OpTypeArray %vec3 %uint_3
+          %S = OpTypeStruct %float %arr_vec3
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %B = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  spvValidatorOptionsSetScalarBlockLayout(getValidatorOptions(), true);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations,
+       BlockLayoutPermitsScalarAlignedStructWithScalarLayoutGood) {
+  // Scalar block layout permits the struct at offset 4, even though
+  // it contains a vector with base alignment 8 and scalar alignment 4.
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 4
+               OpMemberDecorate %st 0 Offset 0
+               OpMemberDecorate %st 1 Offset 8
+               OpDecorate %S Block
+               OpDecorate %B DescriptorSet 0
+               OpDecorate %B Binding 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+       %vec2 = OpTypeVector %float 2
+        %st  = OpTypeStruct %vec2 %float
+          %S = OpTypeStruct %float %st
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %B = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  spvValidatorOptionsSetScalarBlockLayout(getValidatorOptions(), true);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(
+    ValidateDecorations,
+    BlockLayoutPermitsFieldsInBaseAlignmentPaddingAtEndOfStructWithScalarLayoutGood) {
+  // Scalar block layout permits fields in what would normally be the padding at
+  // the end of a struct.
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability Float64
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpMemberDecorate %st 0 Offset 0
+               OpMemberDecorate %st 1 Offset 8
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 12
+               OpDecorate %S Block
+               OpDecorate %B DescriptorSet 0
+               OpDecorate %B Binding 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+     %double = OpTypeFloat 64
+         %st = OpTypeStruct %double %float
+          %S = OpTypeStruct %st %float
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %B = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  spvValidatorOptionsSetScalarBlockLayout(getValidatorOptions(), true);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(
+    ValidateDecorations,
+    BlockLayoutPermitsStraddlingVectorWithScalarLayoutOverrideRelaxBlockLayoutGood) {
+  // Same as previous, but set relaxed block layout first.  Scalar layout always
+  // wins.
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 4
+               OpDecorate %S Block
+               OpDecorate %B DescriptorSet 0
+               OpDecorate %B Binding 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+       %vec4 = OpTypeVector %float 4
+          %S = OpTypeStruct %float %vec4
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %B = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  spvValidatorOptionsSetRelaxBlockLayout(getValidatorOptions(), true);
+  spvValidatorOptionsSetScalarBlockLayout(getValidatorOptions(), true);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(
+    ValidateDecorations,
+    BlockLayoutPermitsStraddlingVectorWithRelaxedLayoutOverridenByScalarBlockLayoutGood) {
+  // Same as previous, but set scalar block layout first.  Scalar layout always
+  // wins.
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 4
+               OpDecorate %S Block
+               OpDecorate %B DescriptorSet 0
+               OpDecorate %B Binding 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+       %vec4 = OpTypeVector %float 4
+          %S = OpTypeStruct %float %vec4
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %B = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  spvValidatorOptionsSetScalarBlockLayout(getValidatorOptions(), true);
+  spvValidatorOptionsSetRelaxBlockLayout(getValidatorOptions(), true);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
 TEST_F(ValidateDecorations, BufferBlock16bitStandardStorageBufferLayout) {
   std::string spirv = R"(
              OpCapability Shader
@@ -1783,7 +2040,7 @@
       getDiagnosticString(),
       HasSubstr(
           "Structure id 3 decorated as Block for variable in Uniform "
-          "storage class must follow standard uniform buffer layout rules: "
+          "storage class must follow relaxed uniform buffer layout rules: "
           "member 1 at offset 8 is not aligned to 16"));
 }
 
diff --git a/tools/val/val.cpp b/tools/val/val.cpp
index 2f4f626..8b1d048 100644
--- a/tools/val/val.cpp
+++ b/tools/val/val.cpp
@@ -48,9 +48,15 @@
   --max-id-bound                   <maximum value for the id bound>
   --relax-logical-pointer          Allow allocating an object of a pointer type and returning
                                    a pointer value from a function in logical addressing mode
-  --relax-block-layout             Enable VK_HR_relaxed_block_layout when checking standard
-                                   uniform/storage buffer layout
-  --skip-block-layout              Skip checking standard uniform/storage buffer layout
+  --relax-block-layout             Enable VK_KHR_relaxed_block_layout when checking standard
+                                   uniform, storage buffer, and push constant layouts.
+                                   This is the default when targeting Vulkan 1.1 or later.
+  --scalar-block-layout            Enable VK_EXT_scalar_block_layout when checking standard
+                                   uniform, storage buffer, and push constant layouts.  Scalar layout
+                                   rules are more permissive than relaxed block layout so in effect
+                                   this will override the --relax-block-layout option.
+  --skip-block-layout              Skip checking standard uniform/storage buffer layout.
+                                   Overrides any --relax-block-layout or --scalar-block-layout option.
   --relax-struct-store             Allow store from one struct type to a
                                    different type with compatible layout and
                                    members.
@@ -128,6 +134,8 @@
         options.SetRelaxLogicalPointer(true);
       } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
         options.SetRelaxBlockLayout(true);
+      } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
+        options.SetScalarBlockLayout(true);
       } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
         options.SetSkipBlockLayout(true);
       } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {