dawn-cmake/src/backend/CommandBufferStateTracker.cpp

550 lines
21 KiB
C++
Raw Normal View History

// 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/Forward.h"
#include "backend/BindGroup.h"
#include "backend/BindGroupLayout.h"
#include "backend/Buffer.h"
#include "backend/ComputePipeline.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* 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 =
2017-07-10 21:07:24 +00:00
1 << VALIDATION_ASPECT_COMPUTE_PIPELINE | // implicitly requires COMPUTE_PASS
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("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 =
2017-07-10 21:07:24 +00:00
1 << VALIDATION_ASPECT_RENDER_PIPELINE | // implicitly requires RENDER_SUBPASS
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::ValidateEndCommandBuffer() const {
if (currentRenderPass != nullptr) {
builder->HandleError("Can't end command buffer with an active render pass");
return false;
}
2017-07-10 21:07:24 +00:00
if (aspects[VALIDATION_ASPECT_COMPUTE_PASS]) {
builder->HandleError("Can't end command buffer with an active compute pass");
return false;
}
return true;
}
bool CommandBufferStateTracker::BeginComputePass() {
if (currentRenderPass != nullptr) {
builder->HandleError("Cannot begin a compute pass while a render pass is active");
return false;
}
aspects.set(VALIDATION_ASPECT_COMPUTE_PASS);
return true;
}
bool CommandBufferStateTracker::EndComputePass() {
if (!aspects[VALIDATION_ASPECT_COMPUTE_PASS]) {
builder->HandleError("Can't end a compute pass without beginning one");
return false;
}
aspects.reset(VALIDATION_ASPECT_COMPUTE_PASS);
UnsetPipeline();
return true;
}
bool CommandBufferStateTracker::BeginSubpass() {
if (currentRenderPass == nullptr) {
builder->HandleError("Can't begin a subpass without an active render pass");
return false;
}
if (aspects[VALIDATION_ASPECT_RENDER_SUBPASS]) {
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 (!EnsureTextureUsage(texture, nxt::TextureUsageBit::OutputAttachment)) {
builder->HandleError("Unable to ensure texture has OutputAttachment usage");
return false;
}
texturesAttached.insert(texture);
}
aspects.set(VALIDATION_ASPECT_RENDER_SUBPASS);
return true;
};
bool CommandBufferStateTracker::EndSubpass() {
if (!aspects[VALIDATION_ASPECT_RENDER_SUBPASS]) {
builder->HandleError("Can't end a subpass without beginning one");
return false;
}
ASSERT(currentRenderPass != nullptr);
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;
}
}
// Everything in texturesAttached should be for the current render subpass.
texturesAttached.clear();
currentSubpass += 1;
aspects.reset(VALIDATION_ASPECT_RENDER_SUBPASS);
UnsetPipeline();
return true;
};
bool CommandBufferStateTracker::BeginRenderPass(RenderPassBase* renderPass, FramebufferBase* framebuffer) {
2017-07-10 21:07:24 +00:00
if (aspects[VALIDATION_ASPECT_COMPUTE_PASS]) {
builder->HandleError("Cannot begin a render pass while a compute pass is active");
return false;
}
if (currentRenderPass != nullptr) {
builder->HandleError("A render pass is already active");
return false;
}
ASSERT(!aspects[VALIDATION_ASPECT_RENDER_SUBPASS]);
if (!framebuffer->GetRenderPass()->IsCompatibleWith(renderPass)) {
builder->HandleError("Framebuffer is incompatible with this render pass");
return false;
}
currentRenderPass = renderPass;
currentFramebuffer = framebuffer;
currentSubpass = 0;
return true;
}
bool CommandBufferStateTracker::EndRenderPass() {
if (currentRenderPass == nullptr) {
builder->HandleError("No render pass is currently active");
return false;
}
if (aspects[VALIDATION_ASPECT_RENDER_SUBPASS]) {
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::SetComputePipeline(ComputePipelineBase* pipeline) {
if (!aspects[VALIDATION_ASPECT_COMPUTE_PASS]) {
builder->HandleError("A compute pass must be active when a compute pipeline is set");
return false;
}
if (currentRenderPass) {
builder->HandleError("Can't use a compute pipeline while a render pass is active");
return false;
}
aspects.set(VALIDATION_ASPECT_COMPUTE_PIPELINE);
return SetPipelineCommon(pipeline);
}
bool CommandBufferStateTracker::SetRenderPipeline(RenderPipelineBase* pipeline) {
if (!aspects[VALIDATION_ASPECT_RENDER_SUBPASS]) {
builder->HandleError("A render subpass 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);
lastRenderPipeline = pipeline;
return SetPipelineCommon(pipeline);
}
bool CommandBufferStateTracker::SetBindGroup(uint32_t index, BindGroupBase* bindgroup) {
if (!ValidateBindGroupUsages(bindgroup)) {
return false;
}
bindgroupsSet.set(index);
bindgroups[index] = bindgroup;
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 (!IsExplicitTextureTransitionPossible(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 if (texturesAttached.find(texture) != texturesAttached.end()) {
builder->HandleError("Texture transition not possible (texture is in use as a framebuffer attachment)");
} else {
builder->HandleError("Texture transition not possible");
}
return false;
}
mostRecentTextureUsages[texture] = usage;
texturesTransitioned.insert(texture);
return true;
}
bool CommandBufferStateTracker::EnsureTextureUsage(TextureBase* texture, nxt::TextureUsageBit usage) {
if (texture->HasFrozenUsage(nxt::TextureUsageBit::OutputAttachment)) {
return true;
}
2017-07-10 20:50:36 +00:00
if (!IsInternalTextureTransitionPossible(texture, usage)) {
return false;
}
2017-07-10 20:50:36 +00:00
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::IsInternalTextureTransitionPossible(TextureBase* texture, nxt::TextureUsageBit usage) const {
ASSERT(usage != nxt::TextureUsageBit::None && nxt::HasZeroOrOneBits(usage));
if (texturesAttached.find(texture) != texturesAttached.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 (aspects[VALIDATION_ASPECT_BIND_GROUPS]) {
return true;
}
// Assumes we have a pipeline already
if (!bindgroupsSet.all()) {
return false;
}
for (size_t i = 0; i < bindgroups.size(); ++i) {
if (auto* bindgroup = bindgroups[i]) {
// TODO(kainino@chromium.org): bind group compatibility
if (bindgroup->GetLayout() != lastPipeline->GetLayout()->GetBindGroupLayout(i)) {
return false;
}
}
}
aspects.set(VALIDATION_ASPECT_BIND_GROUPS);
return true;
}
bool CommandBufferStateTracker::RecomputeHaveAspectVertexBuffers() {
if (aspects[VALIDATION_ASPECT_VERTEX_BUFFERS]) {
return true;
}
// Assumes we have a pipeline already
auto requiredInputs = lastRenderPipeline->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:
{
2017-07-11 01:48:12 +00:00
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:
2017-07-11 01:44:06 +00:00
UNREACHABLE();
}
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("Bind group state not valid");
return false;
}
if (!RecomputeHaveAspectVertexBuffers()) {
builder->HandleError("Some vertex buffers are not set");
return false;
}
return true;
}
bool CommandBufferStateTracker::SetPipelineCommon(PipelineBase* pipeline) {
PipelineLayoutBase* layout = pipeline->GetLayout();
aspects.reset(VALIDATION_ASPECT_BIND_GROUPS);
// Reset bindgroups but mark unused bindgroups as valid
bindgroupsSet = ~layout->GetBindGroupsLayoutMask();
// Only bindgroups that were not the same layout in the last pipeline need to be set again.
if (lastPipeline) {
bindgroupsSet |= layout->InheritedGroupsMask(lastPipeline->GetLayout());
}
lastPipeline = pipeline;
return true;
}
void CommandBufferStateTracker::UnsetPipeline() {
2017-07-11 01:44:06 +00:00
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;
aspects &= ~pipelineDependentAspects;
bindgroups.fill(nullptr);
}
}