[WebGPU] Adds Support experimental Kotlin/JNI bindings

Enables code generation for 'art_experimental' tagged elements in
dawn.json to support experimental apis (AHB, Sync FD, external
textures).

- Update dawn_json_generator to include the 'art_experimental' tag.
- Map the 'int' type to 'jint' in dawn_kotlin.json.
- Tag external textures, AHB, and Sync FD APIs as 'art_experimental'.
- Add newly generated files to tools/android/BUILD.gn
- Support customizing Kotlin structure members (add/exclude) via dawn_kotlin.json.

Bug: b/518640970

Change-Id: Ibc603423a1673b1a299c76df5ae55e0a6eedb409
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/311495
Reviewed-by: Nikita Gupta <nikigupta@google.com>
Commit-Queue: Mridul Goyal <mridulgoyal@google.com>
Reviewed-by: Loko Kung <lokokung@google.com>
diff --git a/generator/dawn_json_generator.py b/generator/dawn_json_generator.py
index 9a51537..723fec4e 100644
--- a/generator/dawn_json_generator.py
+++ b/generator/dawn_json_generator.py
@@ -114,6 +114,7 @@
         'native',
         'deprecated',
         'art',
+        'art_experimental',
     }
 
     tags = json_data.get('tags')
@@ -946,7 +947,9 @@
                           kotlin_json,
                           webgpu_kt_docs_data=None,
                           doc_warn_log_file_path=None):
-    params_kotlin = parse_json(loaded_json, enabled_tags=['art'])
+
+    params_kotlin = parse_json(loaded_json,
+                               enabled_tags=['art', 'art_experimental'])
     params_kotlin['kotlin_package'] = kotlin_json['kotlin_package']
     params_kotlin['jni_primitives'] = kotlin_json['jni_primitives']
     params_kotlin['jni_signatures'] = kotlin_json['jni_signatures']
@@ -957,12 +960,13 @@
     customize_enums = customize_api["enums"]
     customize_callback = customize_api["function pointer"]
 
-    def kotlin_record_members(members):
+    def kotlin_record_members(members, structure_name=None):
         # Members are sorted in the following order.
         # 1. members with no default value (except callbacks).
         # 2. members with default values.
         # 3. callbacks.
-        for member in sorted(kotlin_record_members_unsorted(members),
+        for member in sorted(kotlin_record_members_unsorted(
+                members, structure_name),
                              key=lambda arg: kotlin_default(arg) is not None):
             yield member
 
@@ -978,7 +982,11 @@
                         yield function_member
                     continue
 
-    def kotlin_record_members_unsorted(members):
+    def kotlin_record_members_unsorted(members, structure_name=None):
+        struct_config = customize_structures.get(structure_name,
+                                                 {}) if structure_name else {}
+        exclude_members = struct_config.get('exclude_members', [])
+
         for member in members:
             # length parameters are omitted because Kotlin containers have 'length'.
             if member in [m.length for m in members]:
@@ -1002,8 +1010,29 @@
                          {'category': 'kotlin type'}), None, {})
                 continue
 
+            if member.name.get() in exclude_members or member.name.camelCase(
+            ) in exclude_members:
+                continue
+
             yield member
 
+        for added_member in struct_config.get('additional_members', []):
+            name = Name(added_member['name'])
+            # Default to native for simple types if not specified
+            category = added_member.get('category', 'native')
+            type_name = added_member['type']
+            if type_name in params_kotlin['types']:
+                typ = params_kotlin['types'][type_name]
+            else:
+                typ = Type(type_name, {'category': category})
+            yield RecordMember(name,
+                               typ,
+                               added_member.get('annotation', 'value'), {},
+                               optional=added_member.get('optional', False),
+                               default_value=added_member.get(
+                                   'default_value', None),
+                               skip_serialize=True)
+
     # Calculate if we should, and can, provide a Kotlin default value for a given argument.
     # This will affect its order in the method parameter and structure field lists.
     def kotlin_default(arg):
@@ -1014,8 +1043,10 @@
             if arg.type.category in [
                     'callback function', 'callback info', 'function pointer',
                     'object', 'structure'
-            ]:
+            ] or arg.type.name.get() == 'char':
                 return 'arrayOf()'
+            if arg.type.name.get() == 'float':
+                return 'floatArrayOf()'
             return 'intArrayOf()'
 
         # All other optional types default to 'null'.
@@ -1347,6 +1378,19 @@
 def item_is_enabled(enabled_tags, json_data):
     tags = validate_and_get_tags(json_data)
     if tags is None: return True
+
+    # Strip 'art_experimental' for non-Art targets so it doesn't cause
+    # the item to be excluded from C++/Emscripten builds.
+    if not ('art_experimental' in enabled_tags):
+        original_tags_empty = not tags
+        tags = [tag for tag in tags if tag not in ('art_experimental')]
+        # NOTE: If an item is tagged ONLY with 'art_experimental', it is disabled
+        # for non-Art targets.
+        if not tags and not original_tags_empty:
+            return False
+        if not tags:
+            return True
+
     return any(tag in enabled_tags for tag in tags)
 
 
diff --git a/generator/templates/art/api_jni_types.cpp b/generator/templates/art/api_jni_types.cpp
index fb272a1..9b4b27a 100644
--- a/generator/templates/art/api_jni_types.cpp
+++ b/generator/templates/art/api_jni_types.cpp
@@ -28,15 +28,19 @@
 {% macro arg_to_jni_type(arg) %}
     {%- if arg == None -%}
         void
-    {%- elif arg.length and arg.length != 'constant' -%}
+    {%- elif arg.length and (arg.length != 'constant' or arg.constant_length != 1) -%}
         {%- if arg.type.category in ['callback function', 'function pointer', 'kotlin type', 'object', 'structure'] -%}
             jobjectArray
         {%- elif arg.type.name.get() == 'void' -%}
             jobject
-        {%- elif arg.type.category in ['bitmask', 'enum'] or arg.type.name.get() == 'uint32_t' -%}
+        {%- elif arg.type.category in ['bitmask', 'enum'] or arg.type.name.get() in ['int', 'int32_t', 'uint32_t'] -%}
             jintArray
+        {%- elif arg.type.name.get() == 'float' -%}
+            jfloatArray
+        {%- elif arg.type.name.get() == 'char' -%}
+            jobjectArray
         {%- else -%}
-            {{ unreachable_code() }}
+            {{ unreachable_code('Unsupported array type in arg_to_jni_type: ' ~ arg.type.name.get()) }}
         {%- endif -%}
     {%- else -%}
         {{ to_jni_type(arg.type) }}
@@ -58,7 +62,7 @@
 {% macro jni_signature(member) %}
     {%- if member.type.name.get() == 'string view' -%}
         Ljava/lang/String;
-    {%- elif member.length and member.length != 'constant' -%}
+    {%- elif member.length and (member.length != 'constant' or member.constant_length != 1) -%}
         [{{ jni_signature_single_value(member.type) }}
     {%- else -%}
         {{ jni_signature_single_value(member.type) }}
@@ -71,14 +75,18 @@
     {%- elif type.category in ['bitmask', 'enum'] -%}
         {{ jni_signatures['int32_t'] }}
     {%- elif type.category == 'native' -%}
-        {{ jni_signatures[type.name.get()] }}
+        {%- if type.name.get() == 'char' -%}
+            Ljava/lang/String;
+        {%- else -%}
+            {{ jni_signatures[type.name.get()] }}
+        {%- endif -%}
     {%- else -%}
         {{ unreachable_code('Unsupported type: ' + type.name.get()) }}
     {%- endif -%}
 {% endmacro %}
 
 {% macro convert_to_kotlin(input, output, size, member) %}
-    {% if size is string %}
+    {% if size %}
         {% if member.type.name.get() in ['void const *', 'void *'] %}
             jobject {{ output }} = toByteBuffer(env, {{ input }}, {{ size }});
         {% elif member.type.category in ['object', 'structure'] %}
@@ -92,8 +100,18 @@
         {% elif member.type.category in ['bitmask', 'enum'] or member.type.name.get() in ['int', 'int32_t', 'uint32_t'] %}
             jintArray {{ output }} = env->NewIntArray({{ size }});
             {{'    '}}env->SetIntArrayRegion({{ output }}, 0, {{ size }}, reinterpret_cast<const jint *>({{ input }}));
+        {% elif member.type.name.get() == 'float' %}
+            jfloatArray {{ output }} = env->NewFloatArray({{ size }});
+            {{'    '}}env->SetFloatArrayRegion({{ output }}, 0, {{ size }}, reinterpret_cast<const jfloat *>({{ input }}));
+        {% elif member.type.name.get() == 'char' and member.annotation == 'const*const*' %}
+            jobjectArray {{ output }} = env->NewObjectArray({{ size }}, classes->stringClass, nullptr);
+            for (int idx = 0; idx != {{ size }}; idx++) {
+                jstring element = env->NewStringUTF({{ input }}[idx]);
+                env->SetObjectArrayElement({{ output }}, idx, element);
+                env->DeleteLocalRef(element);
+            }
         {% else %}
-            {{ unreachable_code() }}
+            {{ unreachable_code('Unsupported array type in convert_to_kotlin: ' ~ member.type.name.get()) }}
         {% endif %}
     {% elif member.type.category == 'object' %}
         jobject {{ output }};
diff --git a/generator/templates/art/api_kotlin_structure.kt b/generator/templates/art/api_kotlin_structure.kt
index b19d99e..d44930c 100644
--- a/generator/templates/art/api_kotlin_structure.kt
+++ b/generator/templates/art/api_kotlin_structure.kt
@@ -20,7 +20,7 @@
 
 {% set ns = namespace(callback_count = 0, default_count = 0) %}
 {%- set members = [] -%}
-{%- for member in kotlin_record_members(structure.members) -%}
+{%- for member in kotlin_record_members(structure.members, structure.name.get()) -%}
     {%- if member.name.camelCase().endswith('Callback') -%}
         {%- set ns.callback_count = ns.callback_count + 1 -%}
     {%- endif -%}
diff --git a/generator/templates/art/api_kotlin_types.kt b/generator/templates/art/api_kotlin_types.kt
index af471eb..29ba70a 100644
--- a/generator/templates/art/api_kotlin_types.kt
+++ b/generator/templates/art/api_kotlin_types.kt
@@ -30,20 +30,24 @@
     {%- set optional = arg.optional and not strip_optional %}
     {%- set default_value = arg.default_value %}
     {%- if type.category == 'kotlin type' -%}
-        {{ type.name.get() }}  {#- The name *is* the type #}
+        {{ type.name.get() }}{{ '?' if optional }}  {#- The name *is* the type #}
     {%- elif arg.type.name.get() == 'string view' -%}
         String{{ '?' if optional }}
     {%- elif type.name.get() == 'void' %}
         {{- assert(arg.length and arg.constant_length != 1) -}}  {# void with length is binary data #}
         java.nio.ByteBuffer
-    {%- elif arg.length and arg.length != 'constant' %}
+    {%- elif arg.length and (arg.length != 'constant' or arg.constant_length != 1) %}
         {# * annotation can mean an array, e.g. an output argument #}
         {%- if type.category in ['callback function', 'callback info', 'function pointer', 'object', 'structure'] -%}
-            Array<{{ kotlin_name(type) }}>
+            Array<{{ kotlin_name(type) }}>{{ '?' if optional }}
         {%- elif type.category in ['bitmask', 'enum'] or type.name.get() in ['int', 'int32_t', 'uint32_t'] -%}
-            IntArray
+            IntArray{{ '?' if optional }}
+        {%- elif type.name.get() == 'float' -%}
+            FloatArray{{ '?' if optional }}
+        {%- elif type.name.get() == 'char' -%}
+            Array<String>{{ '?' if optional }}
         {%- else -%}
-            {{ unreachable_code() }}
+            {{ unreachable_code('Unsupported array type: ' + type.name.get()) }}
         {% endif %}
     {%- elif type.category in ['callback function', 'function pointer', 'object'] %}
         {{- kotlin_name(type) }}
diff --git a/generator/templates/art/kotlin_record_conversion.cpp b/generator/templates/art/kotlin_record_conversion.cpp
index 894fb77..2919559 100644
--- a/generator/templates/art/kotlin_record_conversion.cpp
+++ b/generator/templates/art/kotlin_record_conversion.cpp
@@ -26,28 +26,31 @@
 //* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 {% from 'art/api_jni_types.cpp' import arg_to_jni_type, convert_to_kotlin, jni_signature with context %}
 
-{% macro define_kotlin_record_structure(struct_name, members) %}
+{% macro define_kotlin_record_structure(struct_name, members, structure_name=None) %}
     struct {{struct_name}} {
-        {% for member in kotlin_record_members(members) %}
-            //* HACK: Hardcode that ANativeWindow is a jlong instead of an actual pointer. Instead
-            //* of this, we should have manually written method that directly creates the
-            //* wgpu::Surface from the Java Surface.
-            {% if member.name.get() == "window" and member.type.name.get() == "void *"%}
-                jlong {{ as_varName(member.name) }};
-            {% else %}
-                {{ arg_to_jni_type(member) }} {{ as_varName(member.name) }};
+        {% for member in kotlin_record_members(members, structure_name) %}
+            {% if not member.skip_serialize %}
+                //* HACK: Hardcode that ANativeWindow is a jlong instead of an actual pointer. Instead
+                //* of this, we should have manually written method that directly creates the
+                //* wgpu::Surface from the Java Surface.
+                {% if member.name.get() == "window" and member.type.name.get() == "void *"%}
+                    jlong {{ as_varName(member.name) }};
+                {% else %}
+                    {{ arg_to_jni_type(member) }} {{ as_varName(member.name) }};
+                {% endif %}
             {% endif %}
         {% endfor%}
     };
 {% endmacro %}
 
-{% macro define_kotlin_to_struct_conversion(function_name, kotlin_name, struct_name, members,is_structure_converter=False) %}
+{% macro define_kotlin_to_struct_conversion(function_name, kotlin_name, struct_name, members, structure_name=None, is_structure_converter=False) %}
     inline void {{function_name}}(JNIContext* c, const {{kotlin_name}}& inStruct, {{struct_name}}* outStruct) {
         JNIEnv* env = c->env;
         JNIClasses* classes = JNIClasses::getInstance(env);
         *outStruct = {};
 
-        {% for member in kotlin_record_members(members) %}
+        {% for member in kotlin_record_members(members, structure_name) %}
+            {% if not member.skip_serialize %}
             {
                 {% if member.type.category == 'callback function' %}
                     if (inStruct.{{ as_varName(member.name) }})
@@ -122,7 +125,7 @@
 
                             {# Generate the C++ variable conversions (e.g. _status, _message) #}
                             {%- for arg in kotlin_record_members(member.type.arguments) -%}
-                                {{ convert_to_kotlin(arg.name.camelCase(), '_' + arg.name.camelCase(), 'input->' + arg.length.name.camelCase() if arg.length.name, arg) }}
+                                {{ convert_to_kotlin(arg.name.camelCase(), '_' + arg.name.camelCase(), 'input->' + arg.length.name.camelCase() if arg.length and arg.length != 'constant' else (arg.constant_length | string if arg.length == 'constant' and arg.constant_length != 1 else None), arg) }}
                                 {%- set vName = '_' + arg.name.camelCase() -%}
                                 {%- set aName = arg.name.get() -%}
                                 {%- if aName == 'status' %}
@@ -198,37 +201,60 @@
                         {{ unreachable_code() }}
                     {% endif %}
                 {% elif member.length %}
-                    auto& outLength = outStruct->{{member.length.name.camelCase()}};
-                    {% if member.constant_length %}
-                        {{ unreachable_code() }}
-                    {% endif %}
-                    //* Convert container, including the length field.
-                    {% if member.type.name.get() == 'uint32_t' or member.type.category in ['bitmask', 'enum'] %}
-                        out = reinterpret_cast<const {{ as_cType(member.type.name) }}*>(c->GetIntArrayElements(in));
-                        outLength = env->GetArrayLength(in);
-                    {% elif member.type.name.get() == 'void' %}
-                        out = env->GetDirectBufferAddress(in);
-                        outLength = env->GetDirectBufferCapacity(in);
-                    {% else %}
-                        //* These container types are represented in Kotlin as arrays of objects.
-                        outLength = env->GetArrayLength(in);
-                        auto array = c->AllocArray<{{ as_cType(member.type.name) }}>(outLength);
-                        out = array;
-
-                        {% if member.type.category == 'object' %}
-                            jclass memberClass = classes->{{ member.type.name.camelCase() }};
-                            jmethodID getHandle = env->GetMethodID(memberClass, "getHandle", "()J");
+                    {% if member.length != 'constant' %}
+                        auto& outLength = outStruct->{{member.length.name.camelCase()}};
+                        //* Convert container, including the length field.
+                        {% if member.type.name.get() == 'uint32_t' or member.type.category in ['bitmask', 'enum'] %}
+                            out = reinterpret_cast<const {{ as_cType(member.type.name) }}*>(c->GetIntArrayElements(in));
+                            outLength = env->GetArrayLength(in);
+                        {% elif member.type.name.get() == 'void' %}
+                            out = env->GetDirectBufferAddress(in);
+                            outLength = env->GetDirectBufferCapacity(in);
+                        {% elif member.type.name.get() == 'char' %}
+                            outLength = env->GetArrayLength(in);
+                            auto array = c->AllocArray<const char*>(outLength);
                             for (int idx = 0; idx != outLength; idx++) {
-                                jobject element = env->GetObjectArrayElement(in, idx);
-                                array[idx] = reinterpret_cast<{{ as_cType(member.type.name) }}>(
-                                        env->CallLongMethod(element, getHandle));
+                                jstring element = static_cast<jstring>(env->GetObjectArrayElement(in, idx));
+                                array[idx] = c->GetStringUTFChars(element);
                             }
-                        {% elif member.type.category == 'structure' %}
-                            for (int idx = 0; idx != outLength; idx++) {
-                                ToNative(c, env, env->GetObjectArrayElement(in, idx), &array[idx]);
+                            out = array;
+                        {% else %}
+                            //* These container types are represented in Kotlin as arrays of objects.
+                            outLength = env->GetArrayLength(in);
+                            auto array = c->AllocArray<{{ as_cType(member.type.name) }}>(outLength);
+                            out = array;
+
+                            {% if member.type.category == 'object' %}
+                                jclass memberClass = classes->{{ member.type.name.camelCase() }};
+                                jmethodID getHandle = env->GetMethodID(memberClass, "getHandle", "()J");
+                                for (int idx = 0; idx != outLength; idx++) {
+                                    jobject element = env->GetObjectArrayElement(in, idx);
+                                    array[idx] = reinterpret_cast<{{ as_cType(member.type.name) }}>(
+                                            env->CallLongMethod(element, getHandle));
+                                }
+                            {% elif member.type.category == 'structure' %}
+                                for (int idx = 0; idx != outLength; idx++) {
+                                    ToNative(c, env, env->GetObjectArrayElement(in, idx), &array[idx]);
+                                }
+                            {% else %}
+                                {{ unreachable_code() }}
+                            {% endif %}
+                        {% endif %}
+                    {% else %}
+                        {% if member.type.name.get() == 'float' %}
+                            jsize arrayLength = env->GetArrayLength(static_cast<jarray>(in));
+                            if (arrayLength == {{ member.constant_length }}) {
+                                auto array = c->AllocArray<float>({{ member.constant_length }});
+                                env->GetFloatArrayRegion(static_cast<jfloatArray>(in), 0, {{ member.constant_length }}, array);
+                                out = array;
+                            } else {
+                                // If the array length doesn't match the expected constant length,
+                                // do not proceed to avoid potential crashes or uninitialized data.
+                                // The out variable remains in its default-initialized state (e.g., nullptr).
+                                return;
                             }
                         {% else %}
-                            {{ unreachable_code() }}
+                            {{ unreachable_code('Unsupported constant array type: ' ~ member.type.name.get()) }}
                         {% endif %}
                     {% endif %}
                 //* From here members are single values.
@@ -257,6 +283,7 @@
                     {{ unreachable_code() }}
                 {% endif %}
             }
+            {% endif %}
         {% endfor -%}
     }
 {% endmacro %}
diff --git a/generator/templates/art/structures.cpp b/generator/templates/art/structures.cpp
index db54bf8..31cea55 100644
--- a/generator/templates/art/structures.cpp
+++ b/generator/templates/art/structures.cpp
@@ -108,16 +108,16 @@
             jclass clz = classes->{{ structure.name.camelCase() }};
             //* JNI signature needs to be built using the same logic used in the Kotlin structure spec.
             jmethodID ctor = env->GetMethodID(clz, "<init>", "(
-            {%- for member in kotlin_record_members(structure.members) %}
+            {%- for member in kotlin_record_members(structure.members, structure.name.get()) %}
                 {{- jni_signature(member) -}}
             {%- endfor -%}
             {%- for structure in chain_children[structure.name.get()] -%}
                 {{- jni_signature({'type': structure}) -}}
             {%- endfor %})V");
             //* Each field converted using the individual value converter.
-            {% for member in kotlin_record_members(structure.members) %}
+            {% for member in kotlin_record_members(structure.members, structure.name.get()) %}
                 {{ convert_to_kotlin('input->' + member.name.camelCase(), member.name.camelCase(),
-                                     'input->' + member.length.name.camelCase() if member.length.name,
+                                     'input->' + member.length.name.camelCase() if member.length and member.length != 'constant' else (member.constant_length | string if member.length == 'constant' and member.constant_length != 1 else None),
                                      member) | indent(4) -}}
             {% endfor %}
             //* Allow conversion of every child structure.
@@ -145,7 +145,7 @@
             jobject converted = env->NewObject(
                 clz,
                 ctor
-            {%- for member in kotlin_record_members(structure.members) %},
+            {%- for member in kotlin_record_members(structure.members, structure.name.get()) %},
                 {%- if member.type.category == 'kotlin type' -%}
                     nullptr  {#- We can't make these. TODO(b/451995459): Don't even create the converter. #}
                 {%- else -%}
@@ -163,21 +163,23 @@
 
         {% set Struct = as_cType(structure.name) %}
         {% set KotlinRecord = "KotlinRecord" + structure.name.CamelCase() %}
-        {{ define_kotlin_record_structure(KotlinRecord, structure.members)}}
+        {{ define_kotlin_record_structure(KotlinRecord, structure.members, structure.name.get())}}
 
-        {{ define_kotlin_to_struct_conversion("ConvertInternal", KotlinRecord, Struct, structure.members, is_structure_converter=True)}}
+        {{ define_kotlin_to_struct_conversion("ConvertInternal", KotlinRecord, Struct, structure.members, structure.name.get(), is_structure_converter=True)}}
         void ToNative(JNIContext* c, JNIEnv* env, jobject obj, {{ as_cType(structure.name) }}* converted) {
             JNIClasses* classes = JNIClasses::getInstance(env);
             jclass clz = classes->{{ structure.name.camelCase() }};
 
             //* Use getters to fill in the Kotlin record that will get converted to our struct.
             {{KotlinRecord}} kotlinRecord;
-            {% for member in kotlin_record_members(structure.members) %}
-                {
-                    {% set prefix = "is" if member.type.name.get() == "bool" else "get" %}
-                    jmethodID getter = env->GetMethodID(clz, "{{prefix}}{{member.name.CamelCase()}}", "(){{jni_signature(member)}}");
-                    CallGetter(env, getter, obj, &kotlinRecord.{{as_varName(member.name)}});
-                }
+            {% for member in kotlin_record_members(structure.members, structure.name.get()) %}
+                {% if not member.skip_serialize %}
+                    {
+                        {% set prefix = "is" if member.type.name.get() == "bool" else "get" %}
+                        jmethodID getter = env->GetMethodID(clz, "{{prefix}}{{member.name.CamelCase()}}", "(){{jni_signature(member)}}");
+                        CallGetter(env, getter, obj, &kotlinRecord.{{as_varName(member.name)}});
+                    }
+                {% endif %}
             {% endfor %}
 
             //* Fill all struct members from the Kotlin record.
diff --git a/src/dawn/dawn.json b/src/dawn/dawn.json
index 45b81de..85d1a75 100644
--- a/src/dawn/dawn.json
+++ b/src/dawn/dawn.json
@@ -232,7 +232,7 @@
         ]
     },
     "dawn toggles descriptor": {
-        "tags": ["dawn", "native"],
+        "tags": ["dawn", "native", "art_experimental"],
         "category": "structure",
         "chained": "in",
         "chain roots": ["instance descriptor", "request adapter options", "device descriptor"],
@@ -1356,7 +1356,7 @@
             {
                 "name": "create external texture",
                 "returns": "external texture",
-                "tags": ["dawn"],
+                "tags": ["dawn", "art_experimental"],
                 "args": [
                     {"name": "external texture descriptor", "type": "external texture descriptor", "annotation": "const*"}
                 ]
@@ -1364,7 +1364,7 @@
             {
                 "name": "create error external texture",
                 "returns": "external texture",
-                "tags": ["dawn"]
+                "tags": ["dawn", "art_experimental"]
             },
             {
                 "name": "create pipeline layout",
@@ -1799,7 +1799,7 @@
     },
     "extent 2D": {
         "category": "structure",
-        "tags": ["dawn"],
+        "tags": ["dawn", "art_experimental"],
         "members": [
             {"name": "width", "type": "uint32_t"},
             {"name": "height", "type": "uint32_t"}
@@ -1825,7 +1825,7 @@
             },
             {
                 "name": "destroy",
-                "tags": ["dawn"]
+                "tags": ["dawn", "art_experimental"]
             },
             {
                 "name": "expire",
@@ -1839,7 +1839,7 @@
     },
     "external texture rotation": {
         "category": "enum",
-        "tags": ["dawn"],
+        "tags": ["dawn", "art_experimental"],
         "_comment": "Angles are clockwise rotation degrees and not trigonometric.",
         "values": [
             {"value": 1, "name": "rotate 0 degrees"},
@@ -1851,7 +1851,7 @@
     "external texture descriptor": {
         "category": "structure",
         "extensible": "in",
-        "tags": ["dawn"],
+        "tags": ["dawn", "art_experimental"],
         "members": [
             {"name": "label", "type": "string view", "optional": true},
             {"name": "plane 0", "type": "texture view"},
@@ -2516,7 +2516,7 @@
             {"value": 28, "name": "multi planar format p410", "tags": ["dawn"]},
 
             {"value": 29, "name": "shared texture memory vk dedicated allocation", "tags": ["dawn", "native"]},
-            {"value": 30, "name": "shared texture memory a hardware buffer", "tags": ["dawn", "native"]},
+            {"value": 30, "name": "shared texture memory a hardware buffer", "tags": ["dawn", "native", "art_experimental"]},
             {"value": 31, "name": "shared texture memory dma buf", "tags": ["dawn", "native"]},
             {"value": 32, "name": "shared texture memory opaque FD", "tags": ["dawn", "native"]},
             {"value": 33, "name": "shared texture memory zircon handle", "tags": ["dawn", "native"]},
@@ -2525,13 +2525,13 @@
             {"value": 36, "name": "shared texture memory IO surface", "tags": ["dawn", "native"]},
             {"value": 37, "name": "shared texture memory EGL image", "tags": ["dawn", "native"]},
             {"value": 38, "name": "shared fence vk semaphore opaque FD", "tags": ["dawn", "native"]},
-            {"value": 39, "name": "shared fence sync FD", "tags": ["dawn", "native"]},
+            {"value": 39, "name": "shared fence sync FD", "tags": ["dawn", "native", "art_experimental"]},
             {"value": 40, "name": "shared fence vk semaphore zircon handle", "tags": ["dawn", "native"]},
             {"value": 41, "name": "shared fence DXGI shared handle", "tags": ["dawn", "native"]},
             {"value": 42, "name": "shared fence MTL shared event", "tags": ["dawn", "native"]},
             {"value": 43, "name": "shared buffer memory D3D12 resource", "tags": ["dawn", "native"]},
             {"value": 44, "name": "static samplers", "tags": ["dawn"]},
-            {"value": 45, "name": "y cb cr vulkan samplers", "tags": ["dawn"]},
+            {"value": 45, "name": "y cb cr vulkan samplers", "tags": ["dawn", "art_experimental"]},
             {"value": 46, "name": "shader module compilation options", "tags": ["dawn"]},
 
             {"value": 47, "name": "dawn load resolve texture", "tags": ["dawn"]},
@@ -2548,7 +2548,7 @@
             {"value": 58, "name": "chromium experimental sampling resource table", "tags": ["dawn"]},
             {"value": 59, "name": "atomic vec2u min max", "tags": ["dawn"]},
             {"value": 60, "name": "unorm16 formats for external texture", "tags": ["dawn"]},
-            {"value": 61, "name": "opaque y cb cr android for external texture", "tags": ["dawn"]},
+            {"value": 61, "name": "opaque y cb cr android for external texture", "tags": ["dawn", "art_experimental"]},
             {"value": 62, "name": "unorm16 filterable", "tags": ["dawn"]},
             {"value": 63, "name": "render pass render area", "tags": ["dawn"]},
             {"value": 64, "name": "adapter properties drm", "tags": ["dawn", "native"]},
@@ -2853,7 +2853,7 @@
     },
     "origin 2D": {
         "category": "structure",
-        "tags": ["dawn"],
+        "tags": ["dawn", "art_experimental"],
         "members": [
             {"name": "x", "type": "uint32_t", "default": "0"},
             {"name": "y", "type": "uint32_t", "default": "0"}
@@ -4097,7 +4097,7 @@
             {"value": 7, "name": "dawn cache device descriptor", "tags": ["dawn", "native"]},
             {"value": 8, "name": "dawn adapter properties power preference", "tags": ["dawn", "native"]},
             {"value": 9, "name": "dawn buffer descriptor error info from wire client", "tags": ["dawn"]},
-            {"value": 10, "name": "dawn toggles descriptor", "tags": ["dawn", "native"]},
+            {"value": 10, "name": "dawn toggles descriptor", "tags": ["dawn", "native", "art_experimental"]},
             {"value": 11, "name": "dawn shader module SPIRV options descriptor", "tags": ["dawn"]},
             {"value": 12, "name": "request adapter options LUID", "tags": ["dawn", "native"]},
             {"value": 13, "name": "request adapter options get GL proc", "tags": ["dawn", "native"]},
diff --git a/src/dawn/dawn_kotlin.json b/src/dawn/dawn_kotlin.json
index 9761cbe..c97ebce 100644
--- a/src/dawn/dawn_kotlin.json
+++ b/src/dawn/dawn_kotlin.json
@@ -37,6 +37,7 @@
         "double": "jdouble",
         "float": "jfloat",
         "size_t": "jlong",
+        "int": "jint",
         "int32_t": "jint",
         "uint16_t": "jshort",
         "uint32_t": "jint",
@@ -50,6 +51,7 @@
         "bool": "Z",
         "double": "D",
         "float": "F",
+        "int": "I",
         "int16_t": "S",
         "int32_t": "I",
         "int64_t": "J",
@@ -110,6 +112,31 @@
             },
             "future wait info": {
                 "omitted": true
+            },
+            "external texture descriptor": {
+                "exclude_members": [
+                    "plane 0",
+                    "apparent size",
+                    "src transfer function parameters",
+                    "dst transfer function parameters",
+                    "gamut conversion matrix",
+                    "do yuv to rgb conversion only",
+                    "yuv to rgb conversion matrix"
+                ],
+                "additional_members": [
+                    {
+                        "name": "srcColorSpace",
+                        "type": "color space dawn",
+                        "category": "structure",
+                        "optional": false
+                    },
+                    {
+                        "name": "dstColorSpace",
+                        "type": "predefined color space",
+                        "category": "kotlin type",
+                        "optional": false
+                    }
+                ]
             }
         },
         "enums": {
diff --git a/tools/android/BUILD.gn b/tools/android/BUILD.gn
index 66f39811..bbff405 100644
--- a/tools/android/BUILD.gn
+++ b/tools/android/BUILD.gn
@@ -94,6 +94,7 @@
     "java/androidx/webgpu/ErrorFilter.kt",
     "java/androidx/webgpu/ErrorType.kt",
     "java/androidx/webgpu/Exceptions.kt",
+    "java/androidx/webgpu/ExternalTextureRotation.kt",
     "java/androidx/webgpu/FeatureLevel.kt",
     "java/androidx/webgpu/FeatureName.kt",
     "java/androidx/webgpu/FilterMode.kt",
@@ -128,19 +129,23 @@
     "java/androidx/webgpu/GPUComputePipelineDescriptor.kt",
     "java/androidx/webgpu/GPUComputeState.kt",
     "java/androidx/webgpu/GPUConstantEntry.kt",
+    "java/androidx/webgpu/GPUDawnTogglesDescriptor.kt",
     "java/androidx/webgpu/GPUDepthStencilState.kt",
     "java/androidx/webgpu/GPUDevice.kt",
     "java/androidx/webgpu/GPUDeviceDescriptor.kt",
+    "java/androidx/webgpu/GPUExtent2D.kt",
     "java/androidx/webgpu/GPUExtent3D.kt",
     "java/androidx/webgpu/GPUExternalTexture.kt",
     "java/androidx/webgpu/GPUExternalTextureBindingEntry.kt",
     "java/androidx/webgpu/GPUExternalTextureBindingLayout.kt",
+    "java/androidx/webgpu/GPUExternalTextureDescriptor.kt",
     "java/androidx/webgpu/GPUFragmentState.kt",
     "java/androidx/webgpu/GPUInstance.kt",
     "java/androidx/webgpu/GPUInstanceDescriptor.kt",
     "java/androidx/webgpu/GPUInstanceLimits.kt",
     "java/androidx/webgpu/GPULimits.kt",
     "java/androidx/webgpu/GPUMultisampleState.kt",
+    "java/androidx/webgpu/GPUOrigin2D.kt",
     "java/androidx/webgpu/GPUOrigin3D.kt",
     "java/androidx/webgpu/GPUPassTimestampWrites.kt",
     "java/androidx/webgpu/GPUPipelineLayout.kt",
diff --git a/tools/android/webgpu/src/test/java/androidx/webgpu/MappedNamedConstantsTest.kt b/tools/android/webgpu/src/test/java/androidx/webgpu/MappedNamedConstantsTest.kt
index 809cbc3..6ff46b3 100644
--- a/tools/android/webgpu/src/test/java/androidx/webgpu/MappedNamedConstantsTest.kt
+++ b/tools/android/webgpu/src/test/java/androidx/webgpu/MappedNamedConstantsTest.kt
@@ -61,6 +61,7 @@
         DeviceLostReason::class,
         ErrorFilter::class,
         ErrorType::class,
+        ExternalTextureRotation::class,
         FeatureLevel::class,
         FeatureName::class,
         FilterMode::class,