| // Copyright 2018 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/RenderPassEncoder.h" |
| |
| #include "common/Constants.h" |
| #include "dawn_native/Buffer.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" |
| |
| #include <math.h> |
| #include <cstring> |
| |
| 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 RenderPassDescriptor* descriptor, |
| CommandEncoder* commandEncoder, |
| EncodingContext* encodingContext, |
| RenderPassResourceUsageTracker usageTracker, |
| Ref<AttachmentState> attachmentState, |
| QuerySetBase* occlusionQuerySet, |
| uint32_t renderTargetWidth, |
| uint32_t renderTargetHeight, |
| bool depthReadOnly, |
| bool stencilReadOnly) |
| : RenderEncoderBase(device, |
| descriptor->label, |
| encodingContext, |
| std::move(attachmentState), |
| depthReadOnly, |
| stencilReadOnly), |
| mCommandEncoder(commandEncoder), |
| mRenderTargetWidth(renderTargetWidth), |
| mRenderTargetHeight(renderTargetHeight), |
| mOcclusionQuerySet(occlusionQuerySet) { |
| mUsageTracker = std::move(usageTracker); |
| TrackInDevice(); |
| } |
| |
| RenderPassEncoder::RenderPassEncoder(DeviceBase* device, |
| CommandEncoder* commandEncoder, |
| EncodingContext* encodingContext, |
| ErrorTag errorTag) |
| : RenderEncoderBase(device, encodingContext, errorTag), mCommandEncoder(commandEncoder) { |
| } |
| |
| RenderPassEncoder* RenderPassEncoder::MakeError(DeviceBase* device, |
| CommandEncoder* commandEncoder, |
| EncodingContext* encodingContext) { |
| return new RenderPassEncoder(device, commandEncoder, encodingContext, ObjectBase::kError); |
| } |
| |
| void RenderPassEncoder::DestroyImpl() { |
| 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::APIEndPass() { |
| if (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()); |
| } |
| |
| allocator->Allocate<EndRenderPassCmd>(Command::EndRenderPass); |
| DAWN_TRY(mEncodingContext->ExitRenderPass(this, std::move(mUsageTracker), |
| mCommandEncoder.Get(), |
| std::move(mIndirectDrawMetadata))); |
| return {}; |
| }, |
| "encoding %s.EndPass().", this)) { |
| } |
| } |
| |
| void RenderPassEncoder::APISetStencilReference(uint32_t reference) { |
| mEncodingContext->TryEncode( |
| this, |
| [&](CommandAllocator* allocator) -> MaybeError { |
| SetStencilReferenceCmd* cmd = |
| allocator->Allocate<SetStencilReferenceCmd>(Command::SetStencilReference); |
| cmd->reference = reference; |
| |
| 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])); |
| |
| // TODO(dawn:563): Give more detail about why the states are incompatible. |
| DAWN_INVALID_IF( |
| attachmentState != renderBundles[i]->GetAttachmentState(), |
| "Attachment state of renderBundles[%i] (%s) is not compatible with " |
| "attachment state of %s.", |
| i, renderBundles[i], this); |
| |
| 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 i = 0; i < usages.buffers.size(); ++i) { |
| mUsageTracker.BufferUsedAs(usages.buffers[i], usages.bufferUsages[i]); |
| } |
| |
| for (uint32_t i = 0; i < usages.textures.size(); ++i) { |
| mUsageTracker.AddRenderBundleTextureUsage(usages.textures[i], |
| usages.textureUsages[i]); |
| } |
| |
| if (IsValidationEnabled()) { |
| mIndirectDrawMetadata.AddBundle(renderBundles[i]); |
| } |
| } |
| |
| 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(GetDevice()->ValidateObject(querySet)); |
| DAWN_TRY(ValidateTimestampQuery(querySet, queryIndex)); |
| 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); |
| } |
| |
| } // namespace dawn_native |