| // Copyright 2018 The Dawn & Tint Authors |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // 3. Neither the name of the copyright holder nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "dawn/native/RenderPassEncoder.h" |
| |
| #include <math.h> |
| #include <cstring> |
| #include <utility> |
| |
| #include "dawn/common/Constants.h" |
| #include "dawn/native/Buffer.h" |
| #include "dawn/native/ChainUtils.h" |
| #include "dawn/native/CommandEncoder.h" |
| #include "dawn/native/CommandValidation.h" |
| #include "dawn/native/Commands.h" |
| #include "dawn/native/Device.h" |
| #include "dawn/native/ObjectType_autogen.h" |
| #include "dawn/native/QuerySet.h" |
| #include "dawn/native/RenderBundle.h" |
| #include "dawn/native/RenderPipeline.h" |
| |
| namespace dawn::native { |
| namespace { |
| |
| // Check the query at queryIndex is unavailable, otherwise it cannot be written. |
| MaybeError ValidateQueryIndexOverwrite(QuerySetBase* querySet, |
| uint32_t queryIndex, |
| const QueryAvailabilityMap& queryAvailabilityMap) { |
| auto it = queryAvailabilityMap.find(querySet); |
| DAWN_INVALID_IF(it != queryAvailabilityMap.end() && it->second[queryIndex], |
| "Query index %u of %s is written to twice in a render pass.", queryIndex, |
| querySet); |
| |
| return {}; |
| } |
| |
| } // namespace |
| |
| // The usage tracker is passed in here, because it is prepopulated with usages from the |
| // BeginRenderPassCmd. If we had RenderPassEncoder responsible for recording the |
| // command, then this wouldn't be necessary. |
| RenderPassEncoder::RenderPassEncoder(DeviceBase* device, |
| const UnpackedPtr<RenderPassDescriptor>& descriptor, |
| CommandEncoder* commandEncoder, |
| EncodingContext* encodingContext, |
| RenderPassResourceUsageTracker usageTracker, |
| Ref<AttachmentState> attachmentState, |
| uint32_t renderTargetWidth, |
| uint32_t renderTargetHeight, |
| bool depthReadOnly, |
| bool stencilReadOnly, |
| std::function<void()> endCallback) |
| : RenderEncoderBase(device, |
| descriptor->label, |
| encodingContext, |
| std::move(attachmentState), |
| depthReadOnly, |
| stencilReadOnly), |
| mCommandEncoder(commandEncoder), |
| mRenderTargetWidth(renderTargetWidth), |
| mRenderTargetHeight(renderTargetHeight), |
| mOcclusionQuerySet(descriptor->occlusionQuerySet), |
| mEndCallback(std::move(endCallback)) { |
| mUsageTracker = std::move(usageTracker); |
| if (auto* maxDrawCountInfo = descriptor.Get<RenderPassDescriptorMaxDrawCount>()) { |
| mMaxDrawCount = maxDrawCountInfo->maxDrawCount; |
| } |
| GetObjectTrackingList()->Track(this); |
| } |
| |
| // static |
| Ref<RenderPassEncoder> RenderPassEncoder::Create( |
| DeviceBase* device, |
| const UnpackedPtr<RenderPassDescriptor>& descriptor, |
| CommandEncoder* commandEncoder, |
| EncodingContext* encodingContext, |
| RenderPassResourceUsageTracker usageTracker, |
| Ref<AttachmentState> attachmentState, |
| uint32_t renderTargetWidth, |
| uint32_t renderTargetHeight, |
| bool depthReadOnly, |
| bool stencilReadOnly, |
| std::function<void()> endCallback) { |
| return AcquireRef(new RenderPassEncoder(device, descriptor, commandEncoder, encodingContext, |
| std::move(usageTracker), std::move(attachmentState), |
| renderTargetWidth, renderTargetHeight, depthReadOnly, |
| stencilReadOnly, std::move(endCallback))); |
| } |
| |
| RenderPassEncoder::RenderPassEncoder(DeviceBase* device, |
| CommandEncoder* commandEncoder, |
| EncodingContext* encodingContext, |
| ErrorTag errorTag, |
| const char* label) |
| : RenderEncoderBase(device, encodingContext, errorTag, label), |
| mCommandEncoder(commandEncoder) {} |
| |
| // static |
| Ref<RenderPassEncoder> RenderPassEncoder::MakeError(DeviceBase* device, |
| CommandEncoder* commandEncoder, |
| EncodingContext* encodingContext, |
| const char* label) { |
| return AcquireRef( |
| new RenderPassEncoder(device, commandEncoder, encodingContext, ObjectBase::kError, label)); |
| } |
| |
| void RenderPassEncoder::DestroyImpl() { |
| mCommandBufferState.End(); |
| |
| RenderEncoderBase::DestroyImpl(); |
| // Ensure that the pass has exited. This is done for passes only since validation requires |
| // they exit before destruction while bundles do not. |
| mEncodingContext->EnsurePassExited(this); |
| } |
| |
| ObjectType RenderPassEncoder::GetType() const { |
| return ObjectType::RenderPassEncoder; |
| } |
| |
| void RenderPassEncoder::TrackQueryAvailability(QuerySetBase* querySet, uint32_t queryIndex) { |
| DAWN_ASSERT(querySet != nullptr); |
| |
| // Track the query availability with true on render pass for rewrite validation and query |
| // reset on render pass on Vulkan |
| mUsageTracker.TrackQueryAvailability(querySet, queryIndex); |
| |
| // Track it again on command encoder for zero-initializing when resolving unused queries. |
| mCommandEncoder->TrackQueryAvailability(querySet, queryIndex); |
| } |
| |
| void RenderPassEncoder::APIEnd() { |
| // The encoding context might create additional resources, so we need to lock the device. |
| auto deviceLock(GetDevice()->GetScopedLock()); |
| End(); |
| } |
| |
| void RenderPassEncoder::End() { |
| DAWN_ASSERT(GetDevice()->IsLockedByCurrentThreadIfNeeded()); |
| |
| mCommandBufferState.End(); |
| |
| if (mEnded && IsValidationEnabled()) { |
| GetDevice()->HandleError(DAWN_VALIDATION_ERROR("%s was already ended.", this)); |
| return; |
| } |
| |
| mEnded = true; |
| |
| mEncodingContext->TryEncode( |
| this, |
| [&](CommandAllocator* allocator) -> MaybeError { |
| if (IsValidationEnabled()) { |
| DAWN_TRY(ValidateProgrammableEncoderEnd()); |
| |
| DAWN_INVALID_IF( |
| mOcclusionQueryActive, |
| "Render pass %s ended with incomplete occlusion query index %u of %s.", this, |
| mCurrentOcclusionQueryIndex, mOcclusionQuerySet.Get()); |
| |
| DAWN_INVALID_IF(mDrawCount > mMaxDrawCount, |
| "The drawCount (%u) of %s is greater than the maxDrawCount (%u).", |
| mDrawCount, this, mMaxDrawCount); |
| } |
| |
| allocator->Allocate<EndRenderPassCmd>(Command::EndRenderPass); |
| |
| DAWN_TRY(mEncodingContext->ExitRenderPass(this, std::move(mUsageTracker), |
| mCommandEncoder.Get(), |
| std::move(mIndirectDrawMetadata))); |
| return {}; |
| }, |
| "encoding %s.End().", this); |
| |
| if (mEndCallback) { |
| mEndCallback(); |
| } |
| } |
| |
| void RenderPassEncoder::APISetStencilReference(uint32_t reference) { |
| mEncodingContext->TryEncode( |
| this, |
| [&](CommandAllocator* allocator) -> MaybeError { |
| SetStencilReferenceCmd* cmd = |
| allocator->Allocate<SetStencilReferenceCmd>(Command::SetStencilReference); |
| cmd->reference = reference & 255; |
| |
| return {}; |
| }, |
| "encoding %s.SetStencilReference(%u).", this, reference); |
| } |
| |
| void RenderPassEncoder::APISetBlendConstant(const Color* color) { |
| mEncodingContext->TryEncode( |
| this, |
| [&](CommandAllocator* allocator) -> MaybeError { |
| SetBlendConstantCmd* cmd = |
| allocator->Allocate<SetBlendConstantCmd>(Command::SetBlendConstant); |
| cmd->color = *color; |
| |
| return {}; |
| }, |
| "encoding %s.SetBlendConstant(%s).", this, color); |
| } |
| |
| void RenderPassEncoder::APISetViewport(float x, |
| float y, |
| float width, |
| float height, |
| float minDepth, |
| float maxDepth) { |
| mEncodingContext->TryEncode( |
| this, |
| [&](CommandAllocator* allocator) -> MaybeError { |
| if (IsValidationEnabled()) { |
| DAWN_INVALID_IF((isnan(x) || isnan(y) || isnan(width) || isnan(height) || |
| isnan(minDepth) || isnan(maxDepth)), |
| "A parameter of the viewport (x: %f, y: %f, width: %f, height: %f, " |
| "minDepth: %f, maxDepth: %f) is NaN.", |
| x, y, width, height, minDepth, maxDepth); |
| |
| DAWN_INVALID_IF( |
| x < 0 || y < 0 || width < 0 || height < 0, |
| "Viewport bounds (x: %f, y: %f, width: %f, height: %f) contains a negative " |
| "value.", |
| x, y, width, height); |
| |
| DAWN_INVALID_IF( |
| x + width > mRenderTargetWidth || y + height > mRenderTargetHeight, |
| "Viewport bounds (x: %f, y: %f, width: %f, height: %f) are not contained " |
| "in " |
| "the render target dimensions (%u x %u).", |
| x, y, width, height, mRenderTargetWidth, mRenderTargetHeight); |
| |
| // Check for depths being in [0, 1] and min <= max in 3 checks instead of 5. |
| DAWN_INVALID_IF(minDepth < 0 || minDepth > maxDepth || maxDepth > 1, |
| "Viewport minDepth (%f) and maxDepth (%f) are not in [0, 1] or " |
| "minDepth was " |
| "greater than maxDepth.", |
| minDepth, maxDepth); |
| } |
| |
| SetViewportCmd* cmd = allocator->Allocate<SetViewportCmd>(Command::SetViewport); |
| cmd->x = x; |
| cmd->y = y; |
| cmd->width = width; |
| cmd->height = height; |
| cmd->minDepth = minDepth; |
| cmd->maxDepth = maxDepth; |
| |
| return {}; |
| }, |
| "encoding %s.SetViewport(%f, %f, %f, %f, %f, %f).", this, x, y, width, height, minDepth, |
| maxDepth); |
| } |
| |
| void RenderPassEncoder::APISetScissorRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height) { |
| mEncodingContext->TryEncode( |
| this, |
| [&](CommandAllocator* allocator) -> MaybeError { |
| if (IsValidationEnabled()) { |
| DAWN_INVALID_IF( |
| width > mRenderTargetWidth || height > mRenderTargetHeight || |
| x > mRenderTargetWidth - width || y > mRenderTargetHeight - height, |
| "Scissor rect (x: %u, y: %u, width: %u, height: %u) is not contained in " |
| "the render target dimensions (%u x %u).", |
| x, y, width, height, mRenderTargetWidth, mRenderTargetHeight); |
| } |
| |
| SetScissorRectCmd* cmd = |
| allocator->Allocate<SetScissorRectCmd>(Command::SetScissorRect); |
| cmd->x = x; |
| cmd->y = y; |
| cmd->width = width; |
| cmd->height = height; |
| |
| return {}; |
| }, |
| "encoding %s.SetScissorRect(%u, %u, %u, %u).", this, x, y, width, height); |
| } |
| |
| void RenderPassEncoder::APIExecuteBundles(uint32_t count, RenderBundleBase* const* renderBundles) { |
| mEncodingContext->TryEncode( |
| this, |
| [&](CommandAllocator* allocator) -> MaybeError { |
| if (IsValidationEnabled()) { |
| const AttachmentState* attachmentState = GetAttachmentState(); |
| bool depthReadOnlyInPass = IsDepthReadOnly(); |
| bool stencilReadOnlyInPass = IsStencilReadOnly(); |
| for (uint32_t i = 0; i < count; ++i) { |
| DAWN_TRY(GetDevice()->ValidateObject(renderBundles[i])); |
| |
| DAWN_INVALID_IF(attachmentState != renderBundles[i]->GetAttachmentState(), |
| "Attachment state of renderBundles[%i] (%s) is not " |
| "compatible with %s.\n" |
| "%s expects an attachment state of %s.\n" |
| "renderBundles[%i] (%s) has an attachment state of %s.", |
| i, renderBundles[i], this, this, attachmentState, i, |
| renderBundles[i], renderBundles[i]->GetAttachmentState()); |
| |
| bool depthReadOnlyInBundle = renderBundles[i]->IsDepthReadOnly(); |
| DAWN_INVALID_IF(depthReadOnlyInPass && !depthReadOnlyInBundle, |
| "DepthReadOnly (%u) of renderBundle[%i] (%s) is not compatible " |
| "with DepthReadOnly (%u) of %s.", |
| depthReadOnlyInBundle, i, renderBundles[i], depthReadOnlyInPass, |
| this); |
| |
| bool stencilReadOnlyInBundle = renderBundles[i]->IsStencilReadOnly(); |
| DAWN_INVALID_IF(stencilReadOnlyInPass && !stencilReadOnlyInBundle, |
| "StencilReadOnly (%u) of renderBundle[%i] (%s) is not " |
| "compatible with StencilReadOnly (%u) of %s.", |
| stencilReadOnlyInBundle, i, renderBundles[i], |
| stencilReadOnlyInPass, this); |
| } |
| } |
| |
| mCommandBufferState = CommandBufferStateTracker{}; |
| |
| ExecuteBundlesCmd* cmd = |
| allocator->Allocate<ExecuteBundlesCmd>(Command::ExecuteBundles); |
| cmd->count = count; |
| |
| Ref<RenderBundleBase>* bundles = allocator->AllocateData<Ref<RenderBundleBase>>(count); |
| for (uint32_t i = 0; i < count; ++i) { |
| bundles[i] = renderBundles[i]; |
| |
| const RenderPassResourceUsage& usages = bundles[i]->GetResourceUsage(); |
| for (uint32_t j = 0; j < usages.buffers.size(); ++j) { |
| mUsageTracker.BufferUsedAs(usages.buffers[j], usages.bufferSyncInfos[j].usage, |
| usages.bufferSyncInfos[j].shaderStages); |
| } |
| |
| for (uint32_t j = 0; j < usages.textures.size(); ++j) { |
| mUsageTracker.AddRenderBundleTextureUsage(usages.textures[j], |
| usages.textureSyncInfos[j]); |
| } |
| |
| if (IsValidationEnabled()) { |
| mIndirectDrawMetadata.AddBundle(renderBundles[i]); |
| } |
| |
| mDrawCount += bundles[i]->GetDrawCount(); |
| } |
| |
| return {}; |
| }, |
| "encoding %s.ExecuteBundles(%u, ...).", this, count); |
| } |
| |
| void RenderPassEncoder::APIBeginOcclusionQuery(uint32_t queryIndex) { |
| mEncodingContext->TryEncode( |
| this, |
| [&](CommandAllocator* allocator) -> MaybeError { |
| if (IsValidationEnabled()) { |
| DAWN_INVALID_IF(mOcclusionQuerySet.Get() == nullptr, |
| "The occlusionQuerySet in RenderPassDescriptor is not set."); |
| |
| // The type of querySet has been validated by ValidateRenderPassDescriptor |
| |
| DAWN_INVALID_IF(queryIndex >= mOcclusionQuerySet->GetQueryCount(), |
| "Query index (%u) exceeds the number of queries (%u) in %s.", |
| queryIndex, mOcclusionQuerySet->GetQueryCount(), |
| mOcclusionQuerySet.Get()); |
| |
| DAWN_INVALID_IF(mOcclusionQueryActive, |
| "An occlusion query (%u) in %s is already active.", |
| mCurrentOcclusionQueryIndex, mOcclusionQuerySet.Get()); |
| |
| DAWN_TRY_CONTEXT( |
| ValidateQueryIndexOverwrite(mOcclusionQuerySet.Get(), queryIndex, |
| mUsageTracker.GetQueryAvailabilityMap()), |
| "validating the occlusion query index (%u) in %s", queryIndex, |
| mOcclusionQuerySet.Get()); |
| } |
| |
| // Record the current query index for endOcclusionQuery. |
| mCurrentOcclusionQueryIndex = queryIndex; |
| mOcclusionQueryActive = true; |
| |
| BeginOcclusionQueryCmd* cmd = |
| allocator->Allocate<BeginOcclusionQueryCmd>(Command::BeginOcclusionQuery); |
| cmd->querySet = mOcclusionQuerySet.Get(); |
| cmd->queryIndex = queryIndex; |
| |
| return {}; |
| }, |
| "encoding %s.BeginOcclusionQuery(%u).", this, queryIndex); |
| } |
| |
| void RenderPassEncoder::APIEndOcclusionQuery() { |
| mEncodingContext->TryEncode( |
| this, |
| [&](CommandAllocator* allocator) -> MaybeError { |
| if (IsValidationEnabled()) { |
| DAWN_INVALID_IF(!mOcclusionQueryActive, "No occlusion queries are active."); |
| } |
| |
| TrackQueryAvailability(mOcclusionQuerySet.Get(), mCurrentOcclusionQueryIndex); |
| |
| mOcclusionQueryActive = false; |
| |
| EndOcclusionQueryCmd* cmd = |
| allocator->Allocate<EndOcclusionQueryCmd>(Command::EndOcclusionQuery); |
| cmd->querySet = mOcclusionQuerySet.Get(); |
| cmd->queryIndex = mCurrentOcclusionQueryIndex; |
| |
| return {}; |
| }, |
| "encoding %s.EndOcclusionQuery().", this); |
| } |
| |
| void RenderPassEncoder::APIWriteTimestamp(QuerySetBase* querySet, uint32_t queryIndex) { |
| mEncodingContext->TryEncode( |
| this, |
| [&](CommandAllocator* allocator) -> MaybeError { |
| if (IsValidationEnabled()) { |
| DAWN_TRY(ValidateTimestampQuery( |
| GetDevice(), querySet, queryIndex, |
| Feature::ChromiumExperimentalTimestampQueryInsidePasses)); |
| DAWN_TRY_CONTEXT(ValidateQueryIndexOverwrite( |
| querySet, queryIndex, mUsageTracker.GetQueryAvailabilityMap()), |
| "validating the timestamp query index (%u) of %s", queryIndex, |
| querySet); |
| } |
| |
| TrackQueryAvailability(querySet, queryIndex); |
| |
| WriteTimestampCmd* cmd = |
| allocator->Allocate<WriteTimestampCmd>(Command::WriteTimestamp); |
| cmd->querySet = querySet; |
| cmd->queryIndex = queryIndex; |
| |
| return {}; |
| }, |
| "encoding %s.WriteTimestamp(%s, %u).", this, querySet, queryIndex); |
| } |
| |
| void RenderPassEncoder::APIPixelLocalStorageBarrier() { |
| mEncodingContext->TryEncode( |
| this, |
| [&](CommandAllocator* allocator) -> MaybeError { |
| if (IsValidationEnabled()) { |
| DAWN_INVALID_IF(!GetAttachmentState()->HasPixelLocalStorage(), |
| "%s does not define any pixel local storage.", this); |
| } |
| |
| allocator->Allocate<PixelLocalStorageBarrierCmd>(Command::PixelLocalStorageBarrier); |
| return {}; |
| }, |
| "encoding %s.PixelLocalStorageBarrier().", this); |
| } |
| |
| } // namespace dawn::native |