Test shader robust buffer access for dynamic buffer bindings
These tests are partially disabled while bounds clamping is unimplemented on D3D12 for dynamic storage buffers. Bug: dawn:429 Change-Id: Ia8b3ad3e3703b784cd51813c92ff1f2c731b7519 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/68460 Commit-Queue: Austin Eng <enga@chromium.org> Reviewed-by: Loko Kung <lokokung@google.com>
This commit is contained in:
parent
6f1f48fb3b
commit
e9ac87742d
|
@ -14,9 +14,12 @@
|
||||||
|
|
||||||
#include "tests/DawnTest.h"
|
#include "tests/DawnTest.h"
|
||||||
|
|
||||||
|
#include "common/Math.h"
|
||||||
#include "utils/ComboRenderPipelineDescriptor.h"
|
#include "utils/ComboRenderPipelineDescriptor.h"
|
||||||
#include "utils/WGPUHelpers.h"
|
#include "utils/WGPUHelpers.h"
|
||||||
|
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
constexpr uint32_t kRTSize = 400;
|
constexpr uint32_t kRTSize = 400;
|
||||||
constexpr uint32_t kBindingSize = 8;
|
constexpr uint32_t kBindingSize = 8;
|
||||||
|
|
||||||
|
@ -398,9 +401,191 @@ TEST_P(DynamicBufferOffsetTests, UpdateDynamicOffsetsMultipleTimesComputePipelin
|
||||||
EXPECT_BUFFER_U32_RANGE_EQ(expectedData.data(), mStorageBuffers[1], 0, expectedData.size());
|
EXPECT_BUFFER_U32_RANGE_EQ(expectedData.data(), mStorageBuffers[1], 0, expectedData.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using ReadBufferUsage = wgpu::BufferUsage;
|
||||||
|
using OOBRead = bool;
|
||||||
|
using OOBWrite = bool;
|
||||||
|
|
||||||
|
DAWN_TEST_PARAM_STRUCT(ClampedOOBDynamicBufferOffsetParams, ReadBufferUsage, OOBRead, OOBWrite)
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
class ClampedOOBDynamicBufferOffsetTests
|
||||||
|
: public DawnTestWithParams<ClampedOOBDynamicBufferOffsetParams> {};
|
||||||
|
|
||||||
|
// Test robust buffer access behavior for out of bounds accesses to dynamic buffer bindings.
|
||||||
|
TEST_P(ClampedOOBDynamicBufferOffsetTests, CheckOOBAccess) {
|
||||||
|
// TODO(crbug.com/dawn/429): Dynamic storage buffers are not bounds clamped on D3D12.
|
||||||
|
DAWN_SUPPRESS_TEST_IF(IsD3D12() && ((GetParam().mOOBRead && GetParam().mReadBufferUsage ==
|
||||||
|
wgpu::BufferUsage::Storage) ||
|
||||||
|
GetParam().mOOBWrite));
|
||||||
|
|
||||||
|
static constexpr uint32_t kArrayLength = 10u;
|
||||||
|
|
||||||
|
// Out-of-bounds access will start halfway into the array and index off the end.
|
||||||
|
static constexpr uint32_t kOOBOffset = kArrayLength / 2;
|
||||||
|
|
||||||
|
wgpu::BufferBindingType sourceBindingType;
|
||||||
|
switch (GetParam().mReadBufferUsage) {
|
||||||
|
case wgpu::BufferUsage::Uniform:
|
||||||
|
sourceBindingType = wgpu::BufferBindingType::Uniform;
|
||||||
|
break;
|
||||||
|
case wgpu::BufferUsage::Storage:
|
||||||
|
sourceBindingType = wgpu::BufferBindingType::ReadOnlyStorage;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
|
||||||
|
device, {{0, wgpu::ShaderStage::Compute, sourceBindingType, true},
|
||||||
|
{1, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage, true}});
|
||||||
|
wgpu::PipelineLayout layout = utils::MakeBasicPipelineLayout(device, &bgl);
|
||||||
|
|
||||||
|
wgpu::ComputePipeline pipeline;
|
||||||
|
{
|
||||||
|
std::ostringstream shader;
|
||||||
|
shader << "let kArrayLength: u32 = " << kArrayLength << "u;\n";
|
||||||
|
if (GetParam().mOOBRead) {
|
||||||
|
shader << "let kReadOffset: u32 = " << kOOBOffset << "u;\n";
|
||||||
|
} else {
|
||||||
|
shader << "let kReadOffset: u32 = 0u;\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetParam().mOOBWrite) {
|
||||||
|
shader << "let kWriteOffset: u32 = " << kOOBOffset << "u;\n";
|
||||||
|
} else {
|
||||||
|
shader << "let kWriteOffset: u32 = 0u;\n";
|
||||||
|
}
|
||||||
|
switch (GetParam().mReadBufferUsage) {
|
||||||
|
case wgpu::BufferUsage::Uniform:
|
||||||
|
shader << R"(
|
||||||
|
[[block]] struct Src {
|
||||||
|
values : array<vec4<u32>, kArrayLength>;
|
||||||
|
};
|
||||||
|
[[group(0), binding(0)]] var<uniform> src : Src;
|
||||||
|
)";
|
||||||
|
break;
|
||||||
|
case wgpu::BufferUsage::Storage:
|
||||||
|
shader << R"(
|
||||||
|
[[block]] struct Src {
|
||||||
|
values : array<vec4<u32>>;
|
||||||
|
};
|
||||||
|
[[group(0), binding(0)]] var<storage, read> src : Src;
|
||||||
|
)";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
shader << R"(
|
||||||
|
[[block]] struct Dst {
|
||||||
|
values : array<vec4<u32>>;
|
||||||
|
};
|
||||||
|
[[group(0), binding(1)]] var<storage, read_write> dst : Dst;
|
||||||
|
)";
|
||||||
|
shader << R"(
|
||||||
|
[[stage(compute), workgroup_size(1)]] fn main() {
|
||||||
|
for (var i: u32 = 0u; i < kArrayLength; i = i + 1u) {
|
||||||
|
dst.values[i + kWriteOffset] = src.values[i + kReadOffset];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
wgpu::ComputePipelineDescriptor pipelineDesc;
|
||||||
|
pipelineDesc.layout = layout;
|
||||||
|
pipelineDesc.compute.module = utils::CreateShaderModule(device, shader.str().c_str());
|
||||||
|
pipelineDesc.compute.entryPoint = "main";
|
||||||
|
pipeline = device.CreateComputePipeline(&pipelineDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t minUniformBufferOffsetAlignment =
|
||||||
|
GetSupportedLimits().limits.minUniformBufferOffsetAlignment;
|
||||||
|
uint32_t minStorageBufferOffsetAlignment =
|
||||||
|
GetSupportedLimits().limits.minStorageBufferOffsetAlignment;
|
||||||
|
|
||||||
|
uint32_t arrayByteLength = kArrayLength * 4 * sizeof(uint32_t);
|
||||||
|
|
||||||
|
uint32_t uniformBufferOffset = Align(arrayByteLength, minUniformBufferOffsetAlignment);
|
||||||
|
uint32_t storageBufferOffset = Align(arrayByteLength, minStorageBufferOffsetAlignment);
|
||||||
|
|
||||||
|
// Enough space to bind at a dynamic offset.
|
||||||
|
uint32_t uniformBufferSize = uniformBufferOffset + arrayByteLength;
|
||||||
|
uint32_t storageBufferSize = storageBufferOffset + arrayByteLength;
|
||||||
|
|
||||||
|
// Buffers are padded so we can check that bytes after the bound range are not changed.
|
||||||
|
static constexpr uint32_t kEndPadding = 16;
|
||||||
|
|
||||||
|
uint64_t srcBufferSize;
|
||||||
|
uint32_t srcBufferByteOffset;
|
||||||
|
uint32_t dstBufferByteOffset = storageBufferOffset;
|
||||||
|
uint64_t dstBufferSize = storageBufferSize + kEndPadding;
|
||||||
|
switch (GetParam().mReadBufferUsage) {
|
||||||
|
case wgpu::BufferUsage::Uniform:
|
||||||
|
srcBufferSize = uniformBufferSize + kEndPadding;
|
||||||
|
srcBufferByteOffset = uniformBufferOffset;
|
||||||
|
break;
|
||||||
|
case wgpu::BufferUsage::Storage:
|
||||||
|
srcBufferSize = storageBufferSize + kEndPadding;
|
||||||
|
srcBufferByteOffset = storageBufferOffset;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint32_t> srcData(srcBufferSize / sizeof(uint32_t));
|
||||||
|
std::vector<uint32_t> expectedDst(dstBufferSize / sizeof(uint32_t));
|
||||||
|
|
||||||
|
// Fill the src buffer with 0, 1, 2, ...
|
||||||
|
std::iota(srcData.begin(), srcData.end(), 0);
|
||||||
|
wgpu::Buffer src = utils::CreateBufferFromData(device, &srcData[0], srcBufferSize,
|
||||||
|
GetParam().mReadBufferUsage);
|
||||||
|
|
||||||
|
// Fill the dst buffer with 0xFF.
|
||||||
|
memset(expectedDst.data(), 0xFF, dstBufferSize);
|
||||||
|
wgpu::Buffer dst =
|
||||||
|
utils::CreateBufferFromData(device, &expectedDst[0], dstBufferSize,
|
||||||
|
wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc);
|
||||||
|
|
||||||
|
// Produce expected data assuming the implementation performs clamping.
|
||||||
|
for (uint32_t i = 0; i < kArrayLength; ++i) {
|
||||||
|
uint32_t readIndex = GetParam().mOOBRead ? std::min(kOOBOffset + i, kArrayLength - 1) : i;
|
||||||
|
uint32_t writeIndex = GetParam().mOOBWrite ? std::min(kOOBOffset + i, kArrayLength - 1) : i;
|
||||||
|
|
||||||
|
for (uint32_t c = 0; c < 4; ++c) {
|
||||||
|
uint32_t value = srcData[srcBufferByteOffset / 4 + 4 * readIndex + c];
|
||||||
|
expectedDst[dstBufferByteOffset / 4 + 4 * writeIndex + c] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<uint32_t, 2> dynamicOffsets = {srcBufferByteOffset, dstBufferByteOffset};
|
||||||
|
|
||||||
|
wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, bgl,
|
||||||
|
{
|
||||||
|
{0, src, 0, arrayByteLength},
|
||||||
|
{1, dst, 0, arrayByteLength},
|
||||||
|
});
|
||||||
|
|
||||||
|
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
|
||||||
|
wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass();
|
||||||
|
computePassEncoder.SetPipeline(pipeline);
|
||||||
|
computePassEncoder.SetBindGroup(0, bindGroup, dynamicOffsets.size(), dynamicOffsets.data());
|
||||||
|
computePassEncoder.Dispatch(1);
|
||||||
|
computePassEncoder.EndPass();
|
||||||
|
wgpu::CommandBuffer commands = commandEncoder.Finish();
|
||||||
|
queue.Submit(1, &commands);
|
||||||
|
|
||||||
|
EXPECT_BUFFER_U32_RANGE_EQ(expectedDst.data(), dst, 0, dstBufferSize / sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
|
||||||
DAWN_INSTANTIATE_TEST(DynamicBufferOffsetTests,
|
DAWN_INSTANTIATE_TEST(DynamicBufferOffsetTests,
|
||||||
D3D12Backend(),
|
D3D12Backend(),
|
||||||
MetalBackend(),
|
MetalBackend(),
|
||||||
OpenGLBackend(),
|
OpenGLBackend(),
|
||||||
OpenGLESBackend(),
|
OpenGLESBackend(),
|
||||||
VulkanBackend());
|
VulkanBackend());
|
||||||
|
|
||||||
|
// Only instantiate on D3D12 / Metal where we are sure of the robustness implementation.
|
||||||
|
// Tint injects clamping in the shader. OpenGL(ES) / Vulkan robustness is less constrained.
|
||||||
|
DAWN_INSTANTIATE_TEST_P(ClampedOOBDynamicBufferOffsetTests,
|
||||||
|
{D3D12Backend(), MetalBackend()},
|
||||||
|
{wgpu::BufferUsage::Uniform, wgpu::BufferUsage::Storage},
|
||||||
|
{false, true},
|
||||||
|
{false, true});
|
||||||
|
|
Loading…
Reference in New Issue