| // 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 "backend/CommandBufferStateTracker.h" |
| |
| #include "backend/BindGroup.h" |
| #include "backend/BindGroupLayout.h" |
| #include "backend/Buffer.h" |
| #include "backend/ComputePipeline.h" |
| #include "backend/Forward.h" |
| #include "backend/Framebuffer.h" |
| #include "backend/InputState.h" |
| #include "backend/PipelineLayout.h" |
| #include "backend/RenderPass.h" |
| #include "backend/RenderPipeline.h" |
| #include "backend/Texture.h" |
| #include "common/Assert.h" |
| #include "common/BitSetIterator.h" |
| |
| namespace backend { |
| CommandBufferStateTracker::CommandBufferStateTracker(CommandBufferBuilder* mBuilder) |
| : mBuilder(mBuilder) { |
| } |
| |
| bool CommandBufferStateTracker::HaveRenderPass() const { |
| return mCurrentRenderPass != nullptr; |
| } |
| |
| bool CommandBufferStateTracker::HaveRenderSubpass() const { |
| return mAspects[VALIDATION_ASPECT_RENDER_SUBPASS]; |
| } |
| |
| 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)) { |
| mBuilder->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)) { |
| mBuilder->HandleError("Texture is not in the necessary usage"); |
| return false; |
| } |
| return true; |
| } |
| |
| bool CommandBufferStateTracker::ValidateCanDispatch() { |
| constexpr ValidationAspects requiredAspects = |
| 1 << VALIDATION_ASPECT_COMPUTE_PIPELINE | // implicitly requires COMPUTE_PASS |
| 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]) { |
| mBuilder->HandleError("No active compute pipeline"); |
| return false; |
| } |
| // Compute the lazily computed mAspects |
| if (!RecomputeHaveAspectBindGroups()) { |
| mBuilder->HandleError("Bind group state not valid"); |
| 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 | // implicitly requires RENDER_SUBPASS |
| 1 << VALIDATION_ASPECT_BIND_GROUPS | 1 << VALIDATION_ASPECT_VERTEX_BUFFERS; |
| if ((requiredAspects & ~mAspects).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 & ~mAspects).none()) { |
| // Fast return-true path if everything is good |
| return true; |
| } |
| |
| if (!mAspects[VALIDATION_ASPECT_INDEX_BUFFER]) { |
| mBuilder->HandleError("Cannot DrawElements without index buffer set"); |
| return false; |
| } |
| 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_SUBPASS]) { |
| if (stages & ~(nxt::ShaderStageBit::Vertex | nxt::ShaderStageBit::Fragment)) { |
| mBuilder->HandleError( |
| "SetPushConstants stage must be a subset if (vertex|fragment) in subpasses"); |
| return false; |
| } |
| } else { |
| mBuilder->HandleError( |
| "PushConstants must be set in either compute passes or subpasses"); |
| 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::BeginSubpass() { |
| if (mCurrentRenderPass == nullptr) { |
| mBuilder->HandleError("Can't begin a subpass without an active render pass"); |
| return false; |
| } |
| if (mAspects[VALIDATION_ASPECT_RENDER_SUBPASS]) { |
| mBuilder->HandleError("Can't begin a subpass without ending the previous subpass"); |
| return false; |
| } |
| if (mCurrentSubpass >= mCurrentRenderPass->GetSubpassCount()) { |
| mBuilder->HandleError("Can't begin a subpass beyond the last subpass"); |
| return false; |
| } |
| |
| auto& subpassInfo = mCurrentRenderPass->GetSubpassInfo(mCurrentSubpass); |
| for (auto location : IterateBitSet(subpassInfo.colorAttachmentsSet)) { |
| auto attachmentSlot = subpassInfo.colorAttachments[location]; |
| auto* tv = mCurrentFramebuffer->GetTextureView(attachmentSlot); |
| auto* texture = tv->GetTexture(); |
| if (!EnsureTextureUsage(texture, nxt::TextureUsageBit::OutputAttachment)) { |
| mBuilder->HandleError("Unable to ensure texture has OutputAttachment usage"); |
| return false; |
| } |
| mTexturesAttached.insert(texture); |
| } |
| |
| mAspects.set(VALIDATION_ASPECT_RENDER_SUBPASS); |
| return true; |
| } |
| |
| bool CommandBufferStateTracker::EndSubpass() { |
| if (!mAspects[VALIDATION_ASPECT_RENDER_SUBPASS]) { |
| mBuilder->HandleError("Can't end a subpass without beginning one"); |
| return false; |
| } |
| ASSERT(mCurrentRenderPass != nullptr); |
| |
| auto& subpassInfo = mCurrentRenderPass->GetSubpassInfo(mCurrentSubpass); |
| for (auto location : IterateBitSet(subpassInfo.colorAttachmentsSet)) { |
| auto attachmentSlot = subpassInfo.colorAttachments[location]; |
| auto* tv = mCurrentFramebuffer->GetTextureView(attachmentSlot); |
| auto* texture = tv->GetTexture(); |
| if (texture->IsFrozen()) { |
| continue; |
| } |
| } |
| // Everything in mTexturesAttached should be for the current render subpass. |
| mTexturesAttached.clear(); |
| |
| mCurrentSubpass += 1; |
| mInputsSet.reset(); |
| mAspects.reset(VALIDATION_ASPECT_RENDER_SUBPASS); |
| UnsetPipeline(); |
| return true; |
| } |
| |
| bool CommandBufferStateTracker::BeginRenderPass(RenderPassBase* renderPass, |
| FramebufferBase* framebuffer) { |
| 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; |
| } |
| ASSERT(!mAspects[VALIDATION_ASPECT_RENDER_SUBPASS]); |
| if (!framebuffer->GetRenderPass()->IsCompatibleWith(renderPass)) { |
| mBuilder->HandleError("Framebuffer is incompatible with this render pass"); |
| return false; |
| } |
| |
| mCurrentRenderPass = renderPass; |
| mCurrentFramebuffer = framebuffer; |
| mCurrentSubpass = 0; |
| |
| return true; |
| } |
| |
| bool CommandBufferStateTracker::EndRenderPass() { |
| if (mCurrentRenderPass == nullptr) { |
| mBuilder->HandleError("No render pass is currently active"); |
| return false; |
| } |
| if (mAspects[VALIDATION_ASPECT_RENDER_SUBPASS]) { |
| mBuilder->HandleError("Can't end a render pass while a subpass is active"); |
| return false; |
| } |
| if (mCurrentSubpass < mCurrentRenderPass->GetSubpassCount() - 1) { |
| mBuilder->HandleError("Can't end a render pass before the last subpass"); |
| return false; |
| } |
| mCurrentRenderPass = nullptr; |
| mCurrentFramebuffer = nullptr; |
| |
| return true; |
| } |
| |
| 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_SUBPASS]) { |
| mBuilder->HandleError("A render subpass must be active when a render pipeline is set"); |
| return false; |
| } |
| if (!pipeline->GetRenderPass()->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; |
| } |
| |
| bool CommandBufferStateTracker::SetBindGroup(uint32_t index, BindGroupBase* bindgroup) { |
| if (!ValidateBindGroupUsages(bindgroup)) { |
| return false; |
| } |
| mBindgroupsSet.set(index); |
| mBindgroups[index] = bindgroup; |
| |
| return true; |
| } |
| |
| bool CommandBufferStateTracker::SetIndexBuffer(BufferBase* buffer) { |
| if (!HavePipeline()) { |
| mBuilder->HandleError("Can't set the index buffer without a pipeline"); |
| return false; |
| } |
| |
| auto usage = nxt::BufferUsageBit::Index; |
| if (!BufferHasGuaranteedUsageBit(buffer, usage)) { |
| mBuilder->HandleError("Buffer needs the index usage bit to be guaranteed"); |
| return false; |
| } |
| |
| mAspects.set(VALIDATION_ASPECT_INDEX_BUFFER); |
| return true; |
| } |
| |
| bool CommandBufferStateTracker::SetVertexBuffer(uint32_t index, BufferBase* buffer) { |
| if (!HavePipeline()) { |
| mBuilder->HandleError("Can't set vertex buffers without a pipeline"); |
| return false; |
| } |
| |
| auto usage = nxt::BufferUsageBit::Vertex; |
| if (!BufferHasGuaranteedUsageBit(buffer, usage)) { |
| mBuilder->HandleError("Buffer needs vertex usage bit to be guaranteed"); |
| return false; |
| } |
| |
| mInputsSet.set(index); |
| return true; |
| } |
| |
| bool CommandBufferStateTracker::TransitionBufferUsage(BufferBase* buffer, |
| nxt::BufferUsageBit usage) { |
| if (!buffer->IsTransitionPossible(usage)) { |
| if (buffer->IsFrozen()) { |
| mBuilder->HandleError("Buffer transition not possible (usage is frozen)"); |
| } else if (!BufferBase::IsUsagePossible(buffer->GetAllowedUsage(), usage)) { |
| mBuilder->HandleError("Buffer transition not possible (usage not allowed)"); |
| } else { |
| mBuilder->HandleError("Buffer transition not possible"); |
| } |
| return false; |
| } |
| |
| mMostRecentBufferUsages[buffer] = usage; |
| mBuffersTransitioned.insert(buffer); |
| return true; |
| } |
| |
| bool CommandBufferStateTracker::TransitionTextureUsage(TextureBase* texture, |
| nxt::TextureUsageBit usage) { |
| if (!IsExplicitTextureTransitionPossible(texture, usage)) { |
| if (texture->IsFrozen()) { |
| mBuilder->HandleError("Texture transition not possible (usage is frozen)"); |
| } else if (!TextureBase::IsUsagePossible(texture->GetAllowedUsage(), usage)) { |
| mBuilder->HandleError("Texture transition not possible (usage not allowed)"); |
| } else if (mTexturesAttached.find(texture) != mTexturesAttached.end()) { |
| mBuilder->HandleError( |
| "Texture transition not possible (texture is in use as a framebuffer " |
| "attachment)"); |
| } else { |
| mBuilder->HandleError("Texture transition not possible"); |
| } |
| return false; |
| } |
| |
| mMostRecentTextureUsages[texture] = usage; |
| mTexturesTransitioned.insert(texture); |
| return true; |
| } |
| |
| bool CommandBufferStateTracker::EnsureTextureUsage(TextureBase* texture, |
| nxt::TextureUsageBit usage) { |
| if (texture->HasFrozenUsage(usage)) { |
| return true; |
| } |
| if (!IsInternalTextureTransitionPossible(texture, usage)) { |
| return false; |
| } |
| mMostRecentTextureUsages[texture] = usage; |
| mTexturesTransitioned.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 = mMostRecentBufferUsages.find(buffer); |
| return it != mMostRecentBufferUsages.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 = mMostRecentTextureUsages.find(texture); |
| return it != mMostRecentTextureUsages.end() && (it->second & usage); |
| } |
| |
| bool CommandBufferStateTracker::IsInternalTextureTransitionPossible( |
| TextureBase* texture, |
| nxt::TextureUsageBit usage) const { |
| ASSERT(usage != nxt::TextureUsageBit::None && nxt::HasZeroOrOneBits(usage)); |
| if (mTexturesAttached.find(texture) != mTexturesAttached.end()) { |
| return false; |
| } |
| return texture->IsTransitionPossible(usage); |
| } |
| |
| bool CommandBufferStateTracker::IsExplicitTextureTransitionPossible( |
| TextureBase* texture, |
| nxt::TextureUsageBit usage) const { |
| const nxt::TextureUsageBit attachmentUsages = nxt::TextureUsageBit::OutputAttachment; |
| if (usage & attachmentUsages) { |
| return false; |
| } |
| return IsInternalTextureTransitionPossible(texture, usage); |
| } |
| |
| bool CommandBufferStateTracker::RecomputeHaveAspectBindGroups() { |
| if (mAspects[VALIDATION_ASPECT_BIND_GROUPS]) { |
| return true; |
| } |
| // Assumes we have a pipeline already |
| if (!mBindgroupsSet.all()) { |
| return false; |
| } |
| for (size_t i = 0; i < mBindgroups.size(); ++i) { |
| if (auto* bindgroup = mBindgroups[i]) { |
| // TODO(kainino@chromium.org): bind group compatibility |
| if (bindgroup->GetLayout() != mLastPipeline->GetLayout()->GetBindGroupLayout(i)) { |
| return false; |
| } |
| } |
| } |
| mAspects.set(VALIDATION_ASPECT_BIND_GROUPS); |
| return true; |
| } |
| |
| bool CommandBufferStateTracker::RecomputeHaveAspectVertexBuffers() { |
| if (mAspects[VALIDATION_ASPECT_VERTEX_BUFFERS]) { |
| return true; |
| } |
| // Assumes we have a pipeline already |
| auto requiredInputs = mLastRenderPipeline->GetInputState()->GetInputsSetMask(); |
| if ((mInputsSet & requiredInputs) == requiredInputs) { |
| mAspects.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 (mAspects & 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 = nxt::BufferUsageBit::None; |
| switch (type) { |
| case nxt::BindingType::UniformBuffer: |
| requiredUsage = nxt::BufferUsageBit::Uniform; |
| break; |
| |
| case nxt::BindingType::StorageBuffer: |
| requiredUsage = nxt::BufferUsageBit::Storage; |
| break; |
| |
| default: |
| UNREACHABLE(); |
| } |
| |
| auto buffer = group->GetBindingAsBufferView(i)->GetBuffer(); |
| if (!BufferHasGuaranteedUsageBit(buffer, requiredUsage)) { |
| mBuilder->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)) { |
| mBuilder->HandleError("Can't guarantee texture usage needed by bind group"); |
| return false; |
| } |
| } break; |
| case nxt::BindingType::Sampler: |
| continue; |
| } |
| } |
| return true; |
| } |
| |
| bool CommandBufferStateTracker::RevalidateCanDraw() { |
| if (!mAspects[VALIDATION_ASPECT_RENDER_PIPELINE]) { |
| mBuilder->HandleError("No active render pipeline"); |
| return false; |
| } |
| // Compute the lazily computed mAspects |
| if (!RecomputeHaveAspectBindGroups()) { |
| mBuilder->HandleError("Bind group state not valid"); |
| return false; |
| } |
| if (!RecomputeHaveAspectVertexBuffers()) { |
| mBuilder->HandleError("Some vertex buffers are not set"); |
| return false; |
| } |
| return true; |
| } |
| |
| void CommandBufferStateTracker::SetPipelineCommon(PipelineBase* pipeline) { |
| PipelineLayoutBase* layout = pipeline->GetLayout(); |
| |
| mAspects.reset(VALIDATION_ASPECT_BIND_GROUPS); |
| mAspects.reset(VALIDATION_ASPECT_VERTEX_BUFFERS); |
| // Reset bindgroups but mark unused bindgroups as valid |
| mBindgroupsSet = ~layout->GetBindGroupsLayoutMask(); |
| |
| // Only bindgroups that were not the same layout in the last pipeline need to be set again. |
| if (mLastPipeline) { |
| mBindgroupsSet |= layout->InheritedGroupsMask(mLastPipeline->GetLayout()); |
| } |
| |
| 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 |