Dynamic Buffer Offset : OpenGL Backend

In a typical graphics application it is a common usage to update some uniforms once per draw,
and such uniforms include the word positions, orientations, and so on. In the current state of
WebGPU, this means that for each draw call we have to create a new bind group to set the right
uniform values. Bind group creation is expected to be more expensive than
recording draws because a memory allocation is required.

The functionality of dynamic buffer offset is to reduce the number of bind groups that need to
be created.

The patch implements dynamic buffer offset on OpenGL backend using glBindBufferRange and adds
validation to check whether visibility of resources are none.

Bug=dawn:55

Change-Id: I77e10a9677d1737f377301ee89e29d904c91c298
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/9540
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Shaobo Yan <shaobo.yan@intel.com>
diff --git a/src/dawn_native/BindGroupLayout.cpp b/src/dawn_native/BindGroupLayout.cpp
index 90cdb06..4abff6e 100644
--- a/src/dawn_native/BindGroupLayout.cpp
+++ b/src/dawn_native/BindGroupLayout.cpp
@@ -42,6 +42,10 @@
                 return DAWN_VALIDATION_ERROR("some binding index was specified more than once");
             }
 
+            if (binding.visibility == dawn::ShaderStageBit::None) {
+                return DAWN_VALIDATION_ERROR("Visibility of bindings can't be None");
+            }
+
             switch (binding.type) {
                 case dawn::BindingType::UniformBuffer:
                 case dawn::BindingType::StorageBuffer:
diff --git a/src/dawn_native/opengl/CommandBufferGL.cpp b/src/dawn_native/opengl/CommandBufferGL.cpp
index c520a3f..d7a07bc 100644
--- a/src/dawn_native/opengl/CommandBufferGL.cpp
+++ b/src/dawn_native/opengl/CommandBufferGL.cpp
@@ -220,9 +220,12 @@
                             uint32_t index,
                             BindGroupBase* group,
                             PipelineLayout* pipelineLayout,
-                            PipelineGL* pipeline) {
+                            PipelineGL* pipeline,
+                            uint32_t dynamicOffsetCount,
+                            uint64_t* dynamicOffsets) {
             const auto& indices = pipelineLayout->GetBindingIndexInfo()[index];
             const auto& layout = group->GetLayout()->GetBindingInfo();
+            uint32_t currentDynamicIndex = 0;
 
             for (uint32_t bindingIndex : IterateBitSet(layout.mask)) {
                 switch (layout.types[bindingIndex]) {
@@ -230,8 +233,14 @@
                         BufferBinding binding = group->GetBindingAsBufferBinding(bindingIndex);
                         GLuint buffer = ToBackend(binding.buffer)->GetHandle();
                         GLuint uboIndex = indices[bindingIndex];
+                        GLuint offset = binding.offset;
 
-                        gl.BindBufferRange(GL_UNIFORM_BUFFER, uboIndex, buffer, binding.offset,
+                        if (layout.dynamic[bindingIndex]) {
+                            offset += dynamicOffsets[currentDynamicIndex];
+                            ++currentDynamicIndex;
+                        }
+
+                        gl.BindBufferRange(GL_UNIFORM_BUFFER, uboIndex, buffer, offset,
                                            binding.size);
                     } break;
 
@@ -261,9 +270,15 @@
                         BufferBinding binding = group->GetBindingAsBufferBinding(bindingIndex);
                         GLuint buffer = ToBackend(binding.buffer)->GetHandle();
                         GLuint ssboIndex = indices[bindingIndex];
+                        GLuint offset = binding.offset;
 
-                        gl.BindBufferRange(GL_SHADER_STORAGE_BUFFER, ssboIndex, buffer,
-                                           binding.offset, binding.size);
+                        if (layout.dynamic[bindingIndex]) {
+                            offset += dynamicOffsets[currentDynamicIndex];
+                            ++currentDynamicIndex;
+                        }
+
+                        gl.BindBufferRange(GL_SHADER_STORAGE_BUFFER, ssboIndex, buffer, offset,
+                                           binding.size);
                     } break;
 
                     case dawn::BindingType::StorageTexture:
@@ -555,8 +570,13 @@
 
                 case Command::SetBindGroup: {
                     SetBindGroupCmd* cmd = mCommands.NextCommand<SetBindGroupCmd>();
+                    uint64_t* dynamicOffsets = nullptr;
+                    if (cmd->dynamicOffsetCount > 0) {
+                        dynamicOffsets = mCommands.NextData<uint64_t>(cmd->dynamicOffsetCount);
+                    }
                     ApplyBindGroup(gl, cmd->index, cmd->group.Get(),
-                                   ToBackend(lastPipeline->GetLayout()), lastPipeline);
+                                   ToBackend(lastPipeline->GetLayout()), lastPipeline,
+                                   cmd->dynamicOffsetCount, dynamicOffsets);
                 } break;
 
                 default: { UNREACHABLE(); } break;
@@ -830,8 +850,13 @@
 
                 case Command::SetBindGroup: {
                     SetBindGroupCmd* cmd = mCommands.NextCommand<SetBindGroupCmd>();
+                    uint64_t* dynamicOffsets = nullptr;
+                    if (cmd->dynamicOffsetCount > 0) {
+                        dynamicOffsets = mCommands.NextData<uint64_t>(cmd->dynamicOffsetCount);
+                    }
                     ApplyBindGroup(gl, cmd->index, cmd->group.Get(),
-                                   ToBackend(lastPipeline->GetLayout()), lastPipeline);
+                                   ToBackend(lastPipeline->GetLayout()), lastPipeline,
+                                   cmd->dynamicOffsetCount, dynamicOffsets);
                 } break;
 
                 case Command::SetIndexBuffer: {
diff --git a/src/tests/end2end/DynamicBufferOffsetTests.cpp b/src/tests/end2end/DynamicBufferOffsetTests.cpp
index 0a25fd0..1af54ae 100644
--- a/src/tests/end2end/DynamicBufferOffsetTests.cpp
+++ b/src/tests/end2end/DynamicBufferOffsetTests.cpp
@@ -212,4 +212,4 @@
                                kMinDynamicBufferOffsetAlignment, expectedData.size());
 }
 
-DAWN_INSTANTIATE_TEST(DynamicBufferOffsetTests, MetalBackend, VulkanBackend);
+DAWN_INSTANTIATE_TEST(DynamicBufferOffsetTests, MetalBackend, OpenGLBackend, VulkanBackend);
diff --git a/src/tests/unittests/validation/BindGroupValidationTests.cpp b/src/tests/unittests/validation/BindGroupValidationTests.cpp
index 016dc0b..7bf8b0a 100644
--- a/src/tests/unittests/validation/BindGroupValidationTests.cpp
+++ b/src/tests/unittests/validation/BindGroupValidationTests.cpp
@@ -472,6 +472,21 @@
     ASSERT_EQ(layout1.Get(), layout2.Get());
 }
 
+// This test verifies that visibility of bindings in BindGroupLayout can't be none
+TEST_F(BindGroupLayoutValidationTest, BindGroupLayoutVisibilityNone) {
+    utils::MakeBindGroupLayout(
+        device, {
+                    {0, dawn::ShaderStageBit::Vertex, dawn::BindingType::UniformBuffer},
+                });
+
+    dawn::BindGroupLayoutBinding binding = {0, dawn::ShaderStageBit::None,
+                                            dawn::BindingType::UniformBuffer};
+    dawn::BindGroupLayoutDescriptor descriptor;
+    descriptor.bindingCount = 1;
+    descriptor.bindings = &binding;
+    ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&descriptor));
+}
+
 constexpr uint64_t kBufferSize = 2 * kMinDynamicBufferOffsetAlignment + 8;
 constexpr uint32_t kBindingSize = 9;