blob: ff31dbd5f0c53a1feb7f5a96cdef4b821088d205 [file] [log] [blame]
// Copyright 2018 The Amber 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 "src/dawn/engine_dawn.h"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <limits>
#include <string>
#include <utility>
#include <vector>
#include "amber/amber_dawn.h"
#include "dawn/dawncpp.h"
#include "src/dawn/pipeline_info.h"
#include "src/format.h"
#include "src/make_unique.h"
#include "src/sleep.h"
namespace amber {
namespace dawn {
namespace {
// The minimum multiple row pitch observed on Dawn on Metal. Increase this
// as needed for other Dawn backends.
static const uint32_t kMinimumImageRowPitch = 256;
static const float kLodMin = 0.0;
static const float kLodMax = 1000.0;
static const uint32_t kMaxColorAttachments = 4u;
static const uint32_t kMaxVertexInputs = 16u;
static const uint32_t kMaxVertexAttributes = 16u;
static const uint32_t kMaxDawnBindGroup = 4u;
// A DS for creating and setting the defaults for VertexInputDescriptor
// Copied from Dawn utils source code.
struct ComboVertexInputDescriptor {
ComboVertexInputDescriptor() {
::dawn::VertexInputDescriptor* descriptor =
reinterpret_cast<::dawn::VertexInputDescriptor*>(this);
descriptor->indexFormat = ::dawn::IndexFormat::Uint32;
descriptor->bufferCount = 0;
descriptor->nextInChain = nullptr;
// Fill the default values for vertexBuffers and vertexAttributes in
// buffers.
::dawn::VertexAttributeDescriptor vertexAttribute;
vertexAttribute.shaderLocation = 0;
vertexAttribute.offset = 0;
vertexAttribute.format = ::dawn::VertexFormat::Float;
for (uint32_t i = 0; i < kMaxVertexAttributes; ++i) {
cAttributes[i] = vertexAttribute;
}
for (uint32_t i = 0; i < kMaxVertexBuffers; ++i) {
cBuffers[i].stride = 0;
cBuffers[i].stepMode = ::dawn::InputStepMode::Vertex;
cBuffers[i].attributeCount = 0;
cBuffers[i].attributes = nullptr;
}
// cBuffers[i].attributes points to somewhere in cAttributes.
// cBuffers[0].attributes points to &cAttributes[0] by default. Assuming
// cBuffers[0] has two attributes, then cBuffers[1].attributes should
// point to &cAttributes[2]. Likewise, if cBuffers[1] has 3 attributes,
// then cBuffers[2].attributes should point to &cAttributes[5].
// In amber-dawn, the vertex input descriptor is always created assuming
// these relationships are one to one i.e. cBuffers[i].attributes is always
// pointing to &cAttributes[i] and cBuffers[i].attributeCount == 1
cBuffers[0].attributes = &cAttributes[0];
descriptor->buffers = &cBuffers[0];
}
static const uint32_t kMaxVertexBuffers = 16u;
static const uint32_t kMaxVertexBufferStride = 2048u;
const void* nextInChain = nullptr;
::dawn::IndexFormat indexFormat;
uint32_t bufferCount;
::dawn::VertexBufferDescriptor const* buffers;
std::array<::dawn::VertexBufferDescriptor, kMaxVertexBuffers> cBuffers;
std::array<::dawn::VertexAttributeDescriptor, kMaxVertexAttributes>
cAttributes;
};
// This structure is a container for a few variables that are created during
// CreateRenderPipelineDescriptor and CreateRenderPassDescriptor and we want to
// make sure they don't go out of scope before we are done with them
struct DawnPipelineHelper {
Result CreateRenderPipelineDescriptor(
const RenderPipelineInfo& render_pipeline,
const ::dawn::Device& device,
const bool ignore_vertex_and_Index_buffers,
const PipelineData* pipeline_data);
Result CreateRenderPassDescriptor(
const RenderPipelineInfo& render_pipeline,
const ::dawn::Device& device,
const std::vector<::dawn::TextureView>& texture_view,
const ::dawn::LoadOp load_op);
::dawn::RenderPipelineDescriptor renderPipelineDescriptor;
::dawn::RenderPassDescriptor renderPassDescriptor;
ComboVertexInputDescriptor vertexInputDescriptor;
::dawn::RasterizationStateDescriptor rasterizationState;
private:
::dawn::PipelineStageDescriptor fragmentStage;
::dawn::PipelineStageDescriptor vertexStage;
::dawn::RenderPassColorAttachmentDescriptor*
colorAttachmentsInfoPtr[kMaxColorAttachments];
::dawn::RenderPassDepthStencilAttachmentDescriptor depthStencilAttachmentInfo;
std::array<::dawn::ColorStateDescriptor*, kMaxColorAttachments> colorStates;
::dawn::DepthStencilStateDescriptor depthStencilState;
::dawn::ColorStateDescriptor colorStatesDescriptor[kMaxColorAttachments];
::dawn::ColorStateDescriptor colorStateDescriptor;
::dawn::StencilStateFaceDescriptor stencil_front;
::dawn::StencilStateFaceDescriptor stencil_back;
::dawn::BlendDescriptor alpha_blend;
::dawn::BlendDescriptor color_blend;
std::string vertexEntryPoint;
std::string fragmentEntryPoint;
std::array<::dawn::RenderPassColorAttachmentDescriptor, kMaxColorAttachments>
colorAttachmentsInfo;
::dawn::TextureDescriptor depthStencilDescriptor;
::dawn::Texture depthStencilTexture;
::dawn::TextureView depthStencilView;
};
// Creates a device-side texture, and returns it through |result_ptr|.
// Assumes the device exists and is valid. Assumes result_ptr is not null.
// Returns a result code.
Result MakeTexture(const ::dawn::Device& device,
::dawn::TextureFormat format,
uint32_t width,
uint32_t height,
::dawn::Texture* result_ptr) {
assert(device);
assert(result_ptr);
assert(width * height > 0);
::dawn::TextureDescriptor descriptor;
descriptor.dimension = ::dawn::TextureDimension::e2D;
descriptor.size.width = width;
descriptor.size.height = height;
descriptor.size.depth = 1;
descriptor.arrayLayerCount = 1;
descriptor.format = format;
descriptor.mipLevelCount = 1;
descriptor.sampleCount = 1;
descriptor.usage =
::dawn::TextureUsage::CopySrc | ::dawn::TextureUsage::OutputAttachment;
// TODO(dneto): Get a better message by using the Dawn error callback.
*result_ptr = device.CreateTexture(&descriptor);
if (*result_ptr)
return {};
return Result("Dawn: Failed to allocate a framebuffer texture");
}
// Creates a device-side texture, and returns it through |result_ptr|.
// Assumes the device exists and is valid. Assumes result_ptr is not null.
// Returns a result code.
::dawn::Texture MakeDawnTexture(const ::dawn::Device& device,
::dawn::TextureFormat format,
uint32_t width,
uint32_t height) {
assert(device);
assert(width * height > 0);
::dawn::TextureDescriptor descriptor;
descriptor.dimension = ::dawn::TextureDimension::e2D;
descriptor.size.width = width;
descriptor.size.height = height;
descriptor.size.depth = 1;
descriptor.arrayLayerCount = 1;
descriptor.format = format;
descriptor.mipLevelCount = 1;
descriptor.sampleCount = 1;
descriptor.usage =
::dawn::TextureUsage::CopySrc | ::dawn::TextureUsage::OutputAttachment;
return device.CreateTexture(&descriptor);
}
// Result status object and data pointer resulting from a buffer mapping.
struct MapResult {
Result result;
const void* data = nullptr;
uint64_t dataLength = 0;
};
// Handles the update from an asynchronous buffer map request, updating the
// state of the MapResult object hidden inside the |userdata| parameter.
// On a successful mapping outcome, set the data pointer in the map result.
// Otherwise set the map result object to an error, and the data member is
// not changed.
void HandleBufferMapCallback(DawnBufferMapAsyncStatus status,
const void* data,
uint64_t dataLength,
void* userdata) {
MapResult& map_result = *reinterpret_cast<MapResult*>(userdata);
switch (status) {
case DAWN_BUFFER_MAP_ASYNC_STATUS_SUCCESS:
map_result.data = data;
map_result.dataLength = dataLength;
break;
case DAWN_BUFFER_MAP_ASYNC_STATUS_ERROR:
map_result.result = Result("Buffer map for reading failed: error");
break;
case DAWN_BUFFER_MAP_ASYNC_STATUS_UNKNOWN:
case DAWN_BUFFER_MAP_ASYNC_STATUS_FORCE32:
map_result.result = Result("Buffer map for reading failed: unknown");
break;
case DAWN_BUFFER_MAP_ASYNC_STATUS_DEVICE_LOST:
map_result.result = Result("Buffer map for reading failed: device lost");
break;
}
}
// Returns |value| but rounded up to a multiple of |alignment|. |alignment| is
// assumed to be a power of 2.
uint32_t Align(uint32_t value, size_t alignment) {
assert(alignment <= std::numeric_limits<uint32_t>::max());
assert(alignment != 0);
uint32_t alignment32 = static_cast<uint32_t>(alignment);
return (value + (alignment32 - 1)) & ~(alignment32 - 1);
}
} // namespace
// Maps the given buffer. Assumes the buffer has usage bit
// ::dawn::BufferUsage::MapRead set. Returns a MapResult structure, with
// the status saved in the |result| member and the host pointer to the mapped
// data in the |data| member. Mapping a buffer can fail if the context is
// lost, for example. In the failure case, the |data| member will be null.
MapResult MapBuffer(const ::dawn::Device& device, const ::dawn::Buffer& buf) {
MapResult map_result;
buf.MapReadAsync(
HandleBufferMapCallback,
reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(&map_result)));
device.Tick();
// Wait until the callback has been processed. Use an exponential backoff
// interval, but cap it at one second intervals. But never loop forever.
const int max_iters = 100;
const int one_second_in_us = 1000000;
for (int iters = 0, interval = 1;
!map_result.data && map_result.result.IsSuccess();
iters++, interval = std::min(2 * interval, one_second_in_us)) {
device.Tick();
if (iters > max_iters) {
map_result.result = Result("MapBuffer timed out after 100 iterations");
break;
}
USleep(uint32_t(interval));
}
return map_result;
}
// Creates and returns a dawn BufferCopyView
// Copied from Dawn utils source code.
::dawn::BufferCopyView CreateBufferCopyView(::dawn::Buffer buffer,
uint64_t offset,
uint32_t rowPitch,
uint32_t imageHeight) {
::dawn::BufferCopyView bufferCopyView;
bufferCopyView.buffer = buffer;
bufferCopyView.offset = offset;
bufferCopyView.rowPitch = rowPitch;
bufferCopyView.imageHeight = imageHeight;
return bufferCopyView;
}
// Creates and returns a dawn TextureCopyView
// Copied from Dawn utils source code.
::dawn::TextureCopyView CreateTextureCopyView(::dawn::Texture texture,
uint32_t mipLevel,
uint32_t arrayLayer,
::dawn::Origin3D origin) {
::dawn::TextureCopyView textureCopyView;
textureCopyView.texture = texture;
textureCopyView.mipLevel = mipLevel;
textureCopyView.arrayLayer = arrayLayer;
textureCopyView.origin = origin;
return textureCopyView;
}
Result EngineDawn::MapDeviceTextureToHostBuffer(
const RenderPipelineInfo& render_pipeline,
const ::dawn::Device& device) {
const auto width = render_pipeline.pipeline->GetFramebufferWidth();
const auto height = render_pipeline.pipeline->GetFramebufferHeight();
const auto pixelSize = render_pipeline.pipeline->GetColorAttachments()[0]
.buffer->GetElementStride();
const auto dawn_row_pitch = Align(width * pixelSize, kMinimumImageRowPitch);
const auto size = height * dawn_row_pitch;
// Create a temporary buffer to hold the color attachment content and can
// be mapped
::dawn::BufferDescriptor descriptor;
descriptor.size = size;
descriptor.usage =
::dawn::BufferUsage::CopyDst | ::dawn::BufferUsage::MapRead;
::dawn::Buffer copy_buffer = device.CreateBuffer(&descriptor);
::dawn::BufferCopyView copy_buffer_view =
CreateBufferCopyView(copy_buffer, 0, dawn_row_pitch, 0);
::dawn::Origin3D origin3D;
origin3D.x = 0;
origin3D.y = 0;
origin3D.z = 0;
for (uint32_t i = 0;
i < render_pipeline.pipeline->GetColorAttachments().size(); i++) {
::dawn::TextureCopyView device_texture_view =
CreateTextureCopyView(textures_[i], 0, 0, origin3D);
::dawn::Extent3D copySize = {width, height, 1};
auto encoder = device.CreateCommandEncoder();
encoder.CopyTextureToBuffer(&device_texture_view, &copy_buffer_view,
&copySize);
auto commands = encoder.Finish();
auto queue = device.CreateQueue();
queue.Submit(1, &commands);
MapResult mapped_device_texture = MapBuffer(device, copy_buffer);
if (!mapped_device_texture.result.IsSuccess())
return mapped_device_texture.result;
auto& host_texture = render_pipeline.pipeline->GetColorAttachments()[i];
auto* values = host_texture.buffer->ValuePtr();
auto row_stride = pixelSize * width;
assert(row_stride * height == host_texture.buffer->GetSizeInBytes());
// Each Dawn row has enough data to fill the target row.
assert(dawn_row_pitch >= row_stride);
values->resize(host_texture.buffer->GetSizeInBytes());
// Copy the framebuffer contents back into the host-side
// framebuffer-buffer. In the Dawn buffer, the row stride is a multiple of
// kMinimumImageRowPitch bytes, so it might have padding therefore memcpy
// is done row by row.
for (uint h = 0; h < height; h++) {
std::memcpy(values->data() + h * row_stride,
static_cast<const uint8_t*>(mapped_device_texture.data) +
h * dawn_row_pitch,
row_stride);
}
// Always unmap the buffer at the end of the engine's command.
copy_buffer.Unmap();
}
return {};
}
Result EngineDawn::MapDeviceBufferToHostBuffer(
const ComputePipelineInfo& compute_pipeline,
const ::dawn::Device& device) {
for (uint32_t i = 0; i < compute_pipeline.pipeline->GetBuffers().size();
i++) {
auto& device_buffer = compute_pipeline.buffers[i];
auto& host_buffer = compute_pipeline.pipeline->GetBuffers()[i];
// Create a copy of device buffer to use it in a map read operation.
// It's not possible to simply set this bit on the existing buffers since:
// Device error: Only CopyDst is allowed with MapRead
::dawn::BufferDescriptor descriptor;
descriptor.size = host_buffer.buffer->GetSizeInBytes();
descriptor.usage =
::dawn::BufferUsage::CopyDst | ::dawn::BufferUsage::MapRead;
const auto copy_device_buffer = device.CreateBuffer(&descriptor);
const uint64_t source_offset = 0;
const uint64_t destination_offset = 0;
const uint64_t copy_size =
static_cast<uint64_t>(host_buffer.buffer->GetSizeInBytes());
auto encoder = device.CreateCommandEncoder();
encoder.CopyBufferToBuffer(device_buffer, source_offset, copy_device_buffer,
destination_offset, copy_size);
auto commands = encoder.Finish();
auto queue = device.CreateQueue();
queue.Submit(1, &commands);
MapResult mapped_device_buffer = MapBuffer(device, copy_device_buffer);
auto* values = host_buffer.buffer->ValuePtr();
values->resize(host_buffer.buffer->GetSizeInBytes());
std::memcpy(values->data(),
static_cast<const uint8_t*>(mapped_device_buffer.data),
copy_size);
copy_device_buffer.Unmap();
if (!mapped_device_buffer.result.IsSuccess())
return mapped_device_buffer.result;
}
return {};
}
// Creates a dawn buffer of |size| bytes with TransferDst and the given usage
// copied from Dawn utils source code
::dawn::Buffer CreateBufferFromData(const ::dawn::Device& device,
const void* data,
uint64_t size,
::dawn::BufferUsage usage) {
::dawn::BufferDescriptor descriptor;
descriptor.size = size;
descriptor.usage = usage | ::dawn::BufferUsage::CopyDst;
::dawn::Buffer buffer = device.CreateBuffer(&descriptor);
if (data != nullptr)
buffer.SetSubData(0, size, reinterpret_cast<const uint8_t*>(data));
return buffer;
}
// Creates a bind group.
// Helpers to make creating bind groups look nicer:
//
// utils::MakeBindGroup(device, layout, {
// {0, mySampler},
// {1, myBuffer, offset, size},
// {3, myTexture}
// });
// Structure with one constructor per-type of bindings, so that the
// initializer_list accepts bindings with the right type and no extra
// information.
struct BindingInitializationHelper {
BindingInitializationHelper(uint32_t binding,
const ::dawn::Buffer& buffer,
uint64_t offset,
uint64_t size)
: binding(binding), buffer(buffer), offset(offset), size(size) {}
::dawn::BindGroupBinding GetAsBinding() const {
::dawn::BindGroupBinding result;
result.binding = binding;
result.sampler = sampler;
result.textureView = textureView;
result.buffer = buffer;
result.offset = offset;
result.size = size;
return result;
}
uint32_t binding;
::dawn::Sampler sampler;
::dawn::TextureView textureView;
::dawn::Buffer buffer;
uint64_t offset = 0;
uint64_t size = 0;
};
::dawn::BindGroup MakeBindGroup(
const ::dawn::Device& device,
const ::dawn::BindGroupLayout& layout,
const std::vector<BindingInitializationHelper>& bindingsInitializer) {
std::vector<::dawn::BindGroupBinding> bindings;
for (const BindingInitializationHelper& helper : bindingsInitializer) {
bindings.push_back(helper.GetAsBinding());
}
::dawn::BindGroupDescriptor descriptor;
descriptor.layout = layout;
descriptor.bindingCount = bindings.size();
descriptor.bindings = bindings.data();
return device.CreateBindGroup(&descriptor);
}
// Creates a bind group layout.
// Copied from Dawn utils source code.
::dawn::BindGroupLayout MakeBindGroupLayout(
const ::dawn::Device& device,
const std::vector<::dawn::BindGroupLayoutBinding>& bindingsInitializer) {
constexpr ::dawn::ShaderStage kNoStages{};
std::vector<::dawn::BindGroupLayoutBinding> bindings;
for (const ::dawn::BindGroupLayoutBinding& binding : bindingsInitializer) {
if (binding.visibility != kNoStages) {
bindings.push_back(binding);
}
}
::dawn::BindGroupLayoutDescriptor descriptor;
descriptor.bindingCount = static_cast<uint32_t>(bindings.size());
descriptor.bindings = bindings.data();
return device.CreateBindGroupLayout(&descriptor);
}
// Creates a basic pipeline layout.
// Copied from Dawn utils source code.
::dawn::PipelineLayout MakeBasicPipelineLayout(
const ::dawn::Device& device,
std::vector<::dawn::BindGroupLayout> bindingInitializer) {
::dawn::PipelineLayoutDescriptor descriptor;
descriptor.bindGroupLayoutCount = bindingInitializer.size();
descriptor.bindGroupLayouts = bindingInitializer.data();
return device.CreatePipelineLayout(&descriptor);
}
// Converts an Amber format to a Dawn texture format, and sends the result out
// through |dawn_format_ptr|. If the conversion fails, return an error
// result.
Result GetDawnTextureFormat(const ::amber::Format& amber_format,
::dawn::TextureFormat* dawn_format_ptr) {
if (!dawn_format_ptr)
return Result("Internal error: format pointer argument is null");
::dawn::TextureFormat& dawn_format = *dawn_format_ptr;
switch (amber_format.GetFormatType()) {
// TODO(dneto): These are all the formats that Dawn currently knows about.
case FormatType::kR8G8B8A8_UNORM:
dawn_format = ::dawn::TextureFormat::RGBA8Unorm;
break;
case FormatType::kR8G8_UNORM:
dawn_format = ::dawn::TextureFormat::RG8Unorm;
break;
case FormatType::kR8_UNORM:
dawn_format = ::dawn::TextureFormat::R8Unorm;
break;
case FormatType::kR8G8B8A8_UINT:
dawn_format = ::dawn::TextureFormat::RGBA8Uint;
break;
case FormatType::kR8G8_UINT:
dawn_format = ::dawn::TextureFormat::RG8Uint;
break;
case FormatType::kR8_UINT:
dawn_format = ::dawn::TextureFormat::R8Uint;
break;
case FormatType::kB8G8R8A8_UNORM:
dawn_format = ::dawn::TextureFormat::BGRA8Unorm;
break;
case FormatType::kD32_SFLOAT_S8_UINT:
dawn_format = ::dawn::TextureFormat::Depth24PlusStencil8;
break;
default:
return Result(
"Amber format " +
std::to_string(static_cast<uint32_t>(amber_format.GetFormatType())) +
" is invalid for Dawn");
}
return {};
}
// Converts an Amber format to a Dawn Vertex format, and sends the result out
// through |dawn_format_ptr|. If the conversion fails, return an error
// result.
// TODO(sarahM0): support other ::dawn::VertexFormat
Result GetDawnVertexFormat(const ::amber::Format& amber_format,
::dawn::VertexFormat* dawn_format_ptr) {
::dawn::VertexFormat& dawn_format = *dawn_format_ptr;
switch (amber_format.GetFormatType()) {
case FormatType::kR32_SFLOAT:
dawn_format = ::dawn::VertexFormat::Float;
break;
case FormatType::kR32G32_SFLOAT:
dawn_format = ::dawn::VertexFormat::Float2;
break;
case FormatType::kR32G32B32_SFLOAT:
dawn_format = ::dawn::VertexFormat::Float3;
break;
case FormatType::kR32G32B32A32_SFLOAT:
dawn_format = ::dawn::VertexFormat::Float4;
break;
case FormatType::kR8G8_SNORM:
dawn_format = ::dawn::VertexFormat::Char2Norm;
break;
case FormatType::kR8G8B8A8_UNORM:
dawn_format = ::dawn::VertexFormat::UChar4Norm;
break;
case FormatType::kR8G8B8A8_SNORM:
dawn_format = ::dawn::VertexFormat::Char4Norm;
break;
default:
return Result(
"Amber vertex format " +
std::to_string(static_cast<uint32_t>(amber_format.GetFormatType())) +
" is invalid for Dawn or is not supported in amber-dawn");
}
return {};
}
// Converts an Amber format to a Dawn Index format, and sends the result out
// through |dawn_format_ptr|. If the conversion fails, return an error
// result.
Result GetDawnIndexFormat(const ::amber::Format& amber_format,
::dawn::IndexFormat* dawn_format_ptr) {
::dawn::IndexFormat& dawn_format = *dawn_format_ptr;
switch (amber_format.GetFormatType()) {
case FormatType::kR16_UINT:
dawn_format = ::dawn::IndexFormat::Uint16;
break;
case FormatType::kR32_UINT:
dawn_format = ::dawn::IndexFormat::Uint32;
break;
default:
return Result(
"Amber index format " +
std::to_string(static_cast<uint32_t>(amber_format.GetFormatType())) +
" is invalid for Dawn");
}
return {};
}
// Converts an Amber topology to a Dawn topology, and sends the result out
// through |dawn_topology_ptr|. It the conversion fails, return an error result.
Result GetDawnTopology(const ::amber::Topology& amber_topology,
::dawn::PrimitiveTopology* dawn_topology_ptr) {
::dawn::PrimitiveTopology& dawn_topology = *dawn_topology_ptr;
switch (amber_topology) {
case Topology::kPointList:
dawn_topology = ::dawn::PrimitiveTopology::PointList;
break;
case Topology::kLineList:
dawn_topology = ::dawn::PrimitiveTopology::LineList;
break;
case Topology::kLineStrip:
dawn_topology = ::dawn::PrimitiveTopology::LineStrip;
break;
case Topology::kTriangleList:
dawn_topology = ::dawn::PrimitiveTopology::TriangleList;
break;
case Topology::kTriangleStrip:
dawn_topology = ::dawn::PrimitiveTopology::TriangleStrip;
break;
default:
return Result("Amber PrimitiveTopology " +
std::to_string(static_cast<uint32_t>(amber_topology)) +
" is not supported in Dawn");
}
return {};
}
::dawn::CompareFunction GetDawnCompareOp(::amber::CompareOp op) {
switch (op) {
case CompareOp::kNever:
return ::dawn::CompareFunction::Never;
case CompareOp::kLess:
return ::dawn::CompareFunction::Less;
case CompareOp::kEqual:
return ::dawn::CompareFunction::Equal;
case CompareOp::kLessOrEqual:
return ::dawn::CompareFunction::LessEqual;
case CompareOp::kGreater:
return ::dawn::CompareFunction::Greater;
case CompareOp::kNotEqual:
return ::dawn::CompareFunction::NotEqual;
case CompareOp::kGreaterOrEqual:
return ::dawn::CompareFunction::GreaterEqual;
case CompareOp::kAlways:
return ::dawn::CompareFunction::Always;
default:
return ::dawn::CompareFunction::Never;
}
}
::dawn::StencilOperation GetDawnStencilOp(::amber::StencilOp op) {
switch (op) {
case StencilOp::kKeep:
return ::dawn::StencilOperation::Keep;
case StencilOp::kZero:
return ::dawn::StencilOperation::Zero;
case StencilOp::kReplace:
return ::dawn::StencilOperation::Replace;
case StencilOp::kIncrementAndClamp:
return ::dawn::StencilOperation::IncrementClamp;
case StencilOp::kDecrementAndClamp:
return ::dawn::StencilOperation::DecrementClamp;
case StencilOp::kInvert:
return ::dawn::StencilOperation::Invert;
case StencilOp::kIncrementAndWrap:
return ::dawn::StencilOperation::IncrementWrap;
case StencilOp::kDecrementAndWrap:
return ::dawn::StencilOperation::DecrementWrap;
default:
return ::dawn::StencilOperation::Keep;
}
}
::dawn::BlendFactor GetDawnBlendFactor(::amber::BlendFactor factor) {
switch (factor) {
case BlendFactor::kZero:
return ::dawn::BlendFactor::Zero;
case BlendFactor::kOne:
return ::dawn::BlendFactor::One;
case BlendFactor::kSrcColor:
return ::dawn::BlendFactor::SrcColor;
case BlendFactor::kOneMinusSrcColor:
return ::dawn::BlendFactor::OneMinusSrcColor;
case BlendFactor::kDstColor:
return ::dawn::BlendFactor::DstColor;
case BlendFactor::kOneMinusDstColor:
return ::dawn::BlendFactor::OneMinusDstColor;
case BlendFactor::kSrcAlpha:
return ::dawn::BlendFactor::SrcAlpha;
case BlendFactor::kOneMinusSrcAlpha:
return ::dawn::BlendFactor::OneMinusSrcAlpha;
case BlendFactor::kDstAlpha:
return ::dawn::BlendFactor::DstAlpha;
case BlendFactor::kOneMinusDstAlpha:
return ::dawn::BlendFactor::OneMinusDstAlpha;
case BlendFactor::kSrcAlphaSaturate:
return ::dawn::BlendFactor::SrcAlphaSaturated;
default:
assert(false && "Dawn::Unknown BlendFactor");
return ::dawn::BlendFactor::One;
}
}
::dawn::BlendOperation GetDawnBlendOperation(BlendOp op) {
switch (op) {
case BlendOp::kAdd:
return ::dawn::BlendOperation::Add;
case BlendOp::kSubtract:
return ::dawn::BlendOperation::Subtract;
case BlendOp::kReverseSubtract:
return ::dawn::BlendOperation::ReverseSubtract;
case BlendOp::kMin:
return ::dawn::BlendOperation::Min;
case BlendOp::kMax:
return ::dawn::BlendOperation::Max;
default:
assert(false && "Dawn::Unknown BlendOp");
return ::dawn::BlendOperation::Add;
}
}
::dawn::ColorWriteMask GetDawnColorWriteMask(uint8_t amber_color_write_mask) {
if (amber_color_write_mask == 0x00000000)
return ::dawn::ColorWriteMask::None;
else if (amber_color_write_mask == 0x00000001)
return ::dawn::ColorWriteMask::Red;
else if (amber_color_write_mask == 0x00000002)
return ::dawn::ColorWriteMask::Green;
else if (amber_color_write_mask == 0x00000004)
return ::dawn::ColorWriteMask::Blue;
else if (amber_color_write_mask == 0x00000008)
return ::dawn::ColorWriteMask::Alpha;
else if (amber_color_write_mask == 0x0000000F)
return ::dawn::ColorWriteMask::All;
else
assert(false && "Dawn::Unknown ColorWriteMask");
return ::dawn::ColorWriteMask::All;
}
::dawn::FrontFace GetDawnFrontFace(FrontFace amber_front_face) {
return amber_front_face == FrontFace::kClockwise ? ::dawn::FrontFace::CW
: ::dawn::FrontFace::CCW;
}
::dawn::CullMode GetDawnCullMode(CullMode amber_cull_mode) {
switch (amber_cull_mode) {
case CullMode::kNone:
return ::dawn::CullMode::None;
case CullMode::kFront:
return ::dawn::CullMode::Front;
case CullMode::kBack:
return ::dawn::CullMode::Back;
default:
assert(false && "Dawn::Unknown CullMode");
return ::dawn::CullMode::None;
}
}
EngineDawn::EngineDawn() : Engine() {}
EngineDawn::~EngineDawn() = default;
Result EngineDawn::Initialize(EngineConfig* config,
Delegate*,
const std::vector<std::string>&,
const std::vector<std::string>&,
const std::vector<std::string>&) {
if (device_)
return Result("Dawn:Initialize device_ already exists");
if (!config)
return Result("Dawn::Initialize config is null");
DawnEngineConfig* dawn_config = static_cast<DawnEngineConfig*>(config);
if (dawn_config->device == nullptr)
return Result("Dawn:Initialize device is a null pointer");
device_ = dawn_config->device;
return {};
}
Result EngineDawn::CreatePipeline(::amber::Pipeline* pipeline) {
if (!device_) {
return Result("Dawn::CreatePipeline: device is not created");
}
std::unordered_map<ShaderType, ::dawn::ShaderModule, CastHash<ShaderType>>
module_for_type;
::dawn::ShaderModuleDescriptor descriptor;
descriptor.nextInChain = nullptr;
for (const auto& shader_info : pipeline->GetShaders()) {
ShaderType type = shader_info.GetShaderType();
const std::vector<uint32_t>& code = shader_info.GetData();
descriptor.code = code.data();
descriptor.codeSize = uint32_t(code.size());
auto shader = device_->CreateShaderModule(&descriptor);
if (!shader) {
return Result("Dawn::CreatePipeline: failed to create shader");
}
if (module_for_type.count(type)) {
return Result("Dawn::CreatePipeline: module for type already exists");
}
module_for_type[type] = shader;
}
switch (pipeline->GetType()) {
case PipelineType::kCompute: {
auto& module = module_for_type[kShaderTypeCompute];
if (!module)
return Result("Dawn::CreatePipeline: no compute shader provided");
pipeline_map_[pipeline].compute_pipeline.reset(
new ComputePipelineInfo(pipeline, module));
Result result =
AttachBuffers(pipeline_map_[pipeline].compute_pipeline.get());
if (!result.IsSuccess())
return result;
break;
}
case PipelineType::kGraphics: {
// TODO(dneto): Handle other shader types as well. They are optional.
auto& vs = module_for_type[kShaderTypeVertex];
auto& fs = module_for_type[kShaderTypeFragment];
if (!vs) {
return Result(
"Dawn::CreatePipeline: no vertex shader provided for graphics "
"pipeline");
}
if (!fs) {
return Result(
"Dawn::CreatePipeline: no fragment shader provided for graphics "
"pipeline");
}
pipeline_map_[pipeline].render_pipeline.reset(
new RenderPipelineInfo(pipeline, vs, fs));
Result result = AttachBuffersAndTextures(
pipeline_map_[pipeline].render_pipeline.get());
if (!result.IsSuccess())
return result;
break;
}
}
return {};
}
Result EngineDawn::DoClearColor(const ClearColorCommand* command) {
RenderPipelineInfo* render_pipeline = GetRenderPipeline(command);
if (!render_pipeline)
return Result("ClearColor invoked on invalid or missing render pipeline");
render_pipeline->clear_color_value = ::dawn::Color{
command->GetR(), command->GetG(), command->GetB(), command->GetA()};
return {};
}
Result EngineDawn::DoClearStencil(const ClearStencilCommand* command) {
RenderPipelineInfo* render_pipeline = GetRenderPipeline(command);
if (!render_pipeline)
return Result("ClearStencil invoked on invalid or missing render pipeline");
render_pipeline->clear_stencil_value = command->GetValue();
return {};
}
Result EngineDawn::DoClearDepth(const ClearDepthCommand* command) {
RenderPipelineInfo* render_pipeline = GetRenderPipeline(command);
if (!render_pipeline)
return Result("ClearDepth invoked on invalid or missing render pipeline");
render_pipeline->clear_depth_value = command->GetValue();
return {};
}
Result EngineDawn::DoClear(const ClearCommand* command) {
Result result;
RenderPipelineInfo* render_pipeline = GetRenderPipeline(command);
if (!render_pipeline)
return Result("Clear invoked on invalid or missing render pipeline");
DawnPipelineHelper helper;
result = helper.CreateRenderPipelineDescriptor(*render_pipeline, *device_,
false, nullptr);
if (!result.IsSuccess())
return result;
result = helper.CreateRenderPassDescriptor(
*render_pipeline, *device_, texture_views_, ::dawn::LoadOp::Clear);
if (!result.IsSuccess())
return result;
::dawn::RenderPassDescriptor* renderPassDescriptor =
&helper.renderPassDescriptor;
::dawn::CommandEncoder encoder = device_->CreateCommandEncoder();
::dawn::RenderPassEncoder pass =
encoder.BeginRenderPass(renderPassDescriptor);
pass.EndPass();
::dawn::CommandBuffer commands = encoder.Finish();
::dawn::Queue queue = device_->CreateQueue();
queue.Submit(1, &commands);
result = MapDeviceTextureToHostBuffer(*render_pipeline, *device_);
return result;
}
// Creates a Dawn render pipeline descriptor for the given pipeline on the given
// device. When |ignore_vertex_and_Index_buffers| is true, ignores the vertex
// and index buffers attached to |render_pipeline| and instead configures the
// resulting descriptor to have a single vertex buffer with an attribute format
// of Float4 and input stride of 4*sizeof(float)
Result DawnPipelineHelper::CreateRenderPipelineDescriptor(
const RenderPipelineInfo& render_pipeline,
const ::dawn::Device& device,
const bool ignore_vertex_and_Index_buffers,
const PipelineData* pipeline_data) {
Result result;
auto* amber_format =
render_pipeline.pipeline->GetColorAttachments()[0].buffer->GetFormat();
if (!amber_format)
return Result("Color attachment 0 has no format!");
::dawn::TextureFormat fb_format{};
result = GetDawnTextureFormat(*amber_format, &fb_format);
if (!result.IsSuccess())
return result;
::dawn::TextureFormat depth_stencil_format{};
auto* depthBuffer = render_pipeline.pipeline->GetDepthBuffer().buffer;
if (depthBuffer) {
auto* amber_depth_stencil_format = depthBuffer->GetFormat();
if (!amber_depth_stencil_format)
return Result("The depth/stencil attachment has no format!");
result = GetDawnTextureFormat(*amber_depth_stencil_format,
&depth_stencil_format);
if (!result.IsSuccess())
return result;
} else {
depth_stencil_format = ::dawn::TextureFormat::Depth24PlusStencil8;
}
renderPipelineDescriptor.layout =
MakeBasicPipelineLayout(device, render_pipeline.bind_group_layouts);
renderPipelineDescriptor.primitiveTopology =
::dawn::PrimitiveTopology::TriangleList;
renderPipelineDescriptor.sampleCount = 1;
// Lookup shaders' entrypoints
for (const auto& shader_info : render_pipeline.pipeline->GetShaders()) {
if (shader_info.GetShaderType() == kShaderTypeVertex) {
vertexEntryPoint = shader_info.GetEntryPoint();
} else if (shader_info.GetShaderType() == kShaderTypeFragment) {
fragmentEntryPoint = shader_info.GetEntryPoint();
} else {
return Result(
"CreateRenderPipelineDescriptor: An unknown shader is attached to "
"the render pipeline");
}
}
// Fill the default values for vertexInput (buffers and attributes).
// assuming #buffers == #attributes
vertexInputDescriptor.bufferCount =
render_pipeline.pipeline->GetVertexBuffers().size();
for (uint32_t i = 0; i < kMaxVertexInputs; ++i) {
if (ignore_vertex_and_Index_buffers) {
if (i == 0) {
vertexInputDescriptor.bufferCount = 1;
vertexInputDescriptor.cBuffers[0].attributeCount = 1;
vertexInputDescriptor.cBuffers[0].stride = 4 * sizeof(float);
vertexInputDescriptor.cBuffers[0].attributes =
&vertexInputDescriptor.cAttributes[0];
vertexInputDescriptor.cAttributes[0].shaderLocation = 0;
vertexInputDescriptor.cAttributes[0].format =
::dawn::VertexFormat::Float4;
}
} else {
if (i < render_pipeline.pipeline->GetVertexBuffers().size()) {
vertexInputDescriptor.cBuffers[i].attributeCount = 1;
vertexInputDescriptor.cBuffers[i].stride =
render_pipeline.pipeline->GetVertexBuffers()[i]
.buffer->GetElementStride();
vertexInputDescriptor.cBuffers[i].stepMode =
::dawn::InputStepMode::Vertex;
vertexInputDescriptor.cBuffers[i].attributes =
&vertexInputDescriptor.cAttributes[i];
vertexInputDescriptor.cAttributes[i].shaderLocation = i;
auto* amber_vertex_format =
render_pipeline.pipeline->GetVertexBuffers()[i].buffer->GetFormat();
result = GetDawnVertexFormat(
*amber_vertex_format, &vertexInputDescriptor.cAttributes[i].format);
if (!result.IsSuccess())
return result;
}
}
}
// set index buffer format
if (render_pipeline.pipeline->GetIndexBuffer()) {
auto* amber_index_format =
render_pipeline.pipeline->GetIndexBuffer()->GetFormat();
GetDawnIndexFormat(*amber_index_format, &vertexInputDescriptor.indexFormat);
if (!result.IsSuccess())
return result;
}
renderPipelineDescriptor.vertexInput =
reinterpret_cast<::dawn::VertexInputDescriptor*>(&vertexInputDescriptor);
// Set defaults for the vertex stage descriptor.
vertexStage.module = render_pipeline.vertex_shader;
vertexStage.entryPoint = vertexEntryPoint.c_str();
renderPipelineDescriptor.vertexStage = vertexStage;
// Set defaults for the fragment stage descriptor.
fragmentStage.module = render_pipeline.fragment_shader;
fragmentStage.entryPoint = fragmentEntryPoint.c_str();
renderPipelineDescriptor.fragmentStage = std::move(&fragmentStage);
// Set defaults for the rasterization state descriptor.
if (pipeline_data == nullptr) {
rasterizationState.frontFace = ::dawn::FrontFace::CCW;
rasterizationState.cullMode = ::dawn::CullMode::None;
rasterizationState.depthBias = 0;
rasterizationState.depthBiasSlopeScale = 0.0;
rasterizationState.depthBiasClamp = 0.0;
renderPipelineDescriptor.rasterizationState = &rasterizationState;
} else {
rasterizationState.frontFace =
GetDawnFrontFace(pipeline_data->GetFrontFace());
rasterizationState.cullMode = GetDawnCullMode(pipeline_data->GetCullMode());
rasterizationState.depthBias = pipeline_data->GetEnableDepthBias();
rasterizationState.depthBiasSlopeScale =
pipeline_data->GetDepthBiasSlopeFactor();
rasterizationState.depthBiasClamp = pipeline_data->GetDepthBiasClamp();
renderPipelineDescriptor.rasterizationState = &rasterizationState;
}
// Set defaults for the color state descriptors.
if (pipeline_data == nullptr) {
renderPipelineDescriptor.colorStateCount =
render_pipeline.pipeline->GetColorAttachments().size();
alpha_blend.operation = ::dawn::BlendOperation::Add;
alpha_blend.srcFactor = ::dawn::BlendFactor::One;
alpha_blend.dstFactor = ::dawn::BlendFactor::Zero;
colorStateDescriptor.writeMask = ::dawn::ColorWriteMask::All;
colorStateDescriptor.format = fb_format;
colorStateDescriptor.alphaBlend = alpha_blend;
colorStateDescriptor.colorBlend = alpha_blend;
} else {
renderPipelineDescriptor.colorStateCount =
render_pipeline.pipeline->GetColorAttachments().size();
alpha_blend.operation =
GetDawnBlendOperation(pipeline_data->GetColorBlendOp());
alpha_blend.srcFactor =
GetDawnBlendFactor(pipeline_data->GetSrcAlphaBlendFactor());
alpha_blend.dstFactor =
GetDawnBlendFactor(pipeline_data->GetDstAlphaBlendFactor());
color_blend.operation =
GetDawnBlendOperation(pipeline_data->GetAlphaBlendOp());
color_blend.srcFactor =
GetDawnBlendFactor(pipeline_data->GetSrcColorBlendFactor());
color_blend.dstFactor =
GetDawnBlendFactor(pipeline_data->GetDstAlphaBlendFactor());
colorStateDescriptor.writeMask =
GetDawnColorWriteMask(pipeline_data->GetColorWriteMask());
colorStateDescriptor.format = fb_format;
colorStateDescriptor.alphaBlend = alpha_blend;
colorStateDescriptor.colorBlend = color_blend;
}
for (uint32_t i = 0; i < kMaxColorAttachments; ++i) {
::dawn::TextureFormat fb_format{};
{
if (i < render_pipeline.pipeline->GetColorAttachments().size()) {
auto* amber_format = render_pipeline.pipeline->GetColorAttachments()[i]
.buffer->GetFormat();
if (!amber_format)
return Result(
"AttachBuffersAndTextures: One Color attachment has no "
"format!");
result = GetDawnTextureFormat(*amber_format, &fb_format);
if (!result.IsSuccess())
return result;
} else {
fb_format = ::dawn::TextureFormat::RGBA8Unorm;
}
}
colorStatesDescriptor[i] = colorStateDescriptor;
colorStates[i] = &colorStatesDescriptor[i];
colorStates[i]->format = fb_format;
}
renderPipelineDescriptor.colorStates = &colorStates[0];
// Set defaults for the depth stencil state descriptors.
if (pipeline_data == nullptr) {
stencil_front.compare = ::dawn::CompareFunction::Always;
stencil_front.failOp = ::dawn::StencilOperation::Keep;
stencil_front.depthFailOp = ::dawn::StencilOperation::Keep;
stencil_front.passOp = ::dawn::StencilOperation::Keep;
depthStencilState.depthWriteEnabled = false;
depthStencilState.depthCompare = ::dawn::CompareFunction::Always;
depthStencilState.stencilBack = stencil_front;
depthStencilState.stencilFront = stencil_front;
depthStencilState.stencilReadMask = 0xff;
depthStencilState.stencilWriteMask = 0xff;
depthStencilState.format = depth_stencil_format;
renderPipelineDescriptor.depthStencilState = &depthStencilState;
} else {
stencil_front.compare =
GetDawnCompareOp(pipeline_data->GetFrontCompareOp());
stencil_front.failOp = GetDawnStencilOp(pipeline_data->GetFrontFailOp());
stencil_front.depthFailOp =
GetDawnStencilOp(pipeline_data->GetFrontDepthFailOp());
stencil_front.passOp = GetDawnStencilOp(pipeline_data->GetFrontPassOp());
stencil_back.compare = GetDawnCompareOp(pipeline_data->GetBackCompareOp());
stencil_back.failOp = GetDawnStencilOp(pipeline_data->GetBackFailOp());
stencil_back.depthFailOp =
GetDawnStencilOp(pipeline_data->GetBackDepthFailOp());
stencil_back.passOp = GetDawnStencilOp(pipeline_data->GetBackPassOp());
depthStencilState.depthWriteEnabled = pipeline_data->GetEnableDepthWrite();
depthStencilState.depthCompare =
GetDawnCompareOp(pipeline_data->GetDepthCompareOp());
depthStencilState.stencilFront = stencil_front;
depthStencilState.stencilBack = stencil_back;
// WebGPU doesn't support separate front and back stencil mask, they has to
// be the same
depthStencilState.stencilReadMask =
(pipeline_data->GetFrontCompareMask() ==
pipeline_data->GetBackCompareMask())
? pipeline_data->GetFrontCompareMask()
: 0xff;
depthStencilState.stencilWriteMask = (pipeline_data->GetBackWriteMask() ==
pipeline_data->GetFrontWriteMask())
? pipeline_data->GetBackWriteMask()
: 0xff;
depthStencilState.format = depth_stencil_format;
renderPipelineDescriptor.depthStencilState = &depthStencilState;
}
return {};
}
Result DawnPipelineHelper::CreateRenderPassDescriptor(
const RenderPipelineInfo& render_pipeline,
const ::dawn::Device& device,
const std::vector<::dawn::TextureView>& texture_view,
const ::dawn::LoadOp load_op) {
for (uint32_t i = 0; i < kMaxColorAttachments; ++i) {
colorAttachmentsInfo[i].loadOp = load_op;
colorAttachmentsInfo[i].storeOp = ::dawn::StoreOp::Store;
colorAttachmentsInfo[i].clearColor = render_pipeline.clear_color_value;
colorAttachmentsInfoPtr[i] = nullptr;
}
depthStencilAttachmentInfo.clearDepth = render_pipeline.clear_depth_value;
depthStencilAttachmentInfo.clearStencil = render_pipeline.clear_stencil_value;
depthStencilAttachmentInfo.depthLoadOp = load_op;
depthStencilAttachmentInfo.depthStoreOp = ::dawn::StoreOp::Store;
depthStencilAttachmentInfo.stencilLoadOp = load_op;
depthStencilAttachmentInfo.stencilStoreOp = ::dawn::StoreOp::Store;
renderPassDescriptor.colorAttachmentCount =
render_pipeline.pipeline->GetColorAttachments().size();
uint32_t colorAttachmentIndex = 0;
for (const ::dawn::TextureView& colorAttachment : texture_view) {
if (colorAttachment.Get() != nullptr) {
colorAttachmentsInfo[colorAttachmentIndex].attachment = colorAttachment;
colorAttachmentsInfoPtr[colorAttachmentIndex] =
&colorAttachmentsInfo[colorAttachmentIndex];
}
++colorAttachmentIndex;
}
renderPassDescriptor.colorAttachments = colorAttachmentsInfoPtr;
::dawn::TextureFormat depth_stencil_format{};
auto* depthBuffer = render_pipeline.pipeline->GetDepthBuffer().buffer;
if (depthBuffer) {
auto* amber_depth_stencil_format = depthBuffer->GetFormat();
if (!amber_depth_stencil_format)
return Result("The depth/stencil attachment has no format!");
Result result = GetDawnTextureFormat(*amber_depth_stencil_format,
&depth_stencil_format);
if (!result.IsSuccess())
return result;
} else {
depth_stencil_format = ::dawn::TextureFormat::Depth24PlusStencil8;
}
depthStencilDescriptor.dimension = ::dawn::TextureDimension::e2D;
depthStencilDescriptor.size.width =
render_pipeline.pipeline->GetFramebufferWidth();
depthStencilDescriptor.size.height =
render_pipeline.pipeline->GetFramebufferHeight();
depthStencilDescriptor.size.depth = 1;
depthStencilDescriptor.arrayLayerCount = 1;
depthStencilDescriptor.sampleCount = 1;
depthStencilDescriptor.format = depth_stencil_format;
depthStencilDescriptor.mipLevelCount = 1;
depthStencilDescriptor.usage =
::dawn::TextureUsage::OutputAttachment | ::dawn::TextureUsage::CopySrc;
depthStencilTexture = device.CreateTexture(&depthStencilDescriptor);
depthStencilView = depthStencilTexture.CreateView();
if (depthStencilView.Get() != nullptr) {
depthStencilAttachmentInfo.attachment = depthStencilView;
renderPassDescriptor.depthStencilAttachment = &depthStencilAttachmentInfo;
} else {
renderPassDescriptor.depthStencilAttachment = nullptr;
}
return {};
}
Result EngineDawn::DoDrawRect(const DrawRectCommand* command) {
RenderPipelineInfo* render_pipeline = GetRenderPipeline(command);
if (!render_pipeline)
return Result("DrawRect invoked on invalid or missing render pipeline");
if (render_pipeline->vertex_buffers.size() > 1)
return Result(
"DrawRect invoked on a render pipeline with more than one "
"VERTEX_DATA attached");
float x = command->GetX();
float y = command->GetY();
float rectangleWidth = command->GetWidth();
float rectangleHeight = command->GetHeight();
const uint32_t frameWidth = render_pipeline->pipeline->GetFramebufferWidth();
const uint32_t frameHeight =
render_pipeline->pipeline->GetFramebufferHeight();
if (command->IsOrtho()) {
x = ((x / frameWidth) * 2.0f) - 1.0f;
y = ((y / frameHeight) * 2.0f) - 1.0f;
rectangleWidth = (rectangleWidth / frameWidth) * 2.0f;
rectangleHeight = (rectangleHeight / frameHeight) * 2.0f;
}
static const uint32_t indexData[3 * 2] = {
0, 1, 2, 0, 2, 3,
};
auto index_buffer = CreateBufferFromData(
*device_, indexData, sizeof(indexData), ::dawn::BufferUsage::Index);
const float vertexData[4 * 4] = {
// Bottom left
x,
y + rectangleHeight,
0.0f,
1.0f,
// Top left
x,
y,
0.0f,
1.0f,
// Top right
x + rectangleWidth,
y,
0.0f,
1.0f,
// Bottom right
x + rectangleWidth,
y + rectangleHeight,
0.0f,
1.0f,
};
auto vertex_buffer = CreateBufferFromData(
*device_, vertexData, sizeof(vertexData), ::dawn::BufferUsage::Vertex);
DawnPipelineHelper helper;
helper.CreateRenderPipelineDescriptor(*render_pipeline, *device_, true,
command->GetPipelineData());
helper.CreateRenderPassDescriptor(*render_pipeline, *device_, texture_views_,
::dawn::LoadOp::Load);
::dawn::RenderPipelineDescriptor* renderPipelineDescriptor =
&helper.renderPipelineDescriptor;
::dawn::RenderPassDescriptor* renderPassDescriptor =
&helper.renderPassDescriptor;
const ::dawn::RenderPipeline pipeline =
device_->CreateRenderPipeline(renderPipelineDescriptor);
static const uint64_t vertexBufferOffsets[1] = {0};
::dawn::CommandEncoder encoder = device_->CreateCommandEncoder();
::dawn::RenderPassEncoder pass =
encoder.BeginRenderPass(renderPassDescriptor);
pass.SetPipeline(pipeline);
for (uint32_t i = 0; i < render_pipeline->bind_groups.size(); i++) {
if (render_pipeline->bind_groups[i]) {
pass.SetBindGroup(i, render_pipeline->bind_groups[i], 0, nullptr);
}
}
pass.SetVertexBuffers(0, 1, &vertex_buffer, vertexBufferOffsets);
pass.SetIndexBuffer(index_buffer, 0);
pass.DrawIndexed(6, 1, 0, 0, 0);
pass.EndPass();
::dawn::CommandBuffer commands = encoder.Finish();
::dawn::Queue queue = device_->CreateQueue();
queue.Submit(1, &commands);
Result result = MapDeviceTextureToHostBuffer(*render_pipeline, *device_);
return result;
}
Result EngineDawn::DoDrawArrays(const DrawArraysCommand* command) {
Result result;
RenderPipelineInfo* render_pipeline = GetRenderPipeline(command);
if (!render_pipeline)
return Result("DrawArrays invoked on invalid or missing render pipeline");
if (command->IsIndexed()) {
if (!render_pipeline->index_buffer)
return Result("DrawArrays: Draw indexed is used without given indices");
} else {
std::vector<uint32_t> indexData;
for (uint32_t i = 0;
i < command->GetFirstVertexIndex() + command->GetVertexCount(); i++) {
indexData.emplace_back(i);
}
render_pipeline->index_buffer = CreateBufferFromData(
*device_, indexData.data(), indexData.size() * sizeof(uint32_t),
::dawn::BufferUsage::Index);
}
uint32_t instance_count = command->GetInstanceCount();
if (instance_count == 0 && command->GetVertexCount() != 0)
instance_count = 1;
DawnPipelineHelper helper;
result = helper.CreateRenderPipelineDescriptor(
*render_pipeline, *device_, false, command->GetPipelineData());
if (!result.IsSuccess())
return result;
result = helper.CreateRenderPassDescriptor(
*render_pipeline, *device_, texture_views_, ::dawn::LoadOp::Load);
if (!result.IsSuccess())
return result;
::dawn::RenderPipelineDescriptor* renderPipelineDescriptor =
&helper.renderPipelineDescriptor;
::dawn::RenderPassDescriptor* renderPassDescriptor =
&helper.renderPassDescriptor;
result = GetDawnTopology(command->GetTopology(),
&renderPipelineDescriptor->primitiveTopology);
if (!result.IsSuccess())
return result;
static const uint64_t vertexBufferOffsets[1] = {0};
const ::dawn::RenderPipeline pipeline =
device_->CreateRenderPipeline(renderPipelineDescriptor);
::dawn::CommandEncoder encoder = device_->CreateCommandEncoder();
::dawn::RenderPassEncoder pass =
encoder.BeginRenderPass(renderPassDescriptor);
pass.SetPipeline(pipeline);
for (uint32_t i = 0; i < render_pipeline->bind_groups.size(); i++) {
if (render_pipeline->bind_groups[i]) {
pass.SetBindGroup(i, render_pipeline->bind_groups[i], 0, nullptr);
}
}
// TODO(sarahM0): figure out what are startSlot, count and offsets
for (uint32_t i = 0; i < render_pipeline->vertex_buffers.size(); i++) {
pass.SetVertexBuffers(i, /* startSlot */
1, /* count */
&render_pipeline->vertex_buffers[i], /* buffer */
vertexBufferOffsets); /* offsets */
}
// TODO(sarahM0): figure out what this offset means
pass.SetIndexBuffer(render_pipeline->index_buffer, /* buffer */
0); /* offset*/
pass.DrawIndexed(command->GetVertexCount(), /* indexCount */
instance_count, /* instanceCount */
0, /* firstIndex */
command->GetFirstVertexIndex(), /* baseVertex */
0 /* firstInstance */);
pass.EndPass();
::dawn::CommandBuffer commands = encoder.Finish();
::dawn::Queue queue = device_->CreateQueue();
queue.Submit(1, &commands);
result = MapDeviceTextureToHostBuffer(*render_pipeline, *device_);
return result;
}
Result EngineDawn::DoCompute(const ComputeCommand* command) {
Result result;
ComputePipelineInfo* compute_pipeline = GetComputePipeline(command);
if (!compute_pipeline)
return Result("DoComput: invoked on invalid or missing compute pipeline");
::dawn::ComputePipelineDescriptor computePipelineDescriptor;
computePipelineDescriptor.layout = MakeBasicPipelineLayout(
device_->Get(), compute_pipeline->bind_group_layouts);
::dawn::PipelineStageDescriptor pipelineStageDescriptor;
pipelineStageDescriptor.module = compute_pipeline->compute_shader;
pipelineStageDescriptor.entryPoint = "main";
computePipelineDescriptor.computeStage = pipelineStageDescriptor;
::dawn::ComputePipeline pipeline =
device_->CreateComputePipeline(&computePipelineDescriptor);
::dawn::CommandEncoder encoder = device_->CreateCommandEncoder();
::dawn::ComputePassEncoder pass = encoder.BeginComputePass();
pass.SetPipeline(pipeline);
for (uint32_t i = 0; i < compute_pipeline->bind_groups.size(); i++) {
if (compute_pipeline->bind_groups[i]) {
pass.SetBindGroup(i, compute_pipeline->bind_groups[i], 0, nullptr);
}
}
pass.Dispatch(command->GetX(), command->GetY(), command->GetZ());
pass.EndPass();
// Finish recording the command buffer. It only has one command.
auto command_buffer = encoder.Finish();
// Submit the command.
auto queue = device_->CreateQueue();
queue.Submit(1, &command_buffer);
// Copy result back
result = MapDeviceBufferToHostBuffer(*compute_pipeline, *device_);
return result;
}
Result EngineDawn::DoEntryPoint(const EntryPointCommand*) {
return Result("Dawn: Entry point must be \"main\" in Dawn");
}
Result EngineDawn::DoPatchParameterVertices(
const PatchParameterVerticesCommand*) {
return Result("Dawn: PatchParameterVertices is not supported in Dawn");
}
Result EngineDawn::DoBuffer(const BufferCommand* command) {
Result result;
::dawn::Buffer* dawn_buffer = nullptr;
const auto descriptor_set = command->GetDescriptorSet();
const auto binding = command->GetBinding();
RenderPipelineInfo* render_pipeline = GetRenderPipeline(command);
if (render_pipeline) {
auto where = render_pipeline->buffer_map.find({descriptor_set, binding});
if (where != render_pipeline->buffer_map.end()) {
const auto dawn_buffer_index = where->second;
dawn_buffer = &render_pipeline->buffers[dawn_buffer_index];
}
}
ComputePipelineInfo* compute_pipeline = GetComputePipeline(command);
if (compute_pipeline) {
auto where = compute_pipeline->buffer_map.find({descriptor_set, binding});
if (where != compute_pipeline->buffer_map.end()) {
const auto dawn_buffer_index = where->second;
dawn_buffer = &compute_pipeline->buffers[dawn_buffer_index];
}
}
if (!render_pipeline && !compute_pipeline)
return Result("DoBuffer: invoked on invalid or missing pipeline");
if (!command->IsSSBO() && !command->IsUniform())
return Result("DoBuffer: only supports SSBO and uniform buffer type");
if (!dawn_buffer) {
return Result("DoBuffer: no Dawn buffer at descriptor set " +
std::to_string(descriptor_set) + " and binding " +
std::to_string(binding));
}
Buffer* amber_buffer = command->GetBuffer();
if (amber_buffer) {
amber_buffer->SetDataWithOffset(command->GetValues(), command->GetOffset());
dawn_buffer->SetSubData(0, amber_buffer->GetMaxSizeInBytes(),
amber_buffer->ValuePtr()->data());
}
return {};
}
Result EngineDawn::AttachBuffersAndTextures(
RenderPipelineInfo* render_pipeline) {
Result result;
const uint32_t width = render_pipeline->pipeline->GetFramebufferWidth();
const uint32_t height = render_pipeline->pipeline->GetFramebufferHeight();
// Create textures and texture views if we haven't already
std::vector<int32_t> seen_idx(
render_pipeline->pipeline->GetColorAttachments().size(), -1);
for (auto info : render_pipeline->pipeline->GetColorAttachments()) {
if (info.location >=
render_pipeline->pipeline->GetColorAttachments().size())
return Result("color attachment locations must be sequential from 0");
if (seen_idx[info.location] != -1) {
return Result("duplicate attachment location: " +
std::to_string(info.location));
}
seen_idx[info.location] = static_cast<int32_t>(info.location);
}
if (textures_.size() == 0) {
for (uint32_t i = 0; i < kMaxColorAttachments; i++) {
::dawn::TextureFormat fb_format{};
if (i < render_pipeline->pipeline->GetColorAttachments().size()) {
auto* amber_format = render_pipeline->pipeline->GetColorAttachments()[i]
.buffer->GetFormat();
if (!amber_format)
return Result(
"AttachBuffersAndTextures: One Color attachment has no "
"format!");
result = GetDawnTextureFormat(*amber_format, &fb_format);
if (!result.IsSuccess())
return result;
} else {
fb_format = ::dawn::TextureFormat::RGBA8Unorm;
}
textures_.emplace_back(
MakeDawnTexture(*device_, fb_format, width, height));
texture_views_.emplace_back(textures_.back().CreateView());
}
}
// Attach depth-stencil texture
auto* depthBuffer = render_pipeline->pipeline->GetDepthBuffer().buffer;
if (depthBuffer) {
if (!depth_stencil_texture_) {
auto* amber_depth_stencil_format = depthBuffer->GetFormat();
if (!amber_depth_stencil_format)
return Result(
"AttachBuffersAndTextures: The depth/stencil attachment has no "
"format!");
::dawn::TextureFormat depth_stencil_format{};
result = GetDawnTextureFormat(*amber_depth_stencil_format,
&depth_stencil_format);
if (!result.IsSuccess())
return result;
result = MakeTexture(*device_, depth_stencil_format, width, height,
&depth_stencil_texture_);
if (!result.IsSuccess())
return result;
render_pipeline->depth_stencil_texture = depth_stencil_texture_;
} else {
render_pipeline->depth_stencil_texture = depth_stencil_texture_;
}
}
// Attach index buffer
if (render_pipeline->pipeline->GetIndexBuffer()) {
render_pipeline->index_buffer = CreateBufferFromData(
*device_,
render_pipeline->pipeline->GetIndexBuffer()->ValuePtr()->data(),
render_pipeline->pipeline->GetIndexBuffer()->GetSizeInBytes(),
::dawn::BufferUsage::Index);
}
// Attach vertex buffers
for (auto& vertex_info : render_pipeline->pipeline->GetVertexBuffers()) {
render_pipeline->vertex_buffers.emplace_back(CreateBufferFromData(
*device_, vertex_info.buffer->ValuePtr()->data(),
vertex_info.buffer->GetSizeInBytes(), ::dawn::BufferUsage::Vertex));
}
// Do not attach pushConstants
if (render_pipeline->pipeline->GetPushConstantBuffer().buffer != nullptr) {
return Result(
"AttachBuffersAndTextures: Dawn does not support push constants!");
}
::dawn::ShaderStage kAllStages =
::dawn::ShaderStage::Vertex | ::dawn::ShaderStage::Fragment;
std::vector<std::vector<BindingInitializationHelper>> bindingInitalizerHelper(
kMaxDawnBindGroup);
std::vector<std::vector<::dawn::BindGroupLayoutBinding>> layouts_info(
kMaxDawnBindGroup);
uint32_t max_descriptor_set = 0;
// Attach storage/uniform buffers
::dawn::BindGroupLayoutBinding empty_layout_info = {};
if (!render_pipeline->pipeline->GetBuffers().empty()) {
std::vector<uint32_t> max_binding_seen(kMaxDawnBindGroup, -1);
for (auto& buf_info : render_pipeline->pipeline->GetBuffers()) {
while (layouts_info[buf_info.descriptor_set].size() <= buf_info.binding)
layouts_info[buf_info.descriptor_set].push_back(empty_layout_info);
}
}
for (const auto& buf_info : render_pipeline->pipeline->GetBuffers()) {
::dawn::BufferUsage bufferUsage;
::dawn::BindingType bindingType;
switch (buf_info.buffer->GetBufferType()) {
case BufferType::kStorage: {
bufferUsage = ::dawn::BufferUsage::Storage;
bindingType = ::dawn::BindingType::StorageBuffer;
break;
}
case BufferType::kUniform: {
bufferUsage = ::dawn::BufferUsage::Uniform;
bindingType = ::dawn::BindingType::UniformBuffer;
break;
}
default: {
return Result("AttachBuffersAndTextures: unknown buffer type: " +
std::to_string(static_cast<uint32_t>(
buf_info.buffer->GetBufferType())));
break;
}
}
if (buf_info.descriptor_set > kMaxDawnBindGroup - 1) {
return Result("AttachBuffers: Dawn has a maximum of " +
std::to_string(kMaxDawnBindGroup) + " (descriptor sets)");
}
render_pipeline->buffers.emplace_back(
CreateBufferFromData(*device_, buf_info.buffer->ValuePtr()->data(),
buf_info.buffer->GetMaxSizeInBytes(),
bufferUsage | ::dawn::BufferUsage::CopySrc |
::dawn::BufferUsage::CopyDst));
render_pipeline->buffer_map[{buf_info.descriptor_set, buf_info.binding}] =
render_pipeline->buffers.size() - 1;
render_pipeline->used_descriptor_set.insert(buf_info.descriptor_set);
max_descriptor_set = std::max(max_descriptor_set, buf_info.descriptor_set);
::dawn::BindGroupLayoutBinding layout_info;
layout_info.binding = buf_info.binding;
layout_info.visibility = kAllStages;
layout_info.type = bindingType;
layouts_info[buf_info.descriptor_set][buf_info.binding] = layout_info;
BindingInitializationHelper tempBinding = BindingInitializationHelper(
buf_info.binding, render_pipeline->buffers.back(), 0,
buf_info.buffer->GetMaxSizeInBytes());
bindingInitalizerHelper[buf_info.descriptor_set].push_back(tempBinding);
}
for (uint32_t i = 0; i < kMaxDawnBindGroup; i++) {
if (layouts_info[i].size() > 0 && bindingInitalizerHelper[i].size() > 0) {
::dawn::BindGroupLayout bindGroupLayout =
MakeBindGroupLayout(*device_, layouts_info[i]);
render_pipeline->bind_group_layouts.push_back(bindGroupLayout);
::dawn::BindGroup bindGroup =
MakeBindGroup(*device_, render_pipeline->bind_group_layouts[i],
bindingInitalizerHelper[i]);
render_pipeline->bind_groups.push_back(bindGroup);
} else if (i < max_descriptor_set) {
::dawn::BindGroupLayout bindGroupLayout =
MakeBindGroupLayout(*device_, {});
render_pipeline->bind_group_layouts.push_back(bindGroupLayout);
::dawn::BindGroup bindGroup =
MakeBindGroup(*device_, render_pipeline->bind_group_layouts[i],
bindingInitalizerHelper[i]);
render_pipeline->bind_groups.push_back(bindGroup);
}
}
return {};
}
Result EngineDawn::AttachBuffers(ComputePipelineInfo* compute_pipeline) {
Result result;
// Do not attach pushConstants
if (compute_pipeline->pipeline->GetPushConstantBuffer().buffer != nullptr) {
return Result("AttachBuffers: Dawn does not support push constants!");
}
std::vector<std::vector<BindingInitializationHelper>> bindingInitalizerHelper(
kMaxDawnBindGroup);
std::vector<std::vector<::dawn::BindGroupLayoutBinding>> layouts_info(
kMaxDawnBindGroup);
uint32_t max_descriptor_set = 0;
// Attach storage/uniform buffers
::dawn::BindGroupLayoutBinding empty_layout_info = {};
if (!compute_pipeline->pipeline->GetBuffers().empty()) {
std::vector<uint32_t> max_binding_seen(kMaxDawnBindGroup, -1);
for (auto& buf_info : compute_pipeline->pipeline->GetBuffers()) {
while (layouts_info[buf_info.descriptor_set].size() <= buf_info.binding)
layouts_info[buf_info.descriptor_set].push_back(empty_layout_info);
}
}
for (const auto& buf_info : compute_pipeline->pipeline->GetBuffers()) {
::dawn::BufferUsage bufferUsage;
::dawn::BindingType bindingType;
switch (buf_info.buffer->GetBufferType()) {
case BufferType::kStorage: {
bufferUsage = ::dawn::BufferUsage::Storage;
bindingType = ::dawn::BindingType::StorageBuffer;
break;
}
case BufferType::kUniform: {
bufferUsage = ::dawn::BufferUsage::Uniform;
bindingType = ::dawn::BindingType::UniformBuffer;
break;
}
default: {
return Result("AttachBuffers: unknown buffer type: " +
std::to_string(static_cast<uint32_t>(
buf_info.buffer->GetBufferType())));
break;
}
}
if (buf_info.descriptor_set > kMaxDawnBindGroup - 1) {
return Result("AttachBuffers: Dawn has a maximum of " +
std::to_string(kMaxDawnBindGroup) + " (descriptor sets)");
}
compute_pipeline->buffers.emplace_back(
CreateBufferFromData(*device_, buf_info.buffer->ValuePtr()->data(),
buf_info.buffer->GetMaxSizeInBytes(),
bufferUsage | ::dawn::BufferUsage::CopySrc |
::dawn::BufferUsage::CopyDst));
compute_pipeline->buffer_map[{buf_info.descriptor_set, buf_info.binding}] =
compute_pipeline->buffers.size() - 1;
compute_pipeline->used_descriptor_set.insert(buf_info.descriptor_set);
max_descriptor_set = std::max(max_descriptor_set, buf_info.descriptor_set);
::dawn::BindGroupLayoutBinding layout_info;
layout_info.binding = buf_info.binding;
layout_info.visibility = ::dawn::ShaderStage::Compute;
layout_info.type = bindingType;
layouts_info[buf_info.descriptor_set][buf_info.binding] = layout_info;
BindingInitializationHelper tempBinding = BindingInitializationHelper(
buf_info.binding, compute_pipeline->buffers.back(), 0,
buf_info.buffer->GetMaxSizeInBytes());
bindingInitalizerHelper[buf_info.descriptor_set].push_back(tempBinding);
}
for (uint32_t i = 0; i < kMaxDawnBindGroup; i++) {
if (layouts_info[i].size() > 0 && bindingInitalizerHelper[i].size() > 0) {
::dawn::BindGroupLayout bindGroupLayout =
MakeBindGroupLayout(*device_, layouts_info[i]);
compute_pipeline->bind_group_layouts.push_back(bindGroupLayout);
::dawn::BindGroup bindGroup =
MakeBindGroup(*device_, compute_pipeline->bind_group_layouts[i],
bindingInitalizerHelper[i]);
compute_pipeline->bind_groups.push_back(bindGroup);
} else if (i < max_descriptor_set) {
::dawn::BindGroupLayout bindGroupLayout =
MakeBindGroupLayout(*device_, {});
compute_pipeline->bind_group_layouts.push_back(bindGroupLayout);
::dawn::BindGroup bindGroup =
MakeBindGroup(*device_, compute_pipeline->bind_group_layouts[i],
bindingInitalizerHelper[i]);
compute_pipeline->bind_groups.push_back(bindGroup);
}
}
return {};
}
} // namespace dawn
} // namespace amber