Create a command buffer builder state tracker object (#19)

diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt
index 3b9abc3..a9b2552 100644
--- a/src/backend/CMakeLists.txt
+++ b/src/backend/CMakeLists.txt
@@ -35,6 +35,8 @@
     ${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 4343673..df9940a 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 @@
 
     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 @@
         commands->DataWasDestroyed();
     }
 
-    CommandBufferBuilder::CommandBufferBuilder(DeviceBase* device) : Builder(device) {
+    CommandBufferBuilder::CommandBufferBuilder(DeviceBase* device) : Builder(device), state(std::make_unique<CommandBufferStateTracker>(this)) {
     }
 
     CommandBufferBuilder::~CommandBufferBuilder() {
@@ -166,212 +166,27 @@
         }
     }
 
-    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<VALIDATION_ASPECT_COUNT>;
-
     bool CommandBufferBuilder::ValidateGetResult() {
         MoveToIterator();
 
-        ValidationAspects aspects;
-        std::bitset<kMaxBindGroups> bindgroupsSet;
-        std::bitset<kMaxVertexInputs> inputsSet;
-        PipelineBase* lastPipeline = nullptr;
-
-        // TODO(kainino@chromium.org): Manage this state inside an object, change lambdas to methods
-        std::map<BufferBase*, nxt::BufferUsageBit> 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<TextureBase*, nxt::TextureUsageBit> 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<AdvanceSubpassCmd>();
-                        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<BeginRenderPassCmd>();
-                        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<BeginRenderPassCmd>();
+                        auto* renderPass = cmd->renderPass.Get();
+                        auto* framebuffer = cmd->framebuffer.Get();
+                        if (!state->BeginRenderPass(renderPass, framebuffer)) {
                             return false;
                         }
                     }
@@ -391,21 +206,6 @@
                         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 @@
                         // TODO(cwallez@chromium.org): check for overflows
                         uint64_t pixelSize = TextureFormatPixelSize(texture->GetFormat());
                         uint64_t dataSize = width * height * depth * pixelSize;
-
                         if (dataSize + static_cast<uint64_t>(bufferOffset) > static_cast<uint64_t>(buffer->GetSize())) {
                             HandleError("Copy would read after end of the buffer");
                             return false;
@@ -427,68 +226,38 @@
                             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<DispatchCmd>();
-
-                        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<DispatchCmd>();
+                        if (!state->ValidateCanDispatch()) {
+                            return false;
                         }
                     }
                     break;
 
                 case Command::DrawArrays:
+                    {
+                        iterator.NextCommand<DrawArraysCmd>();
+                        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<DrawArraysCmd>();
-                        } else {
-                            ASSERT(type == Command::DrawElements);
-                            DrawElementsCmd* draw = iterator.NextCommand<DrawElementsCmd>();
-
-                            if (!aspects[VALIDATION_ASPECT_INDEX_BUFFER]) {
-                                HandleError("Draw elements requires an index buffer");
-                                return false;
-                            }
+                        iterator.NextCommand<DrawElementsCmd>();
+                        if (!state->ValidateCanDrawElements()) {
+                            return false;
                         }
                     }
                     break;
@@ -496,19 +265,9 @@
                 case Command::EndRenderPass:
                     {
                         iterator.NextCommand<EndRenderPassCmd>();
-                        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 @@
                     {
                         SetPipelineCmd* cmd = iterator.NextCommand<SetPipelineCmd>();
                         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 @@
                 case Command::SetStencilReference:
                     {
                         SetStencilReferenceCmd* cmd = iterator.NextCommand<SetStencilReferenceCmd>();
-                        if (currentRenderPass == nullptr) {
+                        if (!state->HaveRenderPass()) {
                             HandleError("Can't set stencil reference without an active render pass");
                             return false;
                         }
@@ -577,30 +305,18 @@
                 case Command::SetBindGroup:
                     {
                         SetBindGroupCmd* cmd = iterator.NextCommand<SetBindGroupCmd>();
-                        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<SetIndexBufferCmd>();
-                        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 @@
                         iterator.NextData<uint32_t>(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 @@
                 case Command::TransitionBufferUsage:
                     {
                         TransitionBufferUsageCmd* cmd = iterator.NextCommand<TransitionBufferUsageCmd>();
-                        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<TransitionTextureUsageCmd>();
-                        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 8c603cd..485d1f3 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 <memory>
 #include <set>
 #include <utility>
 
@@ -28,6 +29,7 @@
 
     class BindGroupBase;
     class BufferBase;
+    class CommandBufferStateTracker;
     class FramebufferBase;
     class DeviceBase;
     class PipelineBase;
@@ -88,16 +90,13 @@
             CommandBufferBase* GetResultImpl() override;
             void MoveToIterator();
 
+            std::unique_ptr<CommandBufferStateTracker> 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<BufferBase*> buffersTransitioned;
-            std::set<TextureBase*> 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 0000000..7c02be9
--- /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 0000000..7fe4a23
--- /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 <bitset>
+#include <map>
+#include <set>
+
+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<BufferBase*> buffersTransitioned;
+            std::set<TextureBase*> 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<VALIDATION_ASPECT_COUNT>;
+
+            // 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<kMaxBindGroups> bindgroupsSet;
+            std::bitset<kMaxVertexInputs> inputsSet;
+            PipelineBase* lastPipeline = nullptr;
+
+            std::map<BufferBase*, nxt::BufferUsageBit> mostRecentBufferUsages;
+            std::map<TextureBase*, nxt::TextureUsageBit> mostRecentTextureUsages;
+
+            RenderPassBase* currentRenderPass = nullptr;
+            FramebufferBase* currentFramebuffer = nullptr;
+            bool subpassActive = false;
+            uint32_t currentSubpass = 0;
+    };
+}
+
+#endif // BACKEND_COMMON_COMMANDBUFFERSTATETRACKER_H