Implement push_constant-based firstIndex transform.
This transform offsets the vertex_index and/or instance_index builtin
variables by a user-supplied first_vertex and first_instance offset.
This is required for shading languages that use zero-based first_*
variables such as GLSL and HLSL.
Also, GLSL requires push constants to be wrapped in a struct, and the
struct to be emitted, but not emitted as an interface block. Thus,
change the "skip_push_constants" flag in the AddBlockAttribute
to "push_constants_wrap_only", which causes push constants to be
wrapped in a struct, but to not have the BlockAttribute added.
Bug: tint:2140
Change-Id: I7ce7bb67b58efa110e91fc38d840e674adaf9d0b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/169461
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Stephen White <senorblanco@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/dawn/native/RenderPipeline.cpp b/src/dawn/native/RenderPipeline.cpp
index 666f6bc..eaecd18 100644
--- a/src/dawn/native/RenderPipeline.cpp
+++ b/src/dawn/native/RenderPipeline.cpp
@@ -974,6 +974,11 @@
mUsesFragDepth = GetStage(SingleShaderStage::Fragment).metadata->usesFragDepth;
}
+ if (HasStage(SingleShaderStage::Vertex)) {
+ mUsesVertexIndex = GetStage(SingleShaderStage::Vertex).metadata->usesVertexIndex;
+ mUsesInstanceIndex = GetStage(SingleShaderStage::Vertex).metadata->usesInstanceIndex;
+ }
+
SetContentHash(ComputeContentHash());
GetObjectTrackingList()->Track(this);
@@ -1168,6 +1173,16 @@
return mUsesFragDepth;
}
+bool RenderPipelineBase::UsesVertexIndex() const {
+ DAWN_ASSERT(!IsError());
+ return mUsesVertexIndex;
+}
+
+bool RenderPipelineBase::UsesInstanceIndex() const {
+ DAWN_ASSERT(!IsError());
+ return mUsesInstanceIndex;
+}
+
size_t RenderPipelineBase::ComputeContentHash() {
ObjectContentHasher recorder;
diff --git a/src/dawn/native/RenderPipeline.h b/src/dawn/native/RenderPipeline.h
index ee16c51..987eae5 100644
--- a/src/dawn/native/RenderPipeline.h
+++ b/src/dawn/native/RenderPipeline.h
@@ -128,6 +128,8 @@
bool WritesDepth() const;
bool WritesStencil() const;
bool UsesFragDepth() const;
+ bool UsesVertexIndex() const;
+ bool UsesInstanceIndex() const;
const AttachmentState* GetAttachmentState() const;
@@ -168,6 +170,8 @@
bool mWritesDepth = false;
bool mWritesStencil = false;
bool mUsesFragDepth = false;
+ bool mUsesVertexIndex = false;
+ bool mUsesInstanceIndex = false;
};
} // namespace dawn::native
diff --git a/src/dawn/native/opengl/CommandBufferGL.cpp b/src/dawn/native/opengl/CommandBufferGL.cpp
index 745e5c2..578c13b 100644
--- a/src/dawn/native/opengl/CommandBufferGL.cpp
+++ b/src/dawn/native/opengl/CommandBufferGL.cpp
@@ -1123,6 +1123,10 @@
vertexStateBufferBindingTracker.Apply(gl);
bindGroupTracker.Apply(gl);
+ if (lastPipeline->UsesInstanceIndex()) {
+ gl.Uniform1ui(PipelineLayout::PushConstantLocation::FirstInstance,
+ draw->firstInstance);
+ }
if (gl.DrawArraysInstancedBaseInstanceANGLE) {
gl.DrawArraysInstancedBaseInstanceANGLE(
lastPipeline->GetGLPrimitiveTopology(), draw->firstVertex,
@@ -1145,6 +1149,10 @@
vertexStateBufferBindingTracker.Apply(gl);
bindGroupTracker.Apply(gl);
+ if (lastPipeline->UsesInstanceIndex()) {
+ gl.Uniform1ui(PipelineLayout::PushConstantLocation::FirstInstance,
+ draw->firstInstance);
+ }
if (gl.DrawElementsInstancedBaseVertexBaseInstanceANGLE) {
gl.DrawElementsInstancedBaseVertexBaseInstanceANGLE(
lastPipeline->GetGLPrimitiveTopology(), draw->indexCount, indexBufferFormat,
@@ -1181,6 +1189,9 @@
case Command::DrawIndirect: {
DrawIndirectCmd* draw = iter->NextCommand<DrawIndirectCmd>();
+ if (lastPipeline->UsesInstanceIndex()) {
+ gl.Uniform1ui(PipelineLayout::PushConstantLocation::FirstInstance, 0);
+ }
vertexStateBufferBindingTracker.Apply(gl);
bindGroupTracker.Apply(gl);
@@ -1198,6 +1209,9 @@
case Command::DrawIndexedIndirect: {
DrawIndexedIndirectCmd* draw = iter->NextCommand<DrawIndexedIndirectCmd>();
+ if (lastPipeline->UsesInstanceIndex()) {
+ gl.Uniform1ui(PipelineLayout::PushConstantLocation::FirstInstance, 0);
+ }
vertexStateBufferBindingTracker.Apply(gl);
bindGroupTracker.Apply(gl);
diff --git a/src/dawn/native/opengl/PipelineLayoutGL.h b/src/dawn/native/opengl/PipelineLayoutGL.h
index eb4863b..e2ab802 100644
--- a/src/dawn/native/opengl/PipelineLayoutGL.h
+++ b/src/dawn/native/opengl/PipelineLayoutGL.h
@@ -55,6 +55,10 @@
GLuint GetInternalUniformBinding() const;
+ enum PushConstantLocation {
+ FirstInstance = 0,
+ };
+
private:
~PipelineLayout() override = default;
BindingIndexInfo mIndexInfo;
diff --git a/src/dawn/native/opengl/ShaderModuleGL.cpp b/src/dawn/native/opengl/ShaderModuleGL.cpp
index 0ce3792..821a350 100644
--- a/src/dawn/native/opengl/ShaderModuleGL.cpp
+++ b/src/dawn/native/opengl/ShaderModuleGL.cpp
@@ -418,6 +418,9 @@
/* fullSubgroups */ {}));
}
+ r.tintOptions.first_instance_offset =
+ 4 * PipelineLayout::PushConstantLocation::FirstInstance;
+
auto result = tint::glsl::writer::Generate(program, r.tintOptions, remappedEntryPoint);
DAWN_INVALID_IF(result != tint::Success, "An error occurred while generating GLSL:\n%s",
result.Failure().reason.str());
diff --git a/src/dawn/tests/end2end/DepthStencilCopyTests.cpp b/src/dawn/tests/end2end/DepthStencilCopyTests.cpp
index 162defa..dd0ca02 100644
--- a/src/dawn/tests/end2end/DepthStencilCopyTests.cpp
+++ b/src/dawn/tests/end2end/DepthStencilCopyTests.cpp
@@ -1024,8 +1024,9 @@
// glDrawArraysInstancedBaseInstance.
DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES() && !IsANGLE());
- // TODO(crbug.com/dawn/2202): Failing on ANGLE/D3D11 for unknown reasons.
- DAWN_TEST_UNSUPPORTED_IF(IsANGLED3D11());
+ // TODO(crbug.com/dawn/2202): Failing on ANGLE/SwiftShader due to undiagnosed ANGLE bug.
+ // Passes on ANGLE/D3D11.
+ DAWN_TEST_UNSUPPORTED_IF(IsANGLESwiftShader());
// Create a stencil texture
constexpr uint32_t kWidth = 4;
diff --git a/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc b/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
index f1831e9..1a66806 100644
--- a/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
@@ -67,6 +67,7 @@
#include "src/tint/lang/wgsl/ast/transform/expand_compound_assignment.h"
#include "src/tint/lang/wgsl/ast/transform/manager.h"
#include "src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.h"
+#include "src/tint/lang/wgsl/ast/transform/offset_first_index.h"
#include "src/tint/lang/wgsl/ast/transform/preserve_padding.h"
#include "src/tint/lang/wgsl/ast/transform/promote_initializers_to_let.h"
#include "src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl.h"
@@ -152,8 +153,6 @@
data.Add<ast::transform::SingleEntryPoint::Config>(entry_point);
}
- data.Add<ast::transform::AddBlockAttribute::Config>(true);
-
manager.Add<ast::transform::PreservePadding>(); // Must come before DirectVariableAccess
manager.Add<ast::transform::Unshadow>(); // Must come before DirectVariableAccess
@@ -203,6 +202,8 @@
manager.Add<ast::transform::ZeroInitWorkgroupMemory>();
}
+ manager.Add<ast::transform::OffsetFirstIndex>();
+
// CanonicalizeEntryPointIO must come after Robustness
manager.Add<ast::transform::CanonicalizeEntryPointIO>();
@@ -245,6 +246,8 @@
data.Add<ast::transform::CanonicalizeEntryPointIO::Config>(
ast::transform::CanonicalizeEntryPointIO::ShaderStyle::kGlsl);
+ data.Add<ast::transform::OffsetFirstIndex::Config>(std::nullopt, options.first_instance_offset);
+
SanitizedResult result;
ast::transform::DataMap outputs;
result.program = manager.Run(in, data, outputs);
@@ -297,7 +300,8 @@
}
bool is_block =
ast::HasAttribute<ast::transform::AddBlockAttribute::BlockAttribute>(
- str->attributes);
+ str->attributes) &&
+ !sem->UsedAs(core::AddressSpace::kPushConstant);
if (!has_rt_arr && !is_block) {
EmitStructType(current_buffer_, sem);
}
@@ -2093,6 +2097,7 @@
auto name = decl->name->symbol.Name();
auto* type = var->Type()->UnwrapRef();
+ out << "layout(location=0) ";
EmitTypeAndName(out, type, var->AddressSpace(), var->Access(), name);
out << ";";
}
diff --git a/src/tint/lang/glsl/writer/common/options.h b/src/tint/lang/glsl/writer/common/options.h
index c97026e..1f5298f 100644
--- a/src/tint/lang/glsl/writer/common/options.h
+++ b/src/tint/lang/glsl/writer/common/options.h
@@ -28,6 +28,7 @@
#ifndef SRC_TINT_LANG_GLSL_WRITER_COMMON_OPTIONS_H_
#define SRC_TINT_LANG_GLSL_WRITER_COMMON_OPTIONS_H_
+#include <optional>
#include <string>
#include <unordered_map>
@@ -78,6 +79,9 @@
/// Options used in the binding mappings for external textures
ExternalTextureOptions external_texture_options = {};
+ /// Offset of the firstInstance push constant.
+ std::optional<int32_t> first_instance_offset;
+
/// Options used to map WGSL textureNumLevels/textureNumSamples builtins to internal uniform
/// buffer values. If not specified, emits corresponding GLSL builtins
/// textureQueryLevels/textureSamples directly.
diff --git a/src/tint/lang/wgsl/ast/transform/BUILD.bazel b/src/tint/lang/wgsl/ast/transform/BUILD.bazel
index 4b1340a..6229a94 100644
--- a/src/tint/lang/wgsl/ast/transform/BUILD.bazel
+++ b/src/tint/lang/wgsl/ast/transform/BUILD.bazel
@@ -56,6 +56,7 @@
"hoist_to_decl_before.cc",
"manager.cc",
"multiplanar_external_texture.cc",
+ "offset_first_index.cc",
"preserve_padding.cc",
"promote_initializers_to_let.cc",
"promote_side_effects_to_decl.cc",
@@ -91,6 +92,7 @@
"hoist_to_decl_before.h",
"manager.h",
"multiplanar_external_texture.h",
+ "offset_first_index.h",
"preserve_padding.h",
"promote_initializers_to_let.h",
"promote_side_effects_to_decl.h",
@@ -159,6 +161,7 @@
"hoist_to_decl_before_test.cc",
"manager_test.cc",
"multiplanar_external_texture_test.cc",
+ "offset_first_index_test.cc",
"preserve_padding_test.cc",
"promote_initializers_to_let_test.cc",
"promote_side_effects_to_decl_test.cc",
diff --git a/src/tint/lang/wgsl/ast/transform/BUILD.cmake b/src/tint/lang/wgsl/ast/transform/BUILD.cmake
index 11ddfaa..068369c 100644
--- a/src/tint/lang/wgsl/ast/transform/BUILD.cmake
+++ b/src/tint/lang/wgsl/ast/transform/BUILD.cmake
@@ -73,6 +73,8 @@
lang/wgsl/ast/transform/manager.h
lang/wgsl/ast/transform/multiplanar_external_texture.cc
lang/wgsl/ast/transform/multiplanar_external_texture.h
+ lang/wgsl/ast/transform/offset_first_index.cc
+ lang/wgsl/ast/transform/offset_first_index.h
lang/wgsl/ast/transform/preserve_padding.cc
lang/wgsl/ast/transform/preserve_padding.h
lang/wgsl/ast/transform/promote_initializers_to_let.cc
@@ -159,6 +161,7 @@
lang/wgsl/ast/transform/hoist_to_decl_before_test.cc
lang/wgsl/ast/transform/manager_test.cc
lang/wgsl/ast/transform/multiplanar_external_texture_test.cc
+ lang/wgsl/ast/transform/offset_first_index_test.cc
lang/wgsl/ast/transform/preserve_padding_test.cc
lang/wgsl/ast/transform/promote_initializers_to_let_test.cc
lang/wgsl/ast/transform/promote_side_effects_to_decl_test.cc
diff --git a/src/tint/lang/wgsl/ast/transform/BUILD.gn b/src/tint/lang/wgsl/ast/transform/BUILD.gn
index 4632240..b94227e 100644
--- a/src/tint/lang/wgsl/ast/transform/BUILD.gn
+++ b/src/tint/lang/wgsl/ast/transform/BUILD.gn
@@ -78,6 +78,8 @@
"manager.h",
"multiplanar_external_texture.cc",
"multiplanar_external_texture.h",
+ "offset_first_index.cc",
+ "offset_first_index.h",
"preserve_padding.cc",
"preserve_padding.h",
"promote_initializers_to_let.cc",
@@ -160,6 +162,7 @@
"hoist_to_decl_before_test.cc",
"manager_test.cc",
"multiplanar_external_texture_test.cc",
+ "offset_first_index_test.cc",
"preserve_padding_test.cc",
"promote_initializers_to_let_test.cc",
"promote_side_effects_to_decl_test.cc",
diff --git a/src/tint/lang/wgsl/ast/transform/add_block_attribute.cc b/src/tint/lang/wgsl/ast/transform/add_block_attribute.cc
index 607bf54..82e0716 100644
--- a/src/tint/lang/wgsl/ast/transform/add_block_attribute.cc
+++ b/src/tint/lang/wgsl/ast/transform/add_block_attribute.cc
@@ -39,7 +39,6 @@
TINT_INSTANTIATE_TYPEINFO(tint::ast::transform::AddBlockAttribute);
TINT_INSTANTIATE_TYPEINFO(tint::ast::transform::AddBlockAttribute::BlockAttribute);
-TINT_INSTANTIATE_TYPEINFO(tint::ast::transform::AddBlockAttribute::Config);
namespace tint::ast::transform {
@@ -48,13 +47,12 @@
AddBlockAttribute::~AddBlockAttribute() = default;
Transform::ApplyResult AddBlockAttribute::Apply(const Program& src,
- const DataMap& inputs,
+ const DataMap&,
DataMap&) const {
ProgramBuilder b;
program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
auto& sem = src.Sem();
- auto* cfg = inputs.Get<Config>();
// A map from a type in the source program to a block-decorated wrapper that contains it in the
// destination program.
@@ -82,11 +80,6 @@
bool needs_wrapping = !str || // Type is not a structure
str->HasFixedFootprint(); // Struct has a fixed footprint
- if (cfg && cfg->skip_push_constants &&
- var->AddressSpace() == core::AddressSpace::kPushConstant) {
- continue;
- }
-
if (needs_wrapping) {
const char* kMemberName = "inner";
@@ -136,8 +129,4 @@
ctx.dst->AllocateNodeID());
}
-AddBlockAttribute::Config::Config(bool skip_push_consts) : skip_push_constants(skip_push_consts) {}
-
-AddBlockAttribute::Config::~Config() = default;
-
} // namespace tint::ast::transform
diff --git a/src/tint/lang/wgsl/ast/transform/add_block_attribute.h b/src/tint/lang/wgsl/ast/transform/add_block_attribute.h
index 61a884d..544e1ee 100644
--- a/src/tint/lang/wgsl/ast/transform/add_block_attribute.h
+++ b/src/tint/lang/wgsl/ast/transform/add_block_attribute.h
@@ -66,19 +66,6 @@
/// Destructor
~AddBlockAttribute() override;
- /// Transform configuration options
- struct Config final : public Castable<Config, ast::transform::Data> {
- /// Constructor
- /// @param skip_push_const whether to skip push constants
- explicit Config(bool skip_push_const);
-
- /// Destructor
- ~Config() override;
-
- /// Whether to skip push constants
- bool skip_push_constants;
- };
-
/// @copydoc Transform::Apply
ApplyResult Apply(const Program& program,
const DataMap& inputs,
diff --git a/src/tint/lang/wgsl/ast/transform/offset_first_index.cc b/src/tint/lang/wgsl/ast/transform/offset_first_index.cc
new file mode 100644
index 0000000..eb64f60
--- /dev/null
+++ b/src/tint/lang/wgsl/ast/transform/offset_first_index.cc
@@ -0,0 +1,201 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/wgsl/ast/transform/offset_first_index.h"
+
+#include <memory>
+#include <unordered_map>
+#include <utility>
+
+#include "src/tint/lang/core/builtin_value.h"
+#include "src/tint/lang/core/fluent_types.h"
+#include "src/tint/lang/wgsl/program/clone_context.h"
+#include "src/tint/lang/wgsl/program/program_builder.h"
+#include "src/tint/lang/wgsl/resolver/resolve.h"
+#include "src/tint/lang/wgsl/sem/function.h"
+#include "src/tint/lang/wgsl/sem/member_accessor_expression.h"
+#include "src/tint/lang/wgsl/sem/struct.h"
+#include "src/tint/lang/wgsl/sem/variable.h"
+
+using namespace tint::core::fluent_types; // NOLINT
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::transform::OffsetFirstIndex);
+TINT_INSTANTIATE_TYPEINFO(tint::ast::transform::OffsetFirstIndex::Config);
+
+namespace tint::ast::transform {
+namespace {
+
+// Push constant names
+constexpr char kFirstVertexName[] = "first_vertex";
+constexpr char kFirstInstanceName[] = "first_instance";
+
+bool ShouldRun(const Program& program) {
+ for (auto* fn : program.AST().Functions()) {
+ if (fn->PipelineStage() == PipelineStage::kVertex) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace
+
+OffsetFirstIndex::OffsetFirstIndex() = default;
+OffsetFirstIndex::~OffsetFirstIndex() = default;
+
+Transform::ApplyResult OffsetFirstIndex::Apply(const Program& src,
+ const DataMap& inputs,
+ DataMap&) const {
+ if (!ShouldRun(src)) {
+ return SkipTransform;
+ }
+
+ const Config* cfg = inputs.Get<Config>();
+ if (!cfg) {
+ return SkipTransform;
+ }
+
+ ProgramBuilder b;
+ program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
+
+ // Map of builtin usages
+ std::unordered_map<const sem::Variable*, const char*> builtin_vars;
+ std::unordered_map<const core::type::StructMember*, const char*> builtin_members;
+
+ bool has_vertex_index = false;
+ bool has_instance_index = false;
+
+ // Traverse the AST scanning for builtin accesses via variables (includes
+ // parameters) or structure member accesses.
+ for (auto* node : ctx.src->ASTNodes().Objects()) {
+ if (auto* var = node->As<Variable>()) {
+ for (auto* attr : var->attributes) {
+ if (auto* builtin_attr = attr->As<BuiltinAttribute>()) {
+ core::BuiltinValue builtin = src.Sem().Get(builtin_attr)->Value();
+ if (builtin == core::BuiltinValue::kVertexIndex && cfg &&
+ cfg->first_vertex_offset.has_value()) {
+ auto* sem_var = ctx.src->Sem().Get(var);
+ builtin_vars.emplace(sem_var, kFirstVertexName);
+ has_vertex_index = true;
+ }
+ if (builtin == core::BuiltinValue::kInstanceIndex && cfg &&
+ cfg->first_instance_offset.has_value()) {
+ auto* sem_var = ctx.src->Sem().Get(var);
+ builtin_vars.emplace(sem_var, kFirstInstanceName);
+ has_instance_index = true;
+ }
+ }
+ }
+ }
+ if (auto* member = node->As<StructMember>()) {
+ for (auto* attr : member->attributes) {
+ if (auto* builtin_attr = attr->As<BuiltinAttribute>()) {
+ core::BuiltinValue builtin = src.Sem().Get(builtin_attr)->Value();
+ if (builtin == core::BuiltinValue::kVertexIndex && cfg &&
+ cfg->first_vertex_offset.has_value()) {
+ auto* sem_mem = ctx.src->Sem().Get(member);
+ builtin_members.emplace(sem_mem, kFirstVertexName);
+ has_vertex_index = true;
+ }
+ if (builtin == core::BuiltinValue::kInstanceIndex && cfg &&
+ cfg->first_instance_offset.has_value()) {
+ auto* sem_mem = ctx.src->Sem().Get(member);
+ builtin_members.emplace(sem_mem, kFirstInstanceName);
+ has_instance_index = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (!has_vertex_index && !has_instance_index) {
+ return SkipTransform;
+ }
+
+ // Abort on any use of push constants in the module.
+ for (auto* global : src.AST().GlobalVariables()) {
+ if (auto* var = global->As<ast::Var>()) {
+ auto* v = src.Sem().Get(var);
+ if (TINT_UNLIKELY(v->AddressSpace() == core::AddressSpace::kPushConstant)) {
+ TINT_ICE()
+ << "OffsetFirstIndex doesn't know how to handle module that already use push "
+ "constants (yet)";
+ return resolver::Resolve(b);
+ }
+ }
+ }
+
+ b.Enable(wgsl::Extension::kChromiumExperimentalPushConstant);
+
+ // Add push constant members and calculate byte offsets
+ tint::Vector<const StructMember*, 8> members;
+ if (has_vertex_index) {
+ members.Push(b.Member(kFirstVertexName, b.ty.u32(),
+ Vector{b.MemberOffset(AInt(*cfg->first_vertex_offset))}));
+ }
+ if (has_instance_index) {
+ members.Push(b.Member(kFirstInstanceName, b.ty.u32(),
+ Vector{b.MemberOffset(AInt(*cfg->first_instance_offset))}));
+ }
+ auto struct_ = b.Structure(b.Symbols().New("PushConstants"), std::move(members));
+ // Create a global to hold the uniform buffer
+ Symbol buffer_name = b.Symbols().New("push_constants");
+ b.GlobalVar(buffer_name, b.ty.Of(struct_), core::AddressSpace::kPushConstant);
+
+ // Fix up all references to the builtins with the offsets
+ ctx.ReplaceAll([&](const Expression* expr) -> const Expression* {
+ if (auto* sem = ctx.src->Sem().GetVal(expr)) {
+ if (auto* user = sem->UnwrapLoad()->As<sem::VariableUser>()) {
+ auto it = builtin_vars.find(user->Variable());
+ if (it != builtin_vars.end()) {
+ return ctx.dst->Add(ctx.CloneWithoutTransform(expr),
+ ctx.dst->MemberAccessor(buffer_name, it->second));
+ }
+ }
+ if (auto* access = sem->As<sem::StructMemberAccess>()) {
+ auto it = builtin_members.find(access->Member());
+ if (it != builtin_members.end()) {
+ return ctx.dst->Add(ctx.CloneWithoutTransform(expr),
+ ctx.dst->MemberAccessor(buffer_name, it->second));
+ }
+ }
+ }
+ // Not interested in this expression. Just clone.
+ return nullptr;
+ });
+
+ ctx.Clone();
+ return resolver::Resolve(b);
+}
+
+OffsetFirstIndex::Config::Config(std::optional<int32_t> first_vertex_off,
+ std::optional<int32_t> first_instance_off)
+ : first_vertex_offset(first_vertex_off), first_instance_offset(first_instance_off) {}
+
+OffsetFirstIndex::Config::~Config() = default;
+
+} // namespace tint::ast::transform
diff --git a/src/tint/lang/wgsl/ast/transform/offset_first_index.h b/src/tint/lang/wgsl/ast/transform/offset_first_index.h
new file mode 100644
index 0000000..aa37559
--- /dev/null
+++ b/src/tint/lang/wgsl/ast/transform/offset_first_index.h
@@ -0,0 +1,111 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_LANG_WGSL_AST_TRANSFORM_OFFSET_FIRST_INDEX_H_
+#define SRC_TINT_LANG_WGSL_AST_TRANSFORM_OFFSET_FIRST_INDEX_H_
+
+#include "src/tint/lang/wgsl/ast/transform/transform.h"
+
+namespace tint::ast::transform {
+
+/// Adds firstVertex/Instance (injected via push constants) to
+/// vertex/instance index builtins.
+///
+/// This transform assumes that Name transform has been run before.
+///
+/// Some shading languages start vertex and instance numbering at 0,
+/// regardless of the firstVertex/firstInstance value specified. This transform
+/// adds the value of firstVertex/firstInstance to each builtin. This action is
+/// performed by adding a new push constant equal to original builtin +
+/// firstVertex/firstInstance to each function that references one of
+/// these builtins.
+///
+/// For D3D, this affects both firstVertex and firstInstance. For OpenGL,
+/// it applies to only firstInstance. For this reason, the first_vertex_offset
+/// and first_instance_offset are optional, where std::nullopt indicates that
+/// no subsitution is to be performed.
+///
+/// Before:
+/// ```
+/// @builtin(vertex_index) var<in> vert_idx : u32;
+/// @builtin(instance_index) var<in> inst_idx : u32;
+/// fn func() -> u32 {
+/// return vert_idx * inst_idx;
+/// }
+/// ```
+///
+/// After:
+/// ```
+/// struct PushConstants {
+/// first_vertex : u32,
+/// first_instance : u32,
+/// }
+///
+/// var<push_constant> push_constants : PushConstants;
+///
+/// @builtin(vertex_index) var<in> vert_idx : u32;
+/// @builtin(instance_index) var<in> inst_idx : u32;
+/// fn func() -> u32 {
+/// return (vert_idx + push_constants.first_vertex) * (inst_idx +
+/// push_constants.first_instance);
+/// }
+/// ```
+///
+class OffsetFirstIndex final : public Castable<OffsetFirstIndex, Transform> {
+ public:
+ /// Transform configuration options
+ struct Config final : public Castable<Config, ast::transform::Data> {
+ /// Constructor
+ /// @param first_vertex_off Offset of the firstVertex push constant
+ /// @param first_instance_off Location of the firstInstance push constant
+ Config(std::optional<int32_t> first_vertex_off, std::optional<int32_t> first_instance_off);
+
+ /// Destructor
+ ~Config() override;
+
+ /// Offset of the firstVertex push constant
+ const std::optional<uint32_t> first_vertex_offset;
+
+ /// Offset of the firstInstance push constant
+ const std::optional<uint32_t> first_instance_offset;
+ };
+
+ /// Constructor
+ OffsetFirstIndex();
+
+ /// Destructor
+ ~OffsetFirstIndex() override;
+
+ /// @copydoc Transform::Apply
+ ApplyResult Apply(const Program& program,
+ const DataMap& inputs,
+ DataMap& outputs) const override;
+};
+
+} // namespace tint::ast::transform
+
+#endif // SRC_TINT_LANG_WGSL_AST_TRANSFORM_OFFSET_FIRST_INDEX_H_
diff --git a/src/tint/lang/wgsl/ast/transform/offset_first_index_test.cc b/src/tint/lang/wgsl/ast/transform/offset_first_index_test.cc
new file mode 100644
index 0000000..37a79a9
--- /dev/null
+++ b/src/tint/lang/wgsl/ast/transform/offset_first_index_test.cc
@@ -0,0 +1,856 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/wgsl/ast/transform/offset_first_index.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "src/tint/lang/wgsl/ast/transform/helper_test.h"
+
+namespace tint::ast::transform {
+namespace {
+
+using OffsetFirstIndexTest = TransformTest;
+
+TEST_F(OffsetFirstIndexTest, ShouldRunEmptyModule) {
+ auto* src = R"()";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ EXPECT_FALSE(ShouldRun<OffsetFirstIndex>(src, config));
+}
+
+TEST_F(OffsetFirstIndexTest, ShouldRunFragmentStage) {
+ auto* src = R"(
+@fragment
+fn entry() {
+ return;
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ EXPECT_FALSE(ShouldRun<OffsetFirstIndex>(src, config));
+}
+
+TEST_F(OffsetFirstIndexTest, ShouldRunVertexStage) {
+ auto* src = R"(
+@vertex
+fn entry() -> @builtin(position) vec4<f32> {
+ return vec4<f32>();
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ EXPECT_FALSE(ShouldRun<OffsetFirstIndex>(src, config));
+}
+
+TEST_F(OffsetFirstIndexTest, ShouldRunVertexStageWithVertexIndex) {
+ auto* src = R"(
+@vertex
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ return vec4<f32>();
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ EXPECT_TRUE(ShouldRun<OffsetFirstIndex>(src, config));
+}
+
+TEST_F(OffsetFirstIndexTest, ShouldRunVertexStageWithInstanceIndex) {
+ auto* src = R"(
+@vertex
+fn entry(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+ return vec4<f32>();
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ EXPECT_TRUE(ShouldRun<OffsetFirstIndex>(src, config));
+}
+
+TEST_F(OffsetFirstIndexTest, EmptyModule) {
+ auto* src = "";
+ auto* expect = "";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(OffsetFirstIndexTest, BasicVertexShader) {
+ auto* src = R"(
+@vertex
+fn entry() -> @builtin(position) vec4<f32> {
+ return vec4<f32>();
+}
+)";
+ auto* expect = src;
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(OffsetFirstIndexTest, BasicModuleVertexIndex) {
+ auto* src = R"(
+fn test(vert_idx : u32) -> u32 {
+ return vert_idx;
+}
+
+@vertex
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ test(vert_idx);
+ return vec4<f32>();
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+struct PushConstants {
+ /* @offset(0) */
+ first_vertex : u32,
+}
+
+var<push_constant> push_constants : PushConstants;
+
+fn test(vert_idx : u32) -> u32 {
+ return vert_idx;
+}
+
+@vertex
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ test((vert_idx + push_constants.first_vertex));
+ return vec4<f32>();
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(OffsetFirstIndexTest, BasicModuleVertexIndex_OutOfOrder) {
+ auto* src = R"(
+@vertex
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ test(vert_idx);
+ return vec4<f32>();
+}
+
+fn test(vert_idx : u32) -> u32 {
+ return vert_idx;
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+struct PushConstants {
+ /* @offset(0) */
+ first_vertex : u32,
+}
+
+var<push_constant> push_constants : PushConstants;
+
+@vertex
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ test((vert_idx + push_constants.first_vertex));
+ return vec4<f32>();
+}
+
+fn test(vert_idx : u32) -> u32 {
+ return vert_idx;
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(OffsetFirstIndexTest, BasicModuleInstanceIndex) {
+ auto* src = R"(
+fn test(inst_idx : u32) -> u32 {
+ return inst_idx;
+}
+
+@vertex
+fn entry(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+ test(inst_idx);
+ return vec4<f32>();
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+struct PushConstants {
+ @size(4)
+ padding_0 : u32,
+ /* @offset(4) */
+ first_instance : u32,
+}
+
+var<push_constant> push_constants : PushConstants;
+
+fn test(inst_idx : u32) -> u32 {
+ return inst_idx;
+}
+
+@vertex
+fn entry(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+ test((inst_idx + push_constants.first_instance));
+ return vec4<f32>();
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(OffsetFirstIndexTest, BasicModuleInstanceIndex_OutOfOrder) {
+ auto* src = R"(
+@vertex
+fn entry(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+ test(inst_idx);
+ return vec4<f32>();
+}
+
+fn test(inst_idx : u32) -> u32 {
+ return inst_idx;
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+struct PushConstants {
+ @size(4)
+ padding_0 : u32,
+ /* @offset(4) */
+ first_instance : u32,
+}
+
+var<push_constant> push_constants : PushConstants;
+
+@vertex
+fn entry(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+ test((inst_idx + push_constants.first_instance));
+ return vec4<f32>();
+}
+
+fn test(inst_idx : u32) -> u32 {
+ return inst_idx;
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(OffsetFirstIndexTest, BasicModuleBothIndexes) {
+ auto* src = R"(
+fn test(instance_idx : u32, vert_idx : u32) -> u32 {
+ return instance_idx + vert_idx;
+}
+
+struct Inputs {
+ @builtin(instance_index) instance_idx : u32,
+ @builtin(vertex_index) vert_idx : u32,
+};
+
+@vertex
+fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
+ test(inputs.instance_idx, inputs.vert_idx);
+ return vec4<f32>();
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+struct PushConstants {
+ /* @offset(0) */
+ first_vertex : u32,
+ /* @offset(4) */
+ first_instance : u32,
+}
+
+var<push_constant> push_constants : PushConstants;
+
+fn test(instance_idx : u32, vert_idx : u32) -> u32 {
+ return (instance_idx + vert_idx);
+}
+
+struct Inputs {
+ @builtin(instance_index)
+ instance_idx : u32,
+ @builtin(vertex_index)
+ vert_idx : u32,
+}
+
+@vertex
+fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
+ test((inputs.instance_idx + push_constants.first_instance), (inputs.vert_idx + push_constants.first_vertex));
+ return vec4<f32>();
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(OffsetFirstIndexTest, BasicModuleBothIndexes_OutOfOrder) {
+ auto* src = R"(
+@vertex
+fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
+ test(inputs.instance_idx, inputs.vert_idx);
+ return vec4<f32>();
+}
+
+struct Inputs {
+ @builtin(instance_index) instance_idx : u32,
+ @builtin(vertex_index) vert_idx : u32,
+};
+
+fn test(instance_idx : u32, vert_idx : u32) -> u32 {
+ return instance_idx + vert_idx;
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+struct PushConstants {
+ /* @offset(0) */
+ first_vertex : u32,
+ /* @offset(4) */
+ first_instance : u32,
+}
+
+var<push_constant> push_constants : PushConstants;
+
+@vertex
+fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
+ test((inputs.instance_idx + push_constants.first_instance), (inputs.vert_idx + push_constants.first_vertex));
+ return vec4<f32>();
+}
+
+struct Inputs {
+ @builtin(instance_index)
+ instance_idx : u32,
+ @builtin(vertex_index)
+ vert_idx : u32,
+}
+
+fn test(instance_idx : u32, vert_idx : u32) -> u32 {
+ return (instance_idx + vert_idx);
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(OffsetFirstIndexTest, BasicModuleBothIndexesVertexDisabled) {
+ auto* src = R"(
+fn test(instance_idx : u32, vert_idx : u32) -> u32 {
+ return instance_idx + vert_idx;
+}
+
+struct Inputs {
+ @builtin(instance_index) instance_idx : u32,
+ @builtin(vertex_index) vert_idx : u32,
+};
+
+@vertex
+fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
+ test(inputs.instance_idx, inputs.vert_idx);
+ return vec4<f32>();
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+struct PushConstants {
+ /* @offset(0) */
+ first_instance : u32,
+}
+
+var<push_constant> push_constants : PushConstants;
+
+fn test(instance_idx : u32, vert_idx : u32) -> u32 {
+ return (instance_idx + vert_idx);
+}
+
+struct Inputs {
+ @builtin(instance_index)
+ instance_idx : u32,
+ @builtin(vertex_index)
+ vert_idx : u32,
+}
+
+@vertex
+fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
+ test((inputs.instance_idx + push_constants.first_instance), inputs.vert_idx);
+ return vec4<f32>();
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(std::nullopt, 0);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(OffsetFirstIndexTest, BasicModuleBothIndexesInstanceDisabled) {
+ auto* src = R"(
+fn test(instance_idx : u32, vert_idx : u32) -> u32 {
+ return instance_idx + vert_idx;
+}
+
+struct Inputs {
+ @builtin(instance_index) instance_idx : u32,
+ @builtin(vertex_index) vert_idx : u32,
+};
+
+@vertex
+fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
+ test(inputs.instance_idx, inputs.vert_idx);
+ return vec4<f32>();
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+struct PushConstants {
+ /* @offset(0) */
+ first_vertex : u32,
+}
+
+var<push_constant> push_constants : PushConstants;
+
+fn test(instance_idx : u32, vert_idx : u32) -> u32 {
+ return (instance_idx + vert_idx);
+}
+
+struct Inputs {
+ @builtin(instance_index)
+ instance_idx : u32,
+ @builtin(vertex_index)
+ vert_idx : u32,
+}
+
+@vertex
+fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
+ test(inputs.instance_idx, (inputs.vert_idx + push_constants.first_vertex));
+ return vec4<f32>();
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, std::nullopt);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(OffsetFirstIndexTest, BasicModuleBothIndexesBothDisabled) {
+ auto* src = R"(
+fn test(instance_idx : u32, vert_idx : u32) -> u32 {
+ return (instance_idx + vert_idx);
+}
+
+struct Inputs {
+ @builtin(instance_index)
+ instance_idx : u32,
+ @builtin(vertex_index)
+ vert_idx : u32,
+}
+
+@vertex
+fn entry(inputs : Inputs) -> @builtin(position) vec4<f32> {
+ test(inputs.instance_idx, inputs.vert_idx);
+ return vec4<f32>();
+}
+)";
+
+ auto* expect = src;
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(std::nullopt, std::nullopt);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+// Test a shader with a user-declared struct called PushConstants, to force
+// renaming of the Tint-provided PushConstants struct.
+TEST_F(OffsetFirstIndexTest, ForceRenamingPushConstantsStruct) {
+ auto* src = R"(
+struct PushConstants {
+ f : f32,
+}
+
+@group(0) @binding(0) var<uniform> p : PushConstants;
+
+@vertex
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ return vec4<f32>(f32(vert_idx) + p.f);
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+struct PushConstants_1 {
+ /* @offset(0) */
+ first_vertex : u32,
+}
+
+var<push_constant> push_constants : PushConstants_1;
+
+struct PushConstants {
+ f : f32,
+}
+
+@group(0) @binding(0) var<uniform> p : PushConstants;
+
+@vertex
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ return vec4<f32>((f32((vert_idx + push_constants.first_vertex)) + p.f));
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+// Test a shader with a user-declared variable called push_constants, to force
+// renaming of the Tint-provided push_constants variable.
+TEST_F(OffsetFirstIndexTest, ForceRenamingPushConstantsVar) {
+ auto* src = R"(
+@group(0) @binding(0) var<uniform> push_constants : u32;
+
+@vertex
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ return vec4<f32>(f32(vert_idx));
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+struct PushConstants {
+ /* @offset(0) */
+ first_vertex : u32,
+}
+
+var<push_constant> push_constants_1 : PushConstants;
+
+@group(0) @binding(0) var<uniform> push_constants : u32;
+
+@vertex
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ return vec4<f32>(f32((vert_idx + push_constants_1.first_vertex)));
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(OffsetFirstIndexTest, NestedCalls) {
+ auto* src = R"(
+fn func1(vert_idx : u32) -> u32 {
+ return vert_idx;
+}
+
+fn func2(vert_idx : u32) -> u32 {
+ return func1(vert_idx);
+}
+
+@vertex
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ func2(vert_idx);
+ return vec4<f32>();
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+struct PushConstants {
+ /* @offset(0) */
+ first_vertex : u32,
+}
+
+var<push_constant> push_constants : PushConstants;
+
+fn func1(vert_idx : u32) -> u32 {
+ return vert_idx;
+}
+
+fn func2(vert_idx : u32) -> u32 {
+ return func1(vert_idx);
+}
+
+@vertex
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ func2((vert_idx + push_constants.first_vertex));
+ return vec4<f32>();
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(OffsetFirstIndexTest, NestedCalls_OutOfOrder) {
+ auto* src = R"(
+@vertex
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ func2(vert_idx);
+ return vec4<f32>();
+}
+
+fn func2(vert_idx : u32) -> u32 {
+ return func1(vert_idx);
+}
+
+fn func1(vert_idx : u32) -> u32 {
+ return vert_idx;
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+struct PushConstants {
+ /* @offset(0) */
+ first_vertex : u32,
+}
+
+var<push_constant> push_constants : PushConstants;
+
+@vertex
+fn entry(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ func2((vert_idx + push_constants.first_vertex));
+ return vec4<f32>();
+}
+
+fn func2(vert_idx : u32) -> u32 {
+ return func1(vert_idx);
+}
+
+fn func1(vert_idx : u32) -> u32 {
+ return vert_idx;
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(OffsetFirstIndexTest, MultipleEntryPoints) {
+ auto* src = R"(
+fn func(i : u32) -> u32 {
+ return i;
+}
+
+@vertex
+fn entry_a(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ func(vert_idx);
+ return vec4<f32>();
+}
+
+@vertex
+fn entry_b(@builtin(vertex_index) vert_idx : u32, @builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+ func(vert_idx + inst_idx);
+ return vec4<f32>();
+}
+
+@vertex
+fn entry_c(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+ func(inst_idx);
+ return vec4<f32>();
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+struct PushConstants {
+ /* @offset(0) */
+ first_vertex : u32,
+ /* @offset(4) */
+ first_instance : u32,
+}
+
+var<push_constant> push_constants : PushConstants;
+
+fn func(i : u32) -> u32 {
+ return i;
+}
+
+@vertex
+fn entry_a(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ func((vert_idx + push_constants.first_vertex));
+ return vec4<f32>();
+}
+
+@vertex
+fn entry_b(@builtin(vertex_index) vert_idx : u32, @builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+ func(((vert_idx + push_constants.first_vertex) + (inst_idx + push_constants.first_instance)));
+ return vec4<f32>();
+}
+
+@vertex
+fn entry_c(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+ func((inst_idx + push_constants.first_instance));
+ return vec4<f32>();
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(OffsetFirstIndexTest, MultipleEntryPoints_OutOfOrder) {
+ auto* src = R"(
+@vertex
+fn entry_a(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ func(vert_idx);
+ return vec4<f32>();
+}
+
+@vertex
+fn entry_b(@builtin(vertex_index) vert_idx : u32, @builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+ func(vert_idx + inst_idx);
+ return vec4<f32>();
+}
+
+@vertex
+fn entry_c(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+ func(inst_idx);
+ return vec4<f32>();
+}
+
+fn func(i : u32) -> u32 {
+ return i;
+}
+)";
+
+ auto* expect = R"(
+enable chromium_experimental_push_constant;
+
+struct PushConstants {
+ /* @offset(0) */
+ first_vertex : u32,
+ /* @offset(4) */
+ first_instance : u32,
+}
+
+var<push_constant> push_constants : PushConstants;
+
+@vertex
+fn entry_a(@builtin(vertex_index) vert_idx : u32) -> @builtin(position) vec4<f32> {
+ func((vert_idx + push_constants.first_vertex));
+ return vec4<f32>();
+}
+
+@vertex
+fn entry_b(@builtin(vertex_index) vert_idx : u32, @builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+ func(((vert_idx + push_constants.first_vertex) + (inst_idx + push_constants.first_instance)));
+ return vec4<f32>();
+}
+
+@vertex
+fn entry_c(@builtin(instance_index) inst_idx : u32) -> @builtin(position) vec4<f32> {
+ func((inst_idx + push_constants.first_instance));
+ return vec4<f32>();
+}
+
+fn func(i : u32) -> u32 {
+ return i;
+}
+)";
+
+ DataMap config;
+ config.Add<OffsetFirstIndex::Config>(0, 4);
+ auto got = Run<OffsetFirstIndex>(src, std::move(config));
+
+ EXPECT_EQ(expect, str(got));
+}
+
+} // namespace
+} // namespace tint::ast::transform
diff --git a/test/tint/var/uses/push_constant.wgsl.expected.glsl b/test/tint/var/uses/push_constant.wgsl.expected.glsl
index 6ed822e..aea31fb 100644
--- a/test/tint/var/uses/push_constant.wgsl.expected.glsl
+++ b/test/tint/var/uses/push_constant.wgsl.expected.glsl
@@ -1,8 +1,12 @@
#version 310 es
-uniform int a;
+struct a_block {
+ int inner;
+};
+
+layout(location=0) uniform a_block a;
void uses_a() {
- int foo = a;
+ int foo = a.inner;
}
void main1() {
@@ -16,9 +20,13 @@
}
#version 310 es
-uniform int a;
+struct a_block {
+ int inner;
+};
+
+layout(location=0) uniform a_block a;
void uses_a() {
- int foo = a;
+ int foo = a.inner;
}
void uses_uses_a() {
@@ -36,9 +44,13 @@
}
#version 310 es
-uniform int b;
+struct b_block {
+ int inner;
+};
+
+layout(location=0) uniform b_block b;
void uses_b() {
- int foo = b;
+ int foo = b.inner;
}
void main3() {
diff --git a/webgpu-cts/compat-expectations.txt b/webgpu-cts/compat-expectations.txt
index 704869e..64687a7 100644
--- a/webgpu-cts/compat-expectations.txt
+++ b/webgpu-cts/compat-expectations.txt
@@ -381,91 +381,6 @@
crbug.com/dawn/2123 [ nvidia-0x2184 ] webgpu:api,validation,capability_checks,limits,maxStorageBuffersPerShaderStage:createPipeline,at_over:limitTest="atMaximum";testValueName="atLimit";async=true;bindingCombination="vertexAndFragmentWithPossibleVertexStageOverflow";order="forward";bindGroupTest="differentGroups" [ Failure ]
crbug.com/dawn/2123 [ nvidia-0x2184 ] webgpu:api,validation,capability_checks,limits,maxStorageBuffersPerShaderStage:createPipeline,at_over:limitTest="atMaximum";testValueName="atLimit";async=true;bindingCombination="vertexAndFragmentWithPossibleVertexStageOverflow";order="forward";bindGroupTest="sameGroup" [ Failure ]
-# Instancing issues on NVidia GL
-# count=[1-9].*first_instance=[1-9].*instance_count=[1-9]
-# These only occur on ANGLE's NVidia GL and GLES backends. ANGLE's SwiftShader
-# and Vk backends are fine.
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=1;indexed=false;indirect=false;vertex_buffer_offset=0;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=1;indexed=false;indirect=false;vertex_buffer_offset=32;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=4;indexed=false;indirect=false;vertex_buffer_offset=0;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=4;indexed=false;indirect=false;vertex_buffer_offset=32;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=3;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=1;indexed=false;indirect=false;vertex_buffer_offset=0;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=1;indexed=false;indirect=false;vertex_buffer_offset=32;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=4;indexed=false;indirect=false;vertex_buffer_offset=0;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=4;indexed=false;indirect=false;vertex_buffer_offset=32;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=0;count=6;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=1;indexed=false;indirect=false;vertex_buffer_offset=0;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=1;indexed=false;indirect=false;vertex_buffer_offset=32;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=4;indexed=false;indirect=false;vertex_buffer_offset=0;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=4;indexed=false;indirect=false;vertex_buffer_offset=32;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=3;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=1;indexed=false;indirect=false;vertex_buffer_offset=0;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=1;indexed=false;indirect=false;vertex_buffer_offset=32;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=1;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=4;indexed=false;indirect=false;vertex_buffer_offset=0;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=4;indexed=false;indirect=false;vertex_buffer_offset=32;index_buffer_offset="_undef_";base_vertex="_undef_" [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=0;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=0;index_buffer_offset=16;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=0;base_vertex=9 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=0 [ Failure ]
-crbug.com/dawn/2119 webgpu:api,operation,rendering,draw:arguments:first=3;count=6;first_instance=2;instance_count=4;indexed=true;indirect=false;vertex_buffer_offset=32;index_buffer_offset=16;base_vertex=9 [ Failure ]
-
# Zero init issues, but only with batch__=9 (NVidia driver bug)
crbug.com/dawn/2125 [ nvidia-0x2184 ] webgpu:shader,execution,zero_init:compute,zero_init:addressSpace="function";workgroupSize=[1,1,1];batch__=9 [ Failure ]
crbug.com/dawn/2125 [ nvidia-0x2184 ] webgpu:shader,execution,zero_init:compute,zero_init:addressSpace="private";workgroupSize=[1,1,1];batch__=9 [ Failure ]