blob: c1ca2d993bb1822d8a424efa63c561157e5a8214 [file] [log] [blame]
// Copyright 2021 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/DawnNativeTest.h"
#include "dawn_native/CommandBuffer.h"
#include "dawn_native/Commands.h"
#include "dawn_native/ComputePassEncoder.h"
#include "utils/WGPUHelpers.h"
class CommandBufferEncodingTests : public DawnNativeTest {
protected:
void ExpectCommands(dawn_native::CommandIterator* commands,
std::vector<std::pair<dawn_native::Command,
std::function<void(dawn_native::CommandIterator*)>>>
expectedCommands) {
dawn_native::Command commandId;
for (uint32_t commandIndex = 0; commands->NextCommandId(&commandId); ++commandIndex) {
ASSERT_LT(commandIndex, expectedCommands.size()) << "Unexpected command";
ASSERT_EQ(commandId, expectedCommands[commandIndex].first)
<< "at command " << commandIndex;
expectedCommands[commandIndex].second(commands);
}
}
};
// Indirect dispatch validation changes the bind groups in the middle
// of a pass. Test that bindings are restored after the validation runs.
TEST_F(CommandBufferEncodingTests, ComputePassEncoderIndirectDispatchStateRestoration) {
using namespace dawn_native;
wgpu::BindGroupLayout staticLayout =
utils::MakeBindGroupLayout(device, {{
0,
wgpu::ShaderStage::Compute,
wgpu::BufferBindingType::Uniform,
}});
wgpu::BindGroupLayout dynamicLayout =
utils::MakeBindGroupLayout(device, {{
0,
wgpu::ShaderStage::Compute,
wgpu::BufferBindingType::Uniform,
true,
}});
// Create a simple pipeline
wgpu::ComputePipelineDescriptor csDesc;
csDesc.compute.module = utils::CreateShaderModule(device, R"(
[[stage(compute), workgroup_size(1, 1, 1)]]
fn main() {
})");
csDesc.compute.entryPoint = "main";
wgpu::PipelineLayout pl0 = utils::MakePipelineLayout(device, {staticLayout, dynamicLayout});
csDesc.layout = pl0;
wgpu::ComputePipeline pipeline0 = device.CreateComputePipeline(&csDesc);
wgpu::PipelineLayout pl1 = utils::MakePipelineLayout(device, {dynamicLayout, staticLayout});
csDesc.layout = pl1;
wgpu::ComputePipeline pipeline1 = device.CreateComputePipeline(&csDesc);
// Create buffers to use for both the indirect buffer and the bind groups.
wgpu::Buffer indirectBuffer =
utils::CreateBufferFromData<uint32_t>(device, wgpu::BufferUsage::Indirect, {1, 2, 3, 4});
wgpu::BufferDescriptor uniformBufferDesc = {};
uniformBufferDesc.size = 512;
uniformBufferDesc.usage = wgpu::BufferUsage::Uniform;
wgpu::Buffer uniformBuffer = device.CreateBuffer(&uniformBufferDesc);
wgpu::BindGroup staticBG = utils::MakeBindGroup(device, staticLayout, {{0, uniformBuffer}});
wgpu::BindGroup dynamicBG =
utils::MakeBindGroup(device, dynamicLayout, {{0, uniformBuffer, 0, 256}});
uint32_t dynamicOffset = 256;
std::vector<uint32_t> emptyDynamicOffsets = {};
std::vector<uint32_t> singleDynamicOffset = {dynamicOffset};
// Begin encoding commands.
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
CommandBufferStateTracker* stateTracker =
FromAPI(pass.Get())->GetCommandBufferStateTrackerForTesting();
// Perform a dispatch indirect which will be preceded by a validation dispatch.
pass.SetPipeline(pipeline0);
pass.SetBindGroup(0, staticBG);
pass.SetBindGroup(1, dynamicBG, 1, &dynamicOffset);
EXPECT_EQ(ToAPI(stateTracker->GetComputePipeline()), pipeline0.Get());
pass.DispatchIndirect(indirectBuffer, 0);
// Expect restored state.
EXPECT_EQ(ToAPI(stateTracker->GetComputePipeline()), pipeline0.Get());
EXPECT_EQ(ToAPI(stateTracker->GetPipelineLayout()), pl0.Get());
EXPECT_EQ(ToAPI(stateTracker->GetBindGroup(BindGroupIndex(0))), staticBG.Get());
EXPECT_EQ(stateTracker->GetDynamicOffsets(BindGroupIndex(0)), emptyDynamicOffsets);
EXPECT_EQ(ToAPI(stateTracker->GetBindGroup(BindGroupIndex(1))), dynamicBG.Get());
EXPECT_EQ(stateTracker->GetDynamicOffsets(BindGroupIndex(1)), singleDynamicOffset);
// Dispatch again to check that the restored state can be used.
// Also pass an indirect offset which should get replaced with the offset
// into the scratch indirect buffer (0).
pass.DispatchIndirect(indirectBuffer, 4);
// Expect restored state.
EXPECT_EQ(ToAPI(stateTracker->GetComputePipeline()), pipeline0.Get());
EXPECT_EQ(ToAPI(stateTracker->GetPipelineLayout()), pl0.Get());
EXPECT_EQ(ToAPI(stateTracker->GetBindGroup(BindGroupIndex(0))), staticBG.Get());
EXPECT_EQ(stateTracker->GetDynamicOffsets(BindGroupIndex(0)), emptyDynamicOffsets);
EXPECT_EQ(ToAPI(stateTracker->GetBindGroup(BindGroupIndex(1))), dynamicBG.Get());
EXPECT_EQ(stateTracker->GetDynamicOffsets(BindGroupIndex(1)), singleDynamicOffset);
// Change the pipeline
pass.SetPipeline(pipeline1);
pass.SetBindGroup(0, dynamicBG, 1, &dynamicOffset);
pass.SetBindGroup(1, staticBG);
EXPECT_EQ(ToAPI(stateTracker->GetComputePipeline()), pipeline1.Get());
EXPECT_EQ(ToAPI(stateTracker->GetPipelineLayout()), pl1.Get());
pass.DispatchIndirect(indirectBuffer, 0);
// Expect restored state.
EXPECT_EQ(ToAPI(stateTracker->GetComputePipeline()), pipeline1.Get());
EXPECT_EQ(ToAPI(stateTracker->GetPipelineLayout()), pl1.Get());
EXPECT_EQ(ToAPI(stateTracker->GetBindGroup(BindGroupIndex(0))), dynamicBG.Get());
EXPECT_EQ(stateTracker->GetDynamicOffsets(BindGroupIndex(0)), singleDynamicOffset);
EXPECT_EQ(ToAPI(stateTracker->GetBindGroup(BindGroupIndex(1))), staticBG.Get());
EXPECT_EQ(stateTracker->GetDynamicOffsets(BindGroupIndex(1)), emptyDynamicOffsets);
pass.EndPass();
wgpu::CommandBuffer commandBuffer = encoder.Finish();
auto ExpectSetPipeline = [](wgpu::ComputePipeline pipeline) {
return [pipeline](CommandIterator* commands) {
auto* cmd = commands->NextCommand<SetComputePipelineCmd>();
EXPECT_EQ(ToAPI(cmd->pipeline.Get()), pipeline.Get());
};
};
auto ExpectSetBindGroup = [](uint32_t index, wgpu::BindGroup bg,
std::vector<uint32_t> offsets = {}) {
return [index, bg, offsets](CommandIterator* commands) {
auto* cmd = commands->NextCommand<SetBindGroupCmd>();
uint32_t* dynamicOffsets = nullptr;
if (cmd->dynamicOffsetCount > 0) {
dynamicOffsets = commands->NextData<uint32_t>(cmd->dynamicOffsetCount);
}
ASSERT_EQ(cmd->index, BindGroupIndex(index));
ASSERT_EQ(ToAPI(cmd->group.Get()), bg.Get());
ASSERT_EQ(cmd->dynamicOffsetCount, offsets.size());
for (uint32_t i = 0; i < cmd->dynamicOffsetCount; ++i) {
ASSERT_EQ(dynamicOffsets[i], offsets[i]);
}
};
};
// Initialize as null. Once we know the pointer, we'll check
// that it's the same buffer every time.
WGPUBuffer indirectScratchBuffer = nullptr;
auto ExpectDispatchIndirect = [&](CommandIterator* commands) {
auto* cmd = commands->NextCommand<DispatchIndirectCmd>();
if (indirectScratchBuffer == nullptr) {
indirectScratchBuffer = ToAPI(cmd->indirectBuffer.Get());
}
ASSERT_EQ(ToAPI(cmd->indirectBuffer.Get()), indirectScratchBuffer);
ASSERT_EQ(cmd->indirectOffset, uint64_t(0));
};
// Initialize as null. Once we know the pointer, we'll check
// that it's the same pipeline every time.
WGPUComputePipeline validationPipeline = nullptr;
auto ExpectSetValidationPipeline = [&](CommandIterator* commands) {
auto* cmd = commands->NextCommand<SetComputePipelineCmd>();
WGPUComputePipeline pipeline = ToAPI(cmd->pipeline.Get());
if (validationPipeline != nullptr) {
EXPECT_EQ(pipeline, validationPipeline);
} else {
EXPECT_NE(pipeline, nullptr);
validationPipeline = pipeline;
}
};
auto ExpectSetValidationBindGroup = [&](CommandIterator* commands) {
auto* cmd = commands->NextCommand<SetBindGroupCmd>();
ASSERT_EQ(cmd->index, BindGroupIndex(0));
ASSERT_NE(cmd->group.Get(), nullptr);
ASSERT_EQ(cmd->dynamicOffsetCount, 0u);
};
auto ExpectSetValidationDispatch = [&](CommandIterator* commands) {
auto* cmd = commands->NextCommand<DispatchCmd>();
ASSERT_EQ(cmd->x, 1u);
ASSERT_EQ(cmd->y, 1u);
ASSERT_EQ(cmd->z, 1u);
};
ExpectCommands(
FromAPI(commandBuffer.Get())->GetCommandIteratorForTesting(),
{
{Command::BeginComputePass,
[&](CommandIterator* commands) { SkipCommand(commands, Command::BeginComputePass); }},
// Expect the state to be set.
{Command::SetComputePipeline, ExpectSetPipeline(pipeline0)},
{Command::SetBindGroup, ExpectSetBindGroup(0, staticBG)},
{Command::SetBindGroup, ExpectSetBindGroup(1, dynamicBG, {dynamicOffset})},
// Expect the validation.
{Command::SetComputePipeline, ExpectSetValidationPipeline},
{Command::SetBindGroup, ExpectSetValidationBindGroup},
{Command::Dispatch, ExpectSetValidationDispatch},
// Expect the state to be restored.
{Command::SetComputePipeline, ExpectSetPipeline(pipeline0)},
{Command::SetBindGroup, ExpectSetBindGroup(0, staticBG)},
{Command::SetBindGroup, ExpectSetBindGroup(1, dynamicBG, {dynamicOffset})},
// Expect the dispatchIndirect.
{Command::DispatchIndirect, ExpectDispatchIndirect},
// Expect the validation.
{Command::SetComputePipeline, ExpectSetValidationPipeline},
{Command::SetBindGroup, ExpectSetValidationBindGroup},
{Command::Dispatch, ExpectSetValidationDispatch},
// Expect the state to be restored.
{Command::SetComputePipeline, ExpectSetPipeline(pipeline0)},
{Command::SetBindGroup, ExpectSetBindGroup(0, staticBG)},
{Command::SetBindGroup, ExpectSetBindGroup(1, dynamicBG, {dynamicOffset})},
// Expect the dispatchIndirect.
{Command::DispatchIndirect, ExpectDispatchIndirect},
// Expect the state to be set (new pipeline).
{Command::SetComputePipeline, ExpectSetPipeline(pipeline1)},
{Command::SetBindGroup, ExpectSetBindGroup(0, dynamicBG, {dynamicOffset})},
{Command::SetBindGroup, ExpectSetBindGroup(1, staticBG)},
// Expect the validation.
{Command::SetComputePipeline, ExpectSetValidationPipeline},
{Command::SetBindGroup, ExpectSetValidationBindGroup},
{Command::Dispatch, ExpectSetValidationDispatch},
// Expect the state to be restored.
{Command::SetComputePipeline, ExpectSetPipeline(pipeline1)},
{Command::SetBindGroup, ExpectSetBindGroup(0, dynamicBG, {dynamicOffset})},
{Command::SetBindGroup, ExpectSetBindGroup(1, staticBG)},
// Expect the dispatchIndirect.
{Command::DispatchIndirect, ExpectDispatchIndirect},
{Command::EndComputePass,
[&](CommandIterator* commands) { commands->NextCommand<EndComputePassCmd>(); }},
});
}
// Test that after restoring state, it is fully applied to the state tracker
// and does not leak state changes that occured between a snapshot and the
// state restoration.
TEST_F(CommandBufferEncodingTests, StateNotLeakedAfterRestore) {
using namespace dawn_native;
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
CommandBufferStateTracker* stateTracker =
FromAPI(pass.Get())->GetCommandBufferStateTrackerForTesting();
// Snapshot the state.
CommandBufferStateTracker snapshot = *stateTracker;
// Expect no pipeline in the snapshot
EXPECT_FALSE(snapshot.HasPipeline());
// Create a simple pipeline
wgpu::ComputePipelineDescriptor csDesc;
csDesc.compute.module = utils::CreateShaderModule(device, R"(
[[stage(compute), workgroup_size(1, 1, 1)]]
fn main() {
})");
csDesc.compute.entryPoint = "main";
wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc);
// Set the pipeline.
pass.SetPipeline(pipeline);
// Expect the pipeline to be set.
EXPECT_EQ(ToAPI(stateTracker->GetComputePipeline()), pipeline.Get());
// Restore the state.
FromAPI(pass.Get())->RestoreCommandBufferStateForTesting(std::move(snapshot));
// Expect no pipeline
EXPECT_FALSE(stateTracker->HasPipeline());
}