text: // 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
  