blob: 9aeb190b0e4d7ccc2e2649789d88f60ca66d166f [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/format.h"
#include <algorithm>
#include <utility>
#include "src/make_unique.h"
#include "src/type_parser.h"
namespace amber {
namespace {
std::string FormatModeToName(FormatMode mode) {
switch (mode) {
case FormatMode::kUNorm:
return "UNORM";
case FormatMode::kUInt:
return "UINT";
case FormatMode::kUScaled:
return "USCALED";
case FormatMode::kSInt:
return "SINT";
case FormatMode::kSNorm:
return "SNORM";
case FormatMode::kSScaled:
return "SSCALED";
case FormatMode::kSRGB:
return "SRGB";
case FormatMode::kSFloat:
return "SFLOAT";
case FormatMode::kUFloat:
return "UFLOAT";
}
return "";
}
uint32_t CalculatePad(uint32_t val) {
if ((val % 16) == 0)
return 0;
return 16 - (val % 16);
}
} // namespace
Format::Format(type::Type* type) : type_(type) {
auto name = GenerateName();
if (name == "")
format_type_ = FormatType::kUnknown;
else
format_type_ = TypeParser::NameToFormatType(name);
RebuildSegments();
}
Format::~Format() = default;
uint32_t Format::SizeInBytes() const {
uint32_t size = 0;
for (const auto& seg : segments_) {
if (seg.IsPadding()) {
size += seg.PaddingBytes();
continue;
}
size += static_cast<uint32_t>(seg.SizeInBytes());
}
return size;
}
bool Format::Equal(const Format* b) const {
return format_type_ == b->format_type_ && layout_ == b->layout_ &&
type_->Equal(b->type_);
}
uint32_t Format::InputNeededPerElement() const {
uint32_t count = 0;
for (const auto& seg : segments_) {
if (seg.IsPadding())
continue;
count += 1;
}
return count;
}
void Format::SetLayout(Layout layout) {
if (layout == layout_)
return;
layout_ = layout;
RebuildSegments();
}
void Format::RebuildSegments() {
segments_.clear();
AddSegmentsForType(type_);
}
void Format::AddPaddedSegment(uint32_t size) {
// If the last item was already padding we just extend by the |size| bytes
if (!segments_.empty() && segments_.back().IsPadding()) {
segments_[segments_.size() - 1] =
Segment{size + segments_.back().SizeInBytes()};
} else {
segments_.push_back(Segment{size});
}
}
void Format::AddPaddedSegmentPackable(uint32_t size) {
AddPaddedSegment(size);
segments_.back().SetPackable(true);
}
bool Format::AddSegment(const Segment& seg) {
if (!segments_.empty()) {
auto last = segments_.back();
if (last.IsPackable() && last.IsPadding() &&
last.SizeInBytes() >= seg.SizeInBytes()) {
segments_.back() = seg;
auto pad = last.SizeInBytes() - seg.SizeInBytes();
if (pad > 0)
AddPaddedSegmentPackable(pad);
return false;
}
}
segments_.push_back(seg);
return true;
}
bool Format::NeedsPadding(type::Type* t) const {
if (layout_ == Layout::kStd140 && (t->IsMatrix() || t->IsArray()))
return true;
if (t->IsVec3() || (t->IsMatrix() && t->RowCount() == 3))
return true;
return false;
}
uint32_t Format::CalcVecBaseAlignmentInBytes(type::Number* n) const {
// vec3 rounds up to a Vec4, so 4 * N
if (n->IsVec3())
return 4 * n->SizeInBytes();
// vec2 and vec4 are 2 * N and 4 * N respectively
return n->RowCount() * n->SizeInBytes();
}
uint32_t Format::CalcArrayBaseAlignmentInBytes(type::Type* t) const {
uint32_t align = 0;
if (t->IsStruct()) {
align = CalcStructBaseAlignmentInBytes(t->AsStruct());
} else if (t->IsMatrix()) {
align = CalcMatrixBaseAlignmentInBytes(t->AsNumber());
} else if (t->IsVec()) {
align = CalcVecBaseAlignmentInBytes(t->AsNumber());
} else if (t->IsList()) {
align = CalcListBaseAlignmentInBytes(t->AsList());
} else if (t->IsNumber()) {
align = t->SizeInBytes();
}
// In std140 array elements round up to multiple of vec4.
if (layout_ == Layout::kStd140)
align += CalculatePad(align);
return align;
}
uint32_t Format::CalcStructBaseAlignmentInBytes(type::Struct* s) const {
uint32_t base_alignment = 0;
for (const auto& member : s->Members()) {
base_alignment =
std::max(base_alignment, CalcTypeBaseAlignmentInBytes(member.type));
}
return base_alignment;
}
uint32_t Format::CalcMatrixBaseAlignmentInBytes(type::Number* m) const {
// TODO(dsinclair): Deal with row major when needed. Currently this assumes
// the matrix is column major.
uint32_t align = 0;
if (m->RowCount() == 3)
align = 4 * m->SizeInBytes();
else
align = m->RowCount() * m->SizeInBytes();
// STD140 rounds up to 16 byte alignment
if (layout_ == Layout::kStd140)
align += CalculatePad(align);
return align;
}
uint32_t Format::CalcListBaseAlignmentInBytes(type::List* l) const {
return l->SizeInBytes();
}
uint32_t Format::CalcTypeBaseAlignmentInBytes(type::Type* t) const {
if (t->IsArray())
return CalcArrayBaseAlignmentInBytes(t);
if (t->IsVec())
return CalcVecBaseAlignmentInBytes(t->AsNumber());
if (t->IsMatrix())
return CalcMatrixBaseAlignmentInBytes(t->AsNumber());
if (t->IsNumber())
return t->SizeInBytes();
if (t->IsList())
return CalcListBaseAlignmentInBytes(t->AsList());
if (t->IsStruct()) {
// Pad struct to 16 bytes in STD140
uint32_t base = CalcStructBaseAlignmentInBytes(t->AsStruct());
if (layout_ == Layout::kStd140)
base += CalculatePad(base);
return base;
}
assert(false && "Not reached");
return 0;
}
uint32_t Format::AddSegmentsForType(type::Type* type) {
if (type->IsList() && type->AsList()->IsPacked()) {
auto l = type->AsList();
if (AddSegment(Segment(FormatComponentType::kR, FormatMode::kUInt,
l->PackSizeInBits()))) {
return l->SizeInBytes();
}
return 0;
}
// Remove packable from previous packing for types which can't pack back.
if (type->IsStruct() || type->IsVec() || type->IsMatrix() ||
type->IsArray()) {
if (!segments_.empty() && segments_.back().IsPadding())
segments_.back().SetPackable(false);
}
// TODO(dsinclair): How to handle matrix stride .... Stride comes from parent
// member ....
if (type->IsStruct()) {
auto s = type->AsStruct();
auto base_alignment_in_bytes = CalcStructBaseAlignmentInBytes(s);
uint32_t cur_offset = 0;
for (const auto& member : s->Members()) {
if (member.HasOffset()) {
assert(static_cast<uint32_t>(member.offset_in_bytes) >= cur_offset);
AddPaddedSegment(static_cast<uint32_t>(member.offset_in_bytes) -
cur_offset);
cur_offset = static_cast<uint32_t>(member.offset_in_bytes);
}
uint32_t seg_size = 0;
if (member.type->IsSizedArray()) {
for (size_t i = 0; i < member.type->ArraySize(); ++i) {
auto ary_seg_size = AddSegmentsForType(member.type);
// Don't allow array members to pack together
if (!segments_.empty() && segments_.back().IsPadding())
segments_.back().SetPackable(false);
if (member.HasArrayStride()) {
uint32_t array_stride =
static_cast<uint32_t>(member.array_stride_in_bytes);
assert(ary_seg_size <= array_stride &&
"Array element larger than stride");
seg_size += array_stride;
} else {
seg_size += ary_seg_size;
}
}
} else {
seg_size = AddSegmentsForType(member.type);
}
if (seg_size > 0 && seg_size < base_alignment_in_bytes) {
AddPaddedSegmentPackable(base_alignment_in_bytes - seg_size);
seg_size += base_alignment_in_bytes - seg_size;
}
cur_offset += seg_size;
}
if (s->HasStride()) {
assert(cur_offset <= s->StrideInBytes() &&
"Struct has more members then fit within stride");
AddPaddedSegment(s->StrideInBytes() - cur_offset);
cur_offset = s->StrideInBytes();
} else if (layout_ == Layout::kStd140) {
// Round struct up to 16 byte alignment in STD140.
auto pad = CalculatePad(cur_offset);
if (pad > 0) {
AddPaddedSegment(pad);
cur_offset += pad;
}
}
return cur_offset;
}
// List members are only numbers and must not be vecs or matrices.
if (type->IsList()) {
uint32_t size = 0;
auto l = type->AsList();
for (uint32_t i = 0; i < type->ColumnCount(); ++i) {
for (const auto& m : l->Members()) {
if (AddSegment(Segment{m.name, m.mode, m.num_bits}))
size += m.SizeInBytes();
}
if (NeedsPadding(type)) {
auto& seg = l->Members().back();
for (size_t k = 0; k < (4 - type->RowCount()); ++k) {
AddPaddedSegment(seg.SizeInBytes());
size += seg.SizeInBytes();
}
}
}
return size;
}
auto n = type->AsNumber();
uint32_t size = 0;
for (uint32_t i = 0; i < type->ColumnCount(); ++i) {
for (uint32_t k = 0; k < type->RowCount(); ++k) {
if (AddSegment(Segment{static_cast<FormatComponentType>(i),
n->GetFormatMode(), n->NumBits()})) {
size += type->SizeInBytes();
}
}
// In std140 a matrix (column count > 1) has each row stored like an array
// which rounds up to a vec4.
//
// In std140 and std430 a vector of size 3N will round up to a vector of 4N.
if (NeedsPadding(type)) {
for (size_t k = 0; k < (4 - type->RowCount()); ++k) {
AddPaddedSegmentPackable(type->SizeInBytes());
size += type->SizeInBytes();
}
}
// Make sure matrix rows don't accidentally pack together.
if (type->IsMatrix() && segments_.back().IsPadding())
segments_.back().SetPackable(false);
}
return size;
}
std::string Format::GenerateName() const {
if (type_->IsMatrix())
return "";
static const char NAME_PARTS[] = "RGBAXDS";
if (type_->IsList()) {
std::vector<std::pair<std::string, std::string>> parts;
const auto& members = type_->AsList()->Members();
for (size_t i = 0; i < members.size(); ++i) {
const auto& member = members[i];
std::string name(1, NAME_PARTS[static_cast<uint8_t>(member.name)]);
name += std::to_string(member.num_bits);
std::string type = FormatModeToName(member.mode);
parts.push_back({name, type});
}
std::string name = "";
for (size_t i = 0; i < parts.size(); ++i) {
name += parts[i].first;
if (i + 1 < parts.size() && parts[i].second != parts[i + 1].second)
name += "_" + parts[i].second + "_";
// Handle the X8_D24 underscore.
if (parts[i].first[0] == 'X')
name += "_";
}
name += "_" + parts.back().second;
if (type_->AsList()->IsPacked())
name += "_PACK" + std::to_string(type_->AsList()->PackSizeInBits());
return name;
}
if (type_->IsNumber()) {
std::string name = "";
for (uint32_t i = 0; i < type_->RowCount(); ++i)
name += NAME_PARTS[i] + std::to_string(type_->SizeInBytes() * 8);
name += "_" + FormatModeToName(type_->AsNumber()->GetFormatMode());
return name;
}
return "";
}
} // namespace amber