diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt index 3b9abc3988..a9b2552d7b 100644 --- a/src/backend/CMakeLists.txt +++ b/src/backend/CMakeLists.txt @@ -35,6 +35,8 @@ list(APPEND BACKEND_SOURCES ${COMMON_DIR}/CommandBuffer.h ${COMMON_DIR}/DepthStencilState.cpp ${COMMON_DIR}/DepthStencilState.h + ${COMMON_DIR}/CommandBufferStateTracker.cpp + ${COMMON_DIR}/CommandBufferStateTracker.h ${COMMON_DIR}/Device.cpp ${COMMON_DIR}/Device.h ${COMMON_DIR}/Forward.h diff --git a/src/backend/common/CommandBuffer.cpp b/src/backend/common/CommandBuffer.cpp index 43436739b1..df9940a0f5 100644 --- a/src/backend/common/CommandBuffer.cpp +++ b/src/backend/common/CommandBuffer.cpp @@ -15,9 +15,9 @@ #include "CommandBuffer.h" #include "BindGroup.h" -#include "BindGroupLayout.h" #include "Buffer.h" #include "Commands.h" +#include "CommandBufferStateTracker.h" #include "Device.h" #include "InputState.h" #include "Pipeline.h" @@ -31,8 +31,8 @@ namespace backend { CommandBufferBase::CommandBufferBase(CommandBufferBuilder* builder) : device(builder->device), - buffersTransitioned(std::move(builder->buffersTransitioned)), - texturesTransitioned(std::move(builder->texturesTransitioned)) { + buffersTransitioned(std::move(builder->state->buffersTransitioned)), + texturesTransitioned(std::move(builder->state->texturesTransitioned)) { } bool CommandBufferBase::ValidateResourceUsagesImmediate() { @@ -156,7 +156,7 @@ namespace backend { commands->DataWasDestroyed(); } - CommandBufferBuilder::CommandBufferBuilder(DeviceBase* device) : Builder(device) { + CommandBufferBuilder::CommandBufferBuilder(DeviceBase* device) : Builder(device), state(std::make_unique(this)) { } CommandBufferBuilder::~CommandBufferBuilder() { @@ -166,212 +166,27 @@ namespace backend { } } - enum ValidationAspect { - VALIDATION_ASPECT_RENDER_PIPELINE, - VALIDATION_ASPECT_COMPUTE_PIPELINE, - VALIDATION_ASPECT_BINDGROUPS, - VALIDATION_ASPECT_VERTEX_BUFFERS, - VALIDATION_ASPECT_INDEX_BUFFER, - VALIDATION_ASPECT_RENDER_PASS, - - VALIDATION_ASPECT_COUNT, - }; - - using ValidationAspects = std::bitset; - bool CommandBufferBuilder::ValidateGetResult() { MoveToIterator(); - ValidationAspects aspects; - std::bitset bindgroupsSet; - std::bitset inputsSet; - PipelineBase* lastPipeline = nullptr; - - // TODO(kainino@chromium.org): Manage this state inside an object, change lambdas to methods - std::map mostRecentBufferUsages; - auto bufferHasGuaranteedUsageBit = [&](BufferBase* buffer, nxt::BufferUsageBit usage) -> bool { - assert(usage != nxt::BufferUsageBit::None && nxt::HasZeroOrOneBits(usage)); - if (buffer->HasFrozenUsage(usage)) { - return true; - } - auto it = mostRecentBufferUsages.find(buffer); - return it != mostRecentBufferUsages.end() && (it->second & usage); - }; - - std::map mostRecentTextureUsages; - auto textureHasGuaranteedUsageBit = [&](TextureBase* texture, nxt::TextureUsageBit usage) -> bool { - assert(usage != nxt::TextureUsageBit::None && nxt::HasZeroOrOneBits(usage)); - if (texture->HasFrozenUsage(usage)) { - return true; - } - auto it = mostRecentTextureUsages.find(texture); - return it != mostRecentTextureUsages.end() && (it->second & usage); - }; - auto isTextureTransitionPossible = [&](TextureBase* texture, nxt::TextureUsageBit usage) -> bool { - const nxt::TextureUsageBit attachmentUsages = - nxt::TextureUsageBit::ColorAttachment | - nxt::TextureUsageBit::DepthStencilAttachment; - ASSERT(usage != nxt::TextureUsageBit::None && nxt::HasZeroOrOneBits(usage)); - if (usage & attachmentUsages) { - return false; - } - auto it = mostRecentTextureUsages.find(texture); - if (it != mostRecentTextureUsages.end()) { - if (it->second & attachmentUsages) { - return false; - } - } - return texture->IsTransitionPossible(usage); - }; - - auto validateBindGroupUsages = [&](BindGroupBase* group) -> bool { - const auto& layoutInfo = group->GetLayout()->GetBindingInfo(); - for (size_t i = 0; i < kMaxBindingsPerGroup; ++i) { - if (!layoutInfo.mask[i]) { - continue; - } - - nxt::BindingType type = layoutInfo.types[i]; - switch (type) { - case nxt::BindingType::UniformBuffer: - case nxt::BindingType::StorageBuffer: - { - nxt::BufferUsageBit requiredUsage; - switch (type) { - case nxt::BindingType::UniformBuffer: - requiredUsage = nxt::BufferUsageBit::Uniform; - break; - - case nxt::BindingType::StorageBuffer: - requiredUsage = nxt::BufferUsageBit::Storage; - break; - - default: - assert(false); - return false; - } - - auto buffer = group->GetBindingAsBufferView(i)->GetBuffer(); - if (!bufferHasGuaranteedUsageBit(buffer, requiredUsage)) { - HandleError("Can't guarantee buffer usage needed by bind group"); - return false; - } - } - break; - case nxt::BindingType::SampledTexture: - { - auto requiredUsage = nxt::TextureUsageBit::Sampled; - - auto texture = group->GetBindingAsTextureView(i)->GetTexture(); - if (!textureHasGuaranteedUsageBit(texture, requiredUsage)) { - HandleError("Can't guarantee texture usage needed by bind group"); - return false; - } - } - break; - case nxt::BindingType::Sampler: - continue; - } - } - return true; - }; - - // TODO(kainino@chromium.org): Manage this state inside an object, change lambda to a method - RenderPassBase* currentRenderPass = nullptr; - FramebufferBase* currentFramebuffer = nullptr; - uint32_t currentSubpass = 0; - auto beginSubpass = [&]() -> bool { - auto& subpassInfo = currentRenderPass->GetSubpassInfo(currentSubpass); - for (auto location : IterateBitSet(subpassInfo.colorAttachmentsSet)) { - auto attachmentSlot = subpassInfo.colorAttachments[location]; - auto* tv = currentFramebuffer->GetTextureView(attachmentSlot); - // TODO(kainino@chromium.org): the TextureView can only be null - // because of the null=backbuffer hack (null representing the - // backbuffer). Once that hack is removed (once we have WSI) - // this check isn't needed. - if (tv == nullptr) { - continue; - } - - auto* texture = tv->GetTexture(); - if (texture->HasFrozenUsage(nxt::TextureUsageBit::ColorAttachment)) { - continue; - } - if (!texture->IsTransitionPossible(nxt::TextureUsageBit::ColorAttachment)) { - HandleError("Can't transition attachment to ColorAttachment usage"); - return false; - } - mostRecentTextureUsages[texture] = nxt::TextureUsageBit::ColorAttachment; - texturesTransitioned.insert(texture); - } - return true; - }; - auto endSubpass = [&]() { - auto& subpassInfo = currentRenderPass->GetSubpassInfo(currentSubpass); - for (auto location : IterateBitSet(subpassInfo.colorAttachmentsSet)) { - auto attachmentSlot = subpassInfo.colorAttachments[location]; - auto* tv = currentFramebuffer->GetTextureView(attachmentSlot); - // TODO(kainino@chromium.org): the TextureView can only be null - // because of the null=backbuffer hack (null representing the - // backbuffer). Once that hack is removed (once we have WSI) - // this check isn't needed. - if (tv == nullptr) { - continue; - } - - auto* texture = tv->GetTexture(); - if (texture->IsFrozen()) { - continue; - } - mostRecentTextureUsages[texture] = nxt::TextureUsageBit::None; - } - }; - Command type; - while(iterator.NextCommandId(&type)) { + while (iterator.NextCommandId(&type)) { switch (type) { case Command::AdvanceSubpass: { iterator.NextCommand(); - if (currentRenderPass == nullptr) { - HandleError("Can't advance subpass without an active render pass"); + if (!state->AdvanceSubpass()) { return false; } - if (currentSubpass + 1 >= currentRenderPass->GetSubpassCount()) { - HandleError("Can't advance beyond the last subpass"); - return false; - } - - endSubpass(); - currentSubpass += 1; - if (!beginSubpass()) { - return false; - } - aspects.reset(VALIDATION_ASPECT_RENDER_PIPELINE); } break; case Command::BeginRenderPass: { - BeginRenderPassCmd* begin = iterator.NextCommand(); - auto* renderPass = begin->renderPass.Get(); - auto* framebuffer = begin->framebuffer.Get(); - if (currentRenderPass != nullptr) { - HandleError("A render pass is already active"); - return false; - } - if (!framebuffer->GetRenderPass()->IsCompatibleWith(renderPass)) { - HandleError("Framebuffer is incompatible with this render pass"); - return false; - } - - aspects.reset(VALIDATION_ASPECT_COMPUTE_PIPELINE); - aspects.reset(VALIDATION_ASPECT_RENDER_PIPELINE); - aspects.set(VALIDATION_ASPECT_RENDER_PASS); - currentRenderPass = renderPass; - currentFramebuffer = framebuffer; - currentSubpass = 0; - if (!beginSubpass()) { + BeginRenderPassCmd* cmd = iterator.NextCommand(); + auto* renderPass = cmd->renderPass.Get(); + auto* framebuffer = cmd->framebuffer.Get(); + if (!state->BeginRenderPass(renderPass, framebuffer)) { return false; } } @@ -391,21 +206,6 @@ namespace backend { uint64_t z = copy->z; uint32_t level = copy->level; - if (currentRenderPass) { - HandleError("Blit cannot occur during a render pass"); - return false; - } - - if (!bufferHasGuaranteedUsageBit(buffer, nxt::BufferUsageBit::TransferSrc)) { - HandleError("Buffer needs the transfer source usage bit"); - return false; - } - - if (!textureHasGuaranteedUsageBit(texture, nxt::TextureUsageBit::TransferDst)) { - HandleError("Texture needs the transfer destination usage bit"); - return false; - } - if (width == 0 || height == 0 || depth == 0) { HandleError("Empty copy"); return false; @@ -414,7 +214,6 @@ namespace backend { // TODO(cwallez@chromium.org): check for overflows uint64_t pixelSize = TextureFormatPixelSize(texture->GetFormat()); uint64_t dataSize = width * height * depth * pixelSize; - if (dataSize + static_cast(bufferOffset) > static_cast(buffer->GetSize())) { HandleError("Copy would read after end of the buffer"); return false; @@ -427,68 +226,38 @@ namespace backend { HandleError("Copy would write outside of the texture"); return false; } + + if (!state->ValidateCanCopy() || + !state->ValidateCanUseBufferAs(buffer, nxt::BufferUsageBit::TransferSrc) || + !state->ValidateCanUseTextureAs(texture, nxt::TextureUsageBit::TransferDst)) { + return false; + } } break; case Command::Dispatch: { - DispatchCmd* cmd = iterator.NextCommand(); - - constexpr ValidationAspects requiredDispatchAspects = - 1 << VALIDATION_ASPECT_COMPUTE_PIPELINE | - 1 << VALIDATION_ASPECT_BINDGROUPS; - - if ((requiredDispatchAspects & ~aspects).any()) { - // Compute the lazily computed aspects - if (bindgroupsSet.all()) { - aspects.set(VALIDATION_ASPECT_BINDGROUPS); - } - - // Check again if anything is missing - if ((requiredDispatchAspects & ~aspects).any()) { - HandleError("Some dispatch state is missing"); - return false; - } + iterator.NextCommand(); + if (!state->ValidateCanDispatch()) { + return false; } } break; case Command::DrawArrays: + { + iterator.NextCommand(); + if (!state->ValidateCanDrawArrays()) { + return false; + } + } + break; + case Command::DrawElements: { - constexpr ValidationAspects requiredDrawAspects = - 1 << VALIDATION_ASPECT_RENDER_PIPELINE | - 1 << VALIDATION_ASPECT_BINDGROUPS | - 1 << VALIDATION_ASPECT_VERTEX_BUFFERS; - - if ((requiredDrawAspects & ~aspects).any()) { - // Compute the lazily computed aspects - if (bindgroupsSet.all()) { - aspects.set(VALIDATION_ASPECT_BINDGROUPS); - } - - auto requiredInputs = lastPipeline->GetInputState()->GetInputsSetMask(); - if ((inputsSet & ~requiredInputs).none()) { - aspects.set(VALIDATION_ASPECT_VERTEX_BUFFERS); - } - - // Check again if anything is missing - if ((requiredDrawAspects & ~aspects).any()) { - HandleError("Some draw state is missing"); - return false; - } - } - - if (type == Command::DrawArrays) { - DrawArraysCmd* draw = iterator.NextCommand(); - } else { - ASSERT(type == Command::DrawElements); - DrawElementsCmd* draw = iterator.NextCommand(); - - if (!aspects[VALIDATION_ASPECT_INDEX_BUFFER]) { - HandleError("Draw elements requires an index buffer"); - return false; - } + iterator.NextCommand(); + if (!state->ValidateCanDrawElements()) { + return false; } } break; @@ -496,19 +265,9 @@ namespace backend { case Command::EndRenderPass: { iterator.NextCommand(); - if (currentRenderPass == nullptr) { - HandleError("No render pass is currently active"); + if (!state->EndRenderPass()) { return false; } - if (currentSubpass < currentRenderPass->GetSubpassCount() - 1) { - HandleError("Can't end a render pass before the last subpass"); - return false; - } - endSubpass(); - currentRenderPass = nullptr; - currentFramebuffer = nullptr; - aspects.reset(VALIDATION_ASPECT_RENDER_PASS); - aspects.reset(VALIDATION_ASPECT_RENDER_PIPELINE); } break; @@ -516,40 +275,9 @@ namespace backend { { SetPipelineCmd* cmd = iterator.NextCommand(); PipelineBase* pipeline = cmd->pipeline.Get(); - PipelineLayoutBase* layout = pipeline->GetLayout(); - - if (pipeline->IsCompute()) { - if (currentRenderPass) { - HandleError("Can't use a compute pipeline while a render pass is active"); - return false; - } - aspects.set(VALIDATION_ASPECT_COMPUTE_PIPELINE); - } else { - if (!currentRenderPass) { - HandleError("A render pass must be active when a render pipeline is set"); - return false; - } - if (!pipeline->GetRenderPass()->IsCompatibleWith(currentRenderPass)) { - HandleError("Pipeline is incompatible with this render pass"); - return false; - } - aspects.set(VALIDATION_ASPECT_RENDER_PIPELINE); + if (!state->SetPipeline(pipeline)) { + return false; } - aspects.reset(VALIDATION_ASPECT_BINDGROUPS); - aspects.reset(VALIDATION_ASPECT_VERTEX_BUFFERS); - bindgroupsSet = ~layout->GetBindGroupsLayoutMask(); - - // Only bindgroups that were not the same layout in the last pipeline need to be set again. - if (lastPipeline) { - PipelineLayoutBase* lastLayout = lastPipeline->GetLayout(); - for (uint32_t i = 0; i < kMaxBindGroups; ++i) { - if (lastLayout->GetBindGroupLayout(i) == layout->GetBindGroupLayout(i)) { - bindgroupsSet |= uint64_t(1) << i; - } - } - } - - lastPipeline = pipeline; } break; @@ -567,7 +295,7 @@ namespace backend { case Command::SetStencilReference: { SetStencilReferenceCmd* cmd = iterator.NextCommand(); - if (currentRenderPass == nullptr) { + if (!state->HaveRenderPass()) { HandleError("Can't set stencil reference without an active render pass"); return false; } @@ -577,30 +305,18 @@ namespace backend { case Command::SetBindGroup: { SetBindGroupCmd* cmd = iterator.NextCommand(); - uint32_t index = cmd->index; - - if (cmd->group->GetLayout() != lastPipeline->GetLayout()->GetBindGroupLayout(index)) { - HandleError("Bind group layout mismatch"); + if (!state->SetBindGroup(cmd->index, cmd->group.Get())) { return false; } - if (!validateBindGroupUsages(cmd->group.Get())) { - return false; - } - bindgroupsSet |= uint64_t(1) << index; } break; case Command::SetIndexBuffer: { SetIndexBufferCmd* cmd = iterator.NextCommand(); - auto buffer = cmd->buffer; - auto usage = nxt::BufferUsageBit::Index; - if (!bufferHasGuaranteedUsageBit(buffer.Get(), usage)) { - HandleError("Buffer needs the index usage bit to be guaranteed"); + if (!state->SetIndexBuffer(cmd->buffer.Get())) { return false; } - - aspects.set(VALIDATION_ASPECT_INDEX_BUFFER); } break; @@ -611,13 +327,7 @@ namespace backend { iterator.NextData(cmd->count); for (uint32_t i = 0; i < cmd->count; ++i) { - auto buffer = buffers[i]; - auto usage = nxt::BufferUsageBit::Vertex; - if (!bufferHasGuaranteedUsageBit(buffer.Get(), usage)) { - HandleError("Buffer needs vertex usage bit to be guaranteed"); - return false; - } - inputsSet.set(cmd->startSlot + i); + state->SetVertexBuffer(cmd->startSlot + i, buffers[i].Get()); } } break; @@ -625,46 +335,19 @@ namespace backend { case Command::TransitionBufferUsage: { TransitionBufferUsageCmd* cmd = iterator.NextCommand(); - auto buffer = cmd->buffer.Get(); - auto usage = cmd->usage; - - if (!buffer->IsTransitionPossible(usage)) { - if (buffer->IsFrozen()) { - HandleError("Buffer transition not possible (usage is frozen)"); - } else if (!BufferBase::IsUsagePossible(buffer->GetAllowedUsage(), usage)) { - HandleError("Buffer transition not possible (usage not allowed)"); - } else { - HandleError("Buffer transition not possible"); - } + if (!state->TransitionBufferUsage(cmd->buffer.Get(), cmd->usage)) { return false; } - - mostRecentBufferUsages[buffer] = usage; - - buffersTransitioned.insert(buffer); } break; case Command::TransitionTextureUsage: { TransitionTextureUsageCmd* cmd = iterator.NextCommand(); - auto texture = cmd->texture.Get(); - auto usage = cmd->usage; - - if (!isTextureTransitionPossible(texture, usage)) { - if (texture->IsFrozen()) { - HandleError("Texture transition not possible (usage is frozen)"); - } else if (!TextureBase::IsUsagePossible(texture->GetAllowedUsage(), usage)) { - HandleError("Texture transition not possible (usage not allowed)"); - } else { - HandleError("Texture transition not possible"); - } + if (!state->TransitionTextureUsage(cmd->texture.Get(), cmd->usage)) { return false; } - mostRecentTextureUsages[texture] = usage; - - texturesTransitioned.insert(texture); } break; } diff --git a/src/backend/common/CommandBuffer.h b/src/backend/common/CommandBuffer.h index 8c603cdd54..485d1f3cd0 100644 --- a/src/backend/common/CommandBuffer.h +++ b/src/backend/common/CommandBuffer.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef BACKEND_COMMON_COMMANDBUFFERGL_H_ -#define BACKEND_COMMON_COMMANDBUFFERGL_H_ +#ifndef BACKEND_COMMON_COMMANDBUFFER_H_ +#define BACKEND_COMMON_COMMANDBUFFER_H_ #include "nxt/nxtcpp.h" @@ -21,6 +21,7 @@ #include "Builder.h" #include "RefCounted.h" +#include #include #include @@ -28,6 +29,7 @@ namespace backend { class BindGroupBase; class BufferBase; + class CommandBufferStateTracker; class FramebufferBase; class DeviceBase; class PipelineBase; @@ -88,16 +90,13 @@ namespace backend { CommandBufferBase* GetResultImpl() override; void MoveToIterator(); + std::unique_ptr state; CommandAllocator allocator; CommandIterator iterator; bool movedToIterator = false; bool commandsAcquired = false; - // These pointers will remain valid since they are referenced by - // the bind groups which are referenced by this command buffer. - std::set buffersTransitioned; - std::set texturesTransitioned; }; } -#endif // BACKEND_COMMON_COMMANDBUFFERGL_H_ +#endif // BACKEND_COMMON_COMMANDBUFFER_H_ diff --git a/src/backend/common/CommandBufferStateTracker.cpp b/src/backend/common/CommandBufferStateTracker.cpp new file mode 100644 index 0000000000..7c02be9eb6 --- /dev/null +++ b/src/backend/common/CommandBufferStateTracker.cpp @@ -0,0 +1,500 @@ +// Copyright 2017 The NXT 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 "CommandBufferStateTracker.h" + +#include "Forward.h" +#include "BindGroup.h" +#include "BindGroupLayout.h" +#include "BitSetIterator.h" +#include "Buffer.h" +#include "Framebuffer.h" +#include "InputState.h" +#include "Pipeline.h" +#include "PipelineLayout.h" +#include "RenderPass.h" +#include "Texture.h" + +namespace backend { + CommandBufferStateTracker::CommandBufferStateTracker(CommandBufferBuilder* builder) + : builder(builder) { + } + + bool CommandBufferStateTracker::HaveRenderPass() const { + return currentRenderPass != nullptr; + } + + bool CommandBufferStateTracker::ValidateCanCopy() const { + if (currentRenderPass) { + builder->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)) { + builder->HandleError("Buffer is not in the necessary usage"); + return false; + } + return true; + } + + bool CommandBufferStateTracker::ValidateCanUseTextureAs(TextureBase* texture, nxt::TextureUsageBit usage) const { + if (!TextureHasGuaranteedUsageBit(texture, usage)) { + builder->HandleError("Texture is not in the necessary usage"); + return false; + } + return true; + } + + bool CommandBufferStateTracker::ValidateCanDispatch() { + constexpr ValidationAspects requiredAspects = + 1 << VALIDATION_ASPECT_COMPUTE_PIPELINE | + 1 << VALIDATION_ASPECT_BIND_GROUPS; + if ((requiredAspects & ~aspects).none()) { + // Fast return-true path if everything is good + return true; + } + + if (!aspects[VALIDATION_ASPECT_COMPUTE_PIPELINE]) { + builder->HandleError("No active compute pipeline"); + return false; + } + // Compute the lazily computed aspects + if (!RecomputeHaveAspectBindGroups()) { + builder->HandleError("Some bind groups are not set"); + return false; + } + return true; + } + + bool CommandBufferStateTracker::ValidateCanDrawArrays() { + // 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_VERTEX_BUFFERS; + if ((requiredAspects & ~aspects).none()) { + // Fast return-true path if everything is good + return true; + } + + return RevalidateCanDraw(); + } + + 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_VERTEX_BUFFERS | + 1 << VALIDATION_ASPECT_INDEX_BUFFER; + if ((requiredAspects & ~aspects).none()) { + // Fast return-true path if everything is good + return true; + } + + if (!aspects[VALIDATION_ASPECT_INDEX_BUFFER]) { + builder->HandleError("Cannot DrawElements without index buffer set"); + return false; + } + return RevalidateCanDraw(); + } + + bool CommandBufferStateTracker::BeginSubpass() { + if (currentRenderPass == nullptr) { + builder->HandleError("Can't begin a subpass without an active render pass"); + return false; + } + if (subpassActive) { + builder->HandleError("Can't begin a subpass without ending the previous subpass"); + return false; + } + if (currentSubpass >= currentRenderPass->GetSubpassCount()) { + builder->HandleError("Can't begin a subpass beyond the last subpass"); + return false; + } + + auto& subpassInfo = currentRenderPass->GetSubpassInfo(currentSubpass); + for (auto location : IterateBitSet(subpassInfo.colorAttachmentsSet)) { + auto attachmentSlot = subpassInfo.colorAttachments[location]; + auto* tv = currentFramebuffer->GetTextureView(attachmentSlot); + // TODO(kainino@chromium.org): the TextureView can only be null + // because of the null=backbuffer hack (null representing the + // backbuffer). Once that hack is removed (once we have WSI) + // this check isn't needed. + if (tv == nullptr) { + continue; + } + + auto* texture = tv->GetTexture(); + if (texture->HasFrozenUsage(nxt::TextureUsageBit::ColorAttachment)) { + continue; + } + if (!texture->IsTransitionPossible(nxt::TextureUsageBit::ColorAttachment)) { + builder->HandleError("Can't transition attachment to ColorAttachment usage"); + return false; + } + mostRecentTextureUsages[texture] = nxt::TextureUsageBit::ColorAttachment; + texturesTransitioned.insert(texture); + } + + subpassActive = true; + return true; + }; + + bool CommandBufferStateTracker::EndSubpass() { + if (currentRenderPass == nullptr) { + builder->HandleError("Can't end a subpass without an active render pass"); + return false; + } + if (!subpassActive) { + builder->HandleError("Can't end a subpass without beginning one"); + return false; + } + + auto& subpassInfo = currentRenderPass->GetSubpassInfo(currentSubpass); + for (auto location : IterateBitSet(subpassInfo.colorAttachmentsSet)) { + auto attachmentSlot = subpassInfo.colorAttachments[location]; + auto* tv = currentFramebuffer->GetTextureView(attachmentSlot); + // TODO(kainino@chromium.org): the TextureView can only be null + // because of the null=backbuffer hack (null representing the + // backbuffer). Once that hack is removed (once we have WSI) + // this check isn't needed. + if (tv == nullptr) { + continue; + } + + auto* texture = tv->GetTexture(); + if (texture->IsFrozen()) { + continue; + } + + mostRecentTextureUsages[texture] = nxt::TextureUsageBit::None; + } + + currentSubpass += 1; + subpassActive = false; + UnsetPipeline(); + return true; + }; + + bool CommandBufferStateTracker::BeginRenderPass(RenderPassBase* renderPass, FramebufferBase* framebuffer) { + if (currentRenderPass != nullptr) { + builder->HandleError("A render pass is already active"); + return false; + } + if (!framebuffer->GetRenderPass()->IsCompatibleWith(renderPass)) { + builder->HandleError("Framebuffer is incompatible with this render pass"); + return false; + } + + currentRenderPass = renderPass; + currentFramebuffer = framebuffer; + currentSubpass = 0; + subpassActive = false; + + // TODO(kainino@chromium.org): remove this when AdvanceSubpass is removed. + if (!BeginSubpass()) { + return false; + } + + UnsetPipeline(); + return true; + } + + bool CommandBufferStateTracker::AdvanceSubpass() { + // TODO(kainino@chromium.org): remove this function when AdvanceSubpass is removed. + return EndSubpass() && BeginSubpass(); + } + + bool CommandBufferStateTracker::EndRenderPass() { + if (currentRenderPass == nullptr) { + builder->HandleError("No render pass is currently active"); + return false; + } + // TODO(kainino@chromium.org): remove this when AdvanceSubpass is removed. + if (!EndSubpass()) { + return false; + } + if (subpassActive) { + builder->HandleError("Can't end a render pass while a subpass is active"); + return false; + } + if (currentSubpass < currentRenderPass->GetSubpassCount() - 1) { + builder->HandleError("Can't end a render pass before the last subpass"); + return false; + } + currentRenderPass = nullptr; + currentFramebuffer = nullptr; + + return true; + } + + bool CommandBufferStateTracker::SetPipeline(PipelineBase* pipeline) { + PipelineLayoutBase* layout = pipeline->GetLayout(); + + if (pipeline->IsCompute()) { + if (currentRenderPass) { + builder->HandleError("Can't use a compute pipeline while a render pass is active"); + return false; + } + aspects.set(VALIDATION_ASPECT_COMPUTE_PIPELINE); + } else { + if (!currentRenderPass) { + builder->HandleError("A render pass must be active when a render pipeline is set"); + return false; + } + if (!pipeline->GetRenderPass()->IsCompatibleWith(currentRenderPass)) { + builder->HandleError("Pipeline is incompatible with this render pass"); + return false; + } + aspects.set(VALIDATION_ASPECT_RENDER_PIPELINE); + } + aspects.reset(VALIDATION_ASPECT_BIND_GROUPS); + bindgroupsSet = ~layout->GetBindGroupsLayoutMask(); + + // Only bindgroups that were not the same layout in the last pipeline need to be set again. + if (lastPipeline) { + PipelineLayoutBase* lastLayout = lastPipeline->GetLayout(); + for (uint32_t i = 0; i < kMaxBindGroups; ++i) { + if (lastLayout->GetBindGroupLayout(i) == layout->GetBindGroupLayout(i)) { + bindgroupsSet |= uint64_t(1) << i; + } + } + } + + lastPipeline = pipeline; + return true; + } + + bool CommandBufferStateTracker::SetBindGroup(uint32_t index, BindGroupBase* bindgroup) { + if (bindgroup->GetLayout() != lastPipeline->GetLayout()->GetBindGroupLayout(index)) { + builder->HandleError("Bind group layout mismatch"); + return false; + } + if (!ValidateBindGroupUsages(bindgroup)) { + return false; + } + bindgroupsSet.set(index); + + return true; + } + + bool CommandBufferStateTracker::SetIndexBuffer(BufferBase* buffer) { + if (!HavePipeline()) { + builder->HandleError("Can't set the index buffer without a pipeline"); + return false; + } + + auto usage = nxt::BufferUsageBit::Index; + if (!BufferHasGuaranteedUsageBit(buffer, usage)) { + builder->HandleError("Buffer needs the index usage bit to be guaranteed"); + return false; + } + + aspects.set(VALIDATION_ASPECT_INDEX_BUFFER); + return true; + } + + bool CommandBufferStateTracker::SetVertexBuffer(uint32_t index, BufferBase* buffer) { + if (!HavePipeline()) { + builder->HandleError("Can't set vertex buffers without a pipeline"); + return false; + } + + auto usage = nxt::BufferUsageBit::Vertex; + if (!BufferHasGuaranteedUsageBit(buffer, usage)) { + builder->HandleError("Buffer needs vertex usage bit to be guaranteed"); + return false; + } + + inputsSet.set(index); + return true; + } + + bool CommandBufferStateTracker::TransitionBufferUsage(BufferBase* buffer, nxt::BufferUsageBit usage) { + if (!buffer->IsTransitionPossible(usage)) { + if (buffer->IsFrozen()) { + builder->HandleError("Buffer transition not possible (usage is frozen)"); + } else if (!BufferBase::IsUsagePossible(buffer->GetAllowedUsage(), usage)) { + builder->HandleError("Buffer transition not possible (usage not allowed)"); + } else { + builder->HandleError("Buffer transition not possible"); + } + return false; + } + + mostRecentBufferUsages[buffer] = usage; + buffersTransitioned.insert(buffer); + return true; + } + + bool CommandBufferStateTracker::TransitionTextureUsage(TextureBase* texture, nxt::TextureUsageBit usage) { + if (!IsTextureTransitionPossible(texture, usage)) { + if (texture->IsFrozen()) { + builder->HandleError("Texture transition not possible (usage is frozen)"); + } else if (!TextureBase::IsUsagePossible(texture->GetAllowedUsage(), usage)) { + builder->HandleError("Texture transition not possible (usage not allowed)"); + } else { + builder->HandleError("Texture transition not possible"); + } + return false; + } + + mostRecentTextureUsages[texture] = usage; + texturesTransitioned.insert(texture); + return true; + } + + bool CommandBufferStateTracker::BufferHasGuaranteedUsageBit(BufferBase* buffer, nxt::BufferUsageBit usage) const { + ASSERT(usage != nxt::BufferUsageBit::None && nxt::HasZeroOrOneBits(usage)); + if (buffer->HasFrozenUsage(usage)) { + return true; + } + auto it = mostRecentBufferUsages.find(buffer); + return it != mostRecentBufferUsages.end() && (it->second & usage); + }; + + bool CommandBufferStateTracker::TextureHasGuaranteedUsageBit(TextureBase* texture, nxt::TextureUsageBit usage) const { + ASSERT(usage != nxt::TextureUsageBit::None && nxt::HasZeroOrOneBits(usage)); + if (texture->HasFrozenUsage(usage)) { + return true; + } + auto it = mostRecentTextureUsages.find(texture); + return it != mostRecentTextureUsages.end() && (it->second & usage); + }; + + bool CommandBufferStateTracker::IsTextureTransitionPossible(TextureBase* texture, nxt::TextureUsageBit usage) const { + const nxt::TextureUsageBit attachmentUsages = + nxt::TextureUsageBit::ColorAttachment | + nxt::TextureUsageBit::DepthStencilAttachment; + ASSERT(usage != nxt::TextureUsageBit::None && nxt::HasZeroOrOneBits(usage)); + if (usage & attachmentUsages) { + return false; + } + auto it = mostRecentTextureUsages.find(texture); + if (it != mostRecentTextureUsages.end()) { + if (it->second & attachmentUsages) { + return false; + } + } + return texture->IsTransitionPossible(usage); + }; + + bool CommandBufferStateTracker::RecomputeHaveAspectBindGroups() { + if (aspects[VALIDATION_ASPECT_BIND_GROUPS]) { + return true; + } + if (bindgroupsSet.all()) { + aspects.set(VALIDATION_ASPECT_BIND_GROUPS); + return true; + } + return false; + } + + bool CommandBufferStateTracker::RecomputeHaveAspectVertexBuffers() { + if (aspects[VALIDATION_ASPECT_VERTEX_BUFFERS]) { + return true; + } + auto requiredInputs = lastPipeline->GetInputState()->GetInputsSetMask(); + if ((inputsSet & ~requiredInputs).none()) { + aspects.set(VALIDATION_ASPECT_VERTEX_BUFFERS); + return true; + } + return false; + } + + bool CommandBufferStateTracker::HavePipeline() const { + constexpr ValidationAspects pipelineAspects = + 1 << VALIDATION_ASPECT_COMPUTE_PIPELINE | + 1 << VALIDATION_ASPECT_RENDER_PIPELINE; + return (aspects & pipelineAspects).any(); + } + + bool CommandBufferStateTracker::ValidateBindGroupUsages(BindGroupBase* group) const { + const auto& layoutInfo = group->GetLayout()->GetBindingInfo(); + for (size_t i = 0; i < kMaxBindingsPerGroup; ++i) { + if (!layoutInfo.mask[i]) { + continue; + } + + nxt::BindingType type = layoutInfo.types[i]; + switch (type) { + case nxt::BindingType::UniformBuffer: + case nxt::BindingType::StorageBuffer: + { + nxt::BufferUsageBit requiredUsage; + switch (type) { + case nxt::BindingType::UniformBuffer: + requiredUsage = nxt::BufferUsageBit::Uniform; + break; + + case nxt::BindingType::StorageBuffer: + requiredUsage = nxt::BufferUsageBit::Storage; + break; + + default: + assert(false); + return false; + } + + auto buffer = group->GetBindingAsBufferView(i)->GetBuffer(); + if (!BufferHasGuaranteedUsageBit(buffer, requiredUsage)) { + builder->HandleError("Can't guarantee buffer usage needed by bind group"); + return false; + } + } + break; + case nxt::BindingType::SampledTexture: + { + auto requiredUsage = nxt::TextureUsageBit::Sampled; + + auto texture = group->GetBindingAsTextureView(i)->GetTexture(); + if (!TextureHasGuaranteedUsageBit(texture, requiredUsage)) { + builder->HandleError("Can't guarantee texture usage needed by bind group"); + return false; + } + } + break; + case nxt::BindingType::Sampler: + continue; + } + } + return true; + }; + + bool CommandBufferStateTracker::RevalidateCanDraw() { + if (!aspects[VALIDATION_ASPECT_RENDER_PIPELINE]) { + builder->HandleError("No active render pipeline"); + return false; + } + // Compute the lazily computed aspects + if (!RecomputeHaveAspectBindGroups()) { + builder->HandleError("Some bind groups are not set"); + return false; + } + if (!RecomputeHaveAspectVertexBuffers()) { + builder->HandleError("Some vertex buffers are not set"); + return false; + } + return true; + } + + void CommandBufferStateTracker::UnsetPipeline() { + // All of the aspects (currently) are pipeline-dependent. + aspects.reset(); + } +} diff --git a/src/backend/common/CommandBufferStateTracker.h b/src/backend/common/CommandBufferStateTracker.h new file mode 100644 index 0000000000..7fe4a23592 --- /dev/null +++ b/src/backend/common/CommandBufferStateTracker.h @@ -0,0 +1,102 @@ +// Copyright 2017 The NXT 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. + +#ifndef BACKEND_COMMON_COMMANDBUFFERSTATETRACKER_H +#define BACKEND_COMMON_COMMANDBUFFERSTATETRACKER_H + +#include "CommandBuffer.h" + +#include +#include +#include + +namespace backend { + class CommandBufferStateTracker { + public: + 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(); + + // State-modifying methods + bool BeginSubpass(); + bool EndSubpass(); + bool BeginRenderPass(RenderPassBase* renderPass, FramebufferBase* framebuffer); + bool AdvanceSubpass(); + bool EndRenderPass(); + bool SetPipeline(PipelineBase* pipeline); + bool SetBindGroup(uint32_t index, BindGroupBase* bindgroup); + bool SetIndexBuffer(BufferBase* buffer); + bool SetVertexBuffer(uint32_t index, BufferBase* buffer); + bool TransitionBufferUsage(BufferBase* buffer, nxt::BufferUsageBit usage); + bool TransitionTextureUsage(TextureBase* texture, nxt::TextureUsageBit usage); + + // These collections are copied to the CommandBuffer at build time. + // These pointers will remain valid since they are referenced by + // the bind groups which are referenced by this command buffer. + std::set buffersTransitioned; + std::set texturesTransitioned; + + private: + enum ValidationAspect { + VALIDATION_ASPECT_RENDER_PIPELINE, + VALIDATION_ASPECT_COMPUTE_PIPELINE, + VALIDATION_ASPECT_BIND_GROUPS, + VALIDATION_ASPECT_VERTEX_BUFFERS, + VALIDATION_ASPECT_INDEX_BUFFER, + + VALIDATION_ASPECT_COUNT + }; + using ValidationAspects = std::bitset; + + // Usage helper functions + bool BufferHasGuaranteedUsageBit(BufferBase* buffer, nxt::BufferUsageBit usage) const; + bool TextureHasGuaranteedUsageBit(TextureBase* texture, nxt::TextureUsageBit usage) const; + bool IsTextureTransitionPossible(TextureBase* texture, nxt::TextureUsageBit usage) const; + + // Queries for lazily evaluated aspects + bool RecomputeHaveAspectBindGroups(); + bool RecomputeHaveAspectVertexBuffers(); + + bool HavePipeline() const; + bool ValidateBindGroupUsages(BindGroupBase* group) const; + bool RevalidateCanDraw(); + + void UnsetPipeline(); + + CommandBufferBuilder* builder; + + ValidationAspects aspects; + + std::bitset bindgroupsSet; + std::bitset inputsSet; + PipelineBase* lastPipeline = nullptr; + + std::map mostRecentBufferUsages; + std::map mostRecentTextureUsages; + + RenderPassBase* currentRenderPass = nullptr; + FramebufferBase* currentFramebuffer = nullptr; + bool subpassActive = false; + uint32_t currentSubpass = 0; + }; +} + +#endif // BACKEND_COMMON_COMMANDBUFFERSTATETRACKER_H