CommandBufferBuilder::Validate: Iterate per pass

This changes the validation to have one iteration loop for the main
buffer that calls to pass-specific iteration loops for compute passes
and render passes. This is done to simplify code a bit and will help
implement implicit transitions that are "precomputed" per pass in the
command buffer validation and re-used in the backends.

A number of code simplifications were made to CommandBufferStateTracker
since it doesn't need to know if we are in a pass anymore. Further
simplifications will happen when implicit barriers are implemented.
This commit is contained in:
Corentin Wallez 2018-07-04 15:20:19 +02:00 committed by Corentin Wallez
parent a884cb2479
commit b3bd35ed88
4 changed files with 151 additions and 223 deletions

View File

@ -124,6 +124,8 @@ namespace backend {
} // namespace
// CommandBuffer
CommandBufferBase::CommandBufferBase(CommandBufferBuilder* builder)
: mDevice(builder->mDevice),
mBuffersTransitioned(std::move(builder->mState->mBuffersTransitioned)),
@ -150,6 +152,8 @@ namespace backend {
return mDevice;
}
// CommandBufferBuilder
CommandBufferBuilder::CommandBufferBuilder(DeviceBase* device)
: Builder(device), mState(std::make_unique<CommandBufferStateTracker>(this)) {
}
@ -161,23 +165,43 @@ namespace backend {
}
}
CommandIterator CommandBufferBuilder::AcquireCommands() {
ASSERT(!mWereCommandsAcquired);
mWereCommandsAcquired = true;
return std::move(mIterator);
}
CommandBufferBase* CommandBufferBuilder::GetResultImpl() {
MoveToIterator();
return mDevice->CreateCommandBuffer(this);
}
void CommandBufferBuilder::MoveToIterator() {
if (!mWasMovedToIterator) {
mIterator = std::move(mAllocator);
mWasMovedToIterator = true;
}
}
// Implementation of the command buffer validation that can be precomputed before submit
bool CommandBufferBuilder::ValidateGetResult() {
MoveToIterator();
mIterator.Reset();
Command type;
while (mIterator.NextCommandId(&type)) {
switch (type) {
case Command::BeginComputePass: {
mIterator.NextCommand<BeginComputePassCmd>();
if (!mState->BeginComputePass()) {
if (!ValidateComputePass()) {
return false;
}
} break;
case Command::BeginRenderPass: {
BeginRenderPassCmd* cmd = mIterator.NextCommand<BeginRenderPassCmd>();
RenderPassDescriptorBase* info = cmd->info.Get();
if (!mState->BeginRenderPass(info)) {
if (!ValidateRenderPass(cmd->info.Get())) {
return false;
}
} break;
@ -186,7 +210,6 @@ namespace backend {
CopyBufferToBufferCmd* copy = mIterator.NextCommand<CopyBufferToBufferCmd>();
if (!ValidateCopySizeFitsInBuffer(this, copy->source, copy->size) ||
!ValidateCopySizeFitsInBuffer(this, copy->destination, copy->size) ||
!mState->ValidateCanCopy() ||
!mState->ValidateCanUseBufferAs(copy->source.buffer.Get(),
nxt::BufferUsageBit::TransferSrc) ||
!mState->ValidateCanUseBufferAs(copy->destination.buffer.Get(),
@ -206,7 +229,6 @@ namespace backend {
!ValidateCopySizeFitsInBuffer(this, copy->source, bufferCopySize) ||
!ValidateTexelBufferOffset(this, copy->destination.texture.Get(),
copy->source) ||
!mState->ValidateCanCopy() ||
!mState->ValidateCanUseBufferAs(copy->source.buffer.Get(),
nxt::BufferUsageBit::TransferSrc) ||
!mState->ValidateCanUseTextureAs(copy->destination.texture.Get(),
@ -226,7 +248,6 @@ namespace backend {
!ValidateCopySizeFitsInBuffer(this, copy->destination, bufferCopySize) ||
!ValidateTexelBufferOffset(this, copy->source.texture.Get(),
copy->destination) ||
!mState->ValidateCanCopy() ||
!mState->ValidateCanUseTextureAs(copy->source.texture.Get(),
nxt::TextureUsageBit::TransferSrc) ||
!mState->ValidateCanUseBufferAs(copy->destination.buffer.Get(),
@ -235,6 +256,42 @@ namespace backend {
}
} break;
case Command::TransitionBufferUsage: {
TransitionBufferUsageCmd* cmd =
mIterator.NextCommand<TransitionBufferUsageCmd>();
if (!mState->TransitionBufferUsage(cmd->buffer.Get(), cmd->usage)) {
return false;
}
} break;
case Command::TransitionTextureUsage: {
TransitionTextureUsageCmd* cmd =
mIterator.NextCommand<TransitionTextureUsageCmd>();
if (!mState->TransitionTextureUsage(cmd->texture.Get(), cmd->usage)) {
return false;
}
} break;
default:
HandleError("Command disallowed outside of a pass");
return false;
}
}
return true;
}
bool CommandBufferBuilder::ValidateComputePass() {
Command type;
while (mIterator.NextCommandId(&type)) {
switch (type) {
case Command::EndComputePass: {
mIterator.NextCommand<EndComputePassCmd>();
mState->EndPass();
return true;
} break;
case Command::Dispatch: {
mIterator.NextCommand<DispatchCmd>();
if (!mState->ValidateCanDispatch()) {
@ -242,6 +299,58 @@ namespace backend {
}
} break;
case Command::SetComputePipeline: {
SetComputePipelineCmd* cmd = mIterator.NextCommand<SetComputePipelineCmd>();
ComputePipelineBase* pipeline = cmd->pipeline.Get();
if (!mState->SetComputePipeline(pipeline)) {
return false;
}
} break;
case Command::SetPushConstants: {
SetPushConstantsCmd* cmd = mIterator.NextCommand<SetPushConstantsCmd>();
mIterator.NextData<uint32_t>(cmd->count);
// Validation of count and offset has already been done when the command was
// recorded because it impacts the size of an allocation in the
// CommandAllocator.
if (cmd->stages & ~nxt::ShaderStageBit::Compute) {
HandleError(
"SetPushConstants stage must be compute or 0 in compute passes");
return false;
}
} break;
case Command::SetBindGroup: {
SetBindGroupCmd* cmd = mIterator.NextCommand<SetBindGroupCmd>();
if (!mState->SetBindGroup(cmd->index, cmd->group.Get())) {
return false;
}
} break;
default:
HandleError("Command disallowed inside a compute pass");
return false;
}
}
HandleError("Unfinished compute pass");
return false;
}
bool CommandBufferBuilder::ValidateRenderPass(RenderPassDescriptorBase* renderPass) {
if (!mState->BeginRenderPass(renderPass)) {
return false;
}
Command type;
while (mIterator.NextCommandId(&type)) {
switch (type) {
case Command::EndRenderPass: {
mIterator.NextCommand<EndRenderPassCmd>();
mState->EndPass();
return true;
} break;
case Command::DrawArrays: {
mIterator.NextCommand<DrawArraysCmd>();
if (!mState->ValidateCanDrawArrays()) {
@ -256,31 +365,15 @@ namespace backend {
}
} break;
case Command::EndComputePass: {
mIterator.NextCommand<EndComputePassCmd>();
if (!mState->EndComputePass()) {
return false;
}
} break;
case Command::EndRenderPass: {
mIterator.NextCommand<EndRenderPassCmd>();
if (!mState->EndRenderPass()) {
return false;
}
} break;
case Command::SetComputePipeline: {
SetComputePipelineCmd* cmd = mIterator.NextCommand<SetComputePipelineCmd>();
ComputePipelineBase* pipeline = cmd->pipeline.Get();
if (!mState->SetComputePipeline(pipeline)) {
return false;
}
} break;
case Command::SetRenderPipeline: {
SetRenderPipelineCmd* cmd = mIterator.NextCommand<SetRenderPipelineCmd>();
RenderPipelineBase* pipeline = cmd->pipeline.Get();
if (!pipeline->IsCompatibleWith(renderPass)) {
HandleError("Pipeline is incompatible with this render pass");
return false;
}
if (!mState->SetRenderPipeline(pipeline)) {
return false;
}
@ -292,33 +385,25 @@ namespace backend {
// Validation of count and offset has already been done when the command was
// recorded because it impacts the size of an allocation in the
// CommandAllocator.
if (!mState->ValidateSetPushConstants(cmd->stages)) {
if (cmd->stages &
~(nxt::ShaderStageBit::Vertex | nxt::ShaderStageBit::Fragment)) {
HandleError(
"SetPushConstants stage must be a subset of (vertex|fragment) in "
"render passes");
return false;
}
} break;
case Command::SetStencilReference: {
mIterator.NextCommand<SetStencilReferenceCmd>();
if (!mState->HaveRenderPass()) {
HandleError("Can't set stencil reference without an active render pass");
return false;
}
} break;
case Command::SetBlendColor: {
mIterator.NextCommand<SetBlendColorCmd>();
if (!mState->HaveRenderPass()) {
HandleError("Can't set blend color without an active render pass");
return false;
}
} break;
case Command::SetScissorRect: {
mIterator.NextCommand<SetScissorRectCmd>();
if (!mState->HaveRenderPass()) {
HandleError("Can't set scissor rect without an active render pass");
return false;
}
} break;
case Command::SetBindGroup: {
@ -345,42 +430,17 @@ namespace backend {
}
} break;
case Command::TransitionBufferUsage: {
TransitionBufferUsageCmd* cmd =
mIterator.NextCommand<TransitionBufferUsageCmd>();
if (!mState->TransitionBufferUsage(cmd->buffer.Get(), cmd->usage)) {
default:
HandleError("Command disallowed inside a render pass");
return false;
}
} break;
}
case Command::TransitionTextureUsage: {
TransitionTextureUsageCmd* cmd =
mIterator.NextCommand<TransitionTextureUsageCmd>();
if (!mState->TransitionTextureUsage(cmd->texture.Get(), cmd->usage)) {
HandleError("Unfinished render pass");
return false;
}
} break;
}
}
if (!mState->ValidateEndCommandBuffer()) {
return false;
}
return true;
}
CommandIterator CommandBufferBuilder::AcquireCommands() {
ASSERT(!mWereCommandsAcquired);
mWereCommandsAcquired = true;
return std::move(mIterator);
}
CommandBufferBase* CommandBufferBuilder::GetResultImpl() {
MoveToIterator();
return mDevice->CreateCommandBuffer(this);
}
// Implementation of the API's command recording methods
void CommandBufferBuilder::BeginComputePass() {
mAllocator.Allocate<BeginComputePassCmd>(Command::BeginComputePass);
@ -630,11 +690,4 @@ namespace backend {
cmd->usage = usage;
}
void CommandBufferBuilder::MoveToIterator() {
if (!mWasMovedToIterator) {
mIterator = std::move(mAllocator);
mWasMovedToIterator = true;
}
}
} // namespace backend

View File

@ -136,6 +136,9 @@ namespace backend {
CommandBufferBase* GetResultImpl() override;
void MoveToIterator();
bool ValidateComputePass();
bool ValidateRenderPass(RenderPassDescriptorBase* renderPass);
std::unique_ptr<CommandBufferStateTracker> mState;
CommandAllocator mAllocator;
CommandIterator mIterator;

View File

@ -32,18 +32,6 @@ namespace backend {
: mBuilder(mBuilder) {
}
bool CommandBufferStateTracker::HaveRenderPass() const {
return mCurrentRenderPass != nullptr;
}
bool CommandBufferStateTracker::ValidateCanCopy() const {
if (mCurrentRenderPass) {
mBuilder->HandleError("Copy cannot occur during a render pass");
return false;
}
return true;
}
bool CommandBufferStateTracker::ValidateCanUseBufferAs(BufferBase* buffer,
nxt::BufferUsageBit usage) const {
if (!BufferHasGuaranteedUsageBit(buffer, usage)) {
@ -64,14 +52,13 @@ namespace backend {
bool CommandBufferStateTracker::ValidateCanDispatch() {
constexpr ValidationAspects requiredAspects =
1 << VALIDATION_ASPECT_COMPUTE_PIPELINE | // implicitly requires COMPUTE_PASS
1 << VALIDATION_ASPECT_BIND_GROUPS;
1 << VALIDATION_ASPECT_PIPELINE | 1 << VALIDATION_ASPECT_BIND_GROUPS;
if ((requiredAspects & ~mAspects).none()) {
// Fast return-true path if everything is good
return true;
}
if (!mAspects[VALIDATION_ASPECT_COMPUTE_PIPELINE]) {
if (!mAspects[VALIDATION_ASPECT_PIPELINE]) {
mBuilder->HandleError("No active compute pipeline");
return false;
}
@ -84,10 +71,9 @@ namespace backend {
}
bool CommandBufferStateTracker::ValidateCanDrawArrays() {
// TODO(kainino@chromium.org): Check for a current render pass
constexpr ValidationAspects requiredAspects =
1 << VALIDATION_ASPECT_RENDER_PIPELINE | // implicitly requires RENDER_PASS
1 << VALIDATION_ASPECT_BIND_GROUPS | 1 << VALIDATION_ASPECT_VERTEX_BUFFERS;
constexpr ValidationAspects requiredAspects = 1 << VALIDATION_ASPECT_PIPELINE |
1 << VALIDATION_ASPECT_BIND_GROUPS |
1 << VALIDATION_ASPECT_VERTEX_BUFFERS;
if ((requiredAspects & ~mAspects).none()) {
// Fast return-true path if everything is good
return true;
@ -97,9 +83,8 @@ namespace backend {
}
bool CommandBufferStateTracker::ValidateCanDrawElements() {
// TODO(kainino@chromium.org): Check for a current render pass
constexpr ValidationAspects requiredAspects =
1 << VALIDATION_ASPECT_RENDER_PIPELINE | 1 << VALIDATION_ASPECT_BIND_GROUPS |
1 << VALIDATION_ASPECT_PIPELINE | 1 << VALIDATION_ASPECT_BIND_GROUPS |
1 << VALIDATION_ASPECT_VERTEX_BUFFERS | 1 << VALIDATION_ASPECT_INDEX_BUFFER;
if ((requiredAspects & ~mAspects).none()) {
// Fast return-true path if everything is good
@ -113,72 +98,7 @@ namespace backend {
return RevalidateCanDraw();
}
bool CommandBufferStateTracker::ValidateEndCommandBuffer() const {
if (mCurrentRenderPass != nullptr) {
mBuilder->HandleError("Can't end command buffer with an active render pass");
return false;
}
if (mAspects[VALIDATION_ASPECT_COMPUTE_PASS]) {
mBuilder->HandleError("Can't end command buffer with an active compute pass");
return false;
}
return true;
}
bool CommandBufferStateTracker::ValidateSetPushConstants(nxt::ShaderStageBit stages) {
if (mAspects[VALIDATION_ASPECT_COMPUTE_PASS]) {
if (stages & ~nxt::ShaderStageBit::Compute) {
mBuilder->HandleError(
"SetPushConstants stage must be compute or 0 in compute passes");
return false;
}
} else if (mAspects[VALIDATION_ASPECT_RENDER_PASS]) {
if (stages & ~(nxt::ShaderStageBit::Vertex | nxt::ShaderStageBit::Fragment)) {
mBuilder->HandleError(
"SetPushConstants stage must be a subset if (vertex|fragment) in render "
"passes");
return false;
}
} else {
mBuilder->HandleError(
"PushConstants must be set in either compute passes or render passes");
return false;
}
return true;
}
bool CommandBufferStateTracker::BeginComputePass() {
if (mCurrentRenderPass != nullptr) {
mBuilder->HandleError("Cannot begin a compute pass while a render pass is active");
return false;
}
mAspects.set(VALIDATION_ASPECT_COMPUTE_PASS);
return true;
}
bool CommandBufferStateTracker::EndComputePass() {
if (!mAspects[VALIDATION_ASPECT_COMPUTE_PASS]) {
mBuilder->HandleError("Can't end a compute pass without beginning one");
return false;
}
mAspects.reset(VALIDATION_ASPECT_COMPUTE_PASS);
UnsetPipeline();
return true;
}
bool CommandBufferStateTracker::BeginRenderPass(RenderPassDescriptorBase* info) {
if (mAspects[VALIDATION_ASPECT_COMPUTE_PASS]) {
mBuilder->HandleError("Cannot begin a render pass while a compute pass is active");
return false;
}
if (mCurrentRenderPass != nullptr) {
mBuilder->HandleError("A render pass is already active");
return false;
}
mCurrentRenderPass = info;
mAspects.set(VALIDATION_ASPECT_RENDER_PASS);
for (uint32_t i : IterateBitSet(info->GetColorAttachmentMask())) {
TextureBase* texture = info->GetColorAttachment(i).view->GetTexture();
if (!EnsureTextureUsage(texture, nxt::TextureUsageBit::OutputAttachment)) {
@ -200,50 +120,21 @@ namespace backend {
return true;
}
bool CommandBufferStateTracker::EndRenderPass() {
if (mCurrentRenderPass == nullptr) {
mBuilder->HandleError("No render pass is currently active");
return false;
}
void CommandBufferStateTracker::EndPass() {
// Everything in mTexturesAttached should be for the current render pass.
mTexturesAttached.clear();
mInputsSet.reset();
UnsetPipeline();
mAspects.reset(VALIDATION_ASPECT_RENDER_PASS);
mCurrentRenderPass = nullptr;
return true;
mAspects = 0;
mBindgroups.fill(nullptr);
}
bool CommandBufferStateTracker::SetComputePipeline(ComputePipelineBase* pipeline) {
if (!mAspects[VALIDATION_ASPECT_COMPUTE_PASS]) {
mBuilder->HandleError("A compute pass must be active when a compute pipeline is set");
return false;
}
if (mCurrentRenderPass) {
mBuilder->HandleError("Can't use a compute pipeline while a render pass is active");
return false;
}
mAspects.set(VALIDATION_ASPECT_COMPUTE_PIPELINE);
SetPipelineCommon(pipeline);
return true;
}
bool CommandBufferStateTracker::SetRenderPipeline(RenderPipelineBase* pipeline) {
if (!mAspects[VALIDATION_ASPECT_RENDER_PASS]) {
mBuilder->HandleError("A render pass must be active when a render pipeline is set");
return false;
}
if (!pipeline->IsCompatibleWith(mCurrentRenderPass)) {
mBuilder->HandleError("Pipeline is incompatible with this render pass");
return false;
}
mAspects.set(VALIDATION_ASPECT_RENDER_PIPELINE);
mLastRenderPipeline = pipeline;
SetPipelineCommon(pipeline);
return true;
@ -419,9 +310,7 @@ namespace backend {
}
bool CommandBufferStateTracker::HavePipeline() const {
constexpr ValidationAspects pipelineAspects =
1 << VALIDATION_ASPECT_COMPUTE_PIPELINE | 1 << VALIDATION_ASPECT_RENDER_PIPELINE;
return (mAspects & pipelineAspects).any();
return mAspects[VALIDATION_ASPECT_PIPELINE];
}
bool CommandBufferStateTracker::ValidateBindGroupUsages(BindGroupBase* group) const {
@ -472,7 +361,7 @@ namespace backend {
}
bool CommandBufferStateTracker::RevalidateCanDraw() {
if (!mAspects[VALIDATION_ASPECT_RENDER_PIPELINE]) {
if (!mAspects[VALIDATION_ASPECT_PIPELINE]) {
mBuilder->HandleError("No active render pipeline");
return false;
}
@ -491,6 +380,8 @@ namespace backend {
void CommandBufferStateTracker::SetPipelineCommon(PipelineBase* pipeline) {
PipelineLayoutBase* layout = pipeline->GetLayout();
mAspects.set(VALIDATION_ASPECT_PIPELINE);
mAspects.reset(VALIDATION_ASPECT_BIND_GROUPS);
mAspects.reset(VALIDATION_ASPECT_VERTEX_BUFFERS);
// Reset bindgroups but mark unused bindgroups as valid
@ -504,12 +395,4 @@ namespace backend {
mLastPipeline = pipeline;
}
void CommandBufferStateTracker::UnsetPipeline() {
constexpr ValidationAspects pipelineDependentAspects =
1 << VALIDATION_ASPECT_RENDER_PIPELINE | 1 << VALIDATION_ASPECT_COMPUTE_PIPELINE |
1 << VALIDATION_ASPECT_BIND_GROUPS | 1 << VALIDATION_ASPECT_VERTEX_BUFFERS |
1 << VALIDATION_ASPECT_INDEX_BUFFER;
mAspects &= ~pipelineDependentAspects;
mBindgroups.fill(nullptr);
}
} // namespace backend

View File

@ -29,21 +29,16 @@ namespace backend {
explicit CommandBufferStateTracker(CommandBufferBuilder* builder);
// Non-state-modifying validation functions
bool HaveRenderPass() const;
bool ValidateCanCopy() const;
bool ValidateCanUseBufferAs(BufferBase* buffer, nxt::BufferUsageBit usage) const;
bool ValidateCanUseTextureAs(TextureBase* texture, nxt::TextureUsageBit usage) const;
bool ValidateCanDispatch();
bool ValidateCanDrawArrays();
bool ValidateCanDrawElements();
bool ValidateEndCommandBuffer() const;
bool ValidateSetPushConstants(nxt::ShaderStageBit stages);
// State-modifying methods
bool BeginComputePass();
bool EndComputePass();
void EndPass();
bool BeginRenderPass(RenderPassDescriptorBase* info);
bool EndRenderPass();
bool SetComputePipeline(ComputePipelineBase* pipeline);
bool SetRenderPipeline(RenderPipelineBase* pipeline);
bool SetBindGroup(uint32_t index, BindGroupBase* bindgroup);
@ -58,17 +53,13 @@ namespace backend {
// command buffer.
std::set<BufferBase*> mBuffersTransitioned;
std::set<TextureBase*> mTexturesTransitioned;
std::set<TextureBase*> mTexturesAttached;
private:
enum ValidationAspect {
VALIDATION_ASPECT_RENDER_PIPELINE,
VALIDATION_ASPECT_COMPUTE_PIPELINE,
VALIDATION_ASPECT_PIPELINE,
VALIDATION_ASPECT_BIND_GROUPS,
VALIDATION_ASPECT_VERTEX_BUFFERS,
VALIDATION_ASPECT_INDEX_BUFFER,
VALIDATION_ASPECT_RENDER_PASS,
VALIDATION_ASPECT_COMPUTE_PASS,
VALIDATION_ASPECT_COUNT
};
@ -91,7 +82,6 @@ namespace backend {
bool RevalidateCanDraw();
void SetPipelineCommon(PipelineBase* pipeline);
void UnsetPipeline();
CommandBufferBuilder* mBuilder;
@ -105,8 +95,7 @@ namespace backend {
std::map<BufferBase*, nxt::BufferUsageBit> mMostRecentBufferUsages;
std::map<TextureBase*, nxt::TextureUsageBit> mMostRecentTextureUsages;
RenderPassDescriptorBase* mCurrentRenderPass = nullptr;
std::set<TextureBase*> mTexturesAttached;
};
} // namespace backend