| // Copyright 2019 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/RenderEncoderBase.h" | |
| #include "common/Constants.h" | |
| #include "common/Log.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/RenderPipeline.h" | |
| #include "dawn_native/ValidationUtils_autogen.h" | |
| #include <math.h> | |
| #include <cstring> | |
| namespace dawn::native { | |
| RenderEncoderBase::RenderEncoderBase(DeviceBase* device, | |
| const char* label, | |
| EncodingContext* encodingContext, | |
| Ref<AttachmentState> attachmentState, | |
| bool depthReadOnly, | |
| bool stencilReadOnly) | |
| : ProgrammableEncoder(device, label, encodingContext), | |
| mIndirectDrawMetadata(device->GetLimits()), | |
| mAttachmentState(std::move(attachmentState)), | |
| mDisableBaseVertex(device->IsToggleEnabled(Toggle::DisableBaseVertex)), | |
| mDisableBaseInstance(device->IsToggleEnabled(Toggle::DisableBaseInstance)) { | |
| mDepthReadOnly = depthReadOnly; | |
| mStencilReadOnly = stencilReadOnly; | |
| } | |
| RenderEncoderBase::RenderEncoderBase(DeviceBase* device, | |
| EncodingContext* encodingContext, | |
| ErrorTag errorTag) | |
| : ProgrammableEncoder(device, encodingContext, errorTag), | |
| mIndirectDrawMetadata(device->GetLimits()), | |
| mDisableBaseVertex(device->IsToggleEnabled(Toggle::DisableBaseVertex)), | |
| mDisableBaseInstance(device->IsToggleEnabled(Toggle::DisableBaseInstance)) { | |
| } | |
| void RenderEncoderBase::DestroyImpl() { | |
| // Remove reference to the attachment state so that we don't have lingering references to | |
| // it preventing it from being uncached in the device. | |
| mAttachmentState = nullptr; | |
| } | |
| const AttachmentState* RenderEncoderBase::GetAttachmentState() const { | |
| ASSERT(!IsError()); | |
| ASSERT(mAttachmentState != nullptr); | |
| return mAttachmentState.Get(); | |
| } | |
| bool RenderEncoderBase::IsDepthReadOnly() const { | |
| ASSERT(!IsError()); | |
| return mDepthReadOnly; | |
| } | |
| bool RenderEncoderBase::IsStencilReadOnly() const { | |
| ASSERT(!IsError()); | |
| return mStencilReadOnly; | |
| } | |
| Ref<AttachmentState> RenderEncoderBase::AcquireAttachmentState() { | |
| return std::move(mAttachmentState); | |
| } | |
| void RenderEncoderBase::APIDraw(uint32_t vertexCount, | |
| uint32_t instanceCount, | |
| uint32_t firstVertex, | |
| uint32_t firstInstance) { | |
| mEncodingContext->TryEncode( | |
| this, | |
| [&](CommandAllocator* allocator) -> MaybeError { | |
| if (IsValidationEnabled()) { | |
| DAWN_TRY(mCommandBufferState.ValidateCanDraw()); | |
| DAWN_INVALID_IF(mDisableBaseInstance && firstInstance != 0, | |
| "First instance (%u) must be zero.", firstInstance); | |
| DAWN_TRY(mCommandBufferState.ValidateBufferInRangeForVertexBuffer(vertexCount, | |
| firstVertex)); | |
| DAWN_TRY(mCommandBufferState.ValidateBufferInRangeForInstanceBuffer( | |
| instanceCount, firstInstance)); | |
| } | |
| DrawCmd* draw = allocator->Allocate<DrawCmd>(Command::Draw); | |
| draw->vertexCount = vertexCount; | |
| draw->instanceCount = instanceCount; | |
| draw->firstVertex = firstVertex; | |
| draw->firstInstance = firstInstance; | |
| return {}; | |
| }, | |
| "encoding %s.Draw(%u, %u, %u, %u).", this, vertexCount, instanceCount, firstVertex, | |
| firstInstance); | |
| } | |
| void RenderEncoderBase::APIDrawIndexed(uint32_t indexCount, | |
| uint32_t instanceCount, | |
| uint32_t firstIndex, | |
| int32_t baseVertex, | |
| uint32_t firstInstance) { | |
| mEncodingContext->TryEncode( | |
| this, | |
| [&](CommandAllocator* allocator) -> MaybeError { | |
| if (IsValidationEnabled()) { | |
| DAWN_TRY(mCommandBufferState.ValidateCanDrawIndexed()); | |
| DAWN_INVALID_IF(mDisableBaseInstance && firstInstance != 0, | |
| "First instance (%u) must be zero.", firstInstance); | |
| DAWN_INVALID_IF(mDisableBaseVertex && baseVertex != 0, | |
| "Base vertex (%u) must be zero.", baseVertex); | |
| DAWN_TRY( | |
| mCommandBufferState.ValidateIndexBufferInRange(indexCount, firstIndex)); | |
| // Although we don't know actual vertex access range in CPU, we still call the | |
| // ValidateBufferInRangeForVertexBuffer in order to deal with those vertex step | |
| // mode vertex buffer with an array stride of zero. | |
| DAWN_TRY(mCommandBufferState.ValidateBufferInRangeForVertexBuffer(0, 0)); | |
| DAWN_TRY(mCommandBufferState.ValidateBufferInRangeForInstanceBuffer( | |
| instanceCount, firstInstance)); | |
| } | |
| DrawIndexedCmd* draw = allocator->Allocate<DrawIndexedCmd>(Command::DrawIndexed); | |
| draw->indexCount = indexCount; | |
| draw->instanceCount = instanceCount; | |
| draw->firstIndex = firstIndex; | |
| draw->baseVertex = baseVertex; | |
| draw->firstInstance = firstInstance; | |
| return {}; | |
| }, | |
| "encoding %s.DrawIndexed(%u, %u, %u, %i, %u).", this, indexCount, instanceCount, | |
| firstIndex, baseVertex, firstInstance); | |
| } | |
| void RenderEncoderBase::APIDrawIndirect(BufferBase* indirectBuffer, uint64_t indirectOffset) { | |
| mEncodingContext->TryEncode( | |
| this, | |
| [&](CommandAllocator* allocator) -> MaybeError { | |
| if (IsValidationEnabled()) { | |
| DAWN_TRY(GetDevice()->ValidateObject(indirectBuffer)); | |
| DAWN_TRY(ValidateCanUseAs(indirectBuffer, wgpu::BufferUsage::Indirect)); | |
| DAWN_TRY(mCommandBufferState.ValidateCanDraw()); | |
| DAWN_INVALID_IF(indirectOffset % 4 != 0, | |
| "Indirect offset (%u) is not a multiple of 4.", indirectOffset); | |
| DAWN_INVALID_IF( | |
| indirectOffset >= indirectBuffer->GetSize() || | |
| kDrawIndirectSize > indirectBuffer->GetSize() - indirectOffset, | |
| "Indirect offset (%u) is out of bounds of indirect buffer %s size (%u).", | |
| indirectOffset, indirectBuffer, indirectBuffer->GetSize()); | |
| } | |
| DrawIndirectCmd* cmd = allocator->Allocate<DrawIndirectCmd>(Command::DrawIndirect); | |
| cmd->indirectBuffer = indirectBuffer; | |
| cmd->indirectOffset = indirectOffset; | |
| mUsageTracker.BufferUsedAs(indirectBuffer, wgpu::BufferUsage::Indirect); | |
| return {}; | |
| }, | |
| "encoding %s.DrawIndirect(%s, %u).", this, indirectBuffer, indirectOffset); | |
| } | |
| void RenderEncoderBase::APIDrawIndexedIndirect(BufferBase* indirectBuffer, | |
| uint64_t indirectOffset) { | |
| mEncodingContext->TryEncode( | |
| this, | |
| [&](CommandAllocator* allocator) -> MaybeError { | |
| if (IsValidationEnabled()) { | |
| DAWN_TRY(GetDevice()->ValidateObject(indirectBuffer)); | |
| DAWN_TRY(ValidateCanUseAs(indirectBuffer, wgpu::BufferUsage::Indirect)); | |
| DAWN_TRY(mCommandBufferState.ValidateCanDrawIndexed()); | |
| DAWN_INVALID_IF(indirectOffset % 4 != 0, | |
| "Indirect offset (%u) is not a multiple of 4.", indirectOffset); | |
| DAWN_INVALID_IF( | |
| (indirectOffset >= indirectBuffer->GetSize() || | |
| kDrawIndexedIndirectSize > indirectBuffer->GetSize() - indirectOffset), | |
| "Indirect offset (%u) is out of bounds of indirect buffer %s size (%u).", | |
| indirectOffset, indirectBuffer, indirectBuffer->GetSize()); | |
| } | |
| DrawIndexedIndirectCmd* cmd = | |
| allocator->Allocate<DrawIndexedIndirectCmd>(Command::DrawIndexedIndirect); | |
| if (IsValidationEnabled()) { | |
| // Later, EncodeIndirectDrawValidationCommands will allocate a scratch storage | |
| // buffer which will store the validated indirect data. The buffer and offset | |
| // will be updated to point to it. | |
| // |EncodeIndirectDrawValidationCommands| is called at the end of encoding the | |
| // render pass, while the |cmd| pointer is still valid. | |
| cmd->indirectBuffer = nullptr; | |
| mIndirectDrawMetadata.AddIndexedIndirectDraw( | |
| mCommandBufferState.GetIndexFormat(), | |
| mCommandBufferState.GetIndexBufferSize(), indirectBuffer, indirectOffset, | |
| cmd); | |
| } else { | |
| cmd->indirectBuffer = indirectBuffer; | |
| cmd->indirectOffset = indirectOffset; | |
| } | |
| // TODO(crbug.com/dawn/1166): Adding the indirectBuffer is needed for correct usage | |
| // validation, but it will unecessarily transition to indirectBuffer usage in the | |
| // backend. | |
| mUsageTracker.BufferUsedAs(indirectBuffer, wgpu::BufferUsage::Indirect); | |
| return {}; | |
| }, | |
| "encoding %s.DrawIndexedIndirect(%s, %u).", this, indirectBuffer, indirectOffset); | |
| } | |
| void RenderEncoderBase::APISetPipeline(RenderPipelineBase* pipeline) { | |
| mEncodingContext->TryEncode( | |
| this, | |
| [&](CommandAllocator* allocator) -> MaybeError { | |
| if (IsValidationEnabled()) { | |
| DAWN_TRY(GetDevice()->ValidateObject(pipeline)); | |
| // TODO(dawn:563): More detail about why the states are incompatible would be | |
| // nice. | |
| DAWN_INVALID_IF( | |
| pipeline->GetAttachmentState() != mAttachmentState.Get(), | |
| "Attachment state of %s is not compatible with the attachment state of %s", | |
| pipeline, this); | |
| DAWN_INVALID_IF(pipeline->WritesDepth() && mDepthReadOnly, | |
| "%s writes depth while %s's depthReadOnly is true", pipeline, | |
| this); | |
| DAWN_INVALID_IF(pipeline->WritesStencil() && mStencilReadOnly, | |
| "%s writes stencil while %s's stencilReadOnly is true", | |
| pipeline, this); | |
| } | |
| mCommandBufferState.SetRenderPipeline(pipeline); | |
| SetRenderPipelineCmd* cmd = | |
| allocator->Allocate<SetRenderPipelineCmd>(Command::SetRenderPipeline); | |
| cmd->pipeline = pipeline; | |
| return {}; | |
| }, | |
| "encoding %s.SetPipeline(%s).", this, pipeline); | |
| } | |
| void RenderEncoderBase::APISetIndexBuffer(BufferBase* buffer, | |
| wgpu::IndexFormat format, | |
| uint64_t offset, | |
| uint64_t size) { | |
| mEncodingContext->TryEncode( | |
| this, | |
| [&](CommandAllocator* allocator) -> MaybeError { | |
| if (IsValidationEnabled()) { | |
| DAWN_TRY(GetDevice()->ValidateObject(buffer)); | |
| DAWN_TRY(ValidateCanUseAs(buffer, wgpu::BufferUsage::Index)); | |
| DAWN_TRY(ValidateIndexFormat(format)); | |
| DAWN_INVALID_IF(format == wgpu::IndexFormat::Undefined, | |
| "Index format must be specified"); | |
| DAWN_INVALID_IF(offset % uint64_t(IndexFormatSize(format)) != 0, | |
| "Index buffer offset (%u) is not a multiple of the size (%u) " | |
| "of %s.", | |
| offset, IndexFormatSize(format), format); | |
| uint64_t bufferSize = buffer->GetSize(); | |
| DAWN_INVALID_IF(offset > bufferSize, | |
| "Index buffer offset (%u) is larger than the size (%u) of %s.", | |
| offset, bufferSize, buffer); | |
| uint64_t remainingSize = bufferSize - offset; | |
| if (size == wgpu::kWholeSize) { | |
| size = remainingSize; | |
| } else { | |
| DAWN_INVALID_IF(size > remainingSize, | |
| "Index buffer range (offset: %u, size: %u) doesn't fit in " | |
| "the size (%u) of " | |
| "%s.", | |
| offset, size, bufferSize, buffer); | |
| } | |
| } else { | |
| if (size == wgpu::kWholeSize) { | |
| DAWN_ASSERT(buffer->GetSize() >= offset); | |
| size = buffer->GetSize() - offset; | |
| } | |
| } | |
| mCommandBufferState.SetIndexBuffer(format, size); | |
| SetIndexBufferCmd* cmd = | |
| allocator->Allocate<SetIndexBufferCmd>(Command::SetIndexBuffer); | |
| cmd->buffer = buffer; | |
| cmd->format = format; | |
| cmd->offset = offset; | |
| cmd->size = size; | |
| mUsageTracker.BufferUsedAs(buffer, wgpu::BufferUsage::Index); | |
| return {}; | |
| }, | |
| "encoding %s.SetIndexBuffer(%s, %s, %u, %u).", this, buffer, format, offset, size); | |
| } | |
| void RenderEncoderBase::APISetVertexBuffer(uint32_t slot, | |
| BufferBase* buffer, | |
| uint64_t offset, | |
| uint64_t size) { | |
| mEncodingContext->TryEncode( | |
| this, | |
| [&](CommandAllocator* allocator) -> MaybeError { | |
| if (IsValidationEnabled()) { | |
| DAWN_TRY(GetDevice()->ValidateObject(buffer)); | |
| DAWN_TRY(ValidateCanUseAs(buffer, wgpu::BufferUsage::Vertex)); | |
| DAWN_INVALID_IF(slot >= kMaxVertexBuffers, | |
| "Vertex buffer slot (%u) is larger the maximum (%u)", slot, | |
| kMaxVertexBuffers - 1); | |
| DAWN_INVALID_IF(offset % 4 != 0, | |
| "Vertex buffer offset (%u) is not a multiple of 4", offset); | |
| uint64_t bufferSize = buffer->GetSize(); | |
| DAWN_INVALID_IF(offset > bufferSize, | |
| "Vertex buffer offset (%u) is larger than the size (%u) of %s.", | |
| offset, bufferSize, buffer); | |
| uint64_t remainingSize = bufferSize - offset; | |
| if (size == wgpu::kWholeSize) { | |
| size = remainingSize; | |
| } else { | |
| DAWN_INVALID_IF(size > remainingSize, | |
| "Vertex buffer range (offset: %u, size: %u) doesn't fit in " | |
| "the size (%u) " | |
| "of %s.", | |
| offset, size, bufferSize, buffer); | |
| } | |
| } else { | |
| if (size == wgpu::kWholeSize) { | |
| DAWN_ASSERT(buffer->GetSize() >= offset); | |
| size = buffer->GetSize() - offset; | |
| } | |
| } | |
| mCommandBufferState.SetVertexBuffer(VertexBufferSlot(uint8_t(slot)), size); | |
| SetVertexBufferCmd* cmd = | |
| allocator->Allocate<SetVertexBufferCmd>(Command::SetVertexBuffer); | |
| cmd->slot = VertexBufferSlot(static_cast<uint8_t>(slot)); | |
| cmd->buffer = buffer; | |
| cmd->offset = offset; | |
| cmd->size = size; | |
| mUsageTracker.BufferUsedAs(buffer, wgpu::BufferUsage::Vertex); | |
| return {}; | |
| }, | |
| "encoding %s.SetVertexBuffer(%u, %s, %u, %u).", this, slot, buffer, offset, size); | |
| } | |
| void RenderEncoderBase::APISetBindGroup(uint32_t groupIndexIn, | |
| BindGroupBase* group, | |
| uint32_t dynamicOffsetCount, | |
| const uint32_t* dynamicOffsets) { | |
| mEncodingContext->TryEncode( | |
| this, | |
| [&](CommandAllocator* allocator) -> MaybeError { | |
| BindGroupIndex groupIndex(groupIndexIn); | |
| if (IsValidationEnabled()) { | |
| DAWN_TRY(ValidateSetBindGroup(groupIndex, group, dynamicOffsetCount, | |
| dynamicOffsets)); | |
| } | |
| RecordSetBindGroup(allocator, groupIndex, group, dynamicOffsetCount, | |
| dynamicOffsets); | |
| mCommandBufferState.SetBindGroup(groupIndex, group, dynamicOffsetCount, | |
| dynamicOffsets); | |
| mUsageTracker.AddBindGroup(group); | |
| return {}; | |
| }, | |
| // TODO(dawn:1190): For unknown reasons formatting this message fails if `group` is used | |
| // as a string value in the message. This despite the exact same code working as | |
| // intended in ComputePassEncoder::APISetBindGroup. Replacing with a static [BindGroup] | |
| // until the reason for the failure can be determined. | |
| "encoding %s.SetBindGroup(%u, [BindGroup], %u, ...).", this, groupIndexIn, | |
| dynamicOffsetCount); | |
| } | |
| } // namespace dawn::native |