Fix indirect draw output indirect param offset on D3D12

When the flag kDuplicateBaseVertexInstance is on, we need to add
2 * sizeof(uint32_t) for the outputParamsOffset for each draw

Bug: chromium:1478906
Change-Id: I77b7d93c7e1bbb186911caebc3410360caef23c2
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/157941
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Loko Kung <lokokung@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Shrek Shao <shrekshao@google.com>
diff --git a/src/dawn/native/IndirectDrawValidationEncoder.cpp b/src/dawn/native/IndirectDrawValidationEncoder.cpp
index 7391461..7057a40 100644
--- a/src/dawn/native/IndirectDrawValidationEncoder.cpp
+++ b/src/dawn/native/IndirectDrawValidationEncoder.cpp
@@ -409,6 +409,10 @@
                 } else {
                     outputParamsOffset += kDrawIndirectSize;
                 }
+                if (pass.flags & kDuplicateBaseVertexInstance) {
+                    // Add the extra offset for the duplicated base vertex and instance.
+                    outputParamsOffset += 2 * sizeof(uint32_t);
+                }
             }
         }
     }
diff --git a/src/dawn/tests/end2end/DrawIndirectTests.cpp b/src/dawn/tests/end2end/DrawIndirectTests.cpp
index 92a4696..4ec2bca 100644
--- a/src/dawn/tests/end2end/DrawIndirectTests.cpp
+++ b/src/dawn/tests/end2end/DrawIndirectTests.cpp
@@ -25,6 +25,8 @@
 // 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 <vector>
+
 #include "dawn/tests/DawnTest.h"
 
 #include "dawn/utils/ComboRenderPipelineDescriptor.h"
@@ -34,6 +36,8 @@
 namespace {
 
 constexpr uint32_t kRTSize = 4;
+constexpr utils::RGBA8 filled(0, 255, 0, 255);
+constexpr utils::RGBA8 notFilled(0, 0, 0, 0);
 
 class DrawIndirectTest : public DawnTest {
   protected:
@@ -109,9 +113,6 @@
     // the offsets that Tint/GLSL produces.
     DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsLinux());
 
-    utils::RGBA8 filled(0, 255, 0, 255);
-    utils::RGBA8 notFilled(0, 0, 0, 0);
-
     // Test a draw with no indices.
     Test({0, 0, 0, 0}, 0, notFilled, notFilled);
 
@@ -130,9 +131,6 @@
     // the offsets that Tint/GLSL produces.
     DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsLinux());
 
-    utils::RGBA8 filled(0, 255, 0, 255);
-    utils::RGBA8 notFilled(0, 0, 0, 0);
-
     // Test an offset draw call, with indirect buffer containing 2 calls:
     // 1) only the first 3 indices (bottom left triangle)
     // 2) only the last 3 indices (top right triangle)
@@ -152,5 +150,203 @@
                       OpenGLESBackend(),
                       VulkanBackend());
 
+class DrawIndirectUsingFirstVertexTest : public DawnTest {
+  protected:
+    virtual void SetupShaderModule() {
+        vsModule = utils::CreateShaderModule(device, R"(
+            struct VertexInput {
+                @builtin(vertex_index) id : u32,
+                @location(0) pos: vec4f,
+            };
+
+            @group(0) @binding(0) var<uniform> offset: array<vec4f, 2>;
+
+            @vertex
+            fn main(input: VertexInput) -> @builtin(position) vec4f {
+                return input.pos + offset[input.id / 3u];
+            })");
+
+        fsModule = utils::CreateShaderModule(device, R"(
+            @fragment fn main() -> @location(0) vec4f {
+                return vec4f(0.0, 1.0, 0.0, 1.0);
+            })");
+    }
+
+    void GeneralSetup() {
+        renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize);
+
+        SetupShaderModule();
+
+        utils::ComboRenderPipelineDescriptor descriptor;
+        descriptor.vertex.module = vsModule;
+        descriptor.cFragment.module = fsModule;
+        descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleStrip;
+        descriptor.primitive.stripIndexFormat = wgpu::IndexFormat::Uint32;
+        descriptor.vertex.bufferCount = 1;
+        descriptor.cBuffers[0].arrayStride = 4 * sizeof(float);
+        descriptor.cBuffers[0].attributeCount = 1;
+        descriptor.cAttributes[0].format = wgpu::VertexFormat::Float32x4;
+        descriptor.cTargets[0].format = renderPass.colorFormat;
+
+        pipeline = device.CreateRenderPipeline(&descriptor);
+
+        // Offset to the vertices, that needs correcting by the calibration offset from uniform
+        // buffer referenced by instance index to get filled triangle on screen.
+        constexpr float calibration = 99.0f;
+
+        vertexBuffer = utils::CreateBufferFromData<float>(
+            device, wgpu::BufferUsage::Vertex,
+
+            {// The bottom left triangle
+             -1.0f - calibration, 1.0f, 0.0f, 1.0f, 1.0f - calibration, -1.0f, 0.0f, 1.0f,
+             -1.0f - calibration, -1.0f, 0.0f, 1.0f,
+
+             // The top right triangle
+             -1.0f - calibration, 1.0f, 0.0f, 1.0f, 1.0f - calibration, -1.0f, 0.0f, 1.0f,
+             1.0f - calibration, 1.0f, 0.0f, 1.0f});
+
+        // Providing calibration vec4f offset values
+        wgpu::Buffer uniformBuffer =
+            utils::CreateBufferFromData<float>(device, wgpu::BufferUsage::Uniform,
+                                               {
+                                                   // Bad calibration at [0]
+                                                   0.0,
+                                                   0.0,
+                                                   0.0,
+                                                   0.0,
+                                                   // Good calibration at [1]
+                                                   calibration,
+                                                   0.0,
+                                                   0.0,
+                                                   0.0,
+                                               });
+
+        bindGroup =
+            utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, uniformBuffer}});
+    }
+
+    void SetUp() override {
+        DawnTest::SetUp();
+        GeneralSetup();
+    }
+
+    utils::BasicRenderPass renderPass;
+    wgpu::RenderPipeline pipeline;
+    wgpu::Buffer vertexBuffer;
+    wgpu::BindGroup bindGroup;
+    wgpu::ShaderModule vsModule;
+    wgpu::ShaderModule fsModule;
+
+    // Test two DrawIndirect calls with different indirect offsets within one pass.
+    void Test(std::initializer_list<uint32_t> bufferList,
+              utils::RGBA8 bottomLeftExpected,
+              utils::RGBA8 topRightExpected) {
+        // Indirect buffer contains 2 draw params
+        DAWN_ASSERT(bufferList.size() == 8);
+        wgpu::Buffer indirectBuffer =
+            utils::CreateBufferFromData<uint32_t>(device, wgpu::BufferUsage::Indirect, bufferList);
+
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+        {
+            wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
+            pass.SetPipeline(pipeline);
+            pass.SetVertexBuffer(0, vertexBuffer);
+            pass.SetBindGroup(0, bindGroup);
+            pass.DrawIndirect(indirectBuffer, 0);
+            pass.DrawIndirect(indirectBuffer, 4 * sizeof(uint32_t));
+            pass.End();
+        }
+
+        wgpu::CommandBuffer commands = encoder.Finish();
+        queue.Submit(1, &commands);
+
+        EXPECT_PIXEL_RGBA8_EQ(bottomLeftExpected, renderPass.color, 1, 3);
+        EXPECT_PIXEL_RGBA8_EQ(topRightExpected, renderPass.color, 3, 1);
+    }
+};
+
+TEST_P(DrawIndirectUsingFirstVertexTest, IndirectOffset) {
+    // TODO(crbug.com/dawn/1292): Some Intel OpenGL drivers don't seem to like
+    // the offsets that Tint/GLSL produces.
+    DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsLinux());
+    // Won't fix for OpenGLES + ANGLE D3D11
+    DAWN_SUPPRESS_TEST_IF(IsANGLED3D11());
+
+    // Test an offset draw call, with indirect buffer containing 2 calls:
+    // 1) only the first 3 indices (bottom left triangle)
+    // 2) only the last 3 indices (top right triangle)
+
+    // #2 draw has the correct offset applied by vertex index
+    Test({3, 1, 0, 0, 3, 1, 3, 0}, notFilled, filled);
+}
+
+DAWN_INSTANTIATE_TEST(DrawIndirectUsingFirstVertexTest,
+                      D3D11Backend(),
+                      D3D12Backend(),
+                      MetalBackend(),
+                      OpenGLBackend(),
+                      OpenGLESBackend(),
+                      VulkanBackend());
+
+class DrawIndirectUsingInstanceIndexTest : public DrawIndirectUsingFirstVertexTest {
+  protected:
+    std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
+        if (!SupportsFeatures({wgpu::FeatureName::IndirectFirstInstance})) {
+            return {};
+        }
+        return {wgpu::FeatureName::IndirectFirstInstance};
+    }
+
+    void SetupShaderModule() override {
+        vsModule = utils::CreateShaderModule(device, R"(
+            struct VertexInput {
+                @builtin(instance_index) id : u32,
+                @location(0) pos: vec4f,
+            };
+
+            @group(0) @binding(0) var<uniform> offset: array<vec4f, 2>;
+
+            @vertex
+            fn main(input: VertexInput) -> @builtin(position) vec4f {
+                return input.pos + offset[input.id];
+            })");
+
+        fsModule = utils::CreateShaderModule(device, R"(
+            @fragment fn main() -> @location(0) vec4f {
+                return vec4f(0.0, 1.0, 0.0, 1.0);
+            })");
+    }
+
+    void SetUp() override {
+        DawnTest::SetUp();
+        DAWN_TEST_UNSUPPORTED_IF(!device.HasFeature(wgpu::FeatureName::IndirectFirstInstance));
+        GeneralSetup();
+    }
+};
+
+TEST_P(DrawIndirectUsingInstanceIndexTest, IndirectOffset) {
+    // TODO(crbug.com/dawn/1292): Some Intel OpenGL drivers don't seem to like
+    // the offsets that Tint/GLSL produces.
+    DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsLinux());
+
+    // Test an offset draw call, with indirect buffer containing 2 calls:
+    // 1) only the first 3 indices (bottom left triangle)
+    // 2) only the last 3 indices (top right triangle)
+
+    // Test 1: #1 draw has the correct calibration referenced by instance index
+    Test({3, 1, 0, 1, 3, 1, 3, 0}, filled, notFilled);
+
+    // Test 2: #2 draw has the correct offset applied by instance index
+    Test({3, 1, 0, 0, 3, 1, 3, 1}, notFilled, filled);
+}
+
+DAWN_INSTANTIATE_TEST(DrawIndirectUsingInstanceIndexTest,
+                      D3D11Backend(),
+                      D3D12Backend(),
+                      MetalBackend(),
+                      OpenGLBackend(),
+                      OpenGLESBackend(),
+                      VulkanBackend());
+
 }  // anonymous namespace
 }  // namespace dawn