blob: 53e72e4ee15461db45e008470d3bf86fe2e5a324 [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/pipeline.h"
#include <algorithm>
#include <limits>
#include <set>
#include "src/format_parser.h"
#include "src/make_unique.h"
namespace amber {
namespace {
const char* kDefaultColorBufferFormat = "B8G8R8A8_UNORM";
const char* kDefaultDepthBufferFormat = "D32_SFLOAT_S8_UINT";
} // namespace
const char* Pipeline::kGeneratedColorBuffer = "framebuffer";
const char* Pipeline::kGeneratedDepthBuffer = "depth_buffer";
Pipeline::ShaderInfo::ShaderInfo(Shader* shader, ShaderType type)
: shader_(shader), shader_type_(type), entry_point_("main") {}
Pipeline::ShaderInfo::ShaderInfo(const ShaderInfo&) = default;
Pipeline::ShaderInfo::~ShaderInfo() = default;
Pipeline::Pipeline(PipelineType type) : pipeline_type_(type) {}
Pipeline::~Pipeline() = default;
std::unique_ptr<Pipeline> Pipeline::Clone() const {
auto clone = MakeUnique<Pipeline>(pipeline_type_);
clone->shaders_ = shaders_;
clone->color_attachments_ = color_attachments_;
clone->vertex_buffers_ = vertex_buffers_;
clone->buffers_ = buffers_;
clone->depth_buffer_ = depth_buffer_;
clone->index_buffer_ = index_buffer_;
clone->fb_width_ = fb_width_;
clone->fb_height_ = fb_height_;
for (const auto& args : set_arg_values_) {
clone->set_arg_values_.push_back({});
clone->set_arg_values_.back().name = args.name;
clone->set_arg_values_.back().ordinal = args.ordinal;
clone->set_arg_values_.back().fmt = MakeUnique<Format>(*args.fmt);
clone->set_arg_values_.back().value = args.value;
}
if (!opencl_pod_buffers_.empty()) {
// Generate specific buffers for the clone.
clone->GenerateOpenCLPodBuffers();
}
return clone;
}
Result Pipeline::AddShader(Shader* shader, ShaderType shader_type) {
if (!shader)
return Result("shader can not be null when attached to pipeline");
if (pipeline_type_ == PipelineType::kCompute &&
shader_type != kShaderTypeCompute) {
return Result("only compute shaders allowed in a compute pipeline");
}
if (pipeline_type_ == PipelineType::kGraphics &&
shader_type == kShaderTypeCompute) {
return Result("can not add a compute shader to a graphics pipeline");
}
for (auto& info : shaders_) {
const auto* is = info.GetShader();
if (is == shader)
return Result("can not add duplicate shader to pipeline");
if (is->GetType() == shader_type) {
info.SetShader(shader);
return {};
}
}
shaders_.emplace_back(shader, shader_type);
return {};
}
Result Pipeline::SetShaderOptimizations(const Shader* shader,
const std::vector<std::string>& opts) {
if (!shader)
return Result("invalid shader specified for optimizations");
std::set<std::string> seen;
for (const auto& opt : opts) {
if (seen.count(opt) != 0)
return Result("duplicate optimization flag (" + opt + ") set on shader");
seen.insert(opt);
}
for (auto& info : shaders_) {
const auto* is = info.GetShader();
if (is == shader) {
info.SetShaderOptimizations(opts);
return {};
}
}
return Result("unknown shader specified for optimizations: " +
shader->GetName());
}
Result Pipeline::SetShaderCompileOptions(const Shader* shader,
const std::vector<std::string>& opts) {
if (!shader)
return Result("invalid shader specified for compile options");
for (auto& info : shaders_) {
const auto* is = info.GetShader();
if (is == shader) {
info.SetCompileOptions(opts);
return {};
}
}
return Result("unknown shader specified for compile options: " +
shader->GetName());
}
Result Pipeline::SetShaderEntryPoint(const Shader* shader,
const std::string& name) {
if (!shader)
return Result("invalid shader specified for entry point");
if (name.empty())
return Result("entry point should not be blank");
for (auto& info : shaders_) {
if (info.GetShader() == shader) {
if (info.GetEntryPoint() != "main")
return Result("multiple entry points given for the same shader");
info.SetEntryPoint(name);
return {};
}
}
return Result("unknown shader specified for entry point: " +
shader->GetName());
}
Result Pipeline::SetShaderType(const Shader* shader, ShaderType type) {
if (!shader)
return Result("invalid shader specified for shader type");
for (auto& info : shaders_) {
if (info.GetShader() == shader) {
info.SetShaderType(type);
return {};
}
}
return Result("unknown shader specified for shader type: " +
shader->GetName());
}
Result Pipeline::Validate() const {
size_t fb_size = fb_width_ * fb_height_;
for (const auto& attachment : color_attachments_) {
if (attachment.buffer->ElementCount() != fb_size) {
return Result(
"shared framebuffer must have same size over all PIPELINES");
}
}
if (depth_buffer_.buffer && depth_buffer_.buffer->ElementCount() != fb_size)
return Result("shared depth buffer must have same size over all PIPELINES");
for (auto& buf : GetBuffers()) {
if (buf.buffer->GetFormat() == nullptr) {
return Result("buffer (" + std::to_string(buf.descriptor_set) + ":" +
std::to_string(buf.binding) + ") requires a format");
}
}
if (pipeline_type_ == PipelineType::kGraphics)
return ValidateGraphics();
return ValidateCompute();
}
Result Pipeline::ValidateGraphics() const {
if (color_attachments_.empty())
return Result("PIPELINE missing color attachment");
bool found_vertex = false;
for (const auto& info : shaders_) {
const auto* s = info.GetShader();
if (s->GetType() == kShaderTypeVertex) {
found_vertex = true;
break;
}
}
if (!found_vertex)
return Result("graphics pipeline requires a vertex shader");
return {};
}
Result Pipeline::ValidateCompute() const {
if (shaders_.empty())
return Result("compute pipeline requires a compute shader");
return {};
}
void Pipeline::UpdateFramebufferSizes() {
uint32_t size = fb_width_ * fb_height_;
if (size == 0)
return;
for (auto& attachment : color_attachments_) {
attachment.buffer->SetWidth(fb_width_);
attachment.buffer->SetHeight(fb_height_);
attachment.buffer->SetElementCount(size);
}
if (depth_buffer_.buffer) {
depth_buffer_.buffer->SetWidth(fb_width_);
depth_buffer_.buffer->SetHeight(fb_height_);
depth_buffer_.buffer->SetElementCount(size);
}
}
Result Pipeline::AddColorAttachment(Buffer* buf, uint32_t location) {
for (const auto& attachment : color_attachments_) {
if (attachment.location == location)
return Result("can not bind two color buffers to the same LOCATION");
if (attachment.buffer == buf)
return Result("color buffer may only be bound to a PIPELINE once");
}
color_attachments_.push_back(BufferInfo{buf});
auto& info = color_attachments_.back();
info.location = location;
buf->SetWidth(fb_width_);
buf->SetHeight(fb_height_);
buf->SetElementCount(fb_width_ * fb_height_);
return {};
}
Result Pipeline::GetLocationForColorAttachment(Buffer* buf,
uint32_t* loc) const {
for (const auto& info : color_attachments_) {
if (info.buffer == buf) {
*loc = info.location;
return {};
}
}
return Result("Unable to find requested buffer");
}
Result Pipeline::SetDepthBuffer(Buffer* buf) {
if (depth_buffer_.buffer != nullptr)
return Result("can only bind one depth buffer in a PIPELINE");
if (buf->GetBufferType() != BufferType::kDepth)
return Result("expected a depth buffer");
depth_buffer_.buffer = buf;
buf->SetWidth(fb_width_);
buf->SetHeight(fb_height_);
buf->SetElementCount(fb_width_ * fb_height_);
return {};
}
Result Pipeline::SetIndexBuffer(Buffer* buf) {
if (index_buffer_ != nullptr)
return Result("can only bind one INDEX_DATA buffer in a pipeline");
index_buffer_ = buf;
return {};
}
Result Pipeline::AddVertexBuffer(Buffer* buf, uint32_t location) {
for (const auto& vtex : vertex_buffers_) {
if (vtex.location == location)
return Result("can not bind two vertex buffers to the same LOCATION");
if (vtex.buffer == buf)
return Result("vertex buffer may only be bound to a PIPELINE once");
}
if (buf->GetBufferType() != BufferType::kVertex)
return Result("expected a vertex buffer");
vertex_buffers_.push_back(BufferInfo{buf});
vertex_buffers_.back().location = location;
return {};
}
Result Pipeline::SetPushConstantBuffer(Buffer* buf) {
if (push_constant_buffer_.buffer != nullptr)
return Result("can only bind one push constant buffer in a PIPELINE");
if (buf->GetBufferType() != BufferType::kPushConstant)
return Result("expected a push constant buffer");
push_constant_buffer_.buffer = buf;
return {};
}
std::unique_ptr<Buffer> Pipeline::GenerateDefaultColorAttachmentBuffer() const {
FormatParser fp;
std::unique_ptr<Buffer> buf = MakeUnique<Buffer>(BufferType::kColor);
buf->SetName(kGeneratedColorBuffer);
buf->SetFormat(fp.Parse(kDefaultColorBufferFormat));
return buf;
}
std::unique_ptr<Buffer> Pipeline::GenerateDefaultDepthAttachmentBuffer() const {
FormatParser fp;
std::unique_ptr<Buffer> buf = MakeUnique<Buffer>(BufferType::kDepth);
buf->SetName(kGeneratedDepthBuffer);
buf->SetFormat(fp.Parse(kDefaultDepthBufferFormat));
return buf;
}
Buffer* Pipeline::GetBufferForBinding(uint32_t descriptor_set,
uint32_t binding) const {
for (const auto& info : buffers_) {
if (info.descriptor_set == descriptor_set && info.binding == binding)
return info.buffer;
}
return nullptr;
}
void Pipeline::AddBuffer(Buffer* buf,
uint32_t descriptor_set,
uint32_t binding) {
// If this buffer binding already exists, overwrite with the new buffer.
for (auto& info : buffers_) {
if (info.descriptor_set == descriptor_set && info.binding == binding) {
info.buffer = buf;
return;
}
}
buffers_.push_back(BufferInfo{buf});
auto& info = buffers_.back();
info.descriptor_set = descriptor_set;
info.binding = binding;
}
void Pipeline::AddBuffer(Buffer* buf, const std::string& arg_name) {
// If this buffer binding already exists, overwrite with the new buffer.
for (auto& info : buffers_) {
if (info.arg_name == arg_name) {
info.buffer = buf;
return;
}
}
buffers_.push_back(BufferInfo{buf});
auto& info = buffers_.back();
info.arg_name = arg_name;
info.descriptor_set = std::numeric_limits<uint32_t>::max();
info.binding = std::numeric_limits<uint32_t>::max();
info.arg_no = std::numeric_limits<uint32_t>::max();
}
void Pipeline::AddBuffer(Buffer* buf, uint32_t arg_no) {
// If this buffer binding already exists, overwrite with the new buffer.
for (auto& info : buffers_) {
if (info.arg_no == arg_no) {
info.buffer = buf;
return;
}
}
buffers_.push_back(BufferInfo{buf});
auto& info = buffers_.back();
info.arg_no = arg_no;
info.descriptor_set = std::numeric_limits<uint32_t>::max();
info.binding = std::numeric_limits<uint32_t>::max();
}
Result Pipeline::UpdateOpenCLBufferBindings() {
if (!IsCompute() || GetShaders().empty() ||
GetShaders()[0].GetShader()->GetFormat() != kShaderFormatOpenCLC)
return {};
const auto& shader_info = GetShaders()[0];
const auto& descriptor_map = shader_info.GetDescriptorMap();
if (descriptor_map.empty())
return {};
const auto iter = descriptor_map.find(shader_info.GetEntryPoint());
if (iter == descriptor_map.end())
return {};
for (auto& info : buffers_) {
if (info.descriptor_set == std::numeric_limits<uint32_t>::max() &&
info.binding == std::numeric_limits<uint32_t>::max()) {
for (const auto& entry : iter->second) {
if (entry.arg_name == info.arg_name ||
entry.arg_ordinal == info.arg_no) {
// Buffer storage class consistency checks.
if (info.buffer->GetBufferType() == BufferType::kUnknown) {
// Set the appropriate buffer type.
switch (entry.kind) {
case Pipeline::ShaderInfo::DescriptorMapEntry::Kind::UBO:
case Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD_UBO:
info.buffer->SetBufferType(BufferType::kUniform);
break;
case Pipeline::ShaderInfo::DescriptorMapEntry::Kind::SSBO:
case Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD:
info.buffer->SetBufferType(BufferType::kStorage);
break;
default:
return Result("Unhandled buffer type for OPENCL-C shader");
}
} else if (info.buffer->GetBufferType() == BufferType::kUniform) {
if (entry.kind !=
Pipeline::ShaderInfo::DescriptorMapEntry::Kind::UBO &&
entry.kind !=
Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD_UBO) {
return Result("Buffer " + info.buffer->GetName() +
" must be an uniform binding");
}
} else if (info.buffer->GetBufferType() == BufferType::kStorage) {
if (entry.kind !=
Pipeline::ShaderInfo::DescriptorMapEntry::Kind::SSBO &&
entry.kind !=
Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD) {
return Result("Buffer " + info.buffer->GetName() +
" must be a storage binding");
}
} else {
return Result("Unhandled buffer type for OPENCL-C shader");
}
info.descriptor_set = entry.descriptor_set;
info.binding = entry.binding;
}
}
}
}
return {};
}
Result Pipeline::GenerateOpenCLPodBuffers() {
if (!IsCompute() || GetShaders().empty() ||
GetShaders()[0].GetShader()->GetFormat() != kShaderFormatOpenCLC) {
return {};
}
const auto& shader_info = GetShaders()[0];
const auto& descriptor_map = shader_info.GetDescriptorMap();
if (descriptor_map.empty())
return {};
const auto iter = descriptor_map.find(shader_info.GetEntryPoint());
if (iter == descriptor_map.end())
return {};
// For each SET command, do the following:
// 1. Find the descriptor map entry for that argument.
// 2. Find or create the buffer for the descriptor set and binding pair.
// 3. Write the data for the SET command at the right offset.
for (const auto& arg_info : SetArgValues()) {
uint32_t descriptor_set = std::numeric_limits<uint32_t>::max();
uint32_t binding = std::numeric_limits<uint32_t>::max();
uint32_t offset = 0;
uint32_t arg_size = 0;
bool uses_name = !arg_info.name.empty();
Pipeline::ShaderInfo::DescriptorMapEntry::Kind kind =
Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD;
for (const auto& entry : iter->second) {
if (entry.kind != Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD &&
entry.kind !=
Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD_UBO) {
continue;
}
// Found the right entry.
if ((uses_name && entry.arg_name == arg_info.name) ||
entry.arg_ordinal == arg_info.ordinal) {
descriptor_set = entry.descriptor_set;
binding = entry.binding;
offset = entry.pod_offset;
arg_size = entry.pod_arg_size;
kind = entry.kind;
break;
}
}
if (descriptor_set == std::numeric_limits<uint32_t>::max() ||
binding == std::numeric_limits<uint32_t>::max()) {
std::string message =
"could not find descriptor map entry for SET command: kernel " +
shader_info.GetEntryPoint();
if (uses_name) {
message += ", name " + arg_info.name;
} else {
message += ", number " + std::to_string(arg_info.ordinal);
}
return Result(message);
}
auto buf_iter = opencl_pod_buffer_map_.lower_bound(
std::make_pair(descriptor_set, binding));
Buffer* buffer = nullptr;
if (buf_iter == opencl_pod_buffer_map_.end() ||
buf_iter->first.first != descriptor_set ||
buf_iter->first.second != binding) {
// Ensure no buffer was previously bound for this descriptor set and
// binding pair.
for (const auto& buf_info : GetBuffers()) {
if (buf_info.descriptor_set == descriptor_set &&
buf_info.binding == binding) {
return Result("previously bound buffer " +
buf_info.buffer->GetName() +
" to PoD args at descriptor set " +
std::to_string(descriptor_set) + " binding " +
std::to_string(binding));
}
}
// Add a new buffer for this descriptor set and binding.
opencl_pod_buffers_.push_back(MakeUnique<Buffer>());
buffer = opencl_pod_buffers_.back().get();
buffer->SetBufferType(
kind == Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD
? BufferType::kStorage
: BufferType::kUniform);
// Use an 8-bit type because all the data in the descriptor map is
// byte-based and it simplifies the logic for sizing below.
auto fmt = MakeUnique<Format>();
fmt->AddComponent(FormatComponentType::kR, FormatMode::kUInt, 8);
buffer->SetFormat(std::move(fmt));
buffer->SetName(GetName() + "_pod_buffer_" +
std::to_string(descriptor_set) + "_" +
std::to_string(binding));
opencl_pod_buffer_map_.insert(
buf_iter,
std::make_pair(std::make_pair(descriptor_set, binding), buffer));
AddBuffer(buffer, descriptor_set, binding);
} else {
buffer = buf_iter->second;
}
// Resize if necessary.
if (buffer->ValueCount() < offset + arg_size) {
buffer->SetSizeInElements(offset + arg_size);
}
// Check the data size.
if (arg_size != arg_info.fmt->SizeInBytes()) {
std::string message = "SET command uses incorrect data size: kernel " +
shader_info.GetEntryPoint();
if (uses_name) {
message += ", name " + arg_info.name;
} else {
message += ", number " + std::to_string(arg_info.ordinal);
}
return Result(message);
}
Result r = buffer->SetDataWithOffset({arg_info.value}, offset);
if (!r.IsSuccess())
return r;
}
return {};
}
} // namespace amber