aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordan sinclair <dsinclair@google.com>2019-10-15 15:55:08 -0400
committerGitHub <noreply@github.com>2019-10-15 15:55:08 -0400
commitb0708a96acc2e7f04ab6a2e4fcd9391ffaffcc15 (patch)
tree3aa28d80f470e1de095c78bd92b897c14b634a9b
parent98e3e7ed84039d189d6d23eaed55efd62d3edbcf (diff)
downloadamber-b0708a96acc2e7f04ab6a2e4fcd9391ffaffcc15.tar.gz
[AmberScript] Adding struct support (#688)
This CL adds support for the STRUCT syntax in AmberScript. STRUCTS can be used in buffers. They can be compared as expected. If no OFFSET or STRIDE data is provided we will calculate the padding and offsets based on the current layout. Fixes #544
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/amberscript/parser.cc194
-rw-r--r--src/amberscript/parser.h1
-rw-r--r--src/amberscript/parser_attach_test.cc2
-rw-r--r--src/amberscript/parser_buffer_test.cc201
-rw-r--r--src/amberscript/parser_expect_test.cc38
-rw-r--r--src/amberscript/parser_struct_test.cc493
-rw-r--r--src/buffer.cc2
-rw-r--r--src/format.cc3
-rw-r--r--src/script.cc16
-rw-r--r--src/script.h19
-rw-r--r--src/script_test.cc32
-rw-r--r--src/type.h2
-rw-r--r--tests/cases/struct.amber52
-rw-r--r--tests/cases/struct_compare.amber82
-rw-r--r--tests/cases/struct_embedded.amber67
-rw-r--r--tests/cases/struct_embedded_padded_430.amber73
17 files changed, 1255 insertions, 23 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1bb94a2..70ca4fa 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -130,6 +130,7 @@ if (${AMBER_ENABLE_TESTS})
amberscript/parser_set_test.cc
amberscript/parser_shader_opt_test.cc
amberscript/parser_shader_test.cc
+ amberscript/parser_struct_test.cc
amberscript/parser_test.cc
buffer_test.cc
command_data_test.cc
diff --git a/src/amberscript/parser.cc b/src/amberscript/parser.cc
index 92cfbfa..cfa8bc5 100644
--- a/src/amberscript/parser.cc
+++ b/src/amberscript/parser.cc
@@ -16,6 +16,7 @@
#include <cassert>
#include <limits>
+#include <map>
#include <string>
#include <utility>
#include <vector>
@@ -163,6 +164,8 @@ Result Parser::Parse(const std::string& data) {
r = ParseSet();
} else if (tok == "SHADER") {
r = ParseShaderBlock();
+ } else if (tok == "STRUCT") {
+ r = ParseStruct();
} else {
r = Result("unknown token: " + tok);
}
@@ -521,7 +524,7 @@ Result Parser::ParseShaderSpecialization(Pipeline* pipeline) {
auto type = ToType(token->AsString());
if (!type)
- return Result("invalid data_type provided");
+ return Result("invalid data type '" + token->AsString() + "' provided");
if (!type->IsNumber())
return Result("only numeric types are accepted for specialization values");
@@ -850,7 +853,7 @@ Result Parser::ParsePipelineSet(Pipeline* pipeline) {
auto type = ToType(token->AsString());
if (!type)
- return Result("invalid data_type provided");
+ return Result("invalid data type '" + token->AsString() + "' provided");
token = tokenizer_->NextToken();
if (!token->IsInteger() && !token->IsDouble())
@@ -875,6 +878,124 @@ Result Parser::ParsePipelineSet(Pipeline* pipeline) {
return ValidateEndOfStatement("SET command");
}
+Result Parser::ParseStruct() {
+ auto token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("invalid STRUCT name provided");
+
+ auto struct_name = token->AsString();
+ if (struct_name == "STRIDE")
+ return Result("missing STRUCT name");
+
+ auto s = MakeUnique<type::Struct>();
+ auto type = s.get();
+
+ Result r = script_->AddType(struct_name, std::move(s));
+ if (!r.IsSuccess())
+ return r;
+
+ token = tokenizer_->NextToken();
+ if (token->IsString()) {
+ if (token->AsString() != "STRIDE")
+ return Result("invalid token in STRUCT definition");
+
+ token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return Result("missing value for STRIDE");
+ if (!token->IsInteger())
+ return Result("invalid value for STRIDE");
+
+ type->SetStrideInBytes(token->AsUint32());
+ token = tokenizer_->NextToken();
+ }
+ if (!token->IsEOL()) {
+ return Result("extra token " + token->ToOriginalString() +
+ " after STRUCT header");
+ }
+
+ std::map<std::string, bool> seen;
+ for (;;) {
+ token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("invalid type for STRUCT member");
+ if (token->AsString() == "END")
+ break;
+
+ if (token->AsString() == struct_name)
+ return Result("recursive types are not allowed");
+
+ type::Type* member_type = script_->GetType(token->AsString());
+ if (!member_type) {
+ auto t = ToType(token->AsString());
+ if (!t) {
+ return Result("unknown type '" + token->AsString() +
+ "' for STRUCT member");
+ }
+
+ member_type = t.get();
+ script_->RegisterType(std::move(t));
+ }
+
+ token = tokenizer_->NextToken();
+ if (token->IsEOL())
+ return Result("missing name for STRUCT member");
+ if (!token->IsString())
+ return Result("invalid name for STRUCT member");
+
+ auto member_name = token->AsString();
+ if (seen.find(member_name) != seen.end())
+ return Result("duplicate name for STRUCT member");
+
+ seen[member_name] = true;
+
+ auto m = type->AddMember(member_type);
+ m->name = member_name;
+
+ token = tokenizer_->NextToken();
+ while (token->IsString()) {
+ if (token->AsString() == "OFFSET") {
+ token = tokenizer_->NextToken();
+ if (token->IsEOL())
+ return Result("missing value for STRUCT member OFFSET");
+ if (!token->IsInteger())
+ return Result("invalid value for STRUCT member OFFSET");
+
+ m->offset_in_bytes = token->AsInt32();
+ } else if (token->AsString() == "ARRAY_STRIDE") {
+ token = tokenizer_->NextToken();
+ if (token->IsEOL())
+ return Result("missing value for STRUCT member ARRAY_STRIDE");
+ if (!token->IsInteger())
+ return Result("invalid value for STRUCT member ARRAY_STRIDE");
+ if (!member_type->IsArray())
+ return Result("ARRAY_STRIDE only valid on array members");
+
+ m->array_stride_in_bytes = token->AsInt32();
+ } else if (token->AsString() == "MATRIX_STRIDE") {
+ token = tokenizer_->NextToken();
+ if (token->IsEOL())
+ return Result("missing value for STRUCT member MATRIX_STRIDE");
+ if (!token->IsInteger())
+ return Result("invalid value for STRUCT member MATRIX_STRIDE");
+ if (!member_type->IsMatrix())
+ return Result("MATRIX_STRIDE only valid on matrix members");
+
+ m->matrix_stride_in_bytes = token->AsInt32();
+ } else {
+ return Result("unknown param '" + token->AsString() +
+ "' for STRUCT member");
+ }
+
+ token = tokenizer_->NextToken();
+ }
+
+ if (!token->IsEOL())
+ return Result("extra param for STRUCT member");
+ }
+
+ return {};
+}
+
Result Parser::ParseBuffer() {
auto token = tokenizer_->NextToken();
if (!token->IsString())
@@ -903,15 +1024,13 @@ Result Parser::ParseBuffer() {
buffer = MakeUnique<Buffer>();
- TypeParser type_parser;
- auto type = type_parser.Parse(token->AsString());
- if (type == nullptr)
+ auto type = script_->ParseType(token->AsString());
+ if (!type)
return Result("invalid BUFFER FORMAT");
- auto fmt = MakeUnique<Format>(type.get());
+ auto fmt = MakeUnique<Format>(type);
buffer->SetFormat(fmt.get());
script_->RegisterFormat(std::move(fmt));
- script_->RegisterType(std::move(type));
} else {
return Result("unknown BUFFER command provided: " + cmd);
}
@@ -929,22 +1048,22 @@ Result Parser::ParseBufferInitializer(Buffer* buffer) {
if (!token->IsString())
return Result("BUFFER invalid data type");
- TypeParser parser;
- auto type = parser.Parse(token->AsString());
+ auto type = script_->ParseType(token->AsString());
std::unique_ptr<Format> fmt;
if (type != nullptr) {
- fmt = MakeUnique<Format>(type.get());
+ fmt = MakeUnique<Format>(type);
buffer->SetFormat(fmt.get());
} else {
- type = ToType(token->AsString());
- if (!type)
- return Result("invalid data_type provided");
+ auto new_type = ToType(token->AsString());
+ if (!new_type)
+ return Result("invalid data type '" + token->AsString() + "' provided");
- fmt = MakeUnique<Format>(type.get());
+ fmt = MakeUnique<Format>(new_type.get());
buffer->SetFormat(fmt.get());
+ type = new_type.get();
+ script_->RegisterType(std::move(new_type));
}
script_->RegisterFormat(std::move(fmt));
- script_->RegisterType(std::move(type));
token = tokenizer_->NextToken();
if (!token->IsString())
@@ -1079,7 +1198,9 @@ Result Parser::ParseBufferInitializerSeries(Buffer* buffer,
Result Parser::ParseBufferInitializerData(Buffer* buffer) {
auto fmt = buffer->GetFormat();
- bool is_double_type = fmt->IsFloat32() || fmt->IsFloat64();
+ const auto& segs = fmt->GetSegments();
+ size_t seg_idx = 0;
+ uint32_t value_count = 0;
std::vector<Value> values;
for (auto token = tokenizer_->NextToken();; token = tokenizer_->NextToken()) {
@@ -1091,25 +1212,45 @@ Result Parser::ParseBufferInitializerData(Buffer* buffer) {
break;
if (!token->IsInteger() && !token->IsDouble() && !token->IsHex())
return Result("invalid BUFFER data value: " + token->ToOriginalString());
- if (!is_double_type && token->IsDouble())
- return Result("invalid BUFFER data value: " + token->ToOriginalString());
+
+ while (segs[seg_idx].IsPadding()) {
+ ++seg_idx;
+ if (seg_idx >= segs.size())
+ seg_idx = 0;
+ }
Value v;
- if (is_double_type) {
+ if (type::Type::IsFloat(segs[seg_idx].GetFormatMode())) {
token->ConvertToDouble();
double val = token->IsHex() ? static_cast<double>(token->AsHex())
: token->AsDouble();
v.SetDoubleValue(val);
+ ++value_count;
} else {
+ if (token->IsDouble()) {
+ return Result("invalid BUFFER data value: " +
+ token->ToOriginalString());
+ }
+
uint64_t val = token->IsHex() ? token->AsHex() : token->AsUint64();
v.SetIntValue(val);
+ ++value_count;
}
+ ++seg_idx;
+ if (seg_idx >= segs.size())
+ seg_idx = 0;
values.emplace_back(v);
}
+ // Write final padding bytes
+ while (segs[seg_idx].IsPadding()) {
+ ++seg_idx;
+ if (seg_idx >= segs.size())
+ break;
+ }
- buffer->SetValueCount(static_cast<uint32_t>(values.size()));
+ buffer->SetValueCount(value_count);
Result r = buffer->SetData(std::move(values));
if (!r.IsSuccess())
return r;
@@ -1346,10 +1487,18 @@ Result Parser::ParseValues(const std::string& name,
assert(values);
auto token = tokenizer_->NextToken();
+ const auto& segs = fmt->GetSegments();
+ size_t seg_idx = 0;
while (!token->IsEOL() && !token->IsEOS()) {
Value v;
- if (fmt->IsFloat32() || fmt->IsFloat64()) {
+ while (segs[seg_idx].IsPadding()) {
+ ++seg_idx;
+ if (seg_idx >= segs.size())
+ seg_idx = 0;
+ }
+
+ if (type::Type::IsFloat(segs[seg_idx].GetFormatMode())) {
if (!token->IsInteger() && !token->IsDouble()) {
return Result(std::string("Invalid value provided to ") + name +
" command: " + token->ToOriginalString());
@@ -1368,6 +1517,9 @@ Result Parser::ParseValues(const std::string& name,
v.SetIntValue(token->AsUint64());
}
+ ++seg_idx;
+ if (seg_idx >= segs.size())
+ seg_idx = 0;
values->push_back(v);
token = tokenizer_->NextToken();
diff --git a/src/amberscript/parser.h b/src/amberscript/parser.h
index 4c99e17..2412208 100644
--- a/src/amberscript/parser.h
+++ b/src/amberscript/parser.h
@@ -47,6 +47,7 @@ class Parser : public amber::Parser {
Result ToPipelineType(const std::string& str, PipelineType* type);
Result ValidateEndOfStatement(const std::string& name);
+ Result ParseStruct();
Result ParseBuffer();
Result ParseBufferInitializer(Buffer*);
Result ParseBufferInitializerSize(Buffer*);
diff --git a/src/amberscript/parser_attach_test.cc b/src/amberscript/parser_attach_test.cc
index 277366f..1bfaa4b 100644
--- a/src/amberscript/parser_attach_test.cc
+++ b/src/amberscript/parser_attach_test.cc
@@ -425,7 +425,7 @@ END)";
Parser parser;
Result r = parser.Parse(in);
ASSERT_FALSE(r.IsSuccess());
- EXPECT_EQ("6: invalid data_type provided", r.Error());
+ EXPECT_EQ("6: invalid data type 'uint' provided", r.Error());
}
TEST_F(AmberScriptParserTest, PipelineSpecializationBadDataType) {
diff --git a/src/amberscript/parser_buffer_test.cc b/src/amberscript/parser_buffer_test.cc
index a0aa357..1381ec8 100644
--- a/src/amberscript/parser_buffer_test.cc
+++ b/src/amberscript/parser_buffer_test.cc
@@ -733,7 +733,10 @@ TEST_P(AmberScriptParserBufferDataTypeInvalidTest, BufferTypes) {
Parser parser;
Result r = parser.Parse(in);
ASSERT_FALSE(r.IsSuccess()) << test_data.name;
- EXPECT_EQ("1: invalid data_type provided", r.Error()) << test_data.name;
+ EXPECT_EQ(
+ std::string("1: invalid data type '") + test_data.name + "' provided",
+ r.Error())
+ << test_data.name;
}
INSTANTIATE_TEST_SUITE_P(
AmberScriptParserBufferDataTypeInvalidTestSamples,
@@ -762,5 +765,201 @@ INSTANTIATE_TEST_SUITE_P(
NameData{"mat2x2"},
NameData{"mat2x2<>"})); // NOLINT(whitespace/parens)
+TEST_F(AmberScriptParserTest, BufferWithStructStd140) {
+ std::string in = R"(
+STRUCT s
+ uint32 d
+ uint32 e
+END
+
+STRUCT my_data
+ float a
+ uint32 b
+ s c
+END
+
+BUFFER my_buffer DATA_TYPE my_data STD140 DATA
+ 1 # a
+ 64 # b
+128 # c.d
+220 # c.e
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = parser.GetScript();
+ const auto& buffers = script->GetBuffers();
+ ASSERT_EQ(1U, buffers.size());
+
+ ASSERT_TRUE(buffers[0] != nullptr);
+ EXPECT_EQ("my_buffer", buffers[0]->GetName());
+
+ auto* buffer = buffers[0].get();
+ EXPECT_TRUE(buffer->GetFormat()->GetType()->IsStruct());
+ EXPECT_EQ(Format::Layout::kStd140, buffer->GetFormat()->GetLayout());
+
+ EXPECT_EQ(1U, buffer->ElementCount());
+ EXPECT_EQ(32U, buffer->GetSizeInBytes());
+
+ const auto* data = buffer->GetValues<uint8_t>();
+ EXPECT_FLOAT_EQ(1.f, *reinterpret_cast<const float*>(data + 0));
+ EXPECT_EQ(64,
+ *reinterpret_cast<const uint32_t*>(data + 4 /* sizeof(float) */));
+ EXPECT_EQ(128,
+ *reinterpret_cast<const uint32_t*>(data + 16 /* 8 round -> 16 */));
+ EXPECT_EQ(220, *reinterpret_cast<const uint32_t*>(
+ data + 20 /* 8 round -> 16 + 4 */));
+}
+
+TEST_F(AmberScriptParserTest, BufferWithStructStd430) {
+ std::string in = R"(
+STRUCT s
+ uint32 d
+ uint32 e
+END
+
+STRUCT my_data
+ float a
+ uint32 b
+ s c
+END
+
+BUFFER my_buffer DATA_TYPE my_data STD430 DATA
+ 1 # a
+ 64 # b
+128 # c.d
+220 # c.e
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = parser.GetScript();
+ const auto& buffers = script->GetBuffers();
+ ASSERT_EQ(1U, buffers.size());
+
+ ASSERT_TRUE(buffers[0] != nullptr);
+ EXPECT_EQ("my_buffer", buffers[0]->GetName());
+
+ auto* buffer = buffers[0].get();
+ EXPECT_TRUE(buffer->GetFormat()->GetType()->IsStruct());
+ EXPECT_EQ(Format::Layout::kStd430, buffer->GetFormat()->GetLayout());
+
+ EXPECT_EQ(1U, buffer->ElementCount());
+ EXPECT_EQ(16U, buffer->GetSizeInBytes());
+
+ const auto* data = buffer->GetValues<uint8_t>();
+ EXPECT_FLOAT_EQ(1.f, *reinterpret_cast<const float*>(data + 0));
+ EXPECT_EQ(64, *reinterpret_cast<const uint32_t*>(data + 4));
+ EXPECT_EQ(128, *reinterpret_cast<const uint32_t*>(data + 8));
+ EXPECT_EQ(220, *reinterpret_cast<const uint32_t*>(data + 12));
+}
+
+TEST_F(AmberScriptParserTest, BufferWithStructAndPaddingStd430) {
+ std::string in = R"(
+STRUCT s
+ uint32 d OFFSET 8
+ uint32 e OFFSET 16
+END
+
+STRUCT my_data
+ float a OFFSET 8
+ uint32 b OFFSET 16
+ s c;
+END
+
+BUFFER my_buffer DATA_TYPE my_data STD430 DATA
+ 1 # a
+ 64 # b
+128 # c.d
+220 # c.e
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = parser.GetScript();
+ const auto& buffers = script->GetBuffers();
+ ASSERT_EQ(1U, buffers.size());
+
+ ASSERT_TRUE(buffers[0] != nullptr);
+ EXPECT_EQ("my_buffer", buffers[0]->GetName());
+
+ auto* buffer = buffers[0].get();
+ EXPECT_TRUE(buffer->GetFormat()->GetType()->IsStruct());
+ EXPECT_EQ(Format::Layout::kStd430, buffer->GetFormat()->GetLayout());
+
+ EXPECT_EQ(1U, buffer->ElementCount());
+ EXPECT_EQ(40U, buffer->GetSizeInBytes());
+
+ const auto* data = buffer->GetValues<uint8_t>();
+ EXPECT_FLOAT_EQ(1.f, *reinterpret_cast<const float*>(data + 8));
+ EXPECT_EQ(64, *reinterpret_cast<const uint32_t*>(data + 16));
+ EXPECT_EQ(128, *reinterpret_cast<const uint32_t*>(data + 28));
+ EXPECT_EQ(220, *reinterpret_cast<const uint32_t*>(data + 36));
+}
+
+TEST_F(AmberScriptParserTest, BufferWithStructPartialInitialization) {
+ std::string in = R"(
+STRUCT my_data
+ uint32 a
+ float b
+ uint32 c
+ uint32 d
+END
+
+BUFFER my_buffer DATA_TYPE my_data STD430 DATA
+ 1 # a
+ 64 # b
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("12: Mismatched number of items in buffer", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, BufferWithStruct_vec_Std140) {
+ std::string in = R"(
+
+STRUCT my_data
+ float a
+ vec3<float> b
+END
+
+BUFFER my_buffer DATA_TYPE my_data STD140 DATA
+ 1 # a
+ 64 128 220 # b
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = parser.GetScript();
+ const auto& buffers = script->GetBuffers();
+ ASSERT_EQ(1U, buffers.size());
+
+ ASSERT_TRUE(buffers[0] != nullptr);
+ EXPECT_EQ("my_buffer", buffers[0]->GetName());
+
+ auto* buffer = buffers[0].get();
+ EXPECT_TRUE(buffer->GetFormat()->GetType()->IsStruct());
+ EXPECT_EQ(Format::Layout::kStd140, buffer->GetFormat()->GetLayout());
+
+ EXPECT_EQ(1U, buffer->ElementCount());
+ EXPECT_EQ(32U, buffer->GetSizeInBytes());
+
+ const auto* data = buffer->GetValues<uint8_t>();
+ EXPECT_FLOAT_EQ(1.f, *reinterpret_cast<const float*>(data + 0));
+ EXPECT_FLOAT_EQ(64, *reinterpret_cast<const float*>(data + 16));
+ EXPECT_FLOAT_EQ(128, *reinterpret_cast<const float*>(data + 20));
+ EXPECT_FLOAT_EQ(220, *reinterpret_cast<const float*>(data + 24));
+}
+
} // namespace amberscript
} // namespace amber
diff --git a/src/amberscript/parser_expect_test.cc b/src/amberscript/parser_expect_test.cc
index 0c345a7..1e4af52 100644
--- a/src/amberscript/parser_expect_test.cc
+++ b/src/amberscript/parser_expect_test.cc
@@ -659,6 +659,44 @@ EXPECT orig_buf IDX 5 EQ 11)";
EXPECT_EQ(11U, probe->GetValues()[0].AsInt32());
}
+TEST_F(AmberScriptParserTest, ExpectEQStruct) {
+ std::string in = R"(
+STRUCT data
+ float a
+ int32 b
+END
+
+BUFFER orig_buf DATA_TYPE data DATA 2.3 44 4.4 99 END
+EXPECT orig_buf IDX 0 EQ 2.3 44
+EXPECT orig_buf IDX 8 EQ 2.3 44)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = parser.GetScript();
+ const auto& commands = script->GetCommands();
+ ASSERT_EQ(2U, commands.size());
+
+ auto* cmd = commands[0].get();
+ ASSERT_TRUE(cmd->IsProbeSSBO());
+
+ auto* probe = cmd->AsProbeSSBO();
+ EXPECT_EQ(ProbeSSBOCommand::Comparator::kEqual, probe->GetComparator());
+ ASSERT_EQ(2U, probe->GetValues().size());
+ EXPECT_EQ(2.3f, probe->GetValues()[0].AsFloat());
+ EXPECT_EQ(44, probe->GetValues()[1].AsInt32());
+
+ cmd = commands[1].get();
+ ASSERT_TRUE(cmd->IsProbeSSBO());
+
+ probe = cmd->AsProbeSSBO();
+ EXPECT_EQ(ProbeSSBOCommand::Comparator::kEqual, probe->GetComparator());
+ ASSERT_EQ(2U, probe->GetValues().size());
+ EXPECT_EQ(2.3f, probe->GetValues()[0].AsFloat());
+ EXPECT_EQ(44, probe->GetValues()[1].AsInt32());
+}
+
TEST_F(AmberScriptParserTest, ExpectEqMissingValue) {
std::string in = R"(
BUFFER orig_buf DATA_TYPE int32 SIZE 100 FILL 11
diff --git a/src/amberscript/parser_struct_test.cc b/src/amberscript/parser_struct_test.cc
new file mode 100644
index 0000000..f940e12
--- /dev/null
+++ b/src/amberscript/parser_struct_test.cc
@@ -0,0 +1,493 @@
+// Copyright 2019 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 parseried.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gtest/gtest.h"
+#include "src/amberscript/parser.h"
+
+namespace amber {
+namespace amberscript {
+
+using AmberScriptParserTest = testing::Test;
+
+TEST_F(AmberScriptParserTest, Struct) {
+ std::string in = R"(
+STRUCT my_struct
+ uint8 first
+ uint32 second
+ vec3<float> third
+ mat2x4<float> forth
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = parser.GetScript();
+ auto type = script->GetType("my_struct");
+ ASSERT_TRUE(type != nullptr);
+ ASSERT_TRUE(type->IsStruct());
+
+ auto s = type->AsStruct();
+ EXPECT_FALSE(s->HasStride());
+
+ const auto& m = s->Members();
+ ASSERT_EQ(4U, m.size());
+ for (size_t i = 0; i < 4; ++i) {
+ ASSERT_TRUE(m[i].type->IsNumber()) << i;
+ EXPECT_FALSE(m[i].HasOffset());
+ EXPECT_FALSE(m[i].HasArrayStride());
+ EXPECT_FALSE(m[i].HasMatrixStride());
+ }
+
+ EXPECT_TRUE(type::Type::IsUint8(m[0].type->AsNumber()->GetFormatMode(),
+ m[0].type->AsNumber()->NumBits()));
+ EXPECT_TRUE(type::Type::IsUint32(m[1].type->AsNumber()->GetFormatMode(),
+ m[1].type->AsNumber()->NumBits()));
+
+ EXPECT_TRUE(m[2].type->IsVec());
+ EXPECT_EQ(3U, m[2].type->RowCount());
+ EXPECT_TRUE(type::Type::IsFloat32(m[2].type->AsNumber()->GetFormatMode(),
+ m[2].type->AsNumber()->NumBits()));
+
+ EXPECT_TRUE(m[3].type->IsMatrix());
+ EXPECT_EQ(4U, m[3].type->RowCount());
+ EXPECT_EQ(2U, m[3].type->ColumnCount());
+ EXPECT_TRUE(type::Type::IsFloat32(m[3].type->AsNumber()->GetFormatMode(),
+ m[3].type->AsNumber()->NumBits()));
+}
+
+TEST_F(AmberScriptParserTest, StructWithDuplicateName) {
+ std::string in = R"(
+STRUCT my_struct
+ uint8 first
+END
+
+STRUCT my_struct
+ float second
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("6: duplicate type name provided", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructWithStride) {
+ std::string in = R"(
+STRUCT my_struct STRIDE 20
+ uint8 first
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = parser.GetScript();
+ auto type = script->GetType("my_struct");
+ ASSERT_TRUE(type != nullptr);
+ ASSERT_TRUE(type->IsStruct());
+
+ auto s = type->AsStruct();
+ EXPECT_TRUE(s->HasStride());
+ EXPECT_EQ(20U, s->StrideInBytes());
+}
+
+TEST_F(AmberScriptParserTest, StructMissingName) {
+ std::string in = R"(
+STRUCT
+ uint8 first
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: invalid STRUCT name provided", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructMissingNameWithStride) {
+ std::string in = R"(
+STRUCT STRIDE 20
+ uint8 first
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("2: missing STRUCT name", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructInvalidName) {
+ std::string in = R"(
+STRUCT 1234 STRIDE 20
+ uint8 first
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("2: invalid STRUCT name provided", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructMissingStrideValue) {
+ std::string in = R"(
+STRUCT foo STRIDE
+ uint8 first
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: missing value for STRIDE", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructInvalidStrideValue) {
+ std::string in = R"(
+STRUCT foo STRIDE abc
+ uint8 first
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("2: invalid value for STRIDE", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructMissingEND) {
+ std::string in = R"(
+STRUCT foo
+ uint8 first
+)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("4: invalid type for STRUCT member", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructExtraParams) {
+ std::string in = R"(
+STRUCT foo STRIDE 20 BAR
+ uint8 first
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("2: extra token BAR after STRUCT header", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructMemberTypeInvalid) {
+ std::string in = R"(
+STRUCT foo
+ 123 first
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: invalid type for STRUCT member", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructMemberTypeUnknown) {
+ std::string in = R"(
+STRUCT foo
+ uint99 first
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: unknown type 'uint99' for STRUCT member", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructMemberNameMissing) {
+ std::string in = R"(
+STRUCT foo
+ uint8
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("4: missing name for STRUCT member", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructMemberNameInvalid) {
+ std::string in = R"(
+STRUCT foo
+ uint8 123
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: invalid name for STRUCT member", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructMemberNameDuplicate) {
+ std::string in = R"(
+STRUCT foo
+ uint8 name
+ uint8 name
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("4: duplicate name for STRUCT member", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructWithEmbeddedStruct) {
+ std::string in = R"(
+STRUCT sub_struct
+ uint8 first
+END
+
+STRUCT my_struct
+ float second
+ sub_struct third
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = parser.GetScript();
+ auto type = script->GetType("my_struct");
+ ASSERT_TRUE(type != nullptr);
+ ASSERT_TRUE(type->IsStruct());
+
+ auto s = type->AsStruct();
+ EXPECT_FALSE(s->HasStride());
+
+ const auto& m = s->Members();
+ ASSERT_EQ(2U, m.size());
+
+ EXPECT_TRUE(m[0].type->IsNumber());
+ EXPECT_TRUE(m[1].type->IsStruct());
+}
+
+TEST_F(AmberScriptParserTest, StructDisallowsRecursiveInclusion) {
+ std::string in = R"(
+STRUCT my_struct
+ float second
+ my_struct third
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("4: recursive types are not allowed", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructMemberWithOffset) {
+ std::string in = R"(
+STRUCT my_struct
+ uint8 first OFFSET 20
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = parser.GetScript();
+ auto type = script->GetType("my_struct");
+ ASSERT_TRUE(type != nullptr);
+ ASSERT_TRUE(type->IsStruct());
+
+ auto s = type->AsStruct();
+ EXPECT_FALSE(s->HasStride());
+
+ const auto& m = s->Members();
+ ASSERT_EQ(1U, m.size());
+ EXPECT_TRUE(m[0].HasOffset());
+ EXPECT_FALSE(m[0].HasArrayStride());
+ EXPECT_FALSE(m[0].HasMatrixStride());
+ EXPECT_EQ(20, m[0].offset_in_bytes);
+}
+
+TEST_F(AmberScriptParserTest, StructMemberOffsetMissingValue) {
+ std::string in = R"(
+STRUCT my_struct
+ uint8 first OFFSET
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("4: missing value for STRUCT member OFFSET", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructMemberOffsetInvalidValue) {
+ std::string in = R"(
+STRUCT my_struct
+ uint8 first OFFSET abcd
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: invalid value for STRUCT member OFFSET", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, DISABLED_StructMemberWithArrayStride) {
+ std::string in = R"(
+STRUCT my_struct
+ uint8 first[2] ARRAY_STRIDE 20
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = parser.GetScript();
+ auto type = script->GetType("my_struct");
+ ASSERT_TRUE(type != nullptr);
+ ASSERT_TRUE(type->IsStruct());
+
+ auto s = type->AsStruct();
+ EXPECT_FALSE(s->HasStride());
+
+ const auto& m = s->Members();
+ ASSERT_EQ(1U, m.size());
+ EXPECT_FALSE(m[0].HasOffset());
+ EXPECT_TRUE(m[0].HasArrayStride());
+ EXPECT_FALSE(m[0].HasMatrixStride());
+ EXPECT_EQ(20, m[0].array_stride_in_bytes);
+}
+
+TEST_F(AmberScriptParserTest, StructMemberArrayStrideMissingValue) {
+ std::string in = R"(
+STRUCT my_struct
+ uint8 first ARRAY_STRIDE
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("4: missing value for STRUCT member ARRAY_STRIDE", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructMemberArrayStrideInvalidValue) {
+ std::string in = R"(
+STRUCT my_struct
+ uint8 first ARRAY_STRIDE abcd
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: invalid value for STRUCT member ARRAY_STRIDE", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StrictInvalidTypeWithArrayStride) {
+ std::string in = R"(
+STRUCT s
+ uint32 a ARRAY_STRIDE 10
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: ARRAY_STRIDE only valid on array members", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructMemberWithMatrixStride) {
+ std::string in = R"(
+STRUCT my_struct
+ mat2x2<float> first MATRIX_STRIDE 20
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = parser.GetScript();
+ auto type = script->GetType("my_struct");
+ ASSERT_TRUE(type != nullptr);
+ ASSERT_TRUE(type->IsStruct());
+
+ auto s = type->AsStruct();
+ EXPECT_FALSE(s->HasStride());
+
+ const auto& m = s->Members();
+ ASSERT_EQ(1U, m.size());
+ EXPECT_FALSE(m[0].HasOffset());
+ EXPECT_FALSE(m[0].HasArrayStride());
+ EXPECT_TRUE(m[0].HasMatrixStride());
+ EXPECT_EQ(20, m[0].matrix_stride_in_bytes);
+}
+
+TEST_F(AmberScriptParserTest, StructMemberMatrixStrideMissingValue) {
+ std::string in = R"(
+STRUCT my_struct
+ mat2x2<float> first MATRIX_STRIDE
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("4: missing value for STRUCT member MATRIX_STRIDE", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructMemberMatrixStrideInvalidValue) {
+ std::string in = R"(
+STRUCT my_struct
+ mat2x2<float> first MATRIX_STRIDE abcd
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: invalid value for STRUCT member MATRIX_STRIDE", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructInvalidTypeWithMatrixStride) {
+ std::string in = R"(
+STRUCT s
+ uint32 a MATRIX_STRIDE 10
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: MATRIX_STRIDE only valid on matrix members", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructMemberExtraParam) {
+ std::string in = R"(
+STRUCT my_struct
+ uint8 first 1234
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: extra param for STRUCT member", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, StructMemberUnknownParam) {
+ std::string in = R"(
+STRUCT my_struct
+ uint8 first UNKNOWN
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: unknown param 'UNKNOWN' for STRUCT member", r.Error());
+}
+
+} // namespace amberscript
+} // namespace amber
diff --git a/src/buffer.cc b/src/buffer.cc
index c8b8f96..1b9c351 100644
--- a/src/buffer.cc
+++ b/src/buffer.cc
@@ -270,6 +270,8 @@ Result Buffer::SetDataWithOffset(const std::vector<Value>& data,
Value v = data[i++];
ptr += WriteValueFromComponent(v, seg.GetFormatMode(), seg.GetNumBits(),
ptr);
+ if (i >= data.size())
+ break;
}
}
return {};
diff --git a/src/format.cc b/src/format.cc
index 221bcff..9aeb190 100644
--- a/src/format.cc
+++ b/src/format.cc
@@ -97,6 +97,9 @@ uint32_t Format::InputNeededPerElement() const {
}
void Format::SetLayout(Layout layout) {
+ if (layout == layout_)
+ return;
+
layout_ = layout;
RebuildSegments();
}
diff --git a/src/script.cc b/src/script.cc
index 7106194..4fa6f45 100644
--- a/src/script.cc
+++ b/src/script.cc
@@ -14,6 +14,8 @@
#include "src/script.h"
+#include "src/type_parser.h"
+
namespace amber {
Script::Script() = default;
@@ -89,4 +91,18 @@ bool Script::IsKnownFeature(const std::string& name) const {
name == "VariablePointerFeatures.variablePointersStorageBuffer";
}
+type::Type* Script::ParseType(const std::string& str) {
+ auto type = GetType(str);
+ if (type)
+ return type;
+
+ TypeParser parser;
+ auto new_type = parser.Parse(str);
+ if (new_type != nullptr) {
+ type = new_type.get();
+ RegisterType(std::move(new_type));
+ }
+ return type;
+}
+
} // namespace amber
diff --git a/src/script.h b/src/script.h
index 51cf70e..cb28873 100644
--- a/src/script.h
+++ b/src/script.h
@@ -183,6 +183,24 @@ class Script : public RecipeImpl {
return types_.back().get();
}
+ /// Adds |type| to the list of known types. The |type| must have
+ /// a unique name over all types in the script.
+ Result AddType(const std::string& name, std::unique_ptr<type::Type> type) {
+ if (name_to_type_.count(name) > 0)
+ return Result("duplicate type name provided");
+
+ name_to_type_[name] = std::move(type);
+ return {};
+ }
+
+ /// Retrieves the type with |name|, |nullptr| if not found.
+ type::Type* GetType(const std::string& name) const {
+ auto it = name_to_type_.find(name);
+ return it == name_to_type_.end() ? nullptr : it->second.get();
+ }
+
+ type::Type* ParseType(const std::string& str);
+
private:
struct {
std::vector<std::string> required_features;
@@ -195,6 +213,7 @@ class Script : public RecipeImpl {
std::map<std::string, Shader*> name_to_shader_;
std::map<std::string, Buffer*> name_to_buffer_;
std::map<std::string, Pipeline*> name_to_pipeline_;
+ std::map<std::string, std::unique_ptr<type::Type>> name_to_type_;
std::vector<std::unique_ptr<Shader>> shaders_;
std::vector<std::unique_ptr<Command>> commands_;
std::vector<std::unique_ptr<Buffer>> buffers_;
diff --git a/src/script_test.cc b/src/script_test.cc
index e8f7294..0450051 100644
--- a/src/script_test.cc
+++ b/src/script_test.cc
@@ -306,4 +306,36 @@ TEST_F(ScriptTest,
s.GetRequiredInstanceExtensions()[0]);
}
+TEST_F(ScriptTest, AddType) {
+ Script s;
+ Result r = s.AddType("my_type", type::Number::Float(32));
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+}
+
+TEST_F(ScriptTest, AddDuplicateType) {
+ Script s;
+ Result r = s.AddType("my_type", type::Number::Uint(8));
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = s.AddType("my_type", type::Number::Uint(8));
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("duplicate type name provided", r.Error());
+}
+
+TEST_F(ScriptTest, GetType) {
+ auto type = type::Number::Uint(8);
+ auto* ptr = type.get();
+
+ Script s;
+ Result r = s.AddType("my_type", std::move(type));
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ EXPECT_TRUE(ptr->Equal(s.GetType("my_type")));
+}
+
+TEST_F(ScriptTest, GetMissingType) {
+ Script s;
+ EXPECT_TRUE(s.GetPipeline("my_type") == nullptr);
+}
+
} // namespace amber
diff --git a/src/type.h b/src/type.h
index 93aba68..c63779b 100644
--- a/src/type.h
+++ b/src/type.h
@@ -17,6 +17,7 @@
#include <cassert>
#include <memory>
+#include <string>
#include <vector>
#include "src/format_data.h"
@@ -229,6 +230,7 @@ class List : public Type {
class Struct : public Type {
public:
struct Member {
+ std::string name;
Type* type;
int32_t offset_in_bytes = -1;
int32_t array_stride_in_bytes = -1;
diff --git a/tests/cases/struct.amber b/tests/cases/struct.amber
new file mode 100644
index 0000000..a69f683
--- /dev/null
+++ b/tests/cases/struct.amber
@@ -0,0 +1,52 @@
+#!amber
+# Copyright 2019 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
+#
+# https://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.
+
+SHADER vertex vert_shader PASSTHROUGH
+SHADER fragment frag_shader GLSL
+#version 430
+
+layout(location = 0) out vec4 color_out;
+layout(std140, binding = 0) readonly buffer Data {
+ float a;
+ float b;
+} data;
+
+void main() {
+ color_out = vec4(data.a/255, data.b/255, data.a/255, data.b/255);
+}
+END
+
+STRUCT my_data
+ float a
+ float b
+END
+
+BUFFER data DATA_TYPE my_data STD140 DATA
+ 1 # a
+220 # b
+END
+
+BUFFER framebuffer FORMAT B8G8R8A8_UNORM
+
+PIPELINE graphics my_pipeline
+ ATTACH vert_shader
+ ATTACH frag_shader
+
+ BIND BUFFER framebuffer AS color LOCATION 0
+ BIND BUFFER data AS storage DESCRIPTOR_SET 0 BINDING 0
+END
+
+RUN my_pipeline DRAW_RECT POS 0 0 SIZE 250 250
+EXPECT framebuffer IDX 0 0 SIZE 250 250 EQ_RGBA 1 220 1 220
diff --git a/tests/cases/struct_compare.amber b/tests/cases/struct_compare.amber
new file mode 100644
index 0000000..d1fc468
--- /dev/null
+++ b/tests/cases/struct_compare.amber
@@ -0,0 +1,82 @@
+#!amber
+# Copyright 2019 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
+#
+# https://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.
+
+DEVICE_FEATURE fragmentStoresAndAtomics
+
+SHADER vertex vert_shader PASSTHROUGH
+SHADER fragment frag_shader GLSL
+#version 430
+
+struct SubData {
+ int c;
+ float d;
+};
+
+layout(location = 0) out vec4 color_out;
+layout(std140, binding = 0) readonly buffer InData {
+ float a;
+ int b;
+ SubData e;
+} in_data;
+
+layout(std140, binding = 1) writeonly buffer OutData {
+ float a;
+ int b;
+ SubData e;
+} out_data;
+
+void main() {
+ color_out = vec4(1, 1, 1, 1);
+ out_data.a = in_data.a;
+ out_data.b = in_data.b;
+ out_data.e.c = in_data.e.c;
+ out_data.e.d = in_data.e.d;
+}
+END
+
+STRUCT sub_data
+ int32 c
+ float d
+END
+
+STRUCT my_data
+ float a
+ int32 b
+ sub_data e
+END
+
+BUFFER in_data DATA_TYPE my_data STD140 DATA
+1.1 # a
+220 # b
+1024 # e.c
+2.4 # e.d
+END
+
+BUFFER out_data DATA_TYPE my_data STD140 SIZE 1 FILL 0
+
+BUFFER framebuffer FORMAT B8G8R8A8_UNORM
+
+PIPELINE graphics my_pipeline
+ ATTACH vert_shader
+ ATTACH frag_shader
+
+ BIND BUFFER framebuffer AS color LOCATION 0
+ BIND BUFFER in_data AS storage DESCRIPTOR_SET 0 BINDING 0
+ BIND BUFFER out_data AS storage DESCRIPTOR_SET 0 BINDING 1
+END
+
+RUN my_pipeline DRAW_RECT POS 0 0 SIZE 250 250
+EXPECT out_data EQ_BUFFER in_data
+EXPECT out_data IDX 0 EQ 1.1 220 1024 2.4
diff --git a/tests/cases/struct_embedded.amber b/tests/cases/struct_embedded.amber
new file mode 100644
index 0000000..63a8051
--- /dev/null
+++ b/tests/cases/struct_embedded.amber
@@ -0,0 +1,67 @@
+#!amber
+# Copyright 2019 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
+#
+# https://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.
+
+SHADER vertex vert_shader PASSTHROUGH
+SHADER fragment frag_shader GLSL
+#version 430
+
+struct s {
+ float d;
+ float e;
+};
+
+layout(location = 0) out vec4 color_out;
+layout(std140, binding = 0) readonly buffer Data {
+ float a;
+ float b;
+ s c;
+} data;
+
+void main() {
+ color_out = vec4(data.a/255, data.b/255, data.c.d/255, data.c.e/255);
+}
+END
+
+STRUCT s
+ float d
+ float e
+END
+
+STRUCT my_data
+ float a
+ float b
+ s c
+END
+
+
+BUFFER data DATA_TYPE my_data STD140 DATA
+ 1 # a
+ 64 # b
+128 # c.d
+220 # c.e
+END
+
+BUFFER framebuffer FORMAT B8G8R8A8_UNORM
+
+PIPELINE graphics my_pipeline
+ ATTACH vert_shader
+ ATTACH frag_shader
+
+ BIND BUFFER framebuffer AS color LOCATION 0
+ BIND BUFFER data AS storage DESCRIPTOR_SET 0 BINDING 0
+END
+
+RUN my_pipeline DRAW_RECT POS 0 0 SIZE 250 250
+EXPECT framebuffer IDX 0 0 SIZE 250 250 EQ_RGBA 1 64 128 220
diff --git a/tests/cases/struct_embedded_padded_430.amber b/tests/cases/struct_embedded_padded_430.amber
new file mode 100644
index 0000000..174f005
--- /dev/null
+++ b/tests/cases/struct_embedded_padded_430.amber
@@ -0,0 +1,73 @@
+#!amber
+# Copyright 2019 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
+#
+# https://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.
+
+SHADER vertex vert_shader PASSTHROUGH
+SHADER fragment frag_shader GLSL
+#version 430
+
+struct s {
+ float space4;
+ float space5;
+ int d;
+ float space6;
+ int e;
+};
+
+layout(location = 0) out vec4 color_out;
+layout(std430, binding = 0) readonly buffer Data {
+ float space1;
+ float space2;
+ float a;
+ float space3;
+ int b;
+ s c;
+} data;
+
+void main() {
+ color_out = vec4(float(data.a)/255, float(data.b)/255,
+ float(data.c.d)/255, float(data.c.e)/255);
+}
+END
+
+STRUCT s
+ int32 d OFFSET 8
+ int32 e OFFSET 16
+END
+
+STRUCT my_data
+ float a OFFSET 8
+ int32 b OFFSET 16
+ s c
+END
+
+BUFFER data DATA_TYPE my_data STD430 DATA
+ 1 # a
+ 64 # b
+128 # c.d
+220 # c.e
+END
+
+BUFFER framebuffer FORMAT B8G8R8A8_UNORM
+
+PIPELINE graphics my_pipeline
+ ATTACH vert_shader
+ ATTACH frag_shader
+
+ BIND BUFFER framebuffer AS color LOCATION 0
+ BIND BUFFER data AS storage DESCRIPTOR_SET 0 BINDING 0
+END
+
+RUN my_pipeline DRAW_RECT POS 0 0 SIZE 250 250
+EXPECT framebuffer IDX 0 0 SIZE 250 250 EQ_RGBA 1 64 128 220