// 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/BufferLocation.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 #include namespace dawn_native { RenderEncoderBase::RenderEncoderBase(DeviceBase* device, EncodingContext* encodingContext, Ref attachmentState) : ProgrammablePassEncoder(device, encodingContext), mAttachmentState(std::move(attachmentState)), mDisableBaseVertex(device->IsToggleEnabled(Toggle::DisableBaseVertex)), mDisableBaseInstance(device->IsToggleEnabled(Toggle::DisableBaseInstance)) { } RenderEncoderBase::RenderEncoderBase(DeviceBase* device, EncodingContext* encodingContext, ErrorTag errorTag) : ProgrammablePassEncoder(device, encodingContext, errorTag), mDisableBaseVertex(device->IsToggleEnabled(Toggle::DisableBaseVertex)), mDisableBaseInstance(device->IsToggleEnabled(Toggle::DisableBaseInstance)) { } const AttachmentState* RenderEncoderBase::GetAttachmentState() const { ASSERT(!IsError()); ASSERT(mAttachmentState != nullptr); return mAttachmentState.Get(); } Ref 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(Command::Draw); draw->vertexCount = vertexCount; draw->instanceCount = instanceCount; draw->firstVertex = firstVertex; draw->firstInstance = firstInstance; return {}; }, "encoding Draw(%u, %u, %u, %u).", 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(Command::DrawIndexed); draw->indexCount = indexCount; draw->instanceCount = instanceCount; draw->firstIndex = firstIndex; draw->baseVertex = baseVertex; draw->firstInstance = firstInstance; return {}; }, "encoding DrawIndexed(%u, %u, %u, %i, %u).", 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(Command::DrawIndirect); cmd->indirectBuffer = indirectBuffer; cmd->indirectOffset = indirectOffset; mUsageTracker.BufferUsedAs(indirectBuffer, wgpu::BufferUsage::Indirect); return {}; }, "encoding DrawIndirect(%s, %u).", 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()); // Disallow draw indexed indirect until the validation is correctly implemented. if (GetDevice()->IsToggleEnabled(Toggle::DisallowUnsafeAPIs)) { return DAWN_VALIDATION_ERROR( "DrawIndexedIndirect is disallowed because it doesn't correctly " "validate that " "the index range is valid yet."); } 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(Command::DrawIndexedIndirect); if (IsValidationEnabled()) { cmd->indirectBufferLocation = BufferLocation::New(); mIndirectDrawMetadata.AddIndexedIndirectDraw( mCommandBufferState.GetIndexFormat(), mCommandBufferState.GetIndexBufferSize(), indirectBuffer, indirectOffset, cmd->indirectBufferLocation.Get()); } else { cmd->indirectBufferLocation = BufferLocation::New(indirectBuffer, indirectOffset); } mUsageTracker.BufferUsedAs(indirectBuffer, wgpu::BufferUsage::Indirect); return {}; }, "encoding DrawIndexedIndirect(%s, %u).", 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); } mCommandBufferState.SetRenderPipeline(pipeline); SetRenderPipelineCmd* cmd = allocator->Allocate(Command::SetRenderPipeline); cmd->pipeline = pipeline; return {}; }, "encoding SetPipeline(%s).", 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; // Temporarily treat 0 as undefined for size, and give a warning // TODO(dawn:1058): Remove this if block if (size == 0) { size = wgpu::kWholeSize; GetDevice()->EmitDeprecationWarning( "Using size=0 to indicate default binding size for setIndexBuffer " "is deprecated. In the future it will result in a zero-size binding. " "Use `undefined` (wgpu::kWholeSize) or just omit the parameter " "instead."); } 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(Command::SetIndexBuffer); cmd->buffer = buffer; cmd->format = format; cmd->offset = offset; cmd->size = size; mUsageTracker.BufferUsedAs(buffer, wgpu::BufferUsage::Index); return {}; }, "encoding SetIndexBuffer(%s, %s, %u, %u).", 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; // Temporarily treat 0 as undefined for size, and give a warning // TODO(dawn:1058): Remove this if block if (size == 0) { size = wgpu::kWholeSize; GetDevice()->EmitDeprecationWarning( "Using size=0 to indicate default binding size for setVertexBuffer " "is deprecated. In the future it will result in a zero-size binding. " "Use `undefined` (wgpu::kWholeSize) or just omit the parameter " "instead."); } 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(Command::SetVertexBuffer); cmd->slot = VertexBufferSlot(static_cast(slot)); cmd->buffer = buffer; cmd->offset = offset; cmd->size = size; mUsageTracker.BufferUsedAs(buffer, wgpu::BufferUsage::Vertex); return {}; }, "encoding SetVertexBuffer(%u, %s, %u, %u).", 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); mUsageTracker.AddBindGroup(group); return {}; }, "encoding SetBindGroup(%u, %s, %u).", groupIndexIn, group, dynamicOffsetCount); } } // namespace dawn_native