dawn-cmake/src/tests/unittests/validation/ResourceUsageTrackingTests.cpp
Yunchao He 23428ea82f Add validation code for texture subresource usage tracking
This patch also add validation tests for texture subresource tracking
for render pass. Resource usage tracking for compute is per each
dispatch() call, I will add it in next patch.

BUG=dawn:157

Change-Id: I6c4b932e317d66521fa428311e727876d0adf4ea
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/17661
Commit-Queue: Yunchao He <yunchao.he@intel.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
2020-05-04 17:10:49 +00:00

1136 lines
56 KiB
C++

// Copyright 2020 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "tests/unittests/validation/ValidationTest.h"
#include "utils/ComboRenderPipelineDescriptor.h"
#include "utils/WGPUHelpers.h"
namespace {
class ResourceUsageTrackingTest : public ValidationTest {
protected:
wgpu::Buffer CreateBuffer(uint64_t size, wgpu::BufferUsage usage) {
wgpu::BufferDescriptor descriptor;
descriptor.size = size;
descriptor.usage = usage;
return device.CreateBuffer(&descriptor);
}
wgpu::Texture CreateTexture(wgpu::TextureUsage usage) {
wgpu::TextureDescriptor descriptor;
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.size = {1, 1, 1};
descriptor.arrayLayerCount = 1;
descriptor.sampleCount = 1;
descriptor.mipLevelCount = 1;
descriptor.usage = usage;
descriptor.format = kFormat;
return device.CreateTexture(&descriptor);
}
wgpu::RenderPipeline CreateNoOpRenderPipeline() {
wgpu::ShaderModule vsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
#version 450
void main() {
})");
wgpu::ShaderModule fsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
#version 450
void main() {
})");
utils::ComboRenderPipelineDescriptor pipelineDescriptor(device);
pipelineDescriptor.vertexStage.module = vsModule;
pipelineDescriptor.cFragmentStage.module = fsModule;
pipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, nullptr);
return device.CreateRenderPipeline(&pipelineDescriptor);
}
wgpu::ComputePipeline CreateNoOpComputePipeline() {
wgpu::ShaderModule csModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, R"(
#version 450
void main() {
})");
wgpu::ComputePipelineDescriptor pipelineDescriptor;
pipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, nullptr);
pipelineDescriptor.computeStage.module = csModule;
pipelineDescriptor.computeStage.entryPoint = "main";
return device.CreateComputePipeline(&pipelineDescriptor);
}
static constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm;
};
// Test that using a single buffer in multiple read usages in the same pass is allowed.
TEST_F(ResourceUsageTrackingTest, BufferWithMultipleReadUsage) {
// Test render pass
{
// Create a buffer, and use the buffer as both vertex and index buffer.
wgpu::Buffer buffer =
CreateBuffer(4, wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Index);
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
DummyRenderPass dummyRenderPass(device);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
pass.SetIndexBuffer(buffer);
pass.SetVertexBuffer(0, buffer);
pass.EndPass();
encoder.Finish();
}
// Test compute pass
{
// Create buffer and bind group
wgpu::Buffer buffer =
CreateBuffer(4, wgpu::BufferUsage::Uniform | wgpu::BufferUsage::Storage);
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device,
{{0, wgpu::ShaderStage::Compute, wgpu::BindingType::UniformBuffer},
{1, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageBuffer}});
wgpu::BindGroup bg =
utils::MakeBindGroup(device, bgl, {{0, buffer, 0, 4}, {1, buffer, 0, 4}});
// Use the buffer as both uniform and readonly storage buffer in compute pass.
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetBindGroup(0, bg);
pass.EndPass();
encoder.Finish();
}
}
// Test that it is invalid to use the same buffer as both readable and writable in the same
// render pass. But it is valid in compute pass.
TEST_F(ResourceUsageTrackingTest, BufferWithReadAndWriteUsage) {
// test render pass for index buffer and storage buffer
{
// Create buffer and bind group
wgpu::Buffer buffer =
CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index);
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer, 0, 4}});
// It is invalid to use the buffer as both index and storage in render pass
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
DummyRenderPass dummyRenderPass(device);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
pass.SetIndexBuffer(buffer);
pass.SetBindGroup(0, bg);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// test compute pass
{
// Create buffer and bind group
wgpu::Buffer buffer = CreateBuffer(512, wgpu::BufferUsage::Storage);
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device,
{{0, wgpu::ShaderStage::Compute, wgpu::BindingType::StorageBuffer},
{1, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageBuffer}});
wgpu::BindGroup bg =
utils::MakeBindGroup(device, bgl, {{0, buffer, 0, 4}, {1, buffer, 256, 4}});
// It is valid to use the buffer as both storage and readonly storage in compute pass.
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetBindGroup(0, bg);
pass.EndPass();
encoder.Finish();
}
}
// Test that using the same buffer as both readable and writable in different passes is allowed
TEST_F(ResourceUsageTrackingTest, BufferWithReadAndWriteUsageInDifferentPasses) {
// Test render pass
{
// Create buffers that will be used as index and storage buffers
wgpu::Buffer buffer0 =
CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index);
wgpu::Buffer buffer1 =
CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index);
// Create bind groups to use the buffer as storage
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl, {{0, buffer0, 0, 4}});
wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl, {{0, buffer1, 0, 4}});
// Use these two buffers as both index and storage in different render passes
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
DummyRenderPass dummyRenderPass(device);
wgpu::RenderPassEncoder pass0 = encoder.BeginRenderPass(&dummyRenderPass);
pass0.SetIndexBuffer(buffer0);
pass0.SetBindGroup(0, bg1);
pass0.EndPass();
wgpu::RenderPassEncoder pass1 = encoder.BeginRenderPass(&dummyRenderPass);
pass1.SetIndexBuffer(buffer1);
pass1.SetBindGroup(0, bg0);
pass1.EndPass();
encoder.Finish();
}
// Test compute pass
{
// Create buffer and bind groups that will be used as storage and uniform bindings
wgpu::Buffer buffer =
CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Uniform);
wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::UniformBuffer}});
wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, buffer, 0, 4}});
wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, buffer, 0, 4}});
// Use the buffer as both storage and uniform in different compute passes
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass0 = encoder.BeginComputePass();
pass0.SetBindGroup(0, bg0);
pass0.EndPass();
wgpu::ComputePassEncoder pass1 = encoder.BeginComputePass();
pass1.SetBindGroup(1, bg1);
pass1.EndPass();
encoder.Finish();
}
// Test render pass and compute pass mixed together with resource dependency.
{
// Create buffer and bind groups that will be used as storage and uniform bindings
wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage);
wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout(
device,
{{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::ReadonlyStorageBuffer}});
wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, buffer, 0, 4}});
wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, buffer, 0, 4}});
// Use the buffer as storage and uniform in render pass and compute pass respectively
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass0 = encoder.BeginComputePass();
pass0.SetBindGroup(0, bg0);
pass0.EndPass();
DummyRenderPass dummyRenderPass(device);
wgpu::RenderPassEncoder pass1 = encoder.BeginRenderPass(&dummyRenderPass);
pass1.SetBindGroup(1, bg1);
pass1.EndPass();
encoder.Finish();
}
}
// Test that it is invalid to use the same buffer as both readable and writable in different
// draws in a single render pass. But it is valid in different dispatches in a single compute
// pass.
TEST_F(ResourceUsageTrackingTest, BufferWithReadAndWriteUsageInDifferentDrawsOrDispatches) {
// Test render pass
{
// Create a buffer and a bind group
wgpu::Buffer buffer =
CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index);
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer, 0, 4}});
// Create a no-op render pipeline. Note that bind groups can have more bindings
// than pipeline.
wgpu::RenderPipeline rp = CreateNoOpRenderPipeline();
// It is not allowed to use the same buffer as both readable and writable in different
// draws within the same render pass.
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
DummyRenderPass dummyRenderPass(device);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
pass.SetPipeline(rp);
pass.SetIndexBuffer(buffer);
pass.Draw(3);
pass.SetBindGroup(0, bg);
pass.Draw(3);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// test compute pass
{
// Create a buffer and bind groups
wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage);
wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout(
device,
{{0, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageBuffer}});
wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, buffer, 0, 4}});
wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, buffer, 0, 4}});
// Create a no-op compute pipeline. Note that bind groups can have more bindings
// than pipeline.
wgpu::ComputePipeline cp = CreateNoOpComputePipeline();
// It is valid to use the same buffer as both readable and writable in different
// dispatches within the same compute pass.
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetPipeline(cp);
pass.SetBindGroup(0, bg0);
pass.Dispatch(1);
pass.SetBindGroup(0, bg1);
pass.Dispatch(1);
pass.EndPass();
encoder.Finish();
}
}
// Test that it is invalid to use the same buffer as both readable and writable in a single
// draw or dispatch.
TEST_F(ResourceUsageTrackingTest, BufferWithReadAndWriteUsageInSingleDrawOrDispatch) {
// Test render pass
{
// Create a buffer and a bind group
wgpu::Buffer buffer =
CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index);
wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, buffer, 0, 4}});
// Create a no-op render pipeline. Note that bind groups can have more bindings
// than pipeline.
wgpu::RenderPipeline rp = CreateNoOpRenderPipeline();
// It is invalid to use the same buffer as both readable and writable usages in a single
// draw
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
DummyRenderPass dummyRenderPass(device);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
pass.SetPipeline(rp);
pass.SetIndexBuffer(buffer);
pass.SetBindGroup(0, writeBG);
pass.Draw(3);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// test compute pass
{
// Create a buffer and bind groups
wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage);
wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout(
device,
{{0, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageBuffer}});
wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, buffer, 0, 4}});
wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, buffer, 0, 4}});
// Create a no-op compute pipeline. Note that bind groups can have more bindings
// than pipeline.
wgpu::ComputePipeline cp = CreateNoOpComputePipeline();
// It is invalid to use the same buffer as both readable and writable usages in a single
// dispatch
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetPipeline(cp);
pass.SetBindGroup(0, readBG);
pass.SetBindGroup(1, writeBG);
pass.Dispatch(1);
pass.EndPass();
// TODO (yunchao.he@intel.com): add buffer usage tracking for compute
// ASSERT_DEVICE_ERROR(encoder.Finish());
encoder.Finish();
}
}
// Test that using the same buffer as copy src/dst and writable/readable usage is allowed.
TEST_F(ResourceUsageTrackingTest, BufferCopyAndBufferUsageInPass) {
// Create buffers that will be used as both a copy src/dst buffer and a storage buffer
wgpu::Buffer bufferSrc =
CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc);
wgpu::Buffer bufferDst =
CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst);
// Create the bind group to use the buffer as storage
wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, bufferSrc, 0, 4}});
wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageBuffer}});
wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, bufferDst, 0, 4}});
// Use the buffer as both copy src and storage in render pass
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyBufferToBuffer(bufferSrc, 0, bufferDst, 0, 4);
DummyRenderPass dummyRenderPass(device);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
pass.SetBindGroup(0, bg0);
pass.EndPass();
encoder.Finish();
}
// Use the buffer as both copy dst and readonly storage in compute pass
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyBufferToBuffer(bufferSrc, 0, bufferDst, 0, 4);
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetBindGroup(0, bg1);
pass.EndPass();
encoder.Finish();
}
}
// Test that all index buffers and vertex buffers take effect even though some buffers are
// not used because they are overwritten by another consecutive call.
TEST_F(ResourceUsageTrackingTest, BufferWithMultipleSetIndexOrVertexBuffer) {
// Create buffers that will be used as both vertex and index buffer.
wgpu::Buffer buffer0 = CreateBuffer(
4, wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Index | wgpu::BufferUsage::Storage);
wgpu::Buffer buffer1 =
CreateBuffer(4, wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Index);
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer0, 0, 4}});
DummyRenderPass dummyRenderPass(device);
// Set index buffer twice. The second one overwrites the first one. No buffer is used as
// both read and write in the same pass. But the overwritten index buffer (buffer0) still
// take effect during resource tracking.
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
pass.SetIndexBuffer(buffer0);
pass.SetIndexBuffer(buffer1);
pass.SetBindGroup(0, bg);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Set index buffer twice. The second one overwrites the first one. buffer0 is used as both
// read and write in the same pass
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
pass.SetIndexBuffer(buffer1);
pass.SetIndexBuffer(buffer0);
pass.SetBindGroup(0, bg);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Set vertex buffer on the same index twice. The second one overwrites the first one. No
// buffer is used as both read and write in the same pass. But the overwritten vertex buffer
// (buffer0) still take effect during resource tracking.
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
pass.SetVertexBuffer(0, buffer0);
pass.SetVertexBuffer(0, buffer1);
pass.SetBindGroup(0, bg);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Set vertex buffer on the same index twice. The second one overwrites the first one.
// buffer0 is used as both read and write in the same pass
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
pass.SetVertexBuffer(0, buffer1);
pass.SetVertexBuffer(0, buffer0);
pass.SetBindGroup(0, bg);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
// Test that all consecutive SetBindGroup()s take effect even though some bind groups are not
// used because they are overwritten by a consecutive call.
TEST_F(ResourceUsageTrackingTest, BufferWithMultipleSetBindGroupsOnSameIndex) {
// test render pass
{
// Create buffers that will be used as index and storage buffers
wgpu::Buffer buffer0 =
CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index);
wgpu::Buffer buffer1 =
CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index);
// Create the bind group to use the buffer as storage
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl, {{0, buffer0, 0, 4}});
wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl, {{0, buffer1, 0, 4}});
DummyRenderPass dummyRenderPass(device);
// Set bind group on the same index twice. The second one overwrites the first one.
// No buffer is used as both read and write in the same pass. But the overwritten
// bind group still take effect during resource tracking.
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
pass.SetIndexBuffer(buffer0);
pass.SetBindGroup(0, bg0);
pass.SetBindGroup(0, bg1);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Set bind group on the same index twice. The second one overwrites the first one.
// buffer0 is used as both read and write in the same pass
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
pass.SetIndexBuffer(buffer0);
pass.SetBindGroup(0, bg1);
pass.SetBindGroup(0, bg0);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
// test compute pass
{
// Create buffers that will be used as index and storage buffers
wgpu::Buffer buffer0 = CreateBuffer(512, wgpu::BufferUsage::Storage);
wgpu::Buffer buffer1 = CreateBuffer(4, wgpu::BufferUsage::Storage);
// Create the bind group to use the buffer as storage
wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout(
device,
{{0, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageBuffer}});
wgpu::BindGroup writeBG0 = utils::MakeBindGroup(device, writeBGL, {{0, buffer0, 0, 4}});
wgpu::BindGroup readBG0 = utils::MakeBindGroup(device, readBGL, {{0, buffer0, 256, 4}});
wgpu::BindGroup readBG1 = utils::MakeBindGroup(device, readBGL, {{0, buffer1, 0, 4}});
// Create a no-op compute pipeline. Note that bind groups can have more bindings
// than pipeline.
wgpu::ComputePipeline cp = CreateNoOpComputePipeline();
// Set bind group against the same index twice. The second one overwrites the first one.
// Then no buffer is used as both read and write in the same pass. But the overwritten
// bind group still take effect.
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetBindGroup(0, writeBG0);
pass.SetBindGroup(1, readBG0);
pass.SetBindGroup(1, readBG1);
pass.SetPipeline(cp);
pass.Dispatch(1);
pass.EndPass();
// TODO (yunchao.he@intel.com): add buffer usage tracking for compute
// ASSERT_DEVICE_ERROR(encoder.Finish());
encoder.Finish();
}
// Set bind group against the same index twice. The second one overwrites the first one.
// Then buffer0 is used as both read and write in the same pass
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetBindGroup(0, writeBG0);
pass.SetBindGroup(1, readBG1);
pass.SetBindGroup(1, readBG0);
pass.SetPipeline(cp);
pass.Dispatch(1);
pass.EndPass();
// TODO (yunchao.he@intel.com): add buffer usage tracking for compute
// ASSERT_DEVICE_ERROR(encoder.Finish());
encoder.Finish();
}
}
}
// Test that it is invalid to have resource usage conflicts even when all bindings are not
// visible to the programmable pass where it is used.
TEST_F(ResourceUsageTrackingTest, BufferUsageConflictBetweenInvisibleStagesInBindGroup) {
wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage);
// Test render pass for bind group. The conflict of readonly storage and storage usage
// doesn't reside in render related stages at all
{
// Create a bind group whose bindings are not visible in render pass
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::StorageBuffer},
{1, wgpu::ShaderStage::None, wgpu::BindingType::ReadonlyStorageBuffer}});
wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}, {1, buffer}});
// These two bindings are invisible in render pass. But we still track these bindings.
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
DummyRenderPass dummyRenderPass(device);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
pass.SetBindGroup(0, bg);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Test compute pass for bind group. The conflict of readonly storage and storage usage
// doesn't reside in compute related stage at all
{
// Create a bind group whose bindings are not visible in compute pass
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::ReadonlyStorageBuffer},
{1, wgpu::ShaderStage::None, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}, {1, buffer}});
// These two bindings are invisible in compute pass. We still track these invisible
// bindings, but read and write usages in one compute pass is allowed.
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetBindGroup(0, bg);
pass.EndPass();
encoder.Finish();
}
}
// Test that it is invalid to have resource usage conflicts even when one of the bindings is not
// visible to the programmable pass where it is used.
TEST_F(ResourceUsageTrackingTest, BufferUsageConflictWithInvisibleStageInBindGroup) {
// Test render pass for bind group and index buffer. The conflict of storage and index
// buffer usage resides between fragment stage and compute stage. But the compute stage
// binding is not visible in render pass.
{
wgpu::Buffer buffer =
CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index);
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}});
// Buffer usage in compute stage in bind group conflicts with index buffer. And binding
// for compute stage is not visible in render pass. But we still track this binding.
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
DummyRenderPass dummyRenderPass(device);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
pass.SetIndexBuffer(buffer);
pass.SetBindGroup(0, bg);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Test compute pass for bind group. The conflict of readonly storage and storage buffer
// usage resides between compute stage and fragment stage. But the fragment stage binding is
// not visible in compute pass.
{
wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage);
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::ReadonlyStorageBuffer},
{1, wgpu::ShaderStage::Compute, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}, {1, buffer}});
// Buffer usage in compute stage conflicts with buffer usage in fragment stage. And
// binding for fragment stage is not visible in compute pass. We still track this
// invisible binding, but read and write usages in one compute pass is allowed.
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetBindGroup(0, bg);
pass.EndPass();
encoder.Finish();
}
}
// Test that it is valid for multiple readonly usages upon the same buffer even though some
// bindings are not used/visible.
TEST_F(ResourceUsageTrackingTest, MultipleReadonlyBufferUsagesWithInvisibleStage) {
// Test render pass for bind group and index buffer with invisible binding, no conflict
// happens because all buffer usages are readonly.
{
wgpu::Buffer buffer =
CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Index);
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device,
{{0, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageBuffer}});
wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
DummyRenderPass dummyRenderPass(device);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
pass.SetIndexBuffer(buffer);
pass.SetBindGroup(0, bg);
pass.EndPass();
encoder.Finish();
}
// Test compute pass for bind group with invisible binding, no conflict happens because all
// buffer usages are readonly.
{
wgpu::Buffer buffer =
CreateBuffer(4, wgpu::BufferUsage::Storage | wgpu::BufferUsage::Uniform);
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::ReadonlyStorageBuffer},
{1, wgpu::ShaderStage::Compute, wgpu::BindingType::UniformBuffer}});
wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, buffer}, {1, buffer}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetBindGroup(0, bg);
pass.EndPass();
encoder.Finish();
}
}
// Test that it is invalid to have resource usage conflicts even when one of the bindings is not
// used in the pipeline.
TEST_F(ResourceUsageTrackingTest, BufferUsageConflictWithUnusedPipelineBindings) {
wgpu::Buffer buffer = CreateBuffer(4, wgpu::BufferUsage::Storage);
// Test render pass for bind groups with unused bindings. The conflict of readonly storage
// and storage usages resides in different bind groups, although some bindings may not be
// used because its bind group layout is not designated in pipeline layout.
{
// Create bind groups. The bindings are visible for render pass.
wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout(
device,
{{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::ReadonlyStorageBuffer}});
wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, buffer}});
wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, buffer}});
// Create a passthrough render pipeline with a readonly buffer
wgpu::ShaderModule vsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
#version 450
void main() {
})");
wgpu::ShaderModule fsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
#version 450
layout(std140, set = 0, binding = 0) readonly buffer RBuffer {
readonly float value;
} rBuffer;
void main() {
})");
utils::ComboRenderPipelineDescriptor pipelineDescriptor(device);
pipelineDescriptor.vertexStage.module = vsModule;
pipelineDescriptor.cFragmentStage.module = fsModule;
pipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl0);
wgpu::RenderPipeline rp = device.CreateRenderPipeline(&pipelineDescriptor);
// Resource in bg1 conflicts with resources used in bg0. However, bindings in bg1 is
// not used in pipeline. But we still track this binding.
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
DummyRenderPass dummyRenderPass(device);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
pass.SetBindGroup(0, bg0);
pass.SetBindGroup(1, bg1);
pass.SetPipeline(rp);
pass.Draw(3);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Test compute pass for bind groups with unused bindings. The conflict of readonly storage
// and storage usages resides in different bind groups, although some bindings may not be
// used because its bind group layout is not designated in pipeline layout.
{
// Create bind groups. The bindings are visible for compute pass.
wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout(
device,
{{0, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageBuffer}});
wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::StorageBuffer}});
wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl0, {{0, buffer}});
wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl1, {{0, buffer}});
// Create a passthrough compute pipeline with a readonly buffer
wgpu::ShaderModule csModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, R"(
#version 450
layout(std140, set = 0, binding = 0) readonly buffer RBuffer {
readonly float value;
} rBuffer;
void main() {
})");
wgpu::ComputePipelineDescriptor pipelineDescriptor;
pipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl0);
pipelineDescriptor.computeStage.module = csModule;
pipelineDescriptor.computeStage.entryPoint = "main";
wgpu::ComputePipeline cp = device.CreateComputePipeline(&pipelineDescriptor);
// Resource in bg1 conflicts with resources used in bg0. However, the binding in bg1 is
// not used in pipeline. But we still track this binding and read/write usage in one
// dispatch is not allowed.
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetBindGroup(0, bg0);
pass.SetBindGroup(1, bg1);
pass.SetPipeline(cp);
pass.Dispatch(1);
pass.EndPass();
encoder.Finish();
// TODO (yunchao.he@intel.com): add resource tracking per dispatch for compute pass
// ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
// Test that it is invalid to use the same texture as both readable and writable in a single
// draw or dispatch.
TEST_F(ResourceUsageTrackingTest, TextureWithReadAndWriteUsageInSingleDrawOrDispatch) {
// Create a texture that will be used both as a sampled texture and a storage texture
wgpu::Texture texture =
CreateTexture(wgpu::TextureUsage::Sampled | wgpu::TextureUsage::Storage);
wgpu::TextureView view = texture.CreateView();
// Test render pass
{
// Create the bind group to use the texture as sampled and writeonly storage bindings
wgpu::BindGroupLayout sampledBGL = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::SampledTexture}});
wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout(
device,
{{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::WriteonlyStorageTexture, false,
false, wgpu::TextureViewDimension::Undefined,
wgpu::TextureViewDimension::Undefined, wgpu::TextureComponentType::Float,
kFormat}});
wgpu::BindGroup sampledBG = utils::MakeBindGroup(device, sampledBGL, {{0, view}});
wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}});
// Create a no-op render pipeline. Note that bind groups can have more bindings
// than pipeline.
wgpu::RenderPipeline rp = CreateNoOpRenderPipeline();
// It is invalid to use the same texture as both readable and writable usages in a
// single draw
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
DummyRenderPass dummyRenderPass(device);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&dummyRenderPass);
pass.SetPipeline(rp);
pass.SetBindGroup(0, sampledBG);
pass.SetBindGroup(1, writeBG);
pass.Draw(3);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Test compute pass
{
// Create the bind group to use the texture as readonly and writeonly storage bindings
wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageTexture,
false, false, wgpu::TextureViewDimension::Undefined,
wgpu::TextureViewDimension::Undefined, wgpu::TextureComponentType::Float,
kFormat}});
wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::WriteonlyStorageTexture,
false, false, wgpu::TextureViewDimension::Undefined,
wgpu::TextureViewDimension::Undefined, wgpu::TextureComponentType::Float,
kFormat}});
wgpu::BindGroup readBG = utils::MakeBindGroup(device, readBGL, {{0, view}});
wgpu::BindGroup writeBG = utils::MakeBindGroup(device, writeBGL, {{0, view}});
// Create a no-op compute pipeline. Note that bind groups can have more bindings
// than pipeline.
wgpu::ComputePipeline cp = CreateNoOpComputePipeline();
// It is invalid to use the same texture as both readable and writable usages in a
// single dispatch
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetPipeline(cp);
pass.SetBindGroup(0, readBG);
pass.SetBindGroup(1, writeBG);
pass.Dispatch(1);
pass.EndPass();
// TODO (yunchao.he@intel.com): add texture usage tracking for compute
// ASSERT_DEVICE_ERROR(encoder.Finish());
encoder.Finish();
}
}
// Test that using the same texture as both readable and writable in the same pass is disallowed
TEST_F(ResourceUsageTrackingTest, TextureWithReadAndWriteUsage) {
// Test render pass
{
// Create a texture that will be used as both a sampled texture and a render target
wgpu::Texture texture =
CreateTexture(wgpu::TextureUsage::Sampled | wgpu::TextureUsage::OutputAttachment);
wgpu::TextureView view = texture.CreateView();
// Create the bind group to use the texture as sampled
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Vertex, wgpu::BindingType::SampledTexture}});
wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view}});
// Create the render pass that will use the texture as an output attachment
utils::ComboRenderPassDescriptor renderPass({view});
// Use the texture as both sampled and output attachment in the same pass
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
pass.SetBindGroup(0, bg);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// TODO(yunchao.he@intel.com) Test compute pass. Test code is ready, but it depends on
// writeonly storage buffer support
}
// Test that using the same texture as both readable and writable in different passes is
// allowed
TEST_F(ResourceUsageTrackingTest, TextureWithReadAndWriteUsageInDifferentPasses) {
// Test render pass
{
// Create a texture that will be used both as a sampled texture and a render target
wgpu::Texture t0 =
CreateTexture(wgpu::TextureUsage::Sampled | wgpu::TextureUsage::OutputAttachment);
wgpu::TextureView v0 = t0.CreateView();
wgpu::Texture t1 =
CreateTexture(wgpu::TextureUsage::Sampled | wgpu::TextureUsage::OutputAttachment);
wgpu::TextureView v1 = t1.CreateView();
// Create the bind group to use the texture as sampled
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Vertex, wgpu::BindingType::SampledTexture}});
wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl, {{0, v0}});
wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl, {{0, v1}});
// Create the render pass that will use the texture as an output attachment
utils::ComboRenderPassDescriptor renderPass0({v1});
utils::ComboRenderPassDescriptor renderPass1({v0});
// Use the texture as both sampeld and output attachment in different passes
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass0 = encoder.BeginRenderPass(&renderPass0);
pass0.SetBindGroup(0, bg0);
pass0.EndPass();
wgpu::RenderPassEncoder pass1 = encoder.BeginRenderPass(&renderPass1);
pass1.SetBindGroup(0, bg1);
pass1.EndPass();
encoder.Finish();
}
// TODO (yunchao.he@intel.com) Test compute pass. Test code is ready, but it depends on
// writeonly storage texture support.
// TODO (yunchao.he@intel.com) Test compute pass and render pass mixed together with
// resource dependency. Test code is ready, but it depends on writeonly storage texture
// support.
}
// TODO (yunchao.he@intel.com) Test that using the same texture as both readable and writable in
// the different draws/dispatches is disallowed Test code is ready, but it depends on writeonly
// storage texture support.
// Test that using a single texture as copy src/dst and writable/readable usage in pass is
// allowed.
TEST_F(ResourceUsageTrackingTest, TextureCopyAndTextureUsageInPass) {
// Create textures that will be used as both a sampled texture and a render target
wgpu::Texture texture0 = CreateTexture(wgpu::TextureUsage::CopySrc);
wgpu::Texture texture1 =
CreateTexture(wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::Sampled |
wgpu::TextureUsage::OutputAttachment);
wgpu::TextureView view0 = texture0.CreateView();
wgpu::TextureView view1 = texture1.CreateView();
wgpu::TextureCopyView srcView = utils::CreateTextureCopyView(texture0, 0, 0, {0, 0, 0});
wgpu::TextureCopyView dstView = utils::CreateTextureCopyView(texture1, 0, 0, {0, 0, 0});
wgpu::Extent3D copySize = {1, 1, 1};
// Use the texture as both copy dst and output attachment in render pass
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyTextureToTexture(&srcView, &dstView, &copySize);
utils::ComboRenderPassDescriptor renderPass({view1});
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
pass.EndPass();
encoder.Finish();
}
// Use the texture as both copy dst and readable usage in compute pass
{
// Create the bind group to use the texture as sampled
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::SampledTexture}});
wgpu::BindGroup bg = utils::MakeBindGroup(device, bgl, {{0, view1}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyTextureToTexture(&srcView, &dstView, &copySize);
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetBindGroup(0, bg);
pass.EndPass();
encoder.Finish();
}
}
// Test that all consecutive SetBindGroup()s take effect even though some bind groups are not
// used because they are overwritten by a consecutive call.
TEST_F(ResourceUsageTrackingTest, TextureWithMultipleSetBindGroupsOnSameIndex) {
// Test render pass
{
// Create textures that will be used as both a sampled texture and a render target
wgpu::Texture texture0 =
CreateTexture(wgpu::TextureUsage::Sampled | wgpu::TextureUsage::OutputAttachment);
wgpu::TextureView view0 = texture0.CreateView();
wgpu::Texture texture1 =
CreateTexture(wgpu::TextureUsage::Sampled | wgpu::TextureUsage::OutputAttachment);
wgpu::TextureView view1 = texture1.CreateView();
// Create the bind group to use the texture as sampled
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Vertex, wgpu::BindingType::SampledTexture}});
wgpu::BindGroup bg0 = utils::MakeBindGroup(device, bgl, {{0, view0}});
wgpu::BindGroup bg1 = utils::MakeBindGroup(device, bgl, {{0, view1}});
// Create the render pass that will use the texture as an output attachment
utils::ComboRenderPassDescriptor renderPass({view0});
// Set bind group on the same index twice. The second one overwrites the first one.
// No texture is used as both sampled and output attachment in the same pass. But the
// overwritten texture still take effect during resource tracking.
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
pass.SetBindGroup(0, bg0);
pass.SetBindGroup(0, bg1);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
// Set bind group on the same index twice. The second one overwrites the first one.
// texture0 is used as both sampled and output attachment in the same pass
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
pass.SetBindGroup(0, bg1);
pass.SetBindGroup(0, bg0);
pass.EndPass();
ASSERT_DEVICE_ERROR(encoder.Finish());
}
}
// Test compute pass
{
// Create a texture that will be used both as storage texture
wgpu::Texture texture0 = CreateTexture(wgpu::TextureUsage::Storage);
wgpu::TextureView view0 = texture0.CreateView();
wgpu::Texture texture1 = CreateTexture(wgpu::TextureUsage::Storage);
wgpu::TextureView view1 = texture1.CreateView();
// Create the bind group to use the texture as readonly and writeonly bindings
wgpu::BindGroupLayout writeBGL = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::WriteonlyStorageTexture,
false, false, wgpu::TextureViewDimension::Undefined,
wgpu::TextureViewDimension::Undefined, wgpu::TextureComponentType::Float,
kFormat}});
wgpu::BindGroupLayout readBGL = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageTexture,
false, false, wgpu::TextureViewDimension::Undefined,
wgpu::TextureViewDimension::Undefined, wgpu::TextureComponentType::Float,
kFormat}});
wgpu::BindGroup writeBG0 = utils::MakeBindGroup(device, writeBGL, {{0, view0}});
wgpu::BindGroup readBG0 = utils::MakeBindGroup(device, readBGL, {{0, view0}});
wgpu::BindGroup readBG1 = utils::MakeBindGroup(device, readBGL, {{0, view1}});
// Create a no-op compute pipeline. Note that bind groups can have more bindings
// than pipeline.
wgpu::ComputePipeline cp = CreateNoOpComputePipeline();
// Set bind group on the same index twice. The second one overwrites the first one.
// No texture is used as both readonly and writeonly storage in the same pass. But the
// overwritten texture still take effect during resource tracking.
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetBindGroup(0, writeBG0);
pass.SetBindGroup(1, readBG0);
pass.SetBindGroup(1, readBG1);
pass.SetPipeline(cp);
pass.Dispatch(1);
pass.EndPass();
// TODO (yunchao.he@intel.com): add texture usage tracking for compute
// ASSERT_DEVICE_ERROR(encoder.Finish());
encoder.Finish();
}
// Set bind group on the same index twice. The second one overwrites the first one.
// texture0 is used as both writeonly and readonly storage in the same pass.
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetBindGroup(0, writeBG0);
pass.SetBindGroup(1, readBG1);
pass.SetBindGroup(1, readBG0);
pass.SetPipeline(cp);
pass.Dispatch(1);
pass.EndPass();
// TODO (yunchao.he@intel.com): add texture usage tracking for compute
// ASSERT_DEVICE_ERROR(encoder.Finish());
encoder.Finish();
}
}
}
// TODO (yunchao.he@intel.com): Test that all unused bindings bindGroup still take effect for
// resource tracking. Test code is ready, but it depends on writeonly storage buffer support
// TODO (yunchao.he@intel.com):
// * useless bindings in bind groups. For example, a bind group includes bindings for compute
// stage, but the bind group is used in render pass.
//
// * more read write tracking tests for texture which need readonly storage texture and
// writeonly storage texture support
//
// * resource write and read dependency
// 1) across passes (render + render, compute + compute, compute and render mixed) is valid
// 2) across draws/dispatches is invalid
//
// * Add tests for multiple encoders upon the same resource simultaneously. This situation fits
// some cases like VR, multi-threading, etc.
} // anonymous namespace