[WebGPU] Adds the @ExperimentalWebGpuApi opt-in annotation to the Kotlin WebGPU wrapper.

Key changes:
- Generates the `ExperimentalWebGpuApi.kt` annotation file.
- Updates generator templates to automatically annotate classes, methods, or properties tagged as experimental.
- Adds secondary constructor for classes with experimental members
- Removes @JVMOverloads and introduces builder pattern
- Add experimental api annotation to test cases

Bug: b/513132857
Change-Id: Ia7762e2e503477fc7217b3700c71e14cea28263a
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/312615
Reviewed-by: Tarun Saini <sainitarun@google.com>
Reviewed-by: Loko Kung <lokokung@google.com>
Commit-Queue: Mridul Goyal <mridulgoyal@google.com>
diff --git a/generator/dawn_json_generator.py b/generator/dawn_json_generator.py
index 723fec4e..7594140 100644
--- a/generator/dawn_json_generator.py
+++ b/generator/dawn_json_generator.py
@@ -1221,6 +1221,7 @@
     params_kotlin['kotlin_default'] = kotlin_default
     params_kotlin['kotlin_return'] = kotlin_return
     params_kotlin['kotlin_name'] = kotlin_name
+    params_kotlin['customize_structures'] = customize_structures
     params_kotlin['include_method'] = include_method
     params_kotlin['include_structure'] = include_structure
     params_kotlin['include_enum'] = include_enum
diff --git a/generator/templates/art/api_kotlin_enum.kt b/generator/templates/art/api_kotlin_enum.kt
index a7dbfff..1453048 100644
--- a/generator/templates/art/api_kotlin_enum.kt
+++ b/generator/templates/art/api_kotlin_enum.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-{% from 'art/api_kotlin_types.kt' import generate_simple_kdoc, add_kdoc_disclaimer with context %}
+{% from 'art/api_kotlin_types.kt' import generate_simple_kdoc, add_kdoc_disclaimer, kotlin_member_optin, kotlin_class_optin, item_requires_optin with context %}
 {{ add_kdoc_disclaimer() }}
 package {{ kotlin_package }}
 
@@ -32,8 +32,14 @@
 {% if docstring %}
     {{ generate_simple_kdoc(docstring) }}
 {% endif %}
+{% set experimental = kotlin_class_optin(enum) -%}
+{% if experimental %}
+    //* Injects experimental opt-in requirement if the enum itself is experimental.
+    {{ experimental }}
+{% endif %}
 public class {{ enum.name.CamelCase() }} private constructor() {
 
+  {% set ns = namespace(has_experimental=false) %}
   public companion object {
       //* Generating KDocs
       {% set enum_doc = file_docs.get(enum.name.get(), {}) %}
@@ -43,6 +49,11 @@
 
               {{ generate_simple_kdoc(value_docstring, indent_prefix = "      ", line_wrap_prefix = "\n         * ") }}
           {% endif %}
+          {% set optin = kotlin_member_optin(value, parent=enum) | trim %}
+          {% if optin %}
+              {{ optin }}
+              {% set ns.has_experimental = true %}
+          {% endif %}
           public const val {{ as_ktName(value.name.CamelCase()) }}: Int = {{ '{:#010x}'.format(value.value) }}
       {% endfor %}
       internal val names: Map<Int, String> = mapOf(
@@ -59,6 +70,9 @@
   {% if enum.allowConflict %}
     @Suppress("UniqueConstants")  //* Required for duplicate constant values for compatibility.
   {% endif %}
+  {% if ns.has_experimental %}
+      @OptIn(ExperimentalWebGpuApi::class)
+  {% endif %}
   @IntDef(
       {% if enum.category == 'bitmask' %}
           flag = true,
diff --git a/generator/templates/art/api_kotlin_function_pointer.kt b/generator/templates/art/api_kotlin_function_pointer.kt
index e7ee7b4..c4c783f 100644
--- a/generator/templates/art/api_kotlin_function_pointer.kt
+++ b/generator/templates/art/api_kotlin_function_pointer.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-{% from 'art/api_kotlin_types.kt' import kotlin_annotation, kotlin_declaration, kotlin_definition, check_if_doc_present, generate_kdoc, add_kdoc_disclaimer with context %}
+{% from 'art/api_kotlin_types.kt' import kotlin_annotation, kotlin_declaration, kotlin_definition, check_if_doc_present, generate_kdoc, add_kdoc_disclaimer, kotlin_member_optin, kotlin_class_optin, item_requires_optin with context %}
 {{ add_kdoc_disclaimer() }}
 package {{ kotlin_package }}
 
@@ -27,6 +27,11 @@
 {% set arg_docs_map =  funtion_pointer_info.args if funtion_pointer_info else {} %}
 
 {% set function_pointer_args = function_pointer.arguments | list %}
+{% set experimental = kotlin_class_optin(function_pointer) -%}
+{% if experimental %}
+    //* Requires opt-in if this functional interface (callback) is experimental.
+    {{ experimental }}
+{% endif %}
 public fun interface {{ function_pointer.name.CamelCase() }} {
     {% if check_if_doc_present(main_doc, "", arg_docs_map, function_pointer_args) == 'True' %}
     {{ generate_kdoc(main_doc, return_str, arg_docs_map, function_pointer_args , indent_prefix = "    ",line_wrap_prefix = "\n     * ") }}
diff --git a/generator/templates/art/api_kotlin_functions.kt b/generator/templates/art/api_kotlin_functions.kt
index 95c9c87..a389d84 100644
--- a/generator/templates/art/api_kotlin_functions.kt
+++ b/generator/templates/art/api_kotlin_functions.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-{% from 'art/api_kotlin_types.kt' import kotlin_annotation, kotlin_declaration, kotlin_definition, check_if_doc_present, generate_kdoc, add_kdoc_disclaimer with context %}
+{% from 'art/api_kotlin_types.kt' import kotlin_annotation, kotlin_declaration, kotlin_definition, check_if_doc_present, generate_kdoc, add_kdoc_disclaimer, kotlin_member_optin with context %}
 {{ add_kdoc_disclaimer() }}
 @file:JvmName("Functions")
 
@@ -40,6 +40,11 @@
         {% if function.has_default %}
             @JvmOverloads
         {% endif %}
+        {% set experimental = kotlin_member_optin(function) -%}
+        {% if experimental %}
+            //* Requires opt-in if the function, its return type, or any argument is experimental.
+            {{ experimental }}
+        {% endif %}
         @FastNative
         {% if function.returns and function.returns.type.name.canonical_case() == 'status' %}
             @Throws({{"WebGpuException::class"}})
diff --git a/generator/templates/art/api_kotlin_object.kt b/generator/templates/art/api_kotlin_object.kt
index 8f215a9..ecb202a 100644
--- a/generator/templates/art/api_kotlin_object.kt
+++ b/generator/templates/art/api_kotlin_object.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-{% from 'art/api_kotlin_types.kt' import kotlin_annotation, kotlin_declaration, kotlin_definition, check_if_doc_present, generate_kdoc, generate_simple_kdoc, add_kdoc_disclaimer with context %}
+{% from 'art/api_kotlin_types.kt' import kotlin_annotation, kotlin_declaration, kotlin_definition, check_if_doc_present, generate_kdoc, generate_simple_kdoc, add_kdoc_disclaimer, kotlin_member_optin, kotlin_class_optin, item_requires_optin with context %}
 {% from 'art/api_kotlin_async_helpers.kt' import async_wrapper, analyze_callback with context %}
 {{ add_kdoc_disclaimer() }}
 package {{ kotlin_package }}
@@ -34,6 +34,11 @@
 {% if doc_str | trim %}
     {{ generate_simple_kdoc(doc_str) }}
 {% endif %}
+{% set experimental = kotlin_class_optin(obj) -%}
+{% if experimental %}
+    //* Requires opt-in if this entire WebGPU object class itself is experimental.
+    {{ experimental }}
+{% endif %}
 public class {{ kotlin_name(obj) }} private constructor(public val handle: Long): AutoCloseable {
     {% set all_method_info = object_info.methods if object_info else {} %}
     {% for method in obj.methods if include_method(obj, method) %}
@@ -48,6 +53,11 @@
         {{ generate_kdoc(main_doc, return_doc, arg_docs_map, method_args, "\n     * ", indent_prefix = "    ") }}
 
         {%- endif %}
+        {% set experimental = kotlin_member_optin(method, parent=obj) -%}
+        {% if experimental %}
+            //* Requires opt-in if the method, its return type, or any of its arguments require experimental opt-in.
+            {{ experimental }}
+        {% endif %}
         @FastNative
         @JvmName("{{ method.name.camelCase() }}")
         {% for arg in kotlin_record_members(method.arguments) %}
@@ -75,6 +85,9 @@
             //* For the Kotlin getter, strip word 'get' from name and convert the remainder to
             //* camelCase() (lower case first word). E.g. "get foo bar" translated to fooBar.
             {% set name = method.name.chunks[1] + method.name.chunks[2:] | map('title') | join %}
+            {% if experimental %}
+                {{ experimental }}
+            {% endif %}
             @get:JvmName("{{ name }}")
             public val {{ name }}: {{ kotlin_declaration(_kotlin_return) if _kotlin_return else 'Unit' }} get() = {{ method.name.camelCase() }}()
 
diff --git a/generator/templates/art/api_kotlin_structure.kt b/generator/templates/art/api_kotlin_structure.kt
index d44930c..cf9278d 100644
--- a/generator/templates/art/api_kotlin_structure.kt
+++ b/generator/templates/art/api_kotlin_structure.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-{% from 'art/api_kotlin_types.kt' import kotlin_annotation, kotlin_definition, kotlin_declaration, generate_simple_kdoc, add_kdoc_disclaimer with context %}
+{% from 'art/api_kotlin_types.kt' import kotlin_annotation, kotlin_definition, kotlin_declaration, generate_simple_kdoc, add_kdoc_disclaimer, kotlin_member_optin, kotlin_class_optin, item_requires_optin, item_is_experimental, kotlin_parameter, kotlin_chain_parameter, kotlin_property, kotlin_chain_property with context %}
 {{ add_kdoc_disclaimer() }}
 package {{ kotlin_package }}
 
@@ -35,102 +35,322 @@
     {%- set ns.default_count = ns.default_count + 1 -%}
 {%- endfor -%}
 
-{%- set use_builder = ns.default_count > 3 -%}
+{%- set struct_config = customize_structures.get(structure.name.get(), {}) -%}
+{%- set graduated_batches = struct_config.get('graduated_batches', []) -%}
+{%- set class_is_experimental = item_is_experimental(structure) == 'True' -%}
+
+{%- set stable_members = [] -%}
+{%- set experimental_members = [] -%}
+{%- for member in members -%}
+    {%- if not class_is_experimental and item_requires_optin(member, chain_children=chain_children) == 'True' -%}
+        {%- do experimental_members.append(member) -%}
+    {%- else -%}
+        {%- do stable_members.append(member) -%}
+    {%- endif -%}
+{%- endfor -%}
+
+{%- set stable_chain_children = [] -%}
+{%- set experimental_chain_children = [] -%}
+{%- for child in chain_children_list -%}
+    {%- if not class_is_experimental and item_requires_optin(child, chain_children=chain_children) == 'True' -%}
+        {%- do experimental_chain_children.append(child) -%}
+    {%- else -%}
+        {%- do stable_chain_children.append(child) -%}
+    {%- endif -%}
+{%- endfor -%}
+
+{%- set has_experimental = (experimental_members or experimental_chain_children) and not class_is_experimental -%}
+
+{%- set stable_ctor_has_defaults = namespace(val=false) -%}
+{%- for member in stable_members -%}
+    {%- if kotlin_default(member) is not none -%}
+        {%- set stable_ctor_has_defaults.val = true -%}
+        {%- break -%}
+    {%- endif -%}
+{%- endfor -%}
+{%- if not stable_ctor_has_defaults.val -%}
+    {%- if stable_chain_children -%}
+        {%- set stable_ctor_has_defaults.val = true -%}
+    {%- endif -%}
+{%- endif -%}
 
 //* Generating KDocs
-{% set all_structs_info = kdocs.structs %}
-{% set struct_info = all_structs_info.get(structure.name.get()) %}
-{% set main_doc = struct_info.doc if struct_info else "" %}
-{% if main_doc %}
+{% set all_structs_info = kdocs.structs -%}
+{%- set struct_info = all_structs_info.get(structure.name.get()) -%}
+{%- set main_doc = struct_info.doc if struct_info else "" -%}
+{%- if main_doc %}
     {{ generate_simple_kdoc(main_doc) }}
 {% endif %}
-public class {{ kotlin_name(structure) }}
-    {%- if not use_builder %}
-        {%- for member in members %}
-            {% if kotlin_default(member) is not none %} @JvmOverloads constructor{% break %}{% endif %}
-        {%- endfor %}
-    {%- endif %}(
-    {% for member in members %}
-        //* Generating KDocs
-        {% set member_doc = struct_info.members.get(member.name.get(), "") if struct_info and struct_info.members else "" %}
-        {% if member_doc %}
-            {{ generate_simple_kdoc(member_doc, indent_prefix = "    ", line_wrap_prefix = '\n     * ') }}
-        {% endif %}
-        {% if member.type.name.get() == "bool" %}@get:JvmName("is{{ member.name.CamelCase() }}") {% endif %}{{ kotlin_annotation(member) }} public var {{ member.name.camelCase() }}: {{ kotlin_definition(member) }},
-    {% endfor %}
-    {% for structure in chain_children_list %}
-        //* Generating KDocs
-        {% set chain_struct_doc = all_structs_info.get(structure.name.get()).doc if all_structs_info.get(structure.name.get()) else "" %}
-        {% if chain_struct_doc %}
-            {{ generate_simple_kdoc(chain_struct_doc, indent_prefix = "    ", line_wrap_prefix = '\n     * ') }}
-        {% endif %}
-        public var {{ structure.name.camelCase() }}: {{ kotlin_name(structure) }}? = null,
-    {% endfor %}
-)
-{% if ns.callback_count > 1 or use_builder %}
-    {
-      {% if ns.callback_count > 1 %}
-          {% for member in members %}
-              {% if member.name.camelCase().endswith('Callback') %}
-                  {% set callback_name = member.name.camelCase() %}
-                  {% set executor_name = callback_name + 'Executor' %}
-                  {% set function_name = 'set' + member.name.CamelCase() %}
-                  public fun {{ function_name }}(
-                      {{ executor_name }}: java.util.concurrent.Executor,
-                      {{ callback_name }}: {{ kotlin_definition(member) }}
-                  ): Unit {
-                      this.{{ executor_name }} = {{ executor_name }}
-                      this.{{ callback_name }} = {{ callback_name }}
-                  }
-              {% endif %}
-          {% endfor %}
-      {% endif %}
-
-      {% if use_builder %}
-            /**
-             * Builder for [{{ kotlin_name(structure) }}].
-             */
-            public class Builder(
-                {% for member in members %}
-                    {% if kotlin_default(member) is none %}
-                        {{ kotlin_annotation(member) }} private val {{ member.name.camelCase() }}: {{ kotlin_declaration(member) }}{{ "," if not loop.last or ns.default_count > 0 }}
+{%- set experimental = kotlin_class_optin(structure) -%}
+{%- if experimental %}
+    {{ experimental }}
+{% endif %}
+//* Generates legacy constructors for Android ABI compatibility.
+//* Omits newly added non-nullable fields to maintain stable APIs.
+{% macro render_legacy_constructors(graduated_batches, stable_members, stable_chain_children, structure) %}
+    {% if graduated_batches %}
+        {% for batch in graduated_batches %}
+            {% set batch_index = loop.index0 %}
+            {% set current_legacy_members = [] %}
+            {% for m in stable_members %}
+                {% set m_name = m.name.get() %}
+                {% set in_current_or_future_batch = namespace(val=false) %}
+                {% for future_batch in graduated_batches[batch_index:] %}
+                    {% if m_name in future_batch %}
+                        {% set in_current_or_future_batch.val = true %}
                     {% endif %}
                 {% endfor %}
+                {% if not in_current_or_future_batch.val %}
+                    {% do current_legacy_members.append(m) %}
+                {% endif %}
+            {% endfor %}
+            {% set current_legacy_chain_children = [] %}
+            {% for child in stable_chain_children %}
+                {% set child_name = child.name.get() %}
+                {% set in_current_or_future_batch = namespace(val=false) %}
+                {% for future_batch in graduated_batches[batch_index:] %}
+                    {% if child_name in future_batch %}
+                        {% set in_current_or_future_batch.val = true %}
+                    {% endif %}
+                {% endfor %}
+                {% if not in_current_or_future_batch.val %}
+                    {% do current_legacy_chain_children.append(child) %}
+                {% endif %}
+            {% endfor %}
+
+            {% set legacy_params = [] %}
+            {% for m in current_legacy_members %}
+                {% do legacy_params.append(kotlin_parameter(m, parent=structure)) %}
+            {%- endfor %}
+            {% for child in current_legacy_chain_children %}
+                {% do legacy_params.append(kotlin_chain_parameter(child, parent=structure)) %}
+            {%- endfor %}
+
+            @Deprecated(
+                message = "Hidden constructor for binary compatibility",
+                level = DeprecationLevel.HIDDEN,
+            )
+            public constructor(
+                {% for param in legacy_params %}
+                    {{ param }}{{ "," if not loop.last }}
+                {% endfor %}
+            ) : this(
+                {% for m in stable_members %}
+                    {{ m.name.camelCase() }} = {{ m.name.camelCase() if m in current_legacy_members else (kotlin_default(m) if kotlin_default(m) is not none else "null") }},
+                {% endfor %}
+                {% for child in stable_chain_children %}
+                    {{ child.name.camelCase() }} = {{ child.name.camelCase() if child in current_legacy_chain_children else "null" }},
+                {% endfor %}
+            )
+        {%- endfor %}
+    {%- endif %}
+{% endmacro %}
+//* Generates explicit `set<CallbackName>` methods.
+//* Required for Java interop when multiple callbacks exist in a struct.
+{% macro render_callbacks(members, callback_count) %}
+    {% if callback_count > 1 %}
+        {% for member in members %}
+            {% if member.name.camelCase().endswith('Callback') %}
+                {% set callback_name = member.name.camelCase() %}
+                {% set executor_name = callback_name + 'Executor' %}
+                {% set function_name = 'set' + member.name.CamelCase() %}
+                public fun {{ function_name }}(
+                    {{ executor_name }}: java.util.concurrent.Executor,
+                    {{ callback_name }}: {{ kotlin_definition(member) }}
+                ): Unit {
+                    this.{{ executor_name }} = {{ executor_name }}
+                    this.{{ callback_name }} = {{ callback_name }}
+                }
+
+            {% endif %}
+        {% endfor %}
+    {% endif %}
+{% endmacro %}
+//* Generates the Builder inner class for flexible initialization.
+{% macro render_builder(structure, members, chain_children_list, default_count, include_optins, has_experimental, chain_children) %}
+    /**
+     * Builder for [{{ kotlin_name(structure) }}].
+     */
+    public class Builder(
+        {% for member in members %}
+            {% if kotlin_default(member) is none %}
+                {% set optin_str = kotlin_member_optin(member, parent=structure, chain_children=chain_children) if include_optins else "" %}
+                {{ kotlin_annotation(member) }}{{ " " ~ optin_str if optin_str else "" }} private val {{ member.name.camelCase() }}: {{ kotlin_declaration(member) }}{{ "," if not loop.last or default_count > 0 }}
+            {% endif %}
+        {% endfor %}
+    ) {
+        {% for member in members %}
+            {% if kotlin_default(member) is not none %}
+                {% set optin_str = kotlin_member_optin(member, parent=structure, chain_children=chain_children) if include_optins else "" %}
+                {{ kotlin_annotation(member) }}{{ " " ~ optin_str if optin_str else "" }} private var {{ member.name.camelCase() }}: {{ kotlin_definition(member) }}
+            {% endif %}
+        {% endfor %}
+        {% for child in chain_children_list %}
+            {% set optin_str = kotlin_member_optin(child, parent=structure, chain_children=chain_children) if include_optins else "" %}
+            {{ optin_str ~ " " if optin_str else "" }}private var {{ child.name.camelCase() }}: {{ kotlin_name(child) }}? = null
+        {% endfor %}
+
+        {% for member in members %}
+            {% if kotlin_default(member) is not none %}
+                {% set optin_str = kotlin_member_optin(member, parent=structure, chain_children=chain_children) if include_optins else "" %}
+                {{ optin_str ~ " " if optin_str else "" }}public fun set{{ member.name.CamelCase() }}({{ kotlin_annotation(member) }} {{ member.name.camelCase() }}: {{ kotlin_declaration(member) }}): Builder = apply {
+                    this.{{ member.name.camelCase() }} = {{ member.name.camelCase() }}
+                }
+            {% endif %}
+        {% endfor %}
+        {% for child in chain_children_list %}
+            {% set optin_str = kotlin_member_optin(child, parent=structure, chain_children=chain_children) if include_optins else "" %}
+            {{ optin_str ~ " " if optin_str else "" }}public fun set{{ child.name.CamelCase() }}({{ child.name.camelCase() }}: {{ kotlin_name(child) }}?): Builder = apply {
+                this.{{ child.name.camelCase() }} = {{ child.name.camelCase() }}
+            }
+        {% endfor %}
+
+        /**
+         * Builds the [{{ kotlin_name(structure) }}].
+         */
+        {% if include_optins and has_experimental %}
+            @OptIn(ExperimentalWebGpuApi::class)
+        {% endif %}
+        public fun build(): {{ kotlin_name(structure) }} = {{ kotlin_name(structure) }}(
+            {% for member in members %}
+                {{ member.name.camelCase() }} = {{ member.name.camelCase() }},
+            {% endfor %}
+            {% for child in chain_children_list %}
+                {{ child.name.camelCase() }} = {{ child.name.camelCase() }},
+            {% endfor %}
+        )
+    }
+{% endmacro %}
+{%- if not has_experimental -%}
+    public class {{ kotlin_name(structure) }}(
+        {% for member in members %}
+            //* Generating KDocs
+            {% set member_doc = struct_info.members.get(member.name.get(), "") if struct_info and struct_info.members else "" %}
+            {% if member_doc %}
+                {{ generate_simple_kdoc(member_doc, indent_prefix = "    ", line_wrap_prefix = '\n     * ') }}
+            {% endif %}
+            {% if member.type.name.get() == "bool" %}{{ "    " }}@get:JvmName("is{{ member.name.CamelCase() }}") {% else %}{{ "    " }}{% endif %}{{ kotlin_annotation(member) }} public var {{ member.name.camelCase() }}: {{ kotlin_definition(member) }},
+        {% endfor %}
+        {% for child in chain_children_list %}
+            //* Generating KDocs
+            {% set chain_struct_doc = all_structs_info.get(child.name.get()).doc if all_structs_info.get(child.name.get()) else "" %}
+            {% if chain_struct_doc %}
+                {{ generate_simple_kdoc(chain_struct_doc, indent_prefix = "    ", line_wrap_prefix = '\n     * ') }}
+            {% endif %}
+            public var {{ child.name.camelCase() }}: {{ kotlin_name(child) }}? = null,
+        {% endfor %}
+) {
+{% set legacy_code = render_legacy_constructors(graduated_batches, stable_members, stable_chain_children, structure) -%}
+{%- if legacy_code.strip() %}
+    {{ legacy_code | indent(4, True) }}
+{%- endif -%}
+{% set callbacks_code = render_callbacks(members, ns.callback_count) -%}
+{%- if callbacks_code.strip() %}
+    {{ callbacks_code | indent(4, True) }}
+{%- endif -%}
+{% set builder_code = render_builder(structure, members, chain_children_list, ns.default_count, false, has_experimental, chain_children) -%}
+{%- if builder_code.strip() %}
+    {{ builder_code | indent(4, True) }}
+{%- endif -%}
+}
+{%- else -%}
+    public class {{ kotlin_name(structure) }} {
+        {% for member in members %}
+            //* Generating KDocs
+            {% set member_doc = struct_info.members.get(member.name.get(), "") if struct_info and struct_info.members else "" %}
+            {% if member_doc %}
+                {{ generate_simple_kdoc(member_doc, indent_prefix = "        ", line_wrap_prefix = '\n         * ') }}
+            {% endif %}
+            {{ kotlin_property(member, parent=structure) }}
+        {% endfor %}
+        {% for child in chain_children_list %}
+            //* Generating KDocs
+            {% set chain_struct_doc = all_structs_info.get(child.name.get()).doc if all_structs_info.get(child.name.get()) else "" %}
+            {% if chain_struct_doc %}
+                {{ generate_simple_kdoc(chain_struct_doc, indent_prefix = "        ", line_wrap_prefix = '\n         * ') }}
+            {% endif %}
+            {{ kotlin_chain_property(child, parent=structure) }}
+        {% endfor %}
+
+        {% set stable_params = [] -%}
+        {%- for member in stable_members -%}
+            {%- do stable_params.append(kotlin_parameter(member, parent=structure)) -%}
+        {%- endfor -%}
+        {%- for child in stable_chain_children -%}
+            {%- do stable_params.append(kotlin_chain_parameter(child, parent=structure)) -%}
+        {%- endfor -%}
+
+        {%- set all_stable_have_defaults = namespace(val=true) -%}
+        {%- for member in stable_members -%}
+            {%- if kotlin_default(member) is none -%}
+                {%- set all_stable_have_defaults.val = false -%}
+                {%- break -%}
+            {%- endif -%}
+        {% endfor %}
+        {%- if all_stable_have_defaults.val %}
+            //* Explicit no-args constructor to maintain Java binary compatibility.
+            //* Kotlin does not automatically generate one for secondary constructors.
+            public constructor() {
+                {% for member in stable_members %}
+                    this.{{ member.name.camelCase() }} = {{ kotlin_default(member) }}
+                {% endfor %}
+                {% for child in stable_chain_children %}
+                    this.{{ child.name.camelCase() }} = null
+                {% endfor %}
+            }
+        {% endif %}
+
+        public constructor(
+            {% for param in stable_params %}
+                {{ param }}{{ "," if not loop.last }}
+            {% endfor %}
+        ) {
+            {% for member in stable_members %}
+                this.{{ member.name.camelCase() }} = {{ member.name.camelCase() }}
+            {% endfor %}
+            {% for child in stable_chain_children %}
+                this.{{ child.name.camelCase() }} = {{ child.name.camelCase() }}
+            {% endfor %}
+        }
+{% set legacy_code = render_legacy_constructors(graduated_batches, stable_members, stable_chain_children, structure) -%}
+{%- if legacy_code.strip() %}
+    {{ legacy_code | indent(4, True) }}
+{%- endif -%}
+        {% if experimental_members or experimental_chain_children %}
+            {% set exp_params = [] -%}
+            {%- for member in members -%}
+                {%- set is_exp = member in experimental_members -%}
+                {%- do exp_params.append(kotlin_parameter(member, parent=structure, include_default=(not is_exp))) -%}
+            {%- endfor -%}
+            {%- for child in chain_children_list -%}
+                {%- set is_exp = child in experimental_chain_children -%}
+                {%- do exp_params.append(kotlin_chain_parameter(child, parent=structure, include_default=(not is_exp))) -%}
+            {%- endfor %}
+
+            // TODO: When stabilizing, fold the experimental parameters into the stable constructor. The previous stable constructor should be marked with @Deprecated(level = DeprecationLevel.HIDDEN) to maintain backward compatibility.
+            @ExperimentalWebGpuApi
+            public constructor(
+                {% for param in exp_params %}
+                    {{ param }}{{ "," if not loop.last }}
+                {% endfor %}
             ) {
                 {% for member in members %}
-                    {% if kotlin_default(member) is not none %}
-                        {{ kotlin_annotation(member) }} private var {{ member.name.camelCase() }}: {{ kotlin_definition(member) }}
-                    {% endif %}
+                    this.{{ member.name.camelCase() }} = {{ member.name.camelCase() }}
                 {% endfor %}
-                {% for structure in chain_children_list %}
-                    private var {{ structure.name.camelCase() }}: {{ kotlin_name(structure) }}? = null
+                {% for child in chain_children_list %}
+                    this.{{ child.name.camelCase() }} = {{ child.name.camelCase() }}
                 {% endfor %}
-
-                {% for member in members %}
-                    {% if kotlin_default(member) is not none %}
-                        public fun set{{ member.name.CamelCase() }}({{ kotlin_annotation(member) }} {{ member.name.camelCase() }}: {{ kotlin_declaration(member) }}): Builder = apply {
-                            this.{{ member.name.camelCase() }} = {{ member.name.camelCase() }}
-                        }
-                    {% endif %}
-                {% endfor %}
-                {% for structure in chain_children_list %}
-                    public fun set{{ structure.name.CamelCase() }}({{ structure.name.camelCase() }}: {{ kotlin_name(structure) }}?): Builder = apply {
-                        this.{{ structure.name.camelCase() }} = {{ structure.name.camelCase() }}
-                    }
-                {% endfor %}
-
-                /**
-                 * Builds the [{{ kotlin_name(structure) }}].
-                 */
-                public fun build(): {{ kotlin_name(structure) }} = {{ kotlin_name(structure) }}(
-                    {% for member in members %}
-                        {{ member.name.camelCase() }} = {{ member.name.camelCase() }},
-                    {% endfor %}
-                    {% for structure in chain_children_list %}
-                        {{ structure.name.camelCase() }} = {{ structure.name.camelCase() }},
-                    {% endfor %}
-                )
             }
-      {% endif %}
-    }
-{% endif %}
\ No newline at end of file
+        {% endif %}
+{% set callbacks_code = render_callbacks(members, ns.callback_count) -%}
+{% if callbacks_code.strip() %}
+
+    {{ callbacks_code | indent(8, True) }}
+{%- endif -%}
+{% set builder_code = render_builder(structure, members, chain_children_list, ns.default_count, true, has_experimental, chain_children) -%}
+{% if builder_code.strip() %}
+
+    {{ builder_code | indent(4, True) }}
+{%- endif -%}
+}
+{%- endif -%}
\ No newline at end of file
diff --git a/generator/templates/art/api_kotlin_types.kt b/generator/templates/art/api_kotlin_types.kt
index 29ba70a..0e52d8a 100644
--- a/generator/templates/art/api_kotlin_types.kt
+++ b/generator/templates/art/api_kotlin_types.kt
@@ -96,12 +96,121 @@
 
 {% macro kotlin_annotation(arg) -%}
     {%- if arg != None -%}
-        {% set type = arg.type %}
-        {% if type.category in ['bitmask', 'enum'] -%}
-            @{{ type.name.CamelCase() -}}.Type
-        {% endif -%}
-    {% endif -%}
-{% endmacro %}
+        {%- set type = arg.type -%}
+        {%- if type.category in ['bitmask', 'enum'] -%}
+            @{{ type.name.CamelCase() }}.Type
+        {%- endif -%}
+    {%- endif -%}
+{%- endmacro %}
+
+//* Helper macro to recursively scan if an API element or its dependencies are experimental.
+//* In Kotlin, stable types must never expose experimental components in their signatures
+//* without explicit warnings. This macro recursively traverses:
+//* 1. The item's direct tags (e.g. structures, objects, functions).
+//* 2. Members of structures (and their nested types).
+//* 3. Methods of objects (including return types and method arguments).
+//* 4. Constructor or function parameters.
+//* 5. Chained child structures associated with extensible structures.
+{% macro item_requires_optin(item, chain_children=None) -%}
+    {%- set ns = namespace(needs=false) -%}
+    {%- if item -%}
+        {%- if "art_experimental" in item.json_data.get("tags", []) -%}
+            {%- set ns.needs = true -%}
+        {%- endif -%}
+        {%- if not ns.needs and item.type and "art_experimental" in item.type.json_data.get("tags", []) -%}
+            {%- set ns.needs = true -%}
+        {%- endif -%}
+        {%- if not ns.needs and item.members -%}
+            {%- for member in item.members -%}
+                {%- if "art_experimental" in member.json_data.get("tags", []) or "art_experimental" in member.type.json_data.get("tags", []) -%}
+                    {%- set ns.needs = true -%}
+                    {%- break -%}
+                {%- endif -%}
+            {%- endfor -%}
+        {%- endif -%}
+        {%- if not ns.needs and item.methods -%}
+            {%- for method in item.methods -%}
+                {%- if "art_experimental" in method.json_data.get("tags", []) -%}
+                    {%- set ns.needs = true -%}
+                    {%- break -%}
+                {%- endif -%}
+                {%- if method.returns and ("art_experimental" in method.returns.json_data.get("tags", []) or "art_experimental" in method.returns.type.json_data.get("tags", [])) -%}
+                    {%- set ns.needs = true -%}
+                    {%- break -%}
+                {%- endif -%}
+                {%- for arg in method.arguments -%}
+                    {%- if "art_experimental" in arg.type.json_data.get("tags", []) or "art_experimental" in arg.json_data.get("tags", []) -%}
+                        {%- set ns.needs = true -%}
+                        {%- break -%}
+                    {%- endif -%}
+                {%- endfor -%}
+                {%- if ns.needs -%}
+                    {%- break -%}
+                {%- endif -%}
+            {%- endfor -%}
+        {%- endif -%}
+        {%- if not ns.needs and item.arguments -%}
+            {%- for arg in item.arguments -%}
+                {%- if "art_experimental" in arg.json_data.get("tags", []) or "art_experimental" in arg.type.json_data.get("tags", []) -%}
+                    {%- set ns.needs = true -%}
+                    {%- break -%}
+                {%- endif -%}
+            {%- endfor -%}
+        {%- endif -%}
+        {%- if not ns.needs and item.returns -%}
+            {%- if "art_experimental" in item.returns.json_data.get("tags", []) or "art_experimental" in item.returns.type.json_data.get("tags", []) -%}
+                {%- set ns.needs = true -%}
+            {%- endif -%}
+        {%- endif -%}
+        {%- if not ns.needs and chain_children and item.name.get() in chain_children -%}
+            {%- for child in chain_children[item.name.get()] -%}
+                {%- if "art_experimental" in child.json_data.get("tags", []) -%}
+                    {%- set ns.needs = true -%}
+                    {%- break -%}
+                {%- endif -%}
+            {%- endfor -%}
+        {%- endif -%}
+    {%- endif -%}
+    {{- ns.needs -}}
+{%- endmacro %}
+
+//* Helper macro to check if a top-level API element is directly tagged as experimental.
+//* This indicates that the entire WebGPU object or type is considered unstable.
+//* It also checks if any required member is experimental, forcing the class to be experimental
+//* to enable the primary constructor flow and prevent initialization errors.
+{% macro item_is_experimental(item) -%}
+    {%- set ns = namespace(val=false) -%}
+    {%- if item and "art_experimental" in item.json_data.get("tags", []) -%}
+        {%- set ns.val = true -%}
+    {%- elif item and item.members -%}
+        {%- for member in item.members -%}
+            {%- if item_requires_optin(member) == 'True' and kotlin_default(member) is none -%}
+                {%- set ns.val = true -%}
+                {%- break -%}
+            {%- endif -%}
+        {%- endfor -%}
+    {%- endif -%}
+    {{- ns.val -}}
+{%- endmacro %}
+
+//* Injects @ExperimentalWebGpuApi annotation on class declarations.
+//* This requires consumers to opt-in to the entire namespace of an experimental class or enum.
+{% macro kotlin_class_optin(item) -%}
+    {%- if item_is_experimental(item) == 'True' -%}
+        @ExperimentalWebGpuApi
+    {%- endif -%}
+{%- endmacro %}
+
+//* Injects @ExperimentalWebGpuApi annotation on methods, functions, or members.
+//* Ensures consumers are warned when using stable classes that expose experimental operations.
+{% macro kotlin_member_optin(item, parent=None, chain_children=None) -%}
+    {%- if parent and item_is_experimental(parent) == 'True' -%}
+        {# Containing type is already experimental; members inherit this, so skip redundant annotation #}
+    {%- elif item_requires_optin(item, chain_children) == 'True' -%}
+        @ExperimentalWebGpuApi
+    {%- endif -%}
+{%- endmacro %}
+
 
 {% macro do_all_args_have_doc(all_arg_docs, code_args) %}
     //* Ensure inputs are not None
@@ -187,3 +296,47 @@
     {{indent_prefix}} * https://issuetracker.google.com/issues/new?component=1960262
     {{indent_prefix}} */
 {%- endmacro -%}
+
+//* Generates a Kotlin constructor parameter with annotations and default values for a structure member.
+{% macro kotlin_parameter(member, parent, include_default=True) -%}
+    {%- set annotations = [] -%}
+    {%- set annot = kotlin_annotation(member) | trim -%}
+    {%- if annot -%}
+        {%- do annotations.append(annot) -%}
+    {%- endif -%}
+    {%- set annotations_str = annotations | join(' ') -%}
+    {{- annotations_str + ' ' if annotations_str -}}{{- member.name.camelCase() -}}: {{ kotlin_definition(member) if include_default else kotlin_declaration(member) -}}
+{%- endmacro -%}
+
+//* Generates a nullable Kotlin constructor parameter for an extensible chain child structure.
+{% macro kotlin_chain_parameter(child, parent, include_default=True) -%}
+    {{- child.name.camelCase() -}}: {{ kotlin_name(child) }}?{{ ' = null' if include_default else '' }}
+{%- endmacro -%}
+
+//* Generates a public var property declaration for a structure member, handling annotations and opt-ins.
+{% macro kotlin_property(member, parent) -%}
+    {%- set annotations = [] -%}
+    {%- if member.type.name.get() == "bool" -%}
+        {%- do annotations.append('@get:JvmName("is' + member.name.CamelCase() + '")') -%}
+    {%- endif -%}
+    {%- set annot = kotlin_annotation(member) | trim -%}
+    {%- if annot -%}
+        {%- do annotations.append(annot) -%}
+    {%- endif -%}
+    {%- set optin = kotlin_member_optin(member, parent=parent, chain_children=chain_children) | trim -%}
+    {%- if optin -%}
+        {%- do annotations.append(optin) -%}
+    {%- endif -%}
+    {%- set annotations_str = annotations | join(' ') -%}
+    {%- set is_experimental = (item_is_experimental(parent) != 'True') and (item_requires_optin(member) == 'True') -%}
+    {{- annotations_str + ' ' if annotations_str -}}public var {{ member.name.camelCase() -}}: {{ kotlin_definition(member) if is_experimental else kotlin_declaration(member) -}}
+{%- endmacro -%}
+
+//* Generates a public var property declaration for an extensible chain child structure.
+{% macro kotlin_chain_property(child, parent) -%}
+    {%- set optin = kotlin_member_optin(child, parent=parent, chain_children=chain_children) | trim -%}
+    {%- set is_experimental = (item_is_experimental(parent) != 'True') and (item_requires_optin(child) == 'True') -%}
+    {{- optin + ' ' if optin -}}public var {{ child.name.camelCase() }}: {{ kotlin_name(child) }}?{{ ' = null' if is_experimental }}
+{%- endmacro -%}
+
+
diff --git a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/GPUHardwareBufferTest.kt b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/GPUHardwareBufferTest.kt
index bd03d02..e04a23b 100644
--- a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/GPUHardwareBufferTest.kt
+++ b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/GPUHardwareBufferTest.kt
@@ -46,6 +46,7 @@
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
+@OptIn(ExperimentalWebGpuApi::class)
 class GPUHardwareBufferTest {
 
     companion object {
diff --git a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/GPUSyncFenceTest.kt b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/GPUSyncFenceTest.kt
index a00108a..8de8114 100644
--- a/tools/android/webgpu/src/androidTest/java/androidx/webgpu/GPUSyncFenceTest.kt
+++ b/tools/android/webgpu/src/androidTest/java/androidx/webgpu/GPUSyncFenceTest.kt
@@ -43,6 +43,7 @@
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
+@OptIn(ExperimentalWebGpuApi::class)
 class GPUSyncFenceTest {
 
     companion object {
diff --git a/tools/android/webgpu/src/main/java/androidx/webgpu/ExperimentalWebGpuApi.kt b/tools/android/webgpu/src/main/java/androidx/webgpu/ExperimentalWebGpuApi.kt
new file mode 100644
index 0000000..286eb78
--- /dev/null
+++ b/tools/android/webgpu/src/main/java/androidx/webgpu/ExperimentalWebGpuApi.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2026 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webgpu
+
+import kotlin.RequiresOptIn
+import kotlin.annotation.AnnotationRetention
+import kotlin.annotation.Retention
+import kotlin.annotation.Target
+
+@RequiresOptIn(level = RequiresOptIn.Level.ERROR, message = "This WebGPU API is experimental and subject to change.")
+@Retention(AnnotationRetention.BINARY)
+@Target(
+    AnnotationTarget.CLASS,
+    AnnotationTarget.FUNCTION,
+    AnnotationTarget.PROPERTY,
+    AnnotationTarget.FIELD,
+    AnnotationTarget.CONSTRUCTOR
+)
+public annotation class ExperimentalWebGpuApi
diff --git a/tools/android/webgpu/src/main/java/androidx/webgpu/helper/GPUAndroidHardwareBufferUtil.kt b/tools/android/webgpu/src/main/java/androidx/webgpu/helper/GPUAndroidHardwareBufferUtil.kt
index 8b961ed..54994fc 100644
--- a/tools/android/webgpu/src/main/java/androidx/webgpu/helper/GPUAndroidHardwareBufferUtil.kt
+++ b/tools/android/webgpu/src/main/java/androidx/webgpu/helper/GPUAndroidHardwareBufferUtil.kt
@@ -6,9 +6,11 @@
 import androidx.webgpu.GPUDevice
 import androidx.webgpu.GPUExternalTextureDescriptor
 import androidx.webgpu.GPUHardwareBufferExternalTexture
+import androidx.webgpu.ExperimentalWebGpuApi
 import androidx.webgpu.GPUHardwareBufferTexture
 
 @RequiresApi(Build.VERSION_CODES.O)
+@OptIn(ExperimentalWebGpuApi::class)
 public object GPUAndroidHardwareBufferUtil {
 
   public fun createTexture(
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 6ff46b3..e825d51 100644
--- a/tools/android/webgpu/src/test/java/androidx/webgpu/MappedNamedConstantsTest.kt
+++ b/tools/android/webgpu/src/test/java/androidx/webgpu/MappedNamedConstantsTest.kt
@@ -34,6 +34,7 @@
     }
 }
 
+@OptIn(ExperimentalWebGpuApi::class)
 class MappedNamedConstantsTest {
 
     val TYPES_WITH_MAPPED_NAMED_CONSTANTS = arrayOf(