OpenGLES: baseVertex, baseInstance workaround.

Use the Tint shader workaround to add baseVertex to gl_BaseVertex.
Offset the pointer passed to glVertexAttrib[I]Pointer() by baseVertex
or firstInstance, depending on buffer step mode.
Split the BaseVertex end2end test into two, since negative BaseVertex
is problematic on some platforms.
Remove all the other codepaths; always use emulation for now.
Remove all dependencies on the GL_ANGLE_base_vertex_base_instance extension.

Change-Id: I8965c983ccd5588af28ba88b8e1d0d519b4422cc
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/190460
Commit-Queue: Stephen White <senorblanco@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/generator/templates/opengl/OpenGLFunctionsBase.cpp b/generator/templates/opengl/OpenGLFunctionsBase.cpp
index 261f0fb..5789fcc 100644
--- a/generator/templates/opengl/OpenGLFunctionsBase.cpp
+++ b/generator/templates/opengl/OpenGLFunctionsBase.cpp
@@ -61,15 +61,6 @@
         }
     {% endfor %}
 
-    // GL_ANGLE_base_vertex_base_instance
-    // See crbug.com/dawn/1715 for why this is embedded
-    if (IsGLExtensionSupported("GL_ANGLE_base_vertex_base_instance")) {
-        DAWN_TRY(LoadProc(getProc, &DrawArraysInstancedBaseInstanceANGLE, "glDrawArraysInstancedBaseInstanceANGLE"));
-        DAWN_TRY(LoadProc(getProc, &DrawElementsInstancedBaseVertexBaseInstanceANGLE, "glDrawElementsInstancedBaseVertexBaseInstanceANGLE"));
-        DAWN_TRY(LoadProc(getProc, &MultiDrawArraysInstancedBaseInstanceANGLE, "glMultiDrawArraysInstancedBaseInstanceANGLE"));
-        DAWN_TRY(LoadProc(getProc, &MultiDrawElementsInstancedBaseVertexBaseInstanceANGLE, "glMultiDrawElementsInstancedBaseVertexBaseInstanceANGLE"));
-    }
-
     return {};
 }
 #endif
diff --git a/generator/templates/opengl/OpenGLFunctionsBase.h b/generator/templates/opengl/OpenGLFunctionsBase.h
index 0096a37..0f8b01f 100644
--- a/generator/templates/opengl/OpenGLFunctionsBase.h
+++ b/generator/templates/opengl/OpenGLFunctionsBase.h
@@ -46,13 +46,6 @@
 
         {% endfor%}
 
-        // GL_ANGLE_base_vertex_base_instance
-        // See crbug.com/dawn/1715 for why this is embedded
-        PFNGLDRAWARRAYSINSTANCEDBASEINSTANCEANGLEPROC DrawArraysInstancedBaseInstanceANGLE = nullptr;
-        PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEANGLEPROC DrawElementsInstancedBaseVertexBaseInstanceANGLE = nullptr;
-        PFNGLMULTIDRAWARRAYSINSTANCEDBASEINSTANCEANGLEPROC MultiDrawArraysInstancedBaseInstanceANGLE = nullptr;
-        PFNGLMULTIDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEANGLEPROC MultiDrawElementsInstancedBaseVertexBaseInstanceANGLE = nullptr;
-
         bool IsGLExtensionSupported(const char* extension) const;
 
       protected:
diff --git a/generator/templates/opengl/opengl_platform.h b/generator/templates/opengl/opengl_platform.h
index c51363d..d9bfe3e 100644
--- a/generator/templates/opengl/opengl_platform.h
+++ b/generator/templates/opengl/opengl_platform.h
@@ -84,11 +84,4 @@
 
 {% endfor%}
 
-// GL_ANGLE_base_vertex_base_instance
-// See crbug.com/dawn/1715 for why this is embedded
-using PFNGLDRAWARRAYSINSTANCEDBASEINSTANCEANGLEPROC = void(KHRONOS_APIENTRY *)(GLenum mode, GLint first, GLsizei count, GLsizei instanceCount, GLuint baseInstance);
-using PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEANGLEPROC = void(KHRONOS_APIENTRY *)(GLenum mode, GLsizei count, GLenum type, const GLvoid * indices, GLsizei instanceCount, GLint baseVertex, GLuint baseInstance);
-using PFNGLMULTIDRAWARRAYSINSTANCEDBASEINSTANCEANGLEPROC = void(KHRONOS_APIENTRY *)(GLenum mode, const GLint * firsts, const GLsizei * counts, const GLsizei * instanceCounts, const GLuint * baseInstances, GLsizei drawcount);
-using PFNGLMULTIDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEANGLEPROC = void(KHRONOS_APIENTRY *)(GLenum mode, const GLsizei * counts, GLenum type, const GLvoid *const* indices, const GLsizei * instanceCounts, const GLint * baseVertices, const GLuint * baseInstances, GLsizei drawcount);
-
 #undef DAWN_GL_APIENTRY
diff --git a/src/dawn/native/opengl/CommandBufferGL.cpp b/src/dawn/native/opengl/CommandBufferGL.cpp
index cf77e8a..d5e35d2 100644
--- a/src/dawn/native/opengl/CommandBufferGL.cpp
+++ b/src/dawn/native/opengl/CommandBufferGL.cpp
@@ -184,7 +184,17 @@
         mLastPipeline = pipeline;
     }
 
-    void Apply(const OpenGLFunctions& gl) {
+    void Apply(const OpenGLFunctions& gl, int32_t baseVertex, uint32_t firstInstance) {
+        if (mBaseVertex != baseVertex) {
+            mBaseVertex = baseVertex;
+            mDirtyVertexBuffers |= mLastPipeline->GetVertexBuffersUsedAsVertexBuffer();
+        }
+
+        if (mFirstInstance != firstInstance) {
+            mFirstInstance = firstInstance;
+            mDirtyVertexBuffers |= mLastPipeline->GetVertexBuffersUsedAsInstanceBuffer();
+        }
+
         if (mIndexBufferDirty && mIndexBuffer != nullptr) {
             gl.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer->GetHandle());
             mIndexBufferDirty = false;
@@ -198,9 +208,15 @@
 
                 GLuint attribIndex = static_cast<GLuint>(static_cast<uint8_t>(location));
                 GLuint buffer = mVertexBuffers[slot]->GetHandle();
-                uint64_t offset = mVertexBufferOffsets[slot];
+                intptr_t offset = mVertexBufferOffsets[slot];
 
                 const VertexBufferInfo& vertexBuffer = mLastPipeline->GetVertexBuffer(slot);
+
+                if (vertexBuffer.stepMode == wgpu::VertexStepMode::Vertex) {
+                    offset += mBaseVertex * vertexBuffer.arrayStride;
+                } else if (vertexBuffer.stepMode == wgpu::VertexStepMode::Instance) {
+                    offset += mFirstInstance * vertexBuffer.arrayStride;
+                }
                 uint32_t components = GetVertexFormatInfo(attribute.format).componentCount;
                 GLenum formatType = VertexFormatType(attribute.format);
 
@@ -209,11 +225,11 @@
                 if (VertexFormatIsInt(attribute.format)) {
                     gl.VertexAttribIPointer(
                         attribIndex, components, formatType, vertexBuffer.arrayStride,
-                        reinterpret_cast<void*>(static_cast<intptr_t>(offset + attribute.offset)));
+                        reinterpret_cast<void*>(offset + static_cast<intptr_t>(attribute.offset)));
                 } else {
                     gl.VertexAttribPointer(
                         attribIndex, components, formatType, normalized, vertexBuffer.arrayStride,
-                        reinterpret_cast<void*>(static_cast<intptr_t>(offset + attribute.offset)));
+                        reinterpret_cast<void*>(offset + static_cast<intptr_t>(attribute.offset)));
                 }
             }
         }
@@ -229,6 +245,8 @@
     PerVertexBuffer<Buffer*> mVertexBuffers;
     PerVertexBuffer<uint64_t> mVertexBufferOffsets;
 
+    int32_t mBaseVertex = 0;
+    uint32_t mFirstInstance = 0;
     raw_ptr<RenderPipelineBase> mLastPipeline = nullptr;
 };
 
@@ -1113,7 +1131,7 @@
         switch (type) {
             case Command::Draw: {
                 DrawCmd* draw = iter->NextCommand<DrawCmd>();
-                vertexStateBufferBindingTracker.Apply(gl);
+                vertexStateBufferBindingTracker.Apply(gl, 0, draw->firstInstance);
                 bindGroupTracker.Apply(gl);
 
                 if (lastPipeline->UsesInstanceIndex()) {
@@ -1127,7 +1145,7 @@
 
             case Command::DrawIndexed: {
                 DrawIndexedCmd* draw = iter->NextCommand<DrawIndexedCmd>();
-                vertexStateBufferBindingTracker.Apply(gl);
+                vertexStateBufferBindingTracker.Apply(gl, draw->baseVertex, draw->firstInstance);
                 bindGroupTracker.Apply(gl);
 
                 const auto topology = lastPipeline->GetGLPrimitiveTopology();
@@ -1137,39 +1155,19 @@
                     gl.Disable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
                 }
 
+                if (lastPipeline->UsesVertexIndex()) {
+                    gl.Uniform1ui(PipelineLayout::PushConstantLocation::FirstVertex,
+                                  draw->baseVertex);
+                }
                 if (lastPipeline->UsesInstanceIndex()) {
                     gl.Uniform1ui(PipelineLayout::PushConstantLocation::FirstInstance,
                                   draw->firstInstance);
                 }
-                if (gl.DrawElementsInstancedBaseVertexBaseInstanceANGLE) {
-                    gl.DrawElementsInstancedBaseVertexBaseInstanceANGLE(
-                        topology, draw->indexCount, indexBufferFormat,
-                        reinterpret_cast<void*>(draw->firstIndex * indexFormatSize +
-                                                indexBufferBaseOffset),
-                        draw->instanceCount, draw->baseVertex, draw->firstInstance);
-                } else if (draw->firstInstance > 0) {
-                    gl.DrawElementsInstancedBaseVertexBaseInstance(
-                        topology, draw->indexCount, indexBufferFormat,
-                        reinterpret_cast<void*>(draw->firstIndex * indexFormatSize +
-                                                indexBufferBaseOffset),
-                        draw->instanceCount, draw->baseVertex, draw->firstInstance);
-                } else {
-                    // This branch is only needed on OpenGL < 4.2; ES < 3.2
-                    if (draw->baseVertex != 0) {
-                        gl.DrawElementsInstancedBaseVertex(
-                            topology, draw->indexCount, indexBufferFormat,
-                            reinterpret_cast<void*>(draw->firstIndex * indexFormatSize +
-                                                    indexBufferBaseOffset),
-                            draw->instanceCount, draw->baseVertex);
-                    } else {
-                        // This branch is only needed on OpenGL < 3.2; ES < 3.2
-                        gl.DrawElementsInstanced(
-                            topology, draw->indexCount, indexBufferFormat,
-                            reinterpret_cast<void*>(draw->firstIndex * indexFormatSize +
-                                                    indexBufferBaseOffset),
-                            draw->instanceCount);
-                    }
-                }
+                gl.DrawElementsInstanced(
+                    topology, draw->indexCount, indexBufferFormat,
+                    reinterpret_cast<void*>(draw->firstIndex * indexFormatSize +
+                                            indexBufferBaseOffset),
+                    draw->instanceCount);
                 break;
             }
 
@@ -1178,7 +1176,7 @@
                 if (lastPipeline->UsesInstanceIndex()) {
                     gl.Uniform1ui(PipelineLayout::PushConstantLocation::FirstInstance, 0);
                 }
-                vertexStateBufferBindingTracker.Apply(gl);
+                vertexStateBufferBindingTracker.Apply(gl, 0, 0);
                 bindGroupTracker.Apply(gl);
 
                 uint64_t indirectBufferOffset = draw->indirectOffset;
@@ -1198,7 +1196,7 @@
                 if (lastPipeline->UsesInstanceIndex()) {
                     gl.Uniform1ui(PipelineLayout::PushConstantLocation::FirstInstance, 0);
                 }
-                vertexStateBufferBindingTracker.Apply(gl);
+                vertexStateBufferBindingTracker.Apply(gl, 0, 0);
                 bindGroupTracker.Apply(gl);
 
                 Buffer* indirectBuffer = ToBackend(draw->indirectBuffer.Get());
diff --git a/src/dawn/native/opengl/ComputePipelineGL.cpp b/src/dawn/native/opengl/ComputePipelineGL.cpp
index 3344cd5..f193bea 100644
--- a/src/dawn/native/opengl/ComputePipelineGL.cpp
+++ b/src/dawn/native/opengl/ComputePipelineGL.cpp
@@ -47,7 +47,8 @@
 
 MaybeError ComputePipeline::InitializeImpl() {
     DAWN_TRY(InitializeBase(ToBackend(GetDevice())->GetGL(), ToBackend(GetLayout()), GetAllStages(),
-                            /* usesInstanceIndex */ false, /* usesFragDepth */ false));
+                            /* usesVertexIndex */ false, /* usesInstanceIndex */ false,
+                            /* usesFragDepth */ false));
     return {};
 }
 
diff --git a/src/dawn/native/opengl/PhysicalDeviceGL.cpp b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
index 70e6e73..7f89390 100644
--- a/src/dawn/native/opengl/PhysicalDeviceGL.cpp
+++ b/src/dawn/native/opengl/PhysicalDeviceGL.cpp
@@ -334,10 +334,6 @@
                                                TogglesState* deviceToggles) const {
     const OpenGLFunctions& gl = mFunctions;
 
-    bool supportsBaseVertex = gl.IsAtLeastGLES(3, 2) || gl.IsAtLeastGL(3, 2);
-
-    bool supportsBaseInstance = gl.IsAtLeastGLES(3, 2) || gl.IsAtLeastGL(4, 2);
-
     // TODO(crbug.com/dawn/582): Use OES_draw_buffers_indexed where available.
     bool supportsIndexedDrawBuffers = gl.IsAtLeastGLES(3, 2) || gl.IsAtLeastGL(3, 0);
 
@@ -363,28 +359,7 @@
     bool supportsStencilWriteTexture =
         gl.GetVersion().IsDesktop() || gl.IsGLExtensionSupported("GL_OES_texture_stencil8");
 
-    // TODO(crbug.com/dawn/343): We can support the extension variants, but need to load the EXT
-    // procs without the extension suffix.
-    // We'll also need emulation of shader builtins gl_BaseVertex and gl_BaseInstance.
-
-    // supportsBaseVertex |=
-    //     (gl.IsAtLeastGLES(2, 0) &&
-    //      (gl.IsGLExtensionSupported("OES_draw_elements_base_vertex") ||
-    //       gl.IsGLExtensionSupported("EXT_draw_elements_base_vertex"))) ||
-    //     (gl.IsAtLeastGL(3, 1) && gl.IsGLExtensionSupported("ARB_draw_elements_base_vertex"));
-
-    // supportsBaseInstance |=
-    //     (gl.IsAtLeastGLES(3, 1) && gl.IsGLExtensionSupported("EXT_base_instance")) ||
-    //     (gl.IsAtLeastGL(3, 1) && gl.IsGLExtensionSupported("ARB_base_instance"));
-
-    if (gl.IsAtLeastGLES(3, 1) && gl.IsGLExtensionSupported("GL_ANGLE_base_vertex_base_instance")) {
-        supportsBaseVertex = true;
-        supportsBaseInstance = true;
-    }
-
     // TODO(crbug.com/dawn/343): Investigate emulation.
-    deviceToggles->Default(Toggle::DisableBaseVertex, !supportsBaseVertex);
-    deviceToggles->Default(Toggle::DisableBaseInstance, !supportsBaseInstance);
     deviceToggles->Default(Toggle::DisableIndexedDrawBuffers, !supportsIndexedDrawBuffers);
     deviceToggles->Default(Toggle::DisableDepthRead, !supportsDepthRead);
     deviceToggles->Default(Toggle::DisableStencilRead, !supportsStencilRead);
diff --git a/src/dawn/native/opengl/PipelineGL.cpp b/src/dawn/native/opengl/PipelineGL.cpp
index 2ca43fe..4025342 100644
--- a/src/dawn/native/opengl/PipelineGL.cpp
+++ b/src/dawn/native/opengl/PipelineGL.cpp
@@ -53,6 +53,7 @@
 MaybeError PipelineGL::InitializeBase(const OpenGLFunctions& gl,
                                       const PipelineLayout* layout,
                                       const PerStage<ProgrammableStage>& stages,
+                                      bool usesVertexIndex,
                                       bool usesInstanceIndex,
                                       bool usesFragDepth) {
     mProgram = gl.CreateProgram();
@@ -72,11 +73,11 @@
     for (SingleShaderStage stage : IterateStages(activeStages)) {
         const ShaderModule* module = ToBackend(stages[stage].module.Get());
         GLuint shader;
-        DAWN_TRY_ASSIGN(shader, module->CompileShader(gl, stages[stage], stage, usesInstanceIndex,
-                                                      usesFragDepth, &combinedSamplers[stage],
-                                                      layout, &needsPlaceholderSampler,
-                                                      &mNeedsTextureBuiltinUniformBuffer,
-                                                      &mBindingPointEmulatedBuiltins));
+        DAWN_TRY_ASSIGN(
+            shader, module->CompileShader(
+                        gl, stages[stage], stage, usesVertexIndex, usesInstanceIndex, usesFragDepth,
+                        &combinedSamplers[stage], layout, &needsPlaceholderSampler,
+                        &mNeedsTextureBuiltinUniformBuffer, &mBindingPointEmulatedBuiltins));
         gl.AttachShader(mProgram, shader);
         glShaders.push_back(shader);
     }
diff --git a/src/dawn/native/opengl/PipelineGL.h b/src/dawn/native/opengl/PipelineGL.h
index 2370f55..3b5ac9e 100644
--- a/src/dawn/native/opengl/PipelineGL.h
+++ b/src/dawn/native/opengl/PipelineGL.h
@@ -74,6 +74,7 @@
     MaybeError InitializeBase(const OpenGLFunctions& gl,
                               const PipelineLayout* layout,
                               const PerStage<ProgrammableStage>& stages,
+                              bool usesVertexIndex,
                               bool usesInstanceIndex,
                               bool usesFragDepth);
     void DeleteProgram(const OpenGLFunctions& gl);
diff --git a/src/dawn/native/opengl/PipelineLayoutGL.h b/src/dawn/native/opengl/PipelineLayoutGL.h
index bc0fe3a..b12b756 100644
--- a/src/dawn/native/opengl/PipelineLayoutGL.h
+++ b/src/dawn/native/opengl/PipelineLayoutGL.h
@@ -56,9 +56,10 @@
     GLuint GetInternalUniformBinding() const;
 
     enum PushConstantLocation {
-        FirstInstance = 0,
-        MinDepth = 1,
-        MaxDepth = 2,
+        FirstVertex = 0,
+        FirstInstance = 1,
+        MinDepth = 2,
+        MaxDepth = 3,
     };
 
   private:
diff --git a/src/dawn/native/opengl/RenderPipelineGL.cpp b/src/dawn/native/opengl/RenderPipelineGL.cpp
index a4c2b08..57991a0 100644
--- a/src/dawn/native/opengl/RenderPipelineGL.cpp
+++ b/src/dawn/native/opengl/RenderPipelineGL.cpp
@@ -217,7 +217,7 @@
 
 MaybeError RenderPipeline::InitializeImpl() {
     DAWN_TRY(InitializeBase(ToBackend(GetDevice())->GetGL(), ToBackend(GetLayout()), GetAllStages(),
-                            UsesInstanceIndex(), UsesFragDepth()));
+                            UsesVertexIndex(), UsesInstanceIndex(), UsesFragDepth()));
     CreateVAOForVertexState();
     return {};
 }
diff --git a/src/dawn/native/opengl/ShaderModuleGL.cpp b/src/dawn/native/opengl/ShaderModuleGL.cpp
index b2d3aef..8d9e026 100644
--- a/src/dawn/native/opengl/ShaderModuleGL.cpp
+++ b/src/dawn/native/opengl/ShaderModuleGL.cpp
@@ -168,6 +168,7 @@
     const OpenGLFunctions& gl,
     const ProgrammableStage& programmableStage,
     SingleShaderStage stage,
+    bool usesVertexIndex,
     bool usesInstanceIndex,
     bool usesFragDepth,
     CombinedSamplerInfo* combinedSamplers,
@@ -291,6 +292,10 @@
 
     req.tintOptions.disable_robustness = false;
 
+    if (usesVertexIndex) {
+        req.tintOptions.first_vertex_offset = 4 * PipelineLayout::PushConstantLocation::FirstVertex;
+    }
+
     if (usesInstanceIndex) {
         req.tintOptions.first_instance_offset =
             4 * PipelineLayout::PushConstantLocation::FirstInstance;
diff --git a/src/dawn/native/opengl/ShaderModuleGL.h b/src/dawn/native/opengl/ShaderModuleGL.h
index 71251e4..2c04be3 100644
--- a/src/dawn/native/opengl/ShaderModuleGL.h
+++ b/src/dawn/native/opengl/ShaderModuleGL.h
@@ -89,6 +89,7 @@
     ResultOrError<GLuint> CompileShader(const OpenGLFunctions& gl,
                                         const ProgrammableStage& programmableStage,
                                         SingleShaderStage stage,
+                                        bool usesVertexIndex,
                                         bool usesInstanceIndex,
                                         bool usesFragDepth,
                                         CombinedSamplerInfo* combinedSamplers,
diff --git a/src/dawn/native/opengl/supported_extensions.json b/src/dawn/native/opengl/supported_extensions.json
index 75beb21..b74ba3b 100644
--- a/src/dawn/native/opengl/supported_extensions.json
+++ b/src/dawn/native/opengl/supported_extensions.json
@@ -35,9 +35,5 @@
         "GL_OES_EGL_image",
         "GL_EXT_texture_format_BGRA8888",
         "GL_APPLE_texture_format_BGRA8888"
-    ],
-
-    "supported_angle_extensions": [
-        "GL_ANGLE_base_vertex_base_instance"
     ]
 }
diff --git a/src/dawn/tests/end2end/DrawIndexedTests.cpp b/src/dawn/tests/end2end/DrawIndexedTests.cpp
index b7e5f6c..4d7597a 100644
--- a/src/dawn/tests/end2end/DrawIndexedTests.cpp
+++ b/src/dawn/tests/end2end/DrawIndexedTests.cpp
@@ -163,6 +163,21 @@
     Test(3, 1, 0, 4, 0, 0, notFilled, filled);
     // Test a draw with only the last 3 indices of the second quad (bottom left triangle)
     Test(3, 1, 3, 4, 0, 0, filled, notFilled);
+}
+
+// Test the parameter 'baseVertex' of DrawIndexed() works.
+TEST_P(DrawIndexedTest, NegativeBaseVertex) {
+    DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("disable_base_vertex"));
+
+    // TODO(crbug.com/343178421): ANGLE/SwiftShader and ANGLE/D3D11 fail with negative baseVertex.
+    DAWN_SUPPRESS_TEST_IF(IsANGLESwiftShader());
+    DAWN_SUPPRESS_TEST_IF(IsANGLED3D11());
+
+    // Also failing on Qualcomm GLES.
+    DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm());
+
+    utils::RGBA8 filled(0, 255, 0, 255);
+    utils::RGBA8 notFilled(0, 0, 0, 0);
 
     // Test negative baseVertex
     // Test a draw with only the first 3 indices of the first quad (bottom left triangle)
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 1cbcee4..ce607a0 100644
--- a/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
@@ -257,7 +257,8 @@
     data.Add<ast::transform::CanonicalizeEntryPointIO::Config>(
         ast::transform::CanonicalizeEntryPointIO::ShaderStyle::kGlsl);
 
-    data.Add<ast::transform::OffsetFirstIndex::Config>(std::nullopt, options.first_instance_offset);
+    data.Add<ast::transform::OffsetFirstIndex::Config>(options.first_vertex_offset,
+                                                       options.first_instance_offset);
 
     data.Add<ast::transform::ClampFragDepth::Config>(options.depth_range_offsets);
 
diff --git a/src/tint/lang/glsl/writer/common/options.h b/src/tint/lang/glsl/writer/common/options.h
index f6f114a..3ba7540 100644
--- a/src/tint/lang/glsl/writer/common/options.h
+++ b/src/tint/lang/glsl/writer/common/options.h
@@ -156,6 +156,9 @@
     /// Options used in the binding mappings for external textures
     ExternalTextureOptions external_texture_options = {};
 
+    /// Offset of the firstVertex push constant.
+    std::optional<int32_t> first_vertex_offset;
+
     /// Offset of the firstInstance push constant.
     std::optional<int32_t> first_instance_offset;
 
@@ -176,6 +179,7 @@
                  combined_samplers_info,
                  binding_remapper_options,
                  external_texture_options,
+                 first_vertex_offset,
                  first_instance_offset,
                  depth_range_offsets,
                  texture_builtins_from_uniform);