// 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 unverifiedBufferSizes, const std::vector& 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& 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(usedSlotVertex), vertexBuffer.usedBytesInStride); } else { uint64_t requiredSize = (static_cast(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(usedSlotVertex), arrayStride); } } return {}; } MaybeError CommandBufferStateTracker::ValidateBufferInRangeForInstanceBuffer( uint32_t instanceCount, uint32_t firstInstance) { RenderPipelineBase* lastRenderPipeline = GetRenderPipeline(); const ityp::bitset& 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(usedSlotInstance), vertexBuffer.usedBytesInStride); } else { uint64_t requiredSize = (static_cast(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(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(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& 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(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(i), currentBGL, static_cast(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(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(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(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& 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(mLastPipeline); } ComputePipelineBase* CommandBufferStateTracker::GetComputePipeline() const { ASSERT(HasPipeline() && mLastPipeline->GetType() == ObjectType::ComputePipeline); return static_cast(mLastPipeline); } PipelineLayoutBase* CommandBufferStateTracker::GetPipelineLayout() const { return mLastPipelineLayout; } wgpu::IndexFormat CommandBufferStateTracker::GetIndexFormat() const { return mIndexFormat; } uint64_t CommandBufferStateTracker::GetIndexBufferSize() const { return mIndexBufferSize; } } // namespace dawn::native