Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion docs/amber_script.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,12 @@ BUFFER {name} DATA_TYPE {type} {STD140 | STD430} SIZE _size_in_items_ \
# Creates a buffer which will store the given `FORMAT` of data. These
# buffers are used as image and depth buffers in the `PIPELINE` commands.
# The buffer will be sized based on the `RENDER_SIZE` of the `PIPELINE`.
# For multisampled images use value greater than one for `SAMPLES`. Allowed
# sample counts are 1, 2, 4, 8, 16, 32, and 64. Note that Amber doesn't
# preserve multisampled images across pipelines.
BUFFER {name} FORMAT {format_string} \
[ MIP_LEVELS _mip_levels_ (default 1) ]
[ MIP_LEVELS _mip_levels_ (default 1) ] \
[ SAMPLES _samples_ (default 1) ]

# Load buffer data from a PNG image with file name specified by `FILE`.
# The file path is relative to the script file being run. Format specified
Expand All @@ -266,6 +270,7 @@ attributes.
# Specify an image buffer with a format. HEIGHT is necessary for DIM_2D and
# DIM_3D. DEPTH is necessary for DIM_3D.
IMAGE {name} FORMAT {format_string} [ MIP_LEVELS _mip_levels_ (default 1) ] \
[ SAMPLES _samples_ (default 1) ] \
{dimensionality} \
WIDTH {w} [ HEIGHT {h} [ DEPTH {d} ] ] \
{initializer}
Expand Down
199 changes: 107 additions & 92 deletions src/amberscript/parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "src/amberscript/parser.h"

#include <algorithm>
#include <cassert>
#include <limits>
#include <map>
Expand Down Expand Up @@ -141,17 +142,6 @@ AddressMode StrToAddressMode(std::string str) {
return AddressMode::kUnknown;
}

ImageDimension StrToImageDimension(const std::string& str) {
if (str == "DIM_1D")
return ImageDimension::k1D;
if (str == "DIM_2D")
return ImageDimension::k2D;
if (str == "DIM_3D")
return ImageDimension::k3D;

return ImageDimension::kUnknown;
}

CompareOp StrToCompareOp(const std::string& str) {
if (str == "never")
return CompareOp::kNever;
Expand Down Expand Up @@ -263,6 +253,13 @@ Result ParseBufferData(Buffer* buffer,
return {};
}

constexpr uint32_t valid_samples[] = {1, 2, 4, 8, 16, 32, 64};

bool IsValidSampleCount(uint32_t samples) {
return (std::find(std::begin(valid_samples), std::end(valid_samples),
samples) != std::end(valid_samples));
}

} // namespace

Parser::Parser() : amber::Parser(nullptr) {}
Expand Down Expand Up @@ -1811,6 +1808,17 @@ Result Parser::ParseBuffer() {

if (!r.IsSuccess())
return r;
} else if (token->AsString() == "SAMPLES") {
tokenizer_->NextToken();
token = tokenizer_->NextToken();
if (!token->IsInteger())
return Result("expected integer value for SAMPLES");

const uint32_t samples = token->AsUint32();
if (!IsValidSampleCount(samples))
return Result("invalid sample count: " + token->ToOriginalString());

buffer->SetSamples(samples);
} else {
break;
}
Expand All @@ -1837,110 +1845,117 @@ Result Parser::ParseImage() {
if (name == "DATA_TYPE" || name == "FORMAT")
return Result("missing IMAGE name");

token = tokenizer_->NextToken();
if (!token->IsIdentifier())
return Result("invalid IMAGE command provided");

std::unique_ptr<Buffer> buffer = MakeUnique<Buffer>();
buffer->SetName(name);
auto& cmd = token->AsString();
if (cmd == "DATA_TYPE") {
token = tokenizer_->NextToken();
if (!token->IsIdentifier())
return Result("IMAGE invalid data type");
bool width_set = false;
bool height_set = false;
bool depth_set = false;

auto type = script_->ParseType(token->AsString());
std::unique_ptr<Format> fmt;
if (type != nullptr) {
fmt = MakeUnique<Format>(type);
buffer->SetFormat(fmt.get());
} else {
auto new_type = ToType(token->AsString());
if (!new_type)
return Result("invalid data type '" + token->AsString() + "' provided");
token = tokenizer_->PeekNextToken();
while (token->IsIdentifier()) {
if (token->AsString() == "FILL" || token->AsString() == "SERIES_FROM")
break;

fmt = MakeUnique<Format>(new_type.get());
buffer->SetFormat(fmt.get());
script_->RegisterType(std::move(new_type));
}
script_->RegisterFormat(std::move(fmt));
} else if (cmd == "FORMAT") {
token = tokenizer_->NextToken();
if (!token->IsIdentifier())
return Result("IMAGE FORMAT must be an identifier");
tokenizer_->NextToken();

auto type = script_->ParseType(token->AsString());
if (!type)
return Result("invalid IMAGE FORMAT");
if (token->AsString() == "DATA_TYPE") {
token = tokenizer_->NextToken();
if (!token->IsIdentifier())
return Result("IMAGE invalid data type");

auto fmt = MakeUnique<Format>(type);
buffer->SetFormat(fmt.get());
script_->RegisterFormat(std::move(fmt));
auto type = script_->ParseType(token->AsString());
std::unique_ptr<Format> fmt;
if (type != nullptr) {
fmt = MakeUnique<Format>(type);
buffer->SetFormat(fmt.get());
} else {
auto new_type = ToType(token->AsString());
if (!new_type) {
return Result("invalid data type '" + token->AsString() +
"' provided");
}

token = tokenizer_->PeekNextToken();
if (token->IsIdentifier() && token->AsString() == "MIP_LEVELS") {
tokenizer_->NextToken();
fmt = MakeUnique<Format>(new_type.get());
buffer->SetFormat(fmt.get());
script_->RegisterType(std::move(new_type));
}
script_->RegisterFormat(std::move(fmt));
} else if (token->AsString() == "FORMAT") {
token = tokenizer_->NextToken();
if (!token->IsIdentifier())
return Result("IMAGE FORMAT must be an identifier");

auto type = script_->ParseType(token->AsString());
if (!type)
return Result("invalid IMAGE FORMAT");

auto fmt = MakeUnique<Format>(type);
buffer->SetFormat(fmt.get());
script_->RegisterFormat(std::move(fmt));
} else if (token->AsString() == "MIP_LEVELS") {
token = tokenizer_->NextToken();

if (!token->IsInteger())
return Result("invalid value for MIP_LEVELS");

buffer->SetMipLevels(token->AsUint32());
}
} else {
return Result("unknown IMAGE command provided: " + cmd);
}

token = tokenizer_->NextToken();
if (!token->IsIdentifier()) {
return Result("IMAGE dimensionality must be an identifier: " +
token->ToOriginalString());
}
} else if (token->AsString() == "DIM_1D") {
buffer->SetImageDimension(ImageDimension::k1D);
} else if (token->AsString() == "DIM_2D") {
buffer->SetImageDimension(ImageDimension::k2D);
} else if (token->AsString() == "DIM_3D") {
buffer->SetImageDimension(ImageDimension::k3D);
} else if (token->AsString() == "WIDTH") {
token = tokenizer_->NextToken();
if (!token->IsInteger() || token->AsUint32() == 0)
return Result("expected positive IMAGE WIDTH");

auto dim = StrToImageDimension(token->AsString());
if (dim == ImageDimension::kUnknown)
return Result("unknown IMAGE dimensionality");
buffer->SetImageDimension(dim);
buffer->SetWidth(token->AsUint32());
width_set = true;
} else if (token->AsString() == "HEIGHT") {
token = tokenizer_->NextToken();
if (!token->IsInteger() || token->AsUint32() == 0)
return Result("expected positive IMAGE HEIGHT");

token = tokenizer_->NextToken();
if (!token->IsIdentifier() || token->AsString() != "WIDTH")
return Result("expected IMAGE WIDTH");
buffer->SetHeight(token->AsUint32());
height_set = true;
} else if (token->AsString() == "DEPTH") {
token = tokenizer_->NextToken();
if (!token->IsInteger() || token->AsUint32() == 0)
return Result("expected positive IMAGE DEPTH");

// Parse image dimensions.
uint32_t width = 1;
uint32_t height = 1;
uint32_t depth = 1;
token = tokenizer_->NextToken();
if (!token->IsInteger() || token->AsUint32() == 0)
return Result("expected positive IMAGE WIDTH");
width = token->AsUint32();
buffer->SetWidth(width);
buffer->SetDepth(token->AsUint32());
depth_set = true;
} else if (token->AsString() == "SAMPLES") {
token = tokenizer_->NextToken();
if (!token->IsInteger())
return Result("expected integer value for SAMPLES");

if (dim == ImageDimension::k2D || dim == ImageDimension::k3D) {
token = tokenizer_->NextToken();
if (!token->IsIdentifier() || token->AsString() != "HEIGHT")
return Result("expected IMAGE HEIGHT");
const uint32_t samples = token->AsUint32();
if (!IsValidSampleCount(samples))
return Result("invalid sample count: " + token->ToOriginalString());

token = tokenizer_->NextToken();
if (!token->IsInteger() || token->AsUint32() == 0)
return Result("expected positive IMAGE HEIGHT");
height = token->AsUint32();
buffer->SetHeight(height);
buffer->SetSamples(samples);
} else {
return Result("unknown IMAGE command provided: " +
token->ToOriginalString());
}
token = tokenizer_->PeekNextToken();
}

if (dim == ImageDimension::k3D) {
token = tokenizer_->NextToken();
if (!token->IsIdentifier() || token->AsString() != "DEPTH")
return Result("expected IMAGE DEPTH");
if (buffer->GetImageDimension() == ImageDimension::k3D && !depth_set)
return Result("expected IMAGE DEPTH");

token = tokenizer_->NextToken();
if (!token->IsInteger() || token->AsUint32() == 0)
return Result("expected positive IMAGE DEPTH");
depth = token->AsUint32();
buffer->SetDepth(depth);
if ((buffer->GetImageDimension() == ImageDimension::k3D ||
buffer->GetImageDimension() == ImageDimension::k2D) &&
!height_set) {
return Result("expected IMAGE HEIGHT");
}
if (!width_set)
return Result("expected IMAGE WIDTH");

const uint32_t size_in_items = width * height * depth;
const uint32_t size_in_items =
buffer->GetWidth() * buffer->GetHeight() * buffer->GetDepth();
buffer->SetElementCount(size_in_items);

// Parse initializers.
Expand Down
23 changes: 22 additions & 1 deletion src/amberscript/parser_buffer_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,23 @@ TEST_F(AmberScriptParserTest, BufferFormat) {
}
}

TEST_F(AmberScriptParserTest, BufferSamples) {
std::string in = "BUFFER my_buf FORMAT R8G8B8A8_UNORM SAMPLES 2";

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);
auto* buffer = buffers[0].get();
EXPECT_EQ("my_buf", buffer->GetName());
EXPECT_EQ(2u, buffer->GetSamples());
}

struct BufferParseError {
const char* in;
const char* err;
Expand Down Expand Up @@ -690,7 +707,11 @@ INSTANTIATE_TEST_SUITE_P(
BufferParseError{"BUFFER my_buf DATA_TYPE int32 SIZE 5 FILL 5\nBUFFER "
"my_buf DATA_TYPE int16 SIZE 5 FILL 2",
// NOLINTNEXTLINE(whitespace/parens)
"2: duplicate buffer name provided"}));
"2: duplicate buffer name provided"},
BufferParseError{"BUFFER my_buf FORMAT R8G8B8A8_UNORM SAMPLES 9",
"1: invalid sample count: 9"},
BufferParseError{"BUFFER my_buf FORMAT R8G8B8A8_UNORM SAMPLES foo",
"1: expected integer value for SAMPLES"}));

struct BufferData {
const char* name;
Expand Down
45 changes: 43 additions & 2 deletions src/amberscript/parser_image_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ IMAGE image DATA_TYPE uint32 DIM_WRONG
Parser parser;
Result r = parser.Parse(in);
ASSERT_FALSE(r.IsSuccess());
EXPECT_EQ("2: unknown IMAGE dimensionality", r.Error());
EXPECT_EQ("2: unknown IMAGE command provided: DIM_WRONG", r.Error());
}

TEST_F(AmberScriptParserTest, ImageDimensionalityInvalid2) {
Expand All @@ -127,7 +127,7 @@ IMAGE image DATA_TYPE uint32 4
Parser parser;
Result r = parser.Parse(in);
ASSERT_FALSE(r.IsSuccess());
EXPECT_EQ("2: IMAGE dimensionality must be an identifier: 4", r.Error());
EXPECT_EQ("2: expected IMAGE WIDTH", r.Error());
}

TEST_F(AmberScriptParserTest, ImageWidthMissing) {
Expand Down Expand Up @@ -244,6 +244,47 @@ IMAGE image DATA_TYPE uint32 DIM_2D WIDTH 3 HEIGHT 4
EXPECT_EQ(12, buffer->ElementCount());
}

TEST_F(AmberScriptParserTest, Image2DMultiSample) {
std::string in = R"(
IMAGE image DATA_TYPE uint32 DIM_2D WIDTH 3 HEIGHT 4 SAMPLES 4
)";

Parser parser;
Result r = parser.Parse(in);
ASSERT_TRUE(r.IsSuccess());
auto script = parser.GetScript();
const auto& buffers = script->GetBuffers();
ASSERT_EQ(1U, buffers.size());

ASSERT_TRUE(buffers[0] != nullptr);
EXPECT_EQ("image", buffers[0]->GetName());

auto* buffer = buffers[0].get();
EXPECT_EQ(4, buffer->GetSamples());
}

TEST_F(AmberScriptParserTest, Image2DInvalidSampleValue) {
std::string in = R"(
IMAGE image DATA_TYPE uint32 DIM_2D WIDTH 3 HEIGHT 4 SAMPLES foo
)";

Parser parser;
Result r = parser.Parse(in);
ASSERT_FALSE(r.IsSuccess());
EXPECT_EQ("2: expected integer value for SAMPLES", r.Error());
}

TEST_F(AmberScriptParserTest, Image2DInvalidSampleCount) {
std::string in = R"(
IMAGE image DATA_TYPE uint32 DIM_2D WIDTH 3 HEIGHT 4 SAMPLES 5
)";

Parser parser;
Result r = parser.Parse(in);
ASSERT_FALSE(r.IsSuccess());
EXPECT_EQ("2: invalid sample count: 5", r.Error());
}

TEST_F(AmberScriptParserTest, Image3D) {
std::string in = R"(
IMAGE image DATA_TYPE uint32 DIM_3D WIDTH 3 HEIGHT 4 DEPTH 5
Expand Down
Loading