| // Copyright 2017 The Dawn 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 "dawn_native/CommandBufferStateTracker.h" | |
| #include "common/Assert.h" | |
| #include "common/BitSetIterator.h" | |
| #include "dawn_native/BindGroup.h" | |
| #include "dawn_native/ComputePassEncoder.h" | |
| #include "dawn_native/ComputePipeline.h" | |
| #include "dawn_native/Forward.h" | |
| #include "dawn_native/ObjectType_autogen.h" | |
| #include "dawn_native/PipelineLayout.h" | |
| #include "dawn_native/RenderPipeline.h" | |
| // TODO(dawn:563): None of the error messages in this file include the buffer objects they are | |
| // validating against. It would be nice to improve that, but difficult to do without incurring | |
| // additional tracking costs. | |
| namespace dawn::native { | |
| namespace { | |
| bool BufferSizesAtLeastAsBig(const ityp::span<uint32_t, uint64_t> unverifiedBufferSizes, | |
| const std::vector<uint64_t>& pipelineMinBufferSizes) { | |
| ASSERT(unverifiedBufferSizes.size() == pipelineMinBufferSizes.size()); | |
| for (uint32_t i = 0; i < unverifiedBufferSizes.size(); ++i) { | |
| if (unverifiedBufferSizes[i] < pipelineMinBufferSizes[i]) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| } // namespace | |
| enum ValidationAspect { | |
| VALIDATION_ASPECT_PIPELINE, | |
| VALIDATION_ASPECT_BIND_GROUPS, | |
| VALIDATION_ASPECT_VERTEX_BUFFERS, | |
| VALIDATION_ASPECT_INDEX_BUFFER, | |
| VALIDATION_ASPECT_COUNT | |
| }; | |
| static_assert(VALIDATION_ASPECT_COUNT == CommandBufferStateTracker::kNumAspects); | |
| static constexpr CommandBufferStateTracker::ValidationAspects kDispatchAspects = | |
| 1 << VALIDATION_ASPECT_PIPELINE | 1 << VALIDATION_ASPECT_BIND_GROUPS; | |
| static constexpr CommandBufferStateTracker::ValidationAspects kDrawAspects = | |
| 1 << VALIDATION_ASPECT_PIPELINE | 1 << VALIDATION_ASPECT_BIND_GROUPS | | |
| 1 << VALIDATION_ASPECT_VERTEX_BUFFERS; | |
| static constexpr CommandBufferStateTracker::ValidationAspects kDrawIndexedAspects = | |
| 1 << VALIDATION_ASPECT_PIPELINE | 1 << VALIDATION_ASPECT_BIND_GROUPS | | |
| 1 << VALIDATION_ASPECT_VERTEX_BUFFERS | 1 << VALIDATION_ASPECT_INDEX_BUFFER; | |
| static constexpr CommandBufferStateTracker::ValidationAspects kLazyAspects = | |
| 1 << VALIDATION_ASPECT_BIND_GROUPS | 1 << VALIDATION_ASPECT_VERTEX_BUFFERS | | |
| 1 << VALIDATION_ASPECT_INDEX_BUFFER; | |
| MaybeError CommandBufferStateTracker::ValidateCanDispatch() { | |
| return ValidateOperation(kDispatchAspects); | |
| } | |
| MaybeError CommandBufferStateTracker::ValidateCanDraw() { | |
| return ValidateOperation(kDrawAspects); | |
| } | |
| MaybeError CommandBufferStateTracker::ValidateCanDrawIndexed() { | |
| return ValidateOperation(kDrawIndexedAspects); | |
| } | |
| MaybeError CommandBufferStateTracker::ValidateBufferInRangeForVertexBuffer( | |
| uint32_t vertexCount, | |
| uint32_t firstVertex) { | |
| RenderPipelineBase* lastRenderPipeline = GetRenderPipeline(); | |
| const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& | |
| vertexBufferSlotsUsedAsVertexBuffer = | |
| lastRenderPipeline->GetVertexBufferSlotsUsedAsVertexBuffer(); | |
| for (auto usedSlotVertex : IterateBitSet(vertexBufferSlotsUsedAsVertexBuffer)) { | |
| const VertexBufferInfo& vertexBuffer = | |
| lastRenderPipeline->GetVertexBuffer(usedSlotVertex); | |
| uint64_t arrayStride = vertexBuffer.arrayStride; | |
| uint64_t bufferSize = mVertexBufferSizes[usedSlotVertex]; | |
| if (arrayStride == 0) { | |
| DAWN_INVALID_IF(vertexBuffer.usedBytesInStride > bufferSize, | |
| "Bound vertex buffer size (%u) at slot %u with an arrayStride of 0 " | |
| "is smaller than the required size for all attributes (%u)", | |
| bufferSize, static_cast<uint8_t>(usedSlotVertex), | |
| vertexBuffer.usedBytesInStride); | |
| } else { | |
| uint64_t requiredSize = | |
| (static_cast<uint64_t>(firstVertex) + vertexCount) * arrayStride; | |
| // firstVertex and vertexCount are in uint32_t, and arrayStride must not | |
| // be larger than kMaxVertexBufferArrayStride, which is currently 2048. So by | |
| // doing checks in uint64_t we avoid overflows. | |
| DAWN_INVALID_IF( | |
| requiredSize > bufferSize, | |
| "Vertex range (first: %u, count: %u) requires a larger buffer (%u) than the " | |
| "bound buffer size (%u) of the vertex buffer at slot %u with stride (%u).", | |
| firstVertex, vertexCount, requiredSize, bufferSize, | |
| static_cast<uint8_t>(usedSlotVertex), arrayStride); | |
| } | |
| } | |
| return {}; | |
| } | |
| MaybeError CommandBufferStateTracker::ValidateBufferInRangeForInstanceBuffer( | |
| uint32_t instanceCount, | |
| uint32_t firstInstance) { | |
| RenderPipelineBase* lastRenderPipeline = GetRenderPipeline(); | |
| const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& | |
| vertexBufferSlotsUsedAsInstanceBuffer = | |
| lastRenderPipeline->GetVertexBufferSlotsUsedAsInstanceBuffer(); | |
| for (auto usedSlotInstance : IterateBitSet(vertexBufferSlotsUsedAsInstanceBuffer)) { | |
| const VertexBufferInfo& vertexBuffer = | |
| lastRenderPipeline->GetVertexBuffer(usedSlotInstance); | |
| uint64_t arrayStride = vertexBuffer.arrayStride; | |
| uint64_t bufferSize = mVertexBufferSizes[usedSlotInstance]; | |
| if (arrayStride == 0) { | |
| DAWN_INVALID_IF(vertexBuffer.usedBytesInStride > bufferSize, | |
| "Bound vertex buffer size (%u) at slot %u with an arrayStride of 0 " | |
| "is smaller than the required size for all attributes (%u)", | |
| bufferSize, static_cast<uint8_t>(usedSlotInstance), | |
| vertexBuffer.usedBytesInStride); | |
| } else { | |
| uint64_t requiredSize = | |
| (static_cast<uint64_t>(firstInstance) + instanceCount) * arrayStride; | |
| // firstInstance and instanceCount are in uint32_t, and arrayStride must | |
| // not be larger than kMaxVertexBufferArrayStride, which is currently 2048. | |
| // So by doing checks in uint64_t we avoid overflows. | |
| DAWN_INVALID_IF( | |
| requiredSize > bufferSize, | |
| "Instance range (first: %u, count: %u) requires a larger buffer (%u) than the " | |
| "bound buffer size (%u) of the vertex buffer at slot %u with stride (%u).", | |
| firstInstance, instanceCount, requiredSize, bufferSize, | |
| static_cast<uint8_t>(usedSlotInstance), arrayStride); | |
| } | |
| } | |
| return {}; | |
| } | |
| MaybeError CommandBufferStateTracker::ValidateIndexBufferInRange(uint32_t indexCount, | |
| uint32_t firstIndex) { | |
| // Validate the range of index buffer | |
| // firstIndex and indexCount are in uint32_t, while IndexFormatSize is 2 (for | |
| // wgpu::IndexFormat::Uint16) or 4 (for wgpu::IndexFormat::Uint32), so by doing checks in | |
| // uint64_t we avoid overflows. | |
| DAWN_INVALID_IF( | |
| (static_cast<uint64_t>(firstIndex) + indexCount) * IndexFormatSize(mIndexFormat) > | |
| mIndexBufferSize, | |
| "Index range (first: %u, count: %u, format: %s) does not fit in index buffer size " | |
| "(%u).", | |
| firstIndex, indexCount, mIndexFormat, mIndexBufferSize); | |
| return {}; | |
| } | |
| MaybeError CommandBufferStateTracker::ValidateOperation(ValidationAspects requiredAspects) { | |
| // Fast return-true path if everything is good | |
| ValidationAspects missingAspects = requiredAspects & ~mAspects; | |
| if (missingAspects.none()) { | |
| return {}; | |
| } | |
| // Generate an error immediately if a non-lazy aspect is missing as computing lazy aspects | |
| // requires the pipeline to be set. | |
| DAWN_TRY(CheckMissingAspects(missingAspects & ~kLazyAspects)); | |
| RecomputeLazyAspects(missingAspects); | |
| DAWN_TRY(CheckMissingAspects(requiredAspects & ~mAspects)); | |
| return {}; | |
| } | |
| void CommandBufferStateTracker::RecomputeLazyAspects(ValidationAspects aspects) { | |
| ASSERT(mAspects[VALIDATION_ASPECT_PIPELINE]); | |
| ASSERT((aspects & ~kLazyAspects).none()); | |
| if (aspects[VALIDATION_ASPECT_BIND_GROUPS]) { | |
| bool matches = true; | |
| for (BindGroupIndex i : IterateBitSet(mLastPipelineLayout->GetBindGroupLayoutsMask())) { | |
| if (mBindgroups[i] == nullptr || | |
| mLastPipelineLayout->GetBindGroupLayout(i) != mBindgroups[i]->GetLayout() || | |
| !BufferSizesAtLeastAsBig(mBindgroups[i]->GetUnverifiedBufferSizes(), | |
| (*mMinBufferSizes)[i])) { | |
| matches = false; | |
| break; | |
| } | |
| } | |
| if (matches) { | |
| mAspects.set(VALIDATION_ASPECT_BIND_GROUPS); | |
| } | |
| } | |
| if (aspects[VALIDATION_ASPECT_VERTEX_BUFFERS]) { | |
| RenderPipelineBase* lastRenderPipeline = GetRenderPipeline(); | |
| const ityp::bitset<VertexBufferSlot, kMaxVertexBuffers>& requiredVertexBuffers = | |
| lastRenderPipeline->GetVertexBufferSlotsUsed(); | |
| if (IsSubset(requiredVertexBuffers, mVertexBufferSlotsUsed)) { | |
| mAspects.set(VALIDATION_ASPECT_VERTEX_BUFFERS); | |
| } | |
| } | |
| if (aspects[VALIDATION_ASPECT_INDEX_BUFFER] && mIndexBufferSet) { | |
| RenderPipelineBase* lastRenderPipeline = GetRenderPipeline(); | |
| if (!IsStripPrimitiveTopology(lastRenderPipeline->GetPrimitiveTopology()) || | |
| mIndexFormat == lastRenderPipeline->GetStripIndexFormat()) { | |
| mAspects.set(VALIDATION_ASPECT_INDEX_BUFFER); | |
| } | |
| } | |
| } | |
| MaybeError CommandBufferStateTracker::CheckMissingAspects(ValidationAspects aspects) { | |
| if (!aspects.any()) { | |
| return {}; | |
| } | |
| DAWN_INVALID_IF(aspects[VALIDATION_ASPECT_PIPELINE], "No pipeline set."); | |
| if (DAWN_UNLIKELY(aspects[VALIDATION_ASPECT_INDEX_BUFFER])) { | |
| DAWN_INVALID_IF(!mIndexBufferSet, "Index buffer was not set."); | |
| RenderPipelineBase* lastRenderPipeline = GetRenderPipeline(); | |
| wgpu::IndexFormat pipelineIndexFormat = lastRenderPipeline->GetStripIndexFormat(); | |
| if (IsStripPrimitiveTopology(lastRenderPipeline->GetPrimitiveTopology())) { | |
| DAWN_INVALID_IF( | |
| pipelineIndexFormat == wgpu::IndexFormat::Undefined, | |
| "%s has a strip primitive topology (%s) but a strip index format of %s, which " | |
| "prevents it for being used for indexed draw calls.", | |
| lastRenderPipeline, lastRenderPipeline->GetPrimitiveTopology(), | |
| pipelineIndexFormat); | |
| DAWN_INVALID_IF( | |
| mIndexFormat != pipelineIndexFormat, | |
| "Strip index format (%s) of %s does not match index buffer format (%s).", | |
| pipelineIndexFormat, lastRenderPipeline, mIndexFormat); | |
| } | |
| // The chunk of code above should be similar to the one in |RecomputeLazyAspects|. | |
| // It returns the first invalid state found. We shouldn't be able to reach this line | |
| // because to have invalid aspects one of the above conditions must have failed earlier. | |
| // If this is reached, make sure lazy aspects and the error checks above are consistent. | |
| UNREACHABLE(); | |
| return DAWN_FORMAT_VALIDATION_ERROR("Index buffer is invalid."); | |
| } | |
| // TODO(dawn:563): Indicate which slots were not set. | |
| DAWN_INVALID_IF(aspects[VALIDATION_ASPECT_VERTEX_BUFFERS], | |
| "Vertex buffer slots required by %s were not set.", GetRenderPipeline()); | |
| if (DAWN_UNLIKELY(aspects[VALIDATION_ASPECT_BIND_GROUPS])) { | |
| for (BindGroupIndex i : IterateBitSet(mLastPipelineLayout->GetBindGroupLayoutsMask())) { | |
| ASSERT(HasPipeline()); | |
| DAWN_INVALID_IF(mBindgroups[i] == nullptr, "No bind group set at index %u.", | |
| static_cast<uint32_t>(i)); | |
| BindGroupLayoutBase* requiredBGL = mLastPipelineLayout->GetBindGroupLayout(i); | |
| BindGroupLayoutBase* currentBGL = mBindgroups[i]->GetLayout(); | |
| DAWN_INVALID_IF( | |
| requiredBGL->GetPipelineCompatibilityToken() != PipelineCompatibilityToken(0) && | |
| currentBGL->GetPipelineCompatibilityToken() != | |
| requiredBGL->GetPipelineCompatibilityToken(), | |
| "The current pipeline (%s) was created with a default layout, and is not " | |
| "compatible with the %s at index %u which uses a %s that was not created by " | |
| "the pipeline. Either use the bind group layout returned by calling " | |
| "getBindGroupLayout(%u) on the pipeline when creating the bind group, or " | |
| "provide an explicit pipeline layout when creating the pipeline.", | |
| mLastPipeline, mBindgroups[i], static_cast<uint32_t>(i), currentBGL, | |
| static_cast<uint32_t>(i)); | |
| DAWN_INVALID_IF( | |
| requiredBGL->GetPipelineCompatibilityToken() == PipelineCompatibilityToken(0) && | |
| currentBGL->GetPipelineCompatibilityToken() != | |
| PipelineCompatibilityToken(0), | |
| "%s at index %u uses a %s which was created as part of the default layout for " | |
| "a different pipeline than the current one (%s), and as a result is not " | |
| "compatible. Use an explicit bind group layout when creating bind groups and " | |
| "an explicit pipeline layout when creating pipelines to share bind groups " | |
| "between pipelines.", | |
| mBindgroups[i], static_cast<uint32_t>(i), currentBGL, mLastPipeline); | |
| DAWN_INVALID_IF( | |
| mLastPipelineLayout->GetBindGroupLayout(i) != mBindgroups[i]->GetLayout(), | |
| "Bind group layout %s of pipeline layout %s does not match layout %s of bind " | |
| "group %s at index %u.", | |
| requiredBGL, mLastPipelineLayout, currentBGL, mBindgroups[i], | |
| static_cast<uint32_t>(i)); | |
| // TODO(dawn:563): Report the binding sizes and which ones are failing. | |
| DAWN_INVALID_IF(!BufferSizesAtLeastAsBig(mBindgroups[i]->GetUnverifiedBufferSizes(), | |
| (*mMinBufferSizes)[i]), | |
| "Binding sizes are too small for bind group %s at index %u", | |
| mBindgroups[i], static_cast<uint32_t>(i)); | |
| } | |
| // The chunk of code above should be similar to the one in |RecomputeLazyAspects|. | |
| // It returns the first invalid state found. We shouldn't be able to reach this line | |
| // because to have invalid aspects one of the above conditions must have failed earlier. | |
| // If this is reached, make sure lazy aspects and the error checks above are consistent. | |
| UNREACHABLE(); | |
| return DAWN_FORMAT_VALIDATION_ERROR("Bind groups are invalid."); | |
| } | |
| UNREACHABLE(); | |
| } | |
| void CommandBufferStateTracker::SetComputePipeline(ComputePipelineBase* pipeline) { | |
| SetPipelineCommon(pipeline); | |
| } | |
| void CommandBufferStateTracker::SetRenderPipeline(RenderPipelineBase* pipeline) { | |
| SetPipelineCommon(pipeline); | |
| } | |
| void CommandBufferStateTracker::SetBindGroup(BindGroupIndex index, | |
| BindGroupBase* bindgroup, | |
| uint32_t dynamicOffsetCount, | |
| const uint32_t* dynamicOffsets) { | |
| mBindgroups[index] = bindgroup; | |
| mDynamicOffsets[index].assign(dynamicOffsets, dynamicOffsets + dynamicOffsetCount); | |
| mAspects.reset(VALIDATION_ASPECT_BIND_GROUPS); | |
| } | |
| void CommandBufferStateTracker::SetIndexBuffer(wgpu::IndexFormat format, uint64_t size) { | |
| mIndexBufferSet = true; | |
| mIndexFormat = format; | |
| mIndexBufferSize = size; | |
| } | |
| void CommandBufferStateTracker::SetVertexBuffer(VertexBufferSlot slot, uint64_t size) { | |
| mVertexBufferSlotsUsed.set(slot); | |
| mVertexBufferSizes[slot] = size; | |
| } | |
| void CommandBufferStateTracker::SetPipelineCommon(PipelineBase* pipeline) { | |
| mLastPipeline = pipeline; | |
| mLastPipelineLayout = pipeline != nullptr ? pipeline->GetLayout() : nullptr; | |
| mMinBufferSizes = pipeline != nullptr ? &pipeline->GetMinBufferSizes() : nullptr; | |
| mAspects.set(VALIDATION_ASPECT_PIPELINE); | |
| // Reset lazy aspects so they get recomputed on the next operation. | |
| mAspects &= ~kLazyAspects; | |
| } | |
| BindGroupBase* CommandBufferStateTracker::GetBindGroup(BindGroupIndex index) const { | |
| return mBindgroups[index]; | |
| } | |
| const std::vector<uint32_t>& CommandBufferStateTracker::GetDynamicOffsets( | |
| BindGroupIndex index) const { | |
| return mDynamicOffsets[index]; | |
| } | |
| bool CommandBufferStateTracker::HasPipeline() const { | |
| return mLastPipeline != nullptr; | |
| } | |
| RenderPipelineBase* CommandBufferStateTracker::GetRenderPipeline() const { | |
| ASSERT(HasPipeline() && mLastPipeline->GetType() == ObjectType::RenderPipeline); | |
| return static_cast<RenderPipelineBase*>(mLastPipeline); | |
| } | |
| ComputePipelineBase* CommandBufferStateTracker::GetComputePipeline() const { | |
| ASSERT(HasPipeline() && mLastPipeline->GetType() == ObjectType::ComputePipeline); | |
| return static_cast<ComputePipelineBase*>(mLastPipeline); | |
| } | |
| PipelineLayoutBase* CommandBufferStateTracker::GetPipelineLayout() const { | |
| return mLastPipelineLayout; | |
| } | |
| wgpu::IndexFormat CommandBufferStateTracker::GetIndexFormat() const { | |
| return mIndexFormat; | |
| } | |
| uint64_t CommandBufferStateTracker::GetIndexBufferSize() const { | |
| return mIndexBufferSize; | |
| } | |
| } // namespace dawn::native |