Add Toggles to disable base vertex and base instance rendering

These are not supported on some older OpenGL, OpenGL ES, and iOS
devices.

Bug: dawn:343
Change-Id: I70def749ae57fcfe2895f8556674dd241941d3d3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/16163
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Jiawei Shao <jiawei.shao@intel.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/RenderEncoderBase.cpp b/src/dawn_native/RenderEncoderBase.cpp
index 0652d1f..532c448 100644
--- a/src/dawn_native/RenderEncoderBase.cpp
+++ b/src/dawn_native/RenderEncoderBase.cpp
@@ -27,13 +27,17 @@
 namespace dawn_native {
 
     RenderEncoderBase::RenderEncoderBase(DeviceBase* device, EncodingContext* encodingContext)
-        : ProgrammablePassEncoder(device, encodingContext) {
+        : ProgrammablePassEncoder(device, encodingContext),
+          mDisableBaseVertex(device->IsToggleEnabled(Toggle::DisableBaseVertex)),
+          mDisableBaseInstance(device->IsToggleEnabled(Toggle::DisableBaseInstance)) {
     }
 
     RenderEncoderBase::RenderEncoderBase(DeviceBase* device,
                                          EncodingContext* encodingContext,
                                          ErrorTag errorTag)
-        : ProgrammablePassEncoder(device, encodingContext, errorTag) {
+        : ProgrammablePassEncoder(device, encodingContext, errorTag),
+          mDisableBaseVertex(device->IsToggleEnabled(Toggle::DisableBaseVertex)),
+          mDisableBaseInstance(device->IsToggleEnabled(Toggle::DisableBaseInstance)) {
     }
 
     void RenderEncoderBase::Draw(uint32_t vertexCount,
@@ -41,6 +45,10 @@
                                  uint32_t firstVertex,
                                  uint32_t firstInstance) {
         mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            if (mDisableBaseInstance && firstInstance != 0) {
+                return DAWN_VALIDATION_ERROR("Non-zero first instance not supported");
+            }
+
             DrawCmd* draw = allocator->Allocate<DrawCmd>(Command::Draw);
             draw->vertexCount = vertexCount;
             draw->instanceCount = instanceCount;
@@ -57,6 +65,13 @@
                                         int32_t baseVertex,
                                         uint32_t firstInstance) {
         mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
+            if (mDisableBaseInstance && firstInstance != 0) {
+                return DAWN_VALIDATION_ERROR("Non-zero first instance not supported");
+            }
+            if (mDisableBaseInstance && baseVertex != 0) {
+                return DAWN_VALIDATION_ERROR("Non-zero base vertex not supported");
+            }
+
             DrawIndexedCmd* draw = allocator->Allocate<DrawIndexedCmd>(Command::DrawIndexed);
             draw->indexCount = indexCount;
             draw->instanceCount = instanceCount;
diff --git a/src/dawn_native/RenderEncoderBase.h b/src/dawn_native/RenderEncoderBase.h
index 906c9e0..33343ae 100644
--- a/src/dawn_native/RenderEncoderBase.h
+++ b/src/dawn_native/RenderEncoderBase.h
@@ -45,6 +45,10 @@
       protected:
         // Construct an "error" render encoder base.
         RenderEncoderBase(DeviceBase* device, EncodingContext* encodingContext, ErrorTag errorTag);
+
+      private:
+        const bool mDisableBaseVertex;
+        const bool mDisableBaseInstance;
     };
 
 }  // namespace dawn_native
diff --git a/src/dawn_native/Toggles.cpp b/src/dawn_native/Toggles.cpp
index 578d52d..4e002d2 100644
--- a/src/dawn_native/Toggles.cpp
+++ b/src/dawn_native/Toggles.cpp
@@ -106,6 +106,13 @@
              {"metal_disable_sampler_compare",
               "Disables the use of sampler compare on Metal. This is unsupported before A9 "
               "processors."}},
+            {Toggle::DisableBaseVertex,
+             {"disable_base_vertex",
+              "Disables the use of non-zero base vertex which is unsupported on some platforms."}},
+            {Toggle::DisableBaseInstance,
+             {"disable_base_instance",
+              "Disables the use of non-zero base instance which is unsupported on some "
+              "platforms."}},
         }};
 
     }  // anonymous namespace
diff --git a/src/dawn_native/Toggles.h b/src/dawn_native/Toggles.h
index 3891f98..c9d7352 100644
--- a/src/dawn_native/Toggles.h
+++ b/src/dawn_native/Toggles.h
@@ -37,6 +37,8 @@
         UseSpvcParser,
         VulkanUseD32S8,
         MetalDisableSamplerCompare,
+        DisableBaseVertex,
+        DisableBaseInstance,
 
         EnumCount,
         InvalidEnum = EnumCount,
diff --git a/src/dawn_native/metal/CommandBufferMTL.mm b/src/dawn_native/metal/CommandBufferMTL.mm
index 667404d..628f77b 100644
--- a/src/dawn_native/metal/CommandBufferMTL.mm
+++ b/src/dawn_native/metal/CommandBufferMTL.mm
@@ -1018,11 +1018,19 @@
 
                     // The instance count must be non-zero, otherwise no-op
                     if (draw->instanceCount != 0) {
-                        [encoder drawPrimitives:lastPipeline->GetMTLPrimitiveTopology()
-                                    vertexStart:draw->firstVertex
-                                    vertexCount:draw->vertexCount
-                                  instanceCount:draw->instanceCount
-                                   baseInstance:draw->firstInstance];
+                        // MTLFeatureSet_iOS_GPUFamily3_v1 does not support baseInstance
+                        if (draw->firstInstance == 0) {
+                            [encoder drawPrimitives:lastPipeline->GetMTLPrimitiveTopology()
+                                        vertexStart:draw->firstVertex
+                                        vertexCount:draw->vertexCount
+                                      instanceCount:draw->instanceCount];
+                        } else {
+                            [encoder drawPrimitives:lastPipeline->GetMTLPrimitiveTopology()
+                                        vertexStart:draw->firstVertex
+                                        vertexCount:draw->vertexCount
+                                      instanceCount:draw->instanceCount
+                                       baseInstance:draw->firstInstance];
+                        }
                     }
                 } break;
 
@@ -1037,15 +1045,27 @@
 
                     // The index and instance count must be non-zero, otherwise no-op
                     if (draw->indexCount != 0 && draw->instanceCount != 0) {
-                        [encoder drawIndexedPrimitives:lastPipeline->GetMTLPrimitiveTopology()
-                                            indexCount:draw->indexCount
-                                             indexType:lastPipeline->GetMTLIndexType()
-                                           indexBuffer:indexBuffer
-                                     indexBufferOffset:indexBufferBaseOffset +
-                                                       draw->firstIndex * formatSize
-                                         instanceCount:draw->instanceCount
-                                            baseVertex:draw->baseVertex
-                                          baseInstance:draw->firstInstance];
+                        // MTLFeatureSet_iOS_GPUFamily3_v1 does not support baseInstance and
+                        // baseVertex.
+                        if (draw->baseVertex == 0 && draw->firstInstance == 0) {
+                            [encoder drawIndexedPrimitives:lastPipeline->GetMTLPrimitiveTopology()
+                                                indexCount:draw->indexCount
+                                                 indexType:lastPipeline->GetMTLIndexType()
+                                               indexBuffer:indexBuffer
+                                         indexBufferOffset:indexBufferBaseOffset +
+                                                           draw->firstIndex * formatSize
+                                             instanceCount:draw->instanceCount];
+                        } else {
+                            [encoder drawIndexedPrimitives:lastPipeline->GetMTLPrimitiveTopology()
+                                                indexCount:draw->indexCount
+                                                 indexType:lastPipeline->GetMTLIndexType()
+                                               indexBuffer:indexBuffer
+                                         indexBufferOffset:indexBufferBaseOffset +
+                                                           draw->firstIndex * formatSize
+                                             instanceCount:draw->instanceCount
+                                                baseVertex:draw->baseVertex
+                                              baseInstance:draw->firstInstance];
+                        }
                     }
                 } break;
 
diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm
index 02873ac..d449d48 100644
--- a/src/dawn_native/metal/DeviceMTL.mm
+++ b/src/dawn_native/metal/DeviceMTL.mm
@@ -76,6 +76,15 @@
 #endif
             // TODO(crbug.com/dawn/342): Investigate emulation -- possibly expensive.
             SetToggle(Toggle::MetalDisableSamplerCompare, !haveSamplerCompare);
+
+            bool haveBaseVertexBaseInstance = true;
+#if defined(DAWN_PLATFORM_IOS)
+            haveBaseVertexBaseInstance =
+                [mMtlDevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1];
+#endif
+            // TODO(crbug.com/dawn/343): Investigate emulation.
+            SetToggle(Toggle::DisableBaseVertex, !haveBaseVertexBaseInstance);
+            SetToggle(Toggle::DisableBaseInstance, !haveBaseVertexBaseInstance);
         }
 
         // TODO(jiawei.shao@intel.com): tighten this workaround when the driver bug is fixed.
diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp
index 9bbf740..2194016 100644
--- a/src/dawn_native/opengl/CommandBufferGL.cpp
+++ b/src/dawn_native/opengl/CommandBufferGL.cpp
@@ -869,12 +869,23 @@
                                                     indexBufferBaseOffset),
                             draw->instanceCount, draw->baseVertex, draw->firstInstance);
                     } else {
-                        // This branch is only needed on OpenGL < 4.2
-                        gl.DrawElementsInstancedBaseVertex(
-                            lastPipeline->GetGLPrimitiveTopology(), draw->indexCount, formatType,
-                            reinterpret_cast<void*>(draw->firstIndex * formatSize +
-                                                    indexBufferBaseOffset),
-                            draw->instanceCount, draw->baseVertex);
+                        // This branch is only needed on OpenGL < 4.2; ES < 3.2
+                        if (draw->baseVertex != 0) {
+                            gl.DrawElementsInstancedBaseVertex(
+                                lastPipeline->GetGLPrimitiveTopology(), draw->indexCount,
+                                formatType,
+                                reinterpret_cast<void*>(draw->firstIndex * formatSize +
+                                                        indexBufferBaseOffset),
+                                draw->instanceCount, draw->baseVertex);
+                        } else {
+                            // This branch is only needed on OpenGL < 3.2; ES < 3.2
+                            gl.DrawElementsInstanced(
+                                lastPipeline->GetGLPrimitiveTopology(), draw->indexCount,
+                                formatType,
+                                reinterpret_cast<void*>(draw->firstIndex * formatSize +
+                                                        indexBufferBaseOffset),
+                                draw->instanceCount);
+                        }
                     }
                 } break;
 
diff --git a/src/dawn_native/opengl/DeviceGL.cpp b/src/dawn_native/opengl/DeviceGL.cpp
index f6c2e1a..26d2c33 100644
--- a/src/dawn_native/opengl/DeviceGL.cpp
+++ b/src/dawn_native/opengl/DeviceGL.cpp
@@ -36,6 +36,7 @@
                    const DeviceDescriptor* descriptor,
                    const OpenGLFunctions& functions)
         : DeviceBase(adapter, descriptor), gl(functions) {
+        InitTogglesFromDriver();
         if (descriptor != nullptr) {
             ApplyToggleOverrides(descriptor);
         }
@@ -46,6 +47,30 @@
         BaseDestructor();
     }
 
+    void Device::InitTogglesFromDriver() {
+        bool supportsBaseVertex = gl.IsAtLeastGLES(3, 2) || gl.IsAtLeastGL(3, 2);
+
+        bool supportsBaseInstance = gl.IsAtLeastGLES(3, 2) || gl.IsAtLeastGL(4, 2);
+
+        // 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"));
+
+        // TODO(crbug.com/dawn/343): Investigate emulation.
+        SetToggle(Toggle::DisableBaseVertex, !supportsBaseVertex);
+        SetToggle(Toggle::DisableBaseInstance, !supportsBaseInstance);
+    }
+
     const GLFormat& Device::GetGLFormat(const Format& format) {
         ASSERT(format.isSupported);
         ASSERT(format.GetIndex() < mFormatTable.size());
diff --git a/src/dawn_native/opengl/DeviceGL.h b/src/dawn_native/opengl/DeviceGL.h
index 7d0b803..d9ec9c0 100644
--- a/src/dawn_native/opengl/DeviceGL.h
+++ b/src/dawn_native/opengl/DeviceGL.h
@@ -89,6 +89,7 @@
             TextureBase* texture,
             const TextureViewDescriptor* descriptor) override;
 
+        void InitTogglesFromDriver();
         void CheckPassedFences();
         void Destroy() override;
         MaybeError WaitForIdleForDestruction() override;
diff --git a/src/dawn_native/opengl/OpenGLFunctions.cpp b/src/dawn_native/opengl/OpenGLFunctions.cpp
index 0bc5781..ba5b69a 100644
--- a/src/dawn_native/opengl/OpenGLFunctions.cpp
+++ b/src/dawn_native/opengl/OpenGLFunctions.cpp
@@ -74,12 +74,12 @@
         return mSupportedGLExtensionsSet.count(extension) != 0;
     }
 
-    bool OpenGLFunctions::IsAtLeastGL(uint32_t majorVersion, uint32_t minorVersion) {
+    bool OpenGLFunctions::IsAtLeastGL(uint32_t majorVersion, uint32_t minorVersion) const {
         return mStandard == Standard::Desktop &&
                std::tie(mMajorVersion, mMinorVersion) >= std::tie(majorVersion, minorVersion);
     }
 
-    bool OpenGLFunctions::IsAtLeastGLES(uint32_t majorVersion, uint32_t minorVersion) {
+    bool OpenGLFunctions::IsAtLeastGLES(uint32_t majorVersion, uint32_t minorVersion) const {
         return mStandard == Standard::ES &&
                std::tie(mMajorVersion, mMinorVersion) >= std::tie(majorVersion, minorVersion);
     }
diff --git a/src/dawn_native/opengl/OpenGLFunctions.h b/src/dawn_native/opengl/OpenGLFunctions.h
index 14c7e91..e514300 100644
--- a/src/dawn_native/opengl/OpenGLFunctions.h
+++ b/src/dawn_native/opengl/OpenGLFunctions.h
@@ -25,8 +25,8 @@
       public:
         MaybeError Initialize(GetProcAddress getProc);
 
-        bool IsAtLeastGL(uint32_t majorVersion, uint32_t minorVersion);
-        bool IsAtLeastGLES(uint32_t majorVersion, uint32_t minorVersion);
+        bool IsAtLeastGL(uint32_t majorVersion, uint32_t minorVersion) const;
+        bool IsAtLeastGLES(uint32_t majorVersion, uint32_t minorVersion) const;
 
         bool IsGLExtensionSupported(const char* extension) const;