Metal: Support setting bind groups before pipeline to match WebGPU semantics

Bug: dawn:201
Change-Id: I3bd03bbce3c38d0182e5e93f3898a43183bd647d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/10840
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
This commit is contained in:
Austin Eng 2019-09-09 23:38:00 +00:00 committed by Commit Bot service account
parent f35420dfca
commit 57354037bd
2 changed files with 611 additions and 177 deletions

View File

@ -206,7 +206,7 @@ namespace dawn_native { namespace metal {
// MSL code generated by SPIRV-Cross expects.
PerStage<std::array<uint32_t, kGenericMetalBufferSlots>> data;
void Apply(RenderPipeline* pipeline, id<MTLRenderCommandEncoder> render) {
void Apply(id<MTLRenderCommandEncoder> render, RenderPipeline* pipeline) {
dawn::ShaderStage stagesToApply =
dirtyStages & pipeline->GetStagesRequiringStorageBufferLength();
@ -234,7 +234,7 @@ namespace dawn_native { namespace metal {
dirtyStages ^= stagesToApply;
}
void Apply(ComputePipeline* pipeline, id<MTLComputeCommandEncoder> compute) {
void Apply(id<MTLComputeCommandEncoder> compute, ComputePipeline* pipeline) {
if (!(dirtyStages & dawn::ShaderStage::Compute)) {
return;
}
@ -253,128 +253,6 @@ namespace dawn_native { namespace metal {
}
};
// Handles a call to SetBindGroup, directing the commands to the correct encoder.
// There is a single function that takes both encoders to factor code. Other approaches like
// templates wouldn't work because the name of methods are different between the two encoder
// types.
void ApplyBindGroup(uint32_t index,
BindGroup* group,
uint32_t dynamicOffsetCount,
uint64_t* dynamicOffsets,
PipelineLayout* pipelineLayout,
StorageBufferLengthTracker* lengthTracker,
id<MTLRenderCommandEncoder> render,
id<MTLComputeCommandEncoder> compute) {
const auto& layout = group->GetLayout()->GetBindingInfo();
uint32_t currentDynamicBufferIndex = 0;
// TODO(kainino@chromium.org): Maintain buffers and offsets arrays in BindGroup
// so that we only have to do one setVertexBuffers and one setFragmentBuffers
// call here.
for (uint32_t bindingIndex : IterateBitSet(layout.mask)) {
auto stage = layout.visibilities[bindingIndex];
bool hasVertStage = stage & dawn::ShaderStage::Vertex && render != nil;
bool hasFragStage = stage & dawn::ShaderStage::Fragment && render != nil;
bool hasComputeStage = stage & dawn::ShaderStage::Compute && compute != nil;
uint32_t vertIndex = 0;
uint32_t fragIndex = 0;
uint32_t computeIndex = 0;
if (hasVertStage) {
vertIndex = pipelineLayout->GetBindingIndexInfo(
SingleShaderStage::Vertex)[index][bindingIndex];
}
if (hasFragStage) {
fragIndex = pipelineLayout->GetBindingIndexInfo(
SingleShaderStage::Fragment)[index][bindingIndex];
}
if (hasComputeStage) {
computeIndex = pipelineLayout->GetBindingIndexInfo(
SingleShaderStage::Compute)[index][bindingIndex];
}
switch (layout.types[bindingIndex]) {
case dawn::BindingType::UniformBuffer:
case dawn::BindingType::StorageBuffer: {
const BufferBinding& binding =
group->GetBindingAsBufferBinding(bindingIndex);
const id<MTLBuffer> buffer = ToBackend(binding.buffer)->GetMTLBuffer();
NSUInteger offset = binding.offset;
// TODO(shaobo.yan@intel.com): Record bound buffer status to use
// setBufferOffset to achieve better performance.
if (layout.dynamic[bindingIndex]) {
offset += dynamicOffsets[currentDynamicBufferIndex];
currentDynamicBufferIndex++;
}
if (hasVertStage) {
lengthTracker->data[SingleShaderStage::Vertex][vertIndex] =
binding.size;
lengthTracker->dirtyStages |= dawn::ShaderStage::Vertex;
[render setVertexBuffers:&buffer
offsets:&offset
withRange:NSMakeRange(vertIndex, 1)];
}
if (hasFragStage) {
lengthTracker->data[SingleShaderStage::Fragment][fragIndex] =
binding.size;
lengthTracker->dirtyStages |= dawn::ShaderStage::Fragment;
[render setFragmentBuffers:&buffer
offsets:&offset
withRange:NSMakeRange(fragIndex, 1)];
}
if (hasComputeStage) {
lengthTracker->data[SingleShaderStage::Compute][computeIndex] =
binding.size;
lengthTracker->dirtyStages |= dawn::ShaderStage::Compute;
[compute setBuffers:&buffer
offsets:&offset
withRange:NSMakeRange(computeIndex, 1)];
}
} break;
case dawn::BindingType::Sampler: {
auto sampler = ToBackend(group->GetBindingAsSampler(bindingIndex));
if (hasVertStage) {
[render setVertexSamplerState:sampler->GetMTLSamplerState()
atIndex:vertIndex];
}
if (hasFragStage) {
[render setFragmentSamplerState:sampler->GetMTLSamplerState()
atIndex:fragIndex];
}
if (hasComputeStage) {
[compute setSamplerState:sampler->GetMTLSamplerState()
atIndex:computeIndex];
}
} break;
case dawn::BindingType::SampledTexture: {
auto textureView = ToBackend(group->GetBindingAsTextureView(bindingIndex));
if (hasVertStage) {
[render setVertexTexture:textureView->GetMTLTexture()
atIndex:vertIndex];
}
if (hasFragStage) {
[render setFragmentTexture:textureView->GetMTLTexture()
atIndex:fragIndex];
}
if (hasComputeStage) {
[compute setTexture:textureView->GetMTLTexture() atIndex:computeIndex];
}
} break;
case dawn::BindingType::StorageTexture:
case dawn::BindingType::ReadonlyStorageBuffer:
UNREACHABLE();
break;
}
}
}
struct TextureBufferCopySplit {
static constexpr uint32_t kMaxTextureBufferCopyRegions = 3;
@ -511,6 +389,219 @@ namespace dawn_native { namespace metal {
return copy;
}
// Keeps track of the dirty bind groups so they can be lazily applied when we know the
// pipeline state.
class BindGroupTracker {
public:
explicit BindGroupTracker(StorageBufferLengthTracker* lengthTracker)
: mLengthTracker(lengthTracker) {
}
void OnSetBindGroup(uint32_t index,
BindGroup* bindGroup,
uint32_t dynamicOffsetCount,
uint64_t* dynamicOffsets) {
ASSERT(index < kMaxBindGroups);
if (mBindGroupLayoutsMask[index]) {
// It is okay to only dirty bind groups that are used by the current pipeline
// layout. If the pipeline layout changes, then the bind groups it uses will
// become dirty.
mDirtyBindGroups.set(index);
}
mBindGroups[index] = bindGroup;
mDynamicOffsetCounts[index] = dynamicOffsetCount;
memcpy(mDynamicOffsets[index].data(), dynamicOffsets,
sizeof(uint64_t) * dynamicOffsetCount);
}
void OnSetPipeline(PipelineBase* pipeline) {
mPipelineLayout = ToBackend(pipeline->GetLayout());
if (mLastAppliedPipelineLayout == mPipelineLayout) {
return;
}
// Keep track of the bind group layout mask to avoid marking unused bind groups as
// dirty. This also allows us to avoid computing the intersection of the dirty bind
// groups and bind group layout mask in Draw or Dispatch which is very hot code.
mBindGroupLayoutsMask = mPipelineLayout->GetBindGroupLayoutsMask();
// Changing the pipeline layout sets bind groups as dirty. The first |k| matching
// bind groups may be inherited because bind groups are packed in the buffer /
// texture tables in contiguous order.
if (mLastAppliedPipelineLayout != nullptr) {
// Dirty bind groups that cannot be inherited.
mDirtyBindGroups |=
~mPipelineLayout->InheritedGroupsMask(mLastAppliedPipelineLayout);
mDirtyBindGroups &= mBindGroupLayoutsMask;
} else {
mDirtyBindGroups = mBindGroupLayoutsMask;
}
}
template <typename Encoder>
void Apply(Encoder encoder) {
for (uint32_t index : IterateBitSet(mDirtyBindGroups)) {
ApplyBindGroup(encoder, index, mBindGroups[index], mDynamicOffsetCounts[index],
mDynamicOffsets[index].data(), mPipelineLayout);
}
// Reset all dirty bind groups. Dirty bind groups not in the bind group layout mask
// will be dirtied again by the next pipeline change.
mDirtyBindGroups.reset();
mLastAppliedPipelineLayout = mPipelineLayout;
}
private:
// Handles a call to SetBindGroup, directing the commands to the correct encoder.
// There is a single function that takes both encoders to factor code. Other approaches
// like templates wouldn't work because the name of methods are different between the
// two encoder types.
void ApplyBindGroupImpl(id<MTLRenderCommandEncoder> render,
id<MTLComputeCommandEncoder> compute,
uint32_t index,
BindGroup* group,
uint32_t dynamicOffsetCount,
uint64_t* dynamicOffsets,
PipelineLayout* pipelineLayout) {
const auto& layout = group->GetLayout()->GetBindingInfo();
uint32_t currentDynamicBufferIndex = 0;
// TODO(kainino@chromium.org): Maintain buffers and offsets arrays in BindGroup
// so that we only have to do one setVertexBuffers and one setFragmentBuffers
// call here.
for (uint32_t bindingIndex : IterateBitSet(layout.mask)) {
auto stage = layout.visibilities[bindingIndex];
bool hasVertStage = stage & dawn::ShaderStage::Vertex && render != nil;
bool hasFragStage = stage & dawn::ShaderStage::Fragment && render != nil;
bool hasComputeStage = stage & dawn::ShaderStage::Compute && compute != nil;
uint32_t vertIndex = 0;
uint32_t fragIndex = 0;
uint32_t computeIndex = 0;
if (hasVertStage) {
vertIndex = pipelineLayout->GetBindingIndexInfo(
SingleShaderStage::Vertex)[index][bindingIndex];
}
if (hasFragStage) {
fragIndex = pipelineLayout->GetBindingIndexInfo(
SingleShaderStage::Fragment)[index][bindingIndex];
}
if (hasComputeStage) {
computeIndex = pipelineLayout->GetBindingIndexInfo(
SingleShaderStage::Compute)[index][bindingIndex];
}
switch (layout.types[bindingIndex]) {
case dawn::BindingType::UniformBuffer:
case dawn::BindingType::StorageBuffer: {
const BufferBinding& binding =
group->GetBindingAsBufferBinding(bindingIndex);
const id<MTLBuffer> buffer = ToBackend(binding.buffer)->GetMTLBuffer();
NSUInteger offset = binding.offset;
// TODO(shaobo.yan@intel.com): Record bound buffer status to use
// setBufferOffset to achieve better performance.
if (layout.dynamic[bindingIndex]) {
offset += dynamicOffsets[currentDynamicBufferIndex];
currentDynamicBufferIndex++;
}
if (hasVertStage) {
mLengthTracker->data[SingleShaderStage::Vertex][vertIndex] =
binding.size;
mLengthTracker->dirtyStages |= dawn::ShaderStage::Vertex;
[render setVertexBuffers:&buffer
offsets:&offset
withRange:NSMakeRange(vertIndex, 1)];
}
if (hasFragStage) {
mLengthTracker->data[SingleShaderStage::Fragment][fragIndex] =
binding.size;
mLengthTracker->dirtyStages |= dawn::ShaderStage::Fragment;
[render setFragmentBuffers:&buffer
offsets:&offset
withRange:NSMakeRange(fragIndex, 1)];
}
if (hasComputeStage) {
mLengthTracker->data[SingleShaderStage::Compute][computeIndex] =
binding.size;
mLengthTracker->dirtyStages |= dawn::ShaderStage::Compute;
[compute setBuffers:&buffer
offsets:&offset
withRange:NSMakeRange(computeIndex, 1)];
}
} break;
case dawn::BindingType::Sampler: {
auto sampler = ToBackend(group->GetBindingAsSampler(bindingIndex));
if (hasVertStage) {
[render setVertexSamplerState:sampler->GetMTLSamplerState()
atIndex:vertIndex];
}
if (hasFragStage) {
[render setFragmentSamplerState:sampler->GetMTLSamplerState()
atIndex:fragIndex];
}
if (hasComputeStage) {
[compute setSamplerState:sampler->GetMTLSamplerState()
atIndex:computeIndex];
}
} break;
case dawn::BindingType::SampledTexture: {
auto textureView =
ToBackend(group->GetBindingAsTextureView(bindingIndex));
if (hasVertStage) {
[render setVertexTexture:textureView->GetMTLTexture()
atIndex:vertIndex];
}
if (hasFragStage) {
[render setFragmentTexture:textureView->GetMTLTexture()
atIndex:fragIndex];
}
if (hasComputeStage) {
[compute setTexture:textureView->GetMTLTexture()
atIndex:computeIndex];
}
} break;
case dawn::BindingType::StorageTexture:
case dawn::BindingType::ReadonlyStorageBuffer:
UNREACHABLE();
break;
}
}
}
template <typename... Args>
void ApplyBindGroup(id<MTLRenderCommandEncoder> encoder, Args&&... args) {
ApplyBindGroupImpl(encoder, nil, std::forward<Args&&>(args)...);
}
template <typename... Args>
void ApplyBindGroup(id<MTLComputeCommandEncoder> encoder, Args&&... args) {
ApplyBindGroupImpl(nil, encoder, std::forward<Args&&>(args)...);
}
std::bitset<kMaxBindGroups> mDirtyBindGroups;
std::bitset<kMaxBindGroups> mBindGroupLayoutsMask;
std::array<BindGroup*, kMaxBindGroups> mBindGroups;
std::array<uint32_t, kMaxBindGroups> mDynamicOffsetCounts;
std::array<std::array<uint64_t, kMaxBindingsPerGroup>, kMaxBindGroups> mDynamicOffsets;
// |mPipelineLayout| is the current pipeline layout set on the command buffer.
// |mLastAppliedPipelineLayout| is the last pipeline layout for which we applied changes
// to the bind group bindings.
PipelineLayout* mPipelineLayout = nullptr;
PipelineLayout* mLastAppliedPipelineLayout = nullptr;
StorageBufferLengthTracker* mLengthTracker;
};
// Keeps track of the dirty vertex buffer values so they can be lazily applied when we know
// all the relevant state.
class VertexInputBufferTracker {
@ -685,6 +776,7 @@ namespace dawn_native { namespace metal {
void CommandBuffer::EncodeComputePass(id<MTLCommandBuffer> commandBuffer) {
ComputePipeline* lastPipeline = nullptr;
StorageBufferLengthTracker storageBufferLengths = {};
BindGroupTracker bindGroups(&storageBufferLengths);
// Will be autoreleased
id<MTLComputeCommandEncoder> encoder = [commandBuffer computeCommandEncoder];
@ -700,7 +792,9 @@ namespace dawn_native { namespace metal {
case Command::Dispatch: {
DispatchCmd* dispatch = mCommands.NextCommand<DispatchCmd>();
storageBufferLengths.Apply(lastPipeline, encoder);
bindGroups.Apply(encoder);
storageBufferLengths.Apply(encoder, lastPipeline);
[encoder dispatchThreadgroups:MTLSizeMake(dispatch->x, dispatch->y, dispatch->z)
threadsPerThreadgroup:lastPipeline->GetLocalWorkGroupSize()];
@ -708,7 +802,9 @@ namespace dawn_native { namespace metal {
case Command::DispatchIndirect: {
DispatchIndirectCmd* dispatch = mCommands.NextCommand<DispatchIndirectCmd>();
storageBufferLengths.Apply(lastPipeline, encoder);
bindGroups.Apply(encoder);
storageBufferLengths.Apply(encoder, lastPipeline);
Buffer* buffer = ToBackend(dispatch->indirectBuffer.Get());
id<MTLBuffer> indirectBuffer = buffer->GetMTLBuffer();
@ -722,6 +818,8 @@ namespace dawn_native { namespace metal {
SetComputePipelineCmd* cmd = mCommands.NextCommand<SetComputePipelineCmd>();
lastPipeline = ToBackend(cmd->pipeline).Get();
bindGroups.OnSetPipeline(lastPipeline);
lastPipeline->Encode(encoder);
} break;
@ -732,9 +830,8 @@ namespace dawn_native { namespace metal {
dynamicOffsets = mCommands.NextData<uint64_t>(cmd->dynamicOffsetCount);
}
ApplyBindGroup(cmd->index, ToBackend(cmd->group.Get()), cmd->dynamicOffsetCount,
dynamicOffsets, ToBackend(lastPipeline->GetLayout()),
&storageBufferLengths, nil, encoder);
bindGroups.OnSetBindGroup(cmd->index, ToBackend(cmd->group.Get()),
cmd->dynamicOffsetCount, dynamicOffsets);
} break;
case Command::InsertDebugMarker: {
@ -870,6 +967,7 @@ namespace dawn_native { namespace metal {
uint32_t indexBufferBaseOffset = 0;
VertexInputBufferTracker vertexInputBuffers;
StorageBufferLengthTracker storageBufferLengths = {};
BindGroupTracker bindGroups(&storageBufferLengths);
// This will be autoreleased
id<MTLRenderCommandEncoder> encoder =
@ -881,7 +979,8 @@ namespace dawn_native { namespace metal {
DrawCmd* draw = iter->NextCommand<DrawCmd>();
vertexInputBuffers.Apply(encoder, lastPipeline);
storageBufferLengths.Apply(lastPipeline, encoder);
bindGroups.Apply(encoder);
storageBufferLengths.Apply(encoder, lastPipeline);
// The instance count must be non-zero, otherwise no-op
if (draw->instanceCount != 0) {
@ -899,7 +998,8 @@ namespace dawn_native { namespace metal {
IndexFormatSize(lastPipeline->GetVertexInputDescriptor()->indexFormat);
vertexInputBuffers.Apply(encoder, lastPipeline);
storageBufferLengths.Apply(lastPipeline, encoder);
bindGroups.Apply(encoder);
storageBufferLengths.Apply(encoder, lastPipeline);
// The index and instance count must be non-zero, otherwise no-op
if (draw->indexCount != 0 && draw->instanceCount != 0) {
@ -919,7 +1019,8 @@ namespace dawn_native { namespace metal {
DrawIndirectCmd* draw = iter->NextCommand<DrawIndirectCmd>();
vertexInputBuffers.Apply(encoder, lastPipeline);
storageBufferLengths.Apply(lastPipeline, encoder);
bindGroups.Apply(encoder);
storageBufferLengths.Apply(encoder, lastPipeline);
Buffer* buffer = ToBackend(draw->indirectBuffer.Get());
id<MTLBuffer> indirectBuffer = buffer->GetMTLBuffer();
@ -932,7 +1033,8 @@ namespace dawn_native { namespace metal {
DrawIndirectCmd* draw = iter->NextCommand<DrawIndirectCmd>();
vertexInputBuffers.Apply(encoder, lastPipeline);
storageBufferLengths.Apply(lastPipeline, encoder);
bindGroups.Apply(encoder);
storageBufferLengths.Apply(encoder, lastPipeline);
Buffer* buffer = ToBackend(draw->indirectBuffer.Get());
id<MTLBuffer> indirectBuffer = buffer->GetMTLBuffer();
@ -973,6 +1075,8 @@ namespace dawn_native { namespace metal {
RenderPipeline* newPipeline = ToBackend(cmd->pipeline).Get();
vertexInputBuffers.OnSetPipeline(lastPipeline, newPipeline);
bindGroups.OnSetPipeline(newPipeline);
[encoder setDepthStencilState:newPipeline->GetMTLDepthStencilState()];
[encoder setFrontFacingWinding:newPipeline->GetMTLFrontFace()];
[encoder setCullMode:newPipeline->GetMTLCullMode()];
@ -988,9 +1092,8 @@ namespace dawn_native { namespace metal {
dynamicOffsets = iter->NextData<uint64_t>(cmd->dynamicOffsetCount);
}
ApplyBindGroup(cmd->index, ToBackend(cmd->group.Get()), cmd->dynamicOffsetCount,
dynamicOffsets, ToBackend(lastPipeline->GetLayout()),
&storageBufferLengths, encoder, nil);
bindGroups.OnSetBindGroup(cmd->index, ToBackend(cmd->group.Get()),
cmd->dynamicOffsetCount, dynamicOffsets);
} break;
case Command::SetIndexBuffer: {

View File

@ -14,6 +14,7 @@
#include "common/Assert.h"
#include "common/Constants.h"
#include "common/Math.h"
#include "tests/DawnTest.h"
#include "utils/ComboRenderPipelineDescriptor.h"
#include "utils/DawnHelpers.h"
@ -43,6 +44,75 @@ protected:
return device.CreatePipelineLayout(&descriptor);
}
dawn::ShaderModule MakeSimpleVSModule() const {
return utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
#version 450
void main() {
const vec2 pos[3] = vec2[3](vec2(-1.f, -1.f), vec2(1.f, -1.f), vec2(-1.f, 1.f));
gl_Position = vec4(pos[gl_VertexIndex], 0.f, 1.f);
})");
}
dawn::ShaderModule MakeFSModule(std::vector<dawn::BindingType> bindingTypes) const {
ASSERT(bindingTypes.size() <= kMaxBindGroups);
std::ostringstream fs;
fs << R"(
#version 450
layout(location = 0) out vec4 fragColor;
)";
for (size_t i = 0; i < bindingTypes.size(); ++i) {
switch (bindingTypes[i]) {
case dawn::BindingType::UniformBuffer:
fs << "layout (std140, set = " << i << ", binding = 0) uniform UniformBuffer" << i << R"( {
vec4 color;
} buffer)" << i << ";\n";
break;
case dawn::BindingType::StorageBuffer:
fs << "layout (std430, set = " << i << ", binding = 0) buffer StorageBuffer" << i << R"( {
vec4 color;
} buffer)" << i << ";\n";
break;
default:
UNREACHABLE();
}
}
fs << R"(
void main() {
fragColor = vec4(0.0);
)";
for (size_t i = 0; i < bindingTypes.size(); ++i) {
fs << "fragColor += buffer" << i << ".color;\n";
}
fs << "}\n";
return utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, fs.str().c_str());
}
dawn::RenderPipeline MakeTestPipeline(
const utils::BasicRenderPass& renderPass,
std::vector<dawn::BindingType> bindingTypes,
std::vector<dawn::BindGroupLayout> bindGroupLayouts) {
dawn::ShaderModule vsModule = MakeSimpleVSModule();
dawn::ShaderModule fsModule = MakeFSModule(bindingTypes);
dawn::PipelineLayout pipelineLayout = MakeBasicPipelineLayout(device, bindGroupLayouts);
utils::ComboRenderPipelineDescriptor pipelineDescriptor(device);
pipelineDescriptor.layout = pipelineLayout;
pipelineDescriptor.vertexStage.module = vsModule;
pipelineDescriptor.cFragmentStage.module = fsModule;
pipelineDescriptor.cColorStates[0]->format = renderPass.colorFormat;
pipelineDescriptor.cColorStates[0]->colorBlend.operation = dawn::BlendOperation::Add;
pipelineDescriptor.cColorStates[0]->colorBlend.srcFactor = dawn::BlendFactor::One;
pipelineDescriptor.cColorStates[0]->colorBlend.dstFactor = dawn::BlendFactor::One;
return device.CreateRenderPipeline(&pipelineDescriptor);
}
};
// Test a bindgroup reused in two command buffers in the same call to queue.Submit().
@ -380,60 +450,28 @@ TEST_P(BindGroupTests, MultipleBindLayouts) {
// This test reproduces an out-of-bound bug on D3D12 backends when calling draw command twice with
// one pipeline that has 4 bind group sets in one render pass.
TEST_P(BindGroupTests, DrawTwiceInSamePipelineWithFourBindGroupSets)
{
TEST_P(BindGroupTests, DrawTwiceInSamePipelineWithFourBindGroupSets) {
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize);
dawn::ShaderModule vsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
#version 450
void main() {
const vec2 pos[3] = vec2[3](vec2(-1.f, -1.f), vec2(1.f, -1.f), vec2(-1.f, 1.f));
gl_Position = vec4(pos[gl_VertexIndex], 0.f, 1.f);
})");
dawn::ShaderModule fsModule =
utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
#version 450
layout (std140, set = 0, binding = 0) uniform fragmentUniformBuffer1 {
vec4 color1;
};
layout (std140, set = 1, binding = 0) uniform fragmentUniformBuffer2 {
vec4 color2;
};
layout (std140, set = 2, binding = 0) uniform fragmentUniformBuffer3 {
vec4 color3;
};
layout (std140, set = 3, binding = 0) uniform fragmentUniformBuffer4 {
vec4 color4;
};
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = color1 + color2 + color3 + color4;
})");
dawn::BindGroupLayout layout = utils::MakeBindGroupLayout(
device, {{0, dawn::ShaderStage::Fragment, dawn::BindingType::UniformBuffer}});
dawn::PipelineLayout pipelineLayout = MakeBasicPipelineLayout(
device, { layout, layout, layout, layout });
utils::ComboRenderPipelineDescriptor pipelineDescriptor(device);
pipelineDescriptor.layout = pipelineLayout;
pipelineDescriptor.vertexStage.module = vsModule;
pipelineDescriptor.cFragmentStage.module = fsModule;
pipelineDescriptor.cColorStates[0]->format = renderPass.colorFormat;
dawn::RenderPipeline pipeline =
MakeTestPipeline(renderPass,
{dawn::BindingType::UniformBuffer, dawn::BindingType::UniformBuffer,
dawn::BindingType::UniformBuffer, dawn::BindingType::UniformBuffer},
{layout, layout, layout, layout});
dawn::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor);
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
pass.SetPipeline(pipeline);
std::array<float, 4> color = { 0.25, 0, 0, 0.25 };
std::array<float, 4> color = {0.25, 0, 0, 0.25};
dawn::Buffer uniformBuffer =
utils::CreateBufferFromData(device, &color, sizeof(color), dawn::BufferUsage::Uniform);
dawn::BindGroup bindGroup = utils::MakeBindGroup(
device, layout, { { 0, uniformBuffer, 0, sizeof(color) } });
dawn::BindGroup bindGroup =
utils::MakeBindGroup(device, layout, {{0, uniformBuffer, 0, sizeof(color)}});
pass.SetBindGroup(0, bindGroup, 0, nullptr);
pass.SetBindGroup(1, bindGroup, 0, nullptr);
@ -457,4 +495,297 @@ TEST_P(BindGroupTests, DrawTwiceInSamePipelineWithFourBindGroupSets)
EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, max, max);
}
// Test that bind groups can be set before the pipeline.
TEST_P(BindGroupTests, SetBindGroupBeforePipeline) {
// TODO(crbug.com/dawn/201): Implement on all platforms.
DAWN_SKIP_TEST_IF(!IsMetal());
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize);
// Create a bind group layout which uses a single uniform buffer.
dawn::BindGroupLayout layout = utils::MakeBindGroupLayout(
device, {{0, dawn::ShaderStage::Fragment, dawn::BindingType::UniformBuffer}});
// Create a pipeline that uses the uniform bind group layout.
dawn::RenderPipeline pipeline =
MakeTestPipeline(renderPass, {dawn::BindingType::UniformBuffer}, {layout});
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
// Create a bind group with a uniform buffer and fill it with RGBAunorm(1, 0, 0, 1).
std::array<float, 4> color = {1, 0, 0, 1};
dawn::Buffer uniformBuffer =
utils::CreateBufferFromData(device, &color, sizeof(color), dawn::BufferUsage::Uniform);
dawn::BindGroup bindGroup =
utils::MakeBindGroup(device, layout, {{0, uniformBuffer, 0, sizeof(color)}});
// Set the bind group, then the pipeline, and draw.
pass.SetBindGroup(0, bindGroup, 0, nullptr);
pass.SetPipeline(pipeline);
pass.Draw(3, 1, 0, 0);
pass.EndPass();
dawn::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
// The result should be red.
RGBA8 filled(255, 0, 0, 255);
RGBA8 notFilled(0, 0, 0, 0);
int min = 1, max = kRTSize - 3;
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, min);
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, max, min);
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, max);
EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, max, max);
}
// Test that dynamic bind groups can be set before the pipeline.
TEST_P(BindGroupTests, SetDynamicBindGroupBeforePipeline) {
// TODO(crbug.com/dawn/201): Implement on all platforms.
DAWN_SKIP_TEST_IF(!IsMetal());
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize);
// Create a bind group layout which uses a single dynamic uniform buffer.
dawn::BindGroupLayout layout = utils::MakeBindGroupLayout(
device, {{0, dawn::ShaderStage::Fragment, dawn::BindingType::UniformBuffer, true}});
// Create a pipeline that uses the dynamic uniform bind group layout for two bind groups.
dawn::RenderPipeline pipeline = MakeTestPipeline(
renderPass, {dawn::BindingType::UniformBuffer, dawn::BindingType::UniformBuffer},
{layout, layout});
// Prepare data RGBAunorm(1, 0, 0, 0.5) and RGBAunorm(0, 1, 0, 0.5). They will be added in the
// shader.
std::array<float, 4> color0 = {1, 0, 0, 0.5};
std::array<float, 4> color1 = {0, 1, 0, 0.5};
size_t color1Offset = Align(sizeof(color0), kMinDynamicBufferOffsetAlignment);
std::vector<uint8_t> data(color1Offset + sizeof(color1));
memcpy(data.data(), color0.data(), sizeof(color0));
memcpy(data.data() + color1Offset, color1.data(), sizeof(color1));
// Create a bind group and uniform buffer with the color data. It will be bound at the offset
// to each color.
dawn::Buffer uniformBuffer =
utils::CreateBufferFromData(device, data.data(), data.size(), dawn::BufferUsage::Uniform);
dawn::BindGroup bindGroup =
utils::MakeBindGroup(device, layout, {{0, uniformBuffer, 0, 4 * sizeof(float)}});
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
// Set the first dynamic bind group.
uint64_t dynamicOffset = 0;
pass.SetBindGroup(0, bindGroup, 1, &dynamicOffset);
// Set the second dynamic bind group.
dynamicOffset = color1Offset;
pass.SetBindGroup(1, bindGroup, 1, &dynamicOffset);
// Set the pipeline and draw.
pass.SetPipeline(pipeline);
pass.Draw(3, 1, 0, 0);
pass.EndPass();
dawn::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
// The result should be RGBAunorm(1, 0, 0, 0.5) + RGBAunorm(0, 1, 0, 0.5)
RGBA8 filled(255, 255, 0, 255);
RGBA8 notFilled(0, 0, 0, 0);
int min = 1, max = kRTSize - 3;
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, min);
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, max, min);
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, max);
EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, max, max);
}
// Test that bind groups set for one pipeline are still set when the pipeline changes.
TEST_P(BindGroupTests, BindGroupsPersistAfterPipelineChange) {
// TODO(crbug.com/dawn/201): Implement on all platforms.
DAWN_SKIP_TEST_IF(!IsMetal());
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize);
// Create a bind group layout which uses a single dynamic uniform buffer.
dawn::BindGroupLayout uniformLayout = utils::MakeBindGroupLayout(
device, {{0, dawn::ShaderStage::Fragment, dawn::BindingType::UniformBuffer, true}});
// Create a bind group layout which uses a single dynamic storage buffer.
dawn::BindGroupLayout storageLayout = utils::MakeBindGroupLayout(
device, {{0, dawn::ShaderStage::Fragment, dawn::BindingType::StorageBuffer, true}});
// Create a pipeline which uses the uniform buffer and storage buffer bind groups.
dawn::RenderPipeline pipeline0 = MakeTestPipeline(
renderPass, {dawn::BindingType::UniformBuffer, dawn::BindingType::StorageBuffer},
{uniformLayout, storageLayout});
// Create a pipeline which uses the uniform buffer bind group twice.
dawn::RenderPipeline pipeline1 = MakeTestPipeline(
renderPass, {dawn::BindingType::UniformBuffer, dawn::BindingType::UniformBuffer},
{uniformLayout, uniformLayout});
// Prepare data RGBAunorm(1, 0, 0, 0.5) and RGBAunorm(0, 1, 0, 0.5). They will be added in the
// shader.
std::array<float, 4> color0 = {1, 0, 0, 0.5};
std::array<float, 4> color1 = {0, 1, 0, 0.5};
size_t color1Offset = Align(sizeof(color0), kMinDynamicBufferOffsetAlignment);
std::vector<uint8_t> data(color1Offset + sizeof(color1));
memcpy(data.data(), color0.data(), sizeof(color0));
memcpy(data.data() + color1Offset, color1.data(), sizeof(color1));
// Create a bind group and uniform buffer with the color data. It will be bound at the offset
// to each color.
dawn::Buffer uniformBuffer =
utils::CreateBufferFromData(device, data.data(), data.size(), dawn::BufferUsage::Uniform);
dawn::BindGroup bindGroup =
utils::MakeBindGroup(device, uniformLayout, {{0, uniformBuffer, 0, 4 * sizeof(float)}});
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
// Set the first pipeline (uniform, storage).
pass.SetPipeline(pipeline0);
// Set the first bind group at a dynamic offset.
// This bind group matches the slot in the pipeline layout.
uint64_t dynamicOffset = 0;
pass.SetBindGroup(0, bindGroup, 1, &dynamicOffset);
// Set the second bind group at a dynamic offset.
// This bind group does not match the slot in the pipeline layout.
dynamicOffset = color1Offset;
pass.SetBindGroup(1, bindGroup, 1, &dynamicOffset);
// Set the second pipeline (uniform, uniform).
// Both bind groups match the pipeline.
// They should persist and not need to be bound again.
pass.SetPipeline(pipeline1);
pass.Draw(3, 1, 0, 0);
pass.EndPass();
dawn::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
// The result should be RGBAunorm(1, 0, 0, 0.5) + RGBAunorm(0, 1, 0, 0.5)
RGBA8 filled(255, 255, 0, 255);
RGBA8 notFilled(0, 0, 0, 0);
int min = 1, max = kRTSize - 3;
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, min);
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, max, min);
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, max);
EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, max, max);
}
// Do a successful draw. Then, change the pipeline and one bind group.
// Draw to check that the all bind groups are set.
TEST_P(BindGroupTests, DrawThenChangePipelineAndBindGroup) {
// TODO(crbug.com/dawn/201): Implement on all platforms.
DAWN_SKIP_TEST_IF(!IsMetal());
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize);
// Create a bind group layout which uses a single dynamic uniform buffer.
dawn::BindGroupLayout uniformLayout = utils::MakeBindGroupLayout(
device, {{0, dawn::ShaderStage::Fragment, dawn::BindingType::UniformBuffer, true}});
// Create a bind group layout which uses a single dynamic storage buffer.
dawn::BindGroupLayout storageLayout = utils::MakeBindGroupLayout(
device, {{0, dawn::ShaderStage::Fragment, dawn::BindingType::StorageBuffer, true}});
// Create a pipeline with pipeline layout (uniform, uniform, storage).
dawn::RenderPipeline pipeline0 = MakeTestPipeline(
renderPass, {dawn::BindingType::UniformBuffer, dawn::BindingType::UniformBuffer, dawn::BindingType::StorageBuffer},
{uniformLayout, uniformLayout, storageLayout});
// Create a pipeline with pipeline layout (uniform, storage, storage).
dawn::RenderPipeline pipeline1 = MakeTestPipeline(
renderPass, {dawn::BindingType::UniformBuffer, dawn::BindingType::StorageBuffer, dawn::BindingType::StorageBuffer },
{uniformLayout, storageLayout, storageLayout});
// Prepare color data.
// The first draw will use { color0, color1, color2 }.
// The second draw will use { color0, color3, color2 }.
// The pipeline uses additive color blending so the result of two draws should be
// { 2 * color0 + color1 + color2 + color3} = RGBAunorm(1, 1, 1, 1)
std::array<float, 4> color0 = {0.5, 0, 0, 0};
std::array<float, 4> color1 = {0, 1, 0, 0};
std::array<float, 4> color2 = {0, 0, 0, 1};
std::array<float, 4> color3 = {0, 0, 1, 0};
size_t color1Offset = Align(sizeof(color0), kMinDynamicBufferOffsetAlignment);
size_t color2Offset = Align(color1Offset + sizeof(color1), kMinDynamicBufferOffsetAlignment);
size_t color3Offset = Align(color2Offset + sizeof(color2), kMinDynamicBufferOffsetAlignment);
std::vector<uint8_t> data(color3Offset + sizeof(color3), 0);
memcpy(data.data(), color0.data(), sizeof(color0));
memcpy(data.data() + color1Offset, color1.data(), sizeof(color1));
memcpy(data.data() + color2Offset, color2.data(), sizeof(color2));
memcpy(data.data() + color3Offset, color3.data(), sizeof(color3));
// Create a uniform and storage buffer bind groups to bind the color data.
dawn::Buffer uniformBuffer =
utils::CreateBufferFromData(device, data.data(), data.size(), dawn::BufferUsage::Uniform);
dawn::Buffer storageBuffer =
utils::CreateBufferFromData(device, data.data(), data.size(), dawn::BufferUsage::Storage);
dawn::BindGroup uniformBindGroup =
utils::MakeBindGroup(device, uniformLayout, {{0, uniformBuffer, 0, 4 * sizeof(float)}});
dawn::BindGroup storageBindGroup =
utils::MakeBindGroup(device, storageLayout, {{0, storageBuffer, 0, 4 * sizeof(float)}});
dawn::CommandEncoder encoder = device.CreateCommandEncoder();
dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
// Set the pipeline to (uniform, uniform, storage)
pass.SetPipeline(pipeline0);
// Set the first bind group to color0 in the dynamic uniform buffer.
uint64_t dynamicOffset = 0;
pass.SetBindGroup(0, uniformBindGroup, 1, &dynamicOffset);
// Set the first bind group to color1 in the dynamic uniform buffer.
dynamicOffset = color1Offset;
pass.SetBindGroup(1, uniformBindGroup, 1, &dynamicOffset);
// Set the first bind group to color2 in the dynamic storage buffer.
dynamicOffset = color2Offset;
pass.SetBindGroup(2, storageBindGroup, 1, &dynamicOffset);
pass.Draw(3, 1, 0, 0);
// Set the pipeline to (uniform, storage, storage)
// - The first bind group should persist (inherited on some backends)
// - The second bind group needs to be set again to pass validation.
// It changed from uniform to storage.
// - The third bind group should persist. It should be set again by the backend internally.
pass.SetPipeline(pipeline1);
// Set the second bind group to color3 in the dynamic storage buffer.
dynamicOffset = color3Offset;
pass.SetBindGroup(1, storageBindGroup, 1, &dynamicOffset);
pass.Draw(3, 1, 0, 0);
pass.EndPass();
dawn::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
RGBA8 filled(255, 255, 255, 255);
RGBA8 notFilled(0, 0, 0, 0);
int min = 1, max = kRTSize - 3;
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, min);
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, max, min);
EXPECT_PIXEL_RGBA8_EQ(filled, renderPass.color, min, max);
EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, max, max);
}
DAWN_INSTANTIATE_TEST(BindGroupTests, D3D12Backend, MetalBackend, OpenGLBackend, VulkanBackend);