// 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 #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()); if (mDisableBaseInstance && firstInstance != 0) { return DAWN_VALIDATION_ERROR("Non-zero first instance not supported"); } 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 {}; }); } 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()); if (mDisableBaseInstance && firstInstance != 0) { return DAWN_VALIDATION_ERROR("Non-zero first instance not supported"); } if (mDisableBaseVertex && baseVertex != 0) { return DAWN_VALIDATION_ERROR("Non-zero base vertex not supported"); } 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 {}; }); } 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()); if (indirectOffset % 4 != 0) { return DAWN_VALIDATION_ERROR("Indirect offset must be a multiple of 4"); } if (indirectOffset >= indirectBuffer->GetSize() || kDrawIndirectSize > indirectBuffer->GetSize() - indirectOffset) { return DAWN_VALIDATION_ERROR("Indirect offset out of bounds"); } } DrawIndirectCmd* cmd = allocator->Allocate(Command::DrawIndirect); cmd->indirectBuffer = indirectBuffer; cmd->indirectOffset = indirectOffset; mUsageTracker.BufferUsedAs(indirectBuffer, wgpu::BufferUsage::Indirect); return {}; }); } 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()); // Indexed indirect draws need a compute-shader based validation check that the // range of indices is contained inside the index buffer on Metal. Disallow them as // unsafe until the validation is implemented. if (GetDevice()->IsToggleEnabled(Toggle::DisallowUnsafeAPIs)) { return DAWN_VALIDATION_ERROR( "DrawIndexedIndirect is disallowed because it doesn't validate that the " "index " "range is valid yet."); } if (indirectOffset % 4 != 0) { return DAWN_VALIDATION_ERROR("Indirect offset must be a multiple of 4"); } if ((indirectOffset >= indirectBuffer->GetSize() || kDrawIndexedIndirectSize > indirectBuffer->GetSize() - indirectOffset)) { return DAWN_VALIDATION_ERROR("Indirect offset out of bounds"); } } DrawIndexedIndirectCmd* cmd = allocator->Allocate(Command::DrawIndexedIndirect); cmd->indirectBuffer = indirectBuffer; cmd->indirectOffset = indirectOffset; mUsageTracker.BufferUsedAs(indirectBuffer, wgpu::BufferUsage::Indirect); return {}; }); } void RenderEncoderBase::APISetPipeline(RenderPipelineBase* pipeline) { mEncodingContext->TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError { if (IsValidationEnabled()) { DAWN_TRY(GetDevice()->ValidateObject(pipeline)); if (pipeline->GetAttachmentState() != mAttachmentState.Get()) { return DAWN_VALIDATION_ERROR( "Pipeline attachment state is not compatible with render encoder " "attachment state"); } } mCommandBufferState.SetRenderPipeline(pipeline); SetRenderPipelineCmd* cmd = allocator->Allocate(Command::SetRenderPipeline); cmd->pipeline = pipeline; return {}; }); } 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)); if (format == wgpu::IndexFormat::Undefined) { return DAWN_VALIDATION_ERROR("Index format must be specified"); } if (offset % uint64_t(IndexFormatSize(format)) != 0) { return DAWN_VALIDATION_ERROR( "Offset must be a multiple of the index format size"); } uint64_t bufferSize = buffer->GetSize(); if (offset > bufferSize) { return DAWN_VALIDATION_ERROR("Offset larger than the buffer size"); } uint64_t remainingSize = bufferSize - offset; if (size == 0) { size = remainingSize; } else { if (size > remainingSize) { return DAWN_VALIDATION_ERROR("Size + offset larger than the buffer size"); } } } else { if (size == 0) { 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 {}; }); } 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)); if (slot >= kMaxVertexBuffers) { return DAWN_VALIDATION_ERROR("Vertex buffer slot out of bounds"); } if (offset % 4 != 0) { return DAWN_VALIDATION_ERROR("Offset must be a multiple of 4"); } uint64_t bufferSize = buffer->GetSize(); if (offset > bufferSize) { return DAWN_VALIDATION_ERROR("Offset larger than the buffer size"); } uint64_t remainingSize = bufferSize - offset; if (size == 0) { size = remainingSize; } else { if (size > remainingSize) { return DAWN_VALIDATION_ERROR("Size + offset larger than the buffer size"); } } } else { if (size == 0) { 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 {}; }); } 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 {}; }); } } // namespace dawn_native