Adds strformat code-gen for helping auto-generate readable strings for structs.

- Adds generator infra for absl::StrFormat for bind group structs and types.
- Uses absl::ParsedFormat to avoid multiple parsing for format strings.

Bug: dawn:549
Change-Id: Ida4ca65eb85c4474c492161c8ae34f53bd692a3c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/81944
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
diff --git a/generator/dawn_json_generator.py b/generator/dawn_json_generator.py
index 46691ae..29c1be9 100644
--- a/generator/dawn_json_generator.py
+++ b/generator/dawn_json_generator.py
@@ -684,6 +684,15 @@
         return as_cppType(typ.name)
 
 
+def as_formatType(typ):
+    # Unsigned integral types
+    if typ.json_data['type'] in ['bool', 'uint32_t', 'uint64_t']:
+        return 'u'
+
+    # Defaults everything else to strings.
+    return 's'
+
+
 def c_methods(params, typ):
     return typ.methods + [
         x for x in [
@@ -753,7 +762,8 @@
             'as_jsEnumValue': as_jsEnumValue,
             'convert_cType_to_cppType': convert_cType_to_cppType,
             'as_varName': as_varName,
-            'decorate': decorate
+            'decorate': decorate,
+            'as_formatType': as_formatType
         }
 
 
diff --git a/generator/generator_lib.py b/generator/generator_lib.py
index e3d46bd..11b3ed2 100644
--- a/generator/generator_lib.py
+++ b/generator/generator_lib.py
@@ -156,7 +156,8 @@
 
 def _do_renders(renders, template_dir):
     loader = _PreprocessingLoader(template_dir)
-    env = jinja2.Environment(loader=loader,
+    env = jinja2.Environment(extensions=['jinja2.ext.do'],
+                             loader=loader,
                              lstrip_blocks=True,
                              trim_blocks=True,
                              line_comment_prefix='//*')
diff --git a/generator/templates/dawn/native/api_absl_format.cpp b/generator/templates/dawn/native/api_absl_format.cpp
index 6a9477c..a3b7ea2 100644
--- a/generator/templates/dawn/native/api_absl_format.cpp
+++ b/generator/templates/dawn/native/api_absl_format.cpp
@@ -49,6 +49,42 @@
         {% endfor %}
     {% endfor %}
 
+    //
+    // Compatible with absl::StrFormat (Needs to be disjoint from having a 'label' for now.)
+    // Currently uses a hard-coded list to determine which structures are actually supported. If
+    // additional structures are added, be sure to update the header file's list as well.
+    //
+    using absl::ParsedFormat;
+
+    {% for type in by_category["structure"] %}
+        {% if type.name.get() in [
+             "buffer binding layout",
+             "sampler binding layout",
+             "texture binding layout",
+             "storage texture binding layout"
+           ]
+        %}
+        absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
+            AbslFormatConvert(const {{as_cppType(type.name)}}& value,
+                              const absl::FormatConversionSpec& spec,
+                              absl::FormatSink* s) {
+            {% set members = [] %}
+            {% set format = [] %}
+            {% set template = [] %}
+            {% for member in type.members %}
+                {% set memberName = member.name.camelCase() %}
+                {% do members.append("value." + memberName) %}
+                {% do format.append(memberName + ": %" + as_formatType(member)) %}
+                {% do template.append("'" + as_formatType(member) + "'") %}
+            {% endfor %}
+            static const auto* const fmt =
+                new ParsedFormat<{{template|join(",")}}>("{ {{format|join(", ")}} }");
+            s->Append(absl::StrFormat(*fmt, {{members|join(", ")}}));
+            return {true};
+        }
+        {% endif %}
+    {% endfor %}
+
 }  // namespace {{native_namespace}}
 
 {% set namespace = metadata.namespace %}
@@ -59,22 +95,24 @@
     //
 
     {% for type in by_category["enum"] %}
-        absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
-        AbslFormatConvert({{as_cppType(type.name)}} value,
-                            const absl::FormatConversionSpec& spec,
-                            absl::FormatSink* s) {
+    absl::FormatConvertResult<absl::FormatConversionCharSet::kString|absl::FormatConversionCharSet::kIntegral>
+    AbslFormatConvert({{as_cppType(type.name)}} value,
+                      const absl::FormatConversionSpec& spec,
+                      absl::FormatSink* s) {
+        if (spec.conversion_char() == absl::FormatConversionChar::s) {
             s->Append("{{as_cppType(type.name)}}::");
             switch (value) {
             {% for value in type.values %}
                 case {{as_cppType(type.name)}}::{{as_cppEnum(value.name)}}:
-                s->Append("{{as_cppEnum(value.name)}}");
-                break;
+                    s->Append("{{as_cppEnum(value.name)}}");
+                    break;
             {% endfor %}
-                default:
-                s->Append(absl::StrFormat("%x", static_cast<typename std::underlying_type<{{as_cppType(type.name)}}>::type>(value)));
             }
-            return {true};
+        } else {
+            s->Append(absl::StrFormat("%u", static_cast<typename std::underlying_type<{{as_cppType(type.name)}}>::type>(value)));
         }
+        return {true};
+    }
     {% endfor %}
 
     //
@@ -82,10 +120,11 @@
     //
 
     {% for type in by_category["bitmask"] %}
-        absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
-        AbslFormatConvert({{as_cppType(type.name)}} value,
-                            const absl::FormatConversionSpec& spec,
-                            absl::FormatSink* s) {
+    absl::FormatConvertResult<absl::FormatConversionCharSet::kString|absl::FormatConversionCharSet::kIntegral>
+    AbslFormatConvert({{as_cppType(type.name)}} value,
+                      const absl::FormatConversionSpec& spec,
+                      absl::FormatSink* s) {
+        if (spec.conversion_char() == absl::FormatConversionChar::s) {
             s->Append("{{as_cppType(type.name)}}::");
             if (!static_cast<bool>(value)) {
                 {% for value in type.values if value.value == 0 %}
@@ -124,9 +163,11 @@
             if (moreThanOneBit) {
                 s->Append(")");
             }
-
-            return {true};
+        } else {
+            s->Append(absl::StrFormat("%u", static_cast<typename std::underlying_type<{{as_cppType(type.name)}}>::type>(value)));
         }
+        return {true};
+    }
     {% endfor %}
 
 }  // namespace {{namespace}}
diff --git a/generator/templates/dawn/native/api_absl_format.h b/generator/templates/dawn/native/api_absl_format.h
index 5fa583f..ab06098 100644
--- a/generator/templates/dawn/native/api_absl_format.h
+++ b/generator/templates/dawn/native/api_absl_format.h
@@ -36,12 +36,33 @@
         {% for member in type.members %}
             {% if member.name.canonical_case() == "label" %}
                 absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
-                AbslFormatConvert(const {{as_cppType(type.name)}}* value,
-                                  const absl::FormatConversionSpec& spec,
-                                  absl::FormatSink* s);
+                    AbslFormatConvert(const {{as_cppType(type.name)}}* value,
+                                      const absl::FormatConversionSpec& spec,
+                                      absl::FormatSink* s);
             {% endif %}
         {% endfor %}
     {% endfor %}
+
+    //
+    // Compatible with absl::StrFormat (Needs to be disjoint from having a 'label' for now.)
+    // Currently uses a hard-coded list to determine which structures are actually supported. If
+    // additional structures are added, be sure to update the cpp file's list as well.
+    //
+    {% for type in by_category["structure"] %}
+        {% if type.name.get() in [
+             "buffer binding layout",
+             "sampler binding layout",
+             "texture binding layout",
+             "storage texture binding layout"
+           ]
+        %}
+        absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
+            AbslFormatConvert(const {{as_cppType(type.name)}}& value,
+                              const absl::FormatConversionSpec& spec,
+                              absl::FormatSink* s);
+        {% endif %}
+    {% endfor %}
+
 } // namespace {{native_namespace}}
 
 {% set namespace = metadata.namespace %}
@@ -52,7 +73,7 @@
     //
 
     {% for type in by_category["enum"] %}
-        absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
+        absl::FormatConvertResult<absl::FormatConversionCharSet::kString|absl::FormatConversionCharSet::kIntegral>
         AbslFormatConvert({{as_cppType(type.name)}} value,
                           const absl::FormatConversionSpec& spec,
                           absl::FormatSink* s);
@@ -63,7 +84,7 @@
     //
 
     {% for type in by_category["bitmask"] %}
-        absl::FormatConvertResult<absl::FormatConversionCharSet::kString>
+        absl::FormatConvertResult<absl::FormatConversionCharSet::kString|absl::FormatConversionCharSet::kIntegral>
         AbslFormatConvert({{as_cppType(type.name)}} value,
                           const absl::FormatConversionSpec& spec,
                           absl::FormatSink* s);
diff --git a/src/dawn/native/BindGroupLayout.cpp b/src/dawn/native/BindGroupLayout.cpp
index f535545..201aecc 100644
--- a/src/dawn/native/BindGroupLayout.cpp
+++ b/src/dawn/native/BindGroupLayout.cpp
@@ -661,11 +661,13 @@
     }
 
     std::string BindGroupLayoutBase::EntriesToString() const {
-        std::string entries = " [";
+        std::string entries = "[";
+        std::string sep = "";
         const BindGroupLayoutBase::BindingMap& bindingMap = GetBindingMap();
         for (const auto [bindingNumber, bindingIndex] : bindingMap) {
             const BindingInfo& bindingInfo = GetBindingInfo(bindingIndex);
-            entries += absl::StrFormat("%s, ", bindingInfo);
+            entries += absl::StrFormat("%s%s", sep, bindingInfo);
+            sep = ", ";
         }
         entries += "]";
         return entries;
diff --git a/src/dawn/native/BindingInfo.cpp b/src/dawn/native/BindingInfo.cpp
index af4905a..d19017d 100644
--- a/src/dawn/native/BindingInfo.cpp
+++ b/src/dawn/native/BindingInfo.cpp
@@ -24,19 +24,19 @@
         absl::FormatSink* s) {
         switch (value) {
             case BindingInfoType::Buffer:
-                s->Append("Buffer");
+                s->Append("buffer");
                 break;
             case BindingInfoType::Sampler:
-                s->Append("Sampler");
+                s->Append("sampler");
                 break;
             case BindingInfoType::Texture:
-                s->Append("Texture");
+                s->Append("texture");
                 break;
             case BindingInfoType::StorageTexture:
-                s->Append("StorageTexture");
+                s->Append("storageTexture");
                 break;
             case BindingInfoType::ExternalTexture:
-                s->Append("ExternalTexture");
+                s->Append("externalTexture");
                 break;
             default:
                 UNREACHABLE();
@@ -48,44 +48,29 @@
         const BindingInfo& value,
         const absl::FormatConversionSpec& spec,
         absl::FormatSink* s) {
-        s->Append(absl::StrFormat("{\n  binding: %u\n  visibility: %s\n  %s: {\n",
-                                  static_cast<uint32_t>(value.binding), value.visibility,
-                                  value.bindingType));
-
+        static const auto* const fmt =
+            new absl::ParsedFormat<'u', 's', 's', 's'>("{ binding: %u, visibility: %s, %s: %s }");
         switch (value.bindingType) {
             case BindingInfoType::Buffer:
-                s->Append(absl::StrFormat("    type: %s\n", value.buffer.type));
-                if (value.buffer.hasDynamicOffset) {
-                    s->Append("    hasDynamicOffset: true\n");
-                }
-                if (value.buffer.minBindingSize != 0) {
-                    s->Append(
-                        absl::StrFormat("    minBindingSize: %u\n", value.buffer.minBindingSize));
-                }
+                s->Append(absl::StrFormat(*fmt, static_cast<uint32_t>(value.binding),
+                                          value.visibility, value.bindingType, value.buffer));
                 break;
             case BindingInfoType::Sampler:
-                s->Append(absl::StrFormat("    type: %s\n", value.sampler.type));
+                s->Append(absl::StrFormat(*fmt, static_cast<uint32_t>(value.binding),
+                                          value.visibility, value.bindingType, value.sampler));
                 break;
             case BindingInfoType::Texture:
-                s->Append(absl::StrFormat("    sampleType: %s\n", value.texture.sampleType));
-                s->Append(absl::StrFormat("    viewDimension: %s\n", value.texture.viewDimension));
-                if (value.texture.multisampled) {
-                    s->Append("    multisampled: true\n");
-                } else {
-                    s->Append("    multisampled: false\n");
-                }
+                s->Append(absl::StrFormat(*fmt, static_cast<uint32_t>(value.binding),
+                                          value.visibility, value.bindingType, value.texture));
                 break;
             case BindingInfoType::StorageTexture:
-                s->Append(absl::StrFormat("    access: %s\n", value.storageTexture.access));
-                s->Append(absl::StrFormat("    format: %s\n", value.storageTexture.format));
-                s->Append(
-                    absl::StrFormat("    viewDimension: %s\n", value.storageTexture.viewDimension));
+                s->Append(absl::StrFormat(*fmt, static_cast<uint32_t>(value.binding),
+                                          value.visibility, value.bindingType,
+                                          value.storageTexture));
                 break;
             case BindingInfoType::ExternalTexture:
                 break;
         }
-
-        s->Append("  }\n}");
         return {true};
     }