blob: 3a65af7667337b9d06282f38094d11e9f85df9f8 [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/amberscript/parser.h"
#include <cassert>
#include <limits>
#include <string>
#include <utility>
#include <vector>
#include "src/format_parser.h"
#include "src/make_unique.h"
#include "src/shader_data.h"
#include "src/tokenizer.h"
namespace amber {
namespace amberscript {
namespace {
bool IsComparator(const std::string& in) {
return in == "EQ" || in == "NE" || in == "GT" || in == "LT" || in == "GE" ||
in == "LE";
}
ProbeSSBOCommand::Comparator ToComparator(const std::string& in) {
if (in == "EQ")
return ProbeSSBOCommand::Comparator::kEqual;
if (in == "NE")
return ProbeSSBOCommand::Comparator::kNotEqual;
if (in == "GT")
return ProbeSSBOCommand::Comparator::kGreater;
if (in == "LT")
return ProbeSSBOCommand::Comparator::kLess;
if (in == "GE")
return ProbeSSBOCommand::Comparator::kGreaterOrEqual;
assert(in == "LE");
return ProbeSSBOCommand::Comparator::kLessOrEqual;
}
} // namespace
Parser::Parser() : amber::Parser() {}
Parser::~Parser() = default;
std::string Parser::make_error(const std::string& err) {
return std::to_string(tokenizer_->GetCurrentLine()) + ": " + err;
}
Result Parser::Parse(const std::string& data) {
tokenizer_ = MakeUnique<Tokenizer>(data);
for (auto token = tokenizer_->NextToken(); !token->IsEOS();
token = tokenizer_->NextToken()) {
if (token->IsEOL())
continue;
if (!token->IsString())
return Result(make_error("expected string"));
Result r;
std::string tok = token->AsString();
if (IsRepeatable(tok)) {
r = ParseRepeatableCommand(tok);
} else if (tok == "BUFFER") {
r = ParseBuffer();
} else if (tok == "DERIVE_PIPELINE") {
r = ParseDerivePipelineBlock();
} else if (tok == "DEVICE_FEATURE") {
r = ParseDeviceFeature();
} else if (tok == "DEVICE_EXTENSION") {
r = ParseDeviceExtension();
} else if (tok == "INSTANCE_EXTENSION") {
r = ParseInstanceExtension();
} else if (tok == "PIPELINE") {
r = ParsePipelineBlock();
} else if (tok == "REPEAT") {
r = ParseRepeat();
} else if (tok == "SET") {
r = ParseSet();
} else if (tok == "SHADER") {
r = ParseShaderBlock();
} else {
r = Result("unknown token: " + tok);
}
if (!r.IsSuccess())
return Result(make_error(r.Error()));
}
script_->SetCommands(std::move(command_list_));
// Generate any needed color attachments. This is done before
// validating in case one of the pipelines specifies the framebuffer size
// it needs to be verified against all other pipelines.
for (const auto& pipeline : script_->GetPipelines()) {
// Add a color attachment if needed
if (pipeline->GetColorAttachments().empty()) {
auto* buf = script_->GetBuffer(Pipeline::kGeneratedColorBuffer);
if (!buf) {
auto new_buf = pipeline->GenerateDefaultColorAttachmentBuffer();
buf = new_buf.get();
Result r = script_->AddBuffer(std::move(new_buf));
if (!r.IsSuccess())
return r;
}
Result r = pipeline->AddColorAttachment(buf, 0);
if (!r.IsSuccess())
return r;
}
}
// Validate all the pipelines at the end. This allows us to verify the
// framebuffer sizes are consistent over pipelines.
for (const auto& pipeline : script_->GetPipelines()) {
Result r = pipeline->Validate();
if (!r.IsSuccess())
return r;
}
return {};
}
bool Parser::IsRepeatable(const std::string& name) const {
return name == "CLEAR" || name == "CLEAR_COLOR" || name == "COPY" ||
name == "EXPECT" || name == "RUN";
}
// The given |name| must be one of the repeatable commands or this method
// returns an error result.
Result Parser::ParseRepeatableCommand(const std::string& name) {
if (name == "CLEAR")
return ParseClear();
if (name == "CLEAR_COLOR")
return ParseClearColor();
if (name == "COPY")
return ParseCopy();
if (name == "EXPECT")
return ParseExpect();
if (name == "RUN")
return ParseRun();
return Result("invalid repeatable command: " + name);
}
Result Parser::ToShaderType(const std::string& str, ShaderType* type) {
assert(type);
if (str == "vertex")
*type = kShaderTypeVertex;
else if (str == "fragment")
*type = kShaderTypeFragment;
else if (str == "geometry")
*type = kShaderTypeGeometry;
else if (str == "tessellation_evaluation")
*type = kShaderTypeTessellationEvaluation;
else if (str == "tessellation_control")
*type = kShaderTypeTessellationControl;
else if (str == "compute")
*type = kShaderTypeCompute;
else if (str == "multi")
*type = kShaderTypeMulti;
else
return Result("unknown shader type: " + str);
return {};
}
Result Parser::ToShaderFormat(const std::string& str, ShaderFormat* fmt) {
assert(fmt);
if (str == "GLSL")
*fmt = kShaderFormatGlsl;
else if (str == "HLSL")
*fmt = kShaderFormatHlsl;
else if (str == "SPIRV-ASM")
*fmt = kShaderFormatSpirvAsm;
else if (str == "SPIRV-HEX")
*fmt = kShaderFormatSpirvHex;
else if (str == "OPENCL-C")
*fmt = kShaderFormatOpenCLC;
else
return Result("unknown shader format: " + str);
return {};
}
Result Parser::ToPipelineType(const std::string& str, PipelineType* type) {
assert(type);
if (str == "compute")
*type = PipelineType::kCompute;
else if (str == "graphics")
*type = PipelineType::kGraphics;
else
return Result("unknown pipeline type: " + str);
return {};
}
Result Parser::ToDatumType(const std::string& str, DatumType* type) {
assert(type);
if (str == "int8") {
type->SetType(DataType::kInt8);
} else if (str == "int16") {
type->SetType(DataType::kInt16);
} else if (str == "int32") {
type->SetType(DataType::kInt32);
} else if (str == "int64") {
type->SetType(DataType::kInt64);
} else if (str == "uint8") {
type->SetType(DataType::kUint8);
} else if (str == "uint16") {
type->SetType(DataType::kUint16);
} else if (str == "uint32") {
type->SetType(DataType::kUint32);
} else if (str == "uint64") {
type->SetType(DataType::kUint64);
} else if (str == "float") {
type->SetType(DataType::kFloat);
} else if (str == "double") {
type->SetType(DataType::kDouble);
} else if (str.length() > 7 && str.substr(0, 3) == "vec") {
if (str[4] != '<' || str[str.length() - 1] != '>')
return Result("invalid data_type provided");
if (str[3] == '2')
type->SetRowCount(2);
else if (str[3] == '3')
type->SetRowCount(3);
else if (str[3] == '4')
type->SetRowCount(4);
else
return Result("invalid data_type provided");
DatumType subtype;
Result r = ToDatumType(str.substr(5, str.length() - 6), &subtype);
if (!r.IsSuccess())
return r;
if (subtype.RowCount() > 1 || subtype.ColumnCount() > 1)
return Result("invalid data_type provided");
type->SetType(subtype.GetType());
} else if (str.length() > 9 && str.substr(0, 3) == "mat") {
if (str[4] != 'x' || str[6] != '<' || str[str.length() - 1] != '>')
return Result("invalid data_type provided");
if (str[3] == '2')
type->SetColumnCount(2);
else if (str[3] == '3')
type->SetColumnCount(3);
else if (str[3] == '4')
type->SetColumnCount(4);
else
return Result("invalid data_type provided");
if (str[5] == '2')
type->SetRowCount(2);
else if (str[5] == '3')
type->SetRowCount(3);
else if (str[5] == '4')
type->SetRowCount(4);
else
return Result("invalid data_type provided");
DatumType subtype;
Result r = ToDatumType(str.substr(7, str.length() - 8), &subtype);
if (!r.IsSuccess())
return r;
if (subtype.RowCount() > 1 || subtype.ColumnCount() > 1)
return Result("invalid data_type provided");
type->SetType(subtype.GetType());
} else {
return Result("invalid data_type provided");
}
return {};
}
Result Parser::ValidateEndOfStatement(const std::string& name) {
auto token = tokenizer_->NextToken();
if (token->IsEOL() || token->IsEOS())
return {};
return Result("extra parameters after " + name);
}
Result Parser::ParseShaderBlock() {
auto token = tokenizer_->NextToken();
if (!token->IsString())
return Result("invalid token when looking for shader type");
ShaderType type = kShaderTypeVertex;
Result r = ToShaderType(token->AsString(), &type);
if (!r.IsSuccess())
return r;
auto shader = MakeUnique<Shader>(type);
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("invalid token when looking for shader name");
shader->SetName(token->AsString());
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("invalid token when looking for shader format");
std::string fmt = token->AsString();
if (fmt == "PASSTHROUGH") {
if (type != kShaderTypeVertex) {
return Result(
"invalid shader type for PASSTHROUGH. Only vertex "
"PASSTHROUGH allowed");
}
shader->SetFormat(kShaderFormatSpirvAsm);
shader->SetData(kPassThroughShader);
r = script_->AddShader(std::move(shader));
if (!r.IsSuccess())
return r;
return ValidateEndOfStatement("SHADER PASSTHROUGH");
}
ShaderFormat format = kShaderFormatGlsl;
r = ToShaderFormat(fmt, &format);
if (!r.IsSuccess())
return r;
shader->SetFormat(format);
r = ValidateEndOfStatement("SHADER command");
if (!r.IsSuccess())
return r;
std::string data = tokenizer_->ExtractToNext("END");
if (data.empty())
return Result("SHADER must not be empty");
shader->SetData(data);
token = tokenizer_->NextToken();
if (!token->IsString() || token->AsString() != "END")
return Result("SHADER missing END command");
r = script_->AddShader(std::move(shader));
if (!r.IsSuccess())
return r;
return ValidateEndOfStatement("END");
}
Result Parser::ParsePipelineBlock() {
auto token = tokenizer_->NextToken();
if (!token->IsString())
return Result("invalid token when looking for pipeline type");
PipelineType type = PipelineType::kCompute;
Result r = ToPipelineType(token->AsString(), &type);
if (!r.IsSuccess())
return r;
auto pipeline = MakeUnique<Pipeline>(type);
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("invalid token when looking for pipeline name");
pipeline->SetName(token->AsString());
r = ValidateEndOfStatement("PIPELINE command");
if (!r.IsSuccess())
return r;
return ParsePipelineBody("PIPELINE", std::move(pipeline));
}
Result Parser::ParsePipelineBody(const std::string& cmd_name,
std::unique_ptr<Pipeline> pipeline) {
std::unique_ptr<Token> token;
for (token = tokenizer_->NextToken(); !token->IsEOS();
token = tokenizer_->NextToken()) {
if (token->IsEOL())
continue;
if (!token->IsString())
return Result("expected string");
Result r;
std::string tok = token->AsString();
if (tok == "END") {
break;
} else if (tok == "ATTACH") {
r = ParsePipelineAttach(pipeline.get());
} else if (tok == "SHADER_OPTIMIZATION") {
r = ParsePipelineShaderOptimizations(pipeline.get());
} else if (tok == "FRAMEBUFFER_SIZE") {
r = ParsePipelineFramebufferSize(pipeline.get());
} else if (tok == "BIND") {
r = ParsePipelineBind(pipeline.get());
} else if (tok == "VERTEX_DATA") {
r = ParsePipelineVertexData(pipeline.get());
} else if (tok == "INDEX_DATA") {
r = ParsePipelineIndexData(pipeline.get());
} else if (tok == "SET") {
r = ParsePipelineSet(pipeline.get());
} else if (tok == "COMPILE_OPTIONS") {
r = ParsePipelineShaderCompileOptions(pipeline.get());
} else {
r = Result("unknown token in pipeline block: " + tok);
}
if (!r.IsSuccess())
return r;
}
if (!token->IsString() || token->AsString() != "END")
return Result(cmd_name + " missing END command");
Result r = script_->AddPipeline(std::move(pipeline));
if (!r.IsSuccess())
return r;
return ValidateEndOfStatement("END");
}
Result Parser::ParsePipelineAttach(Pipeline* pipeline) {
auto token = tokenizer_->NextToken();
if (!token->IsString())
return Result("invalid token in ATTACH command");
auto* shader = script_->GetShader(token->AsString());
if (!shader)
return Result("unknown shader in ATTACH command");
token = tokenizer_->NextToken();
if (token->IsEOL() || token->IsEOS()) {
if (shader->GetType() == kShaderTypeMulti)
return Result("multi shader ATTACH requires TYPE");
Result r = pipeline->AddShader(shader, shader->GetType());
if (!r.IsSuccess())
return r;
return {};
}
if (!token->IsString())
return Result("Invalid token after ATTACH");
bool set_shader_type = false;
ShaderType shader_type = shader->GetType();
auto type = token->AsString();
if (type == "TYPE") {
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("invalid type in ATTACH");
Result r = ToShaderType(token->AsString(), &shader_type);
if (!r.IsSuccess())
return r;
set_shader_type = true;
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("ATTACH TYPE requires an ENTRY_POINT");
type = token->AsString();
}
if (set_shader_type && type != "ENTRY_POINT")
return Result("Unknown ATTACH parameter: " + type);
if (shader->GetType() == ShaderType::kShaderTypeMulti && !set_shader_type)
return Result("ATTACH missing TYPE for multi shader");
Result r = pipeline->AddShader(shader, shader_type);
if (!r.IsSuccess())
return r;
if (type == "ENTRY_POINT") {
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("missing shader name in ATTACH ENTRY_POINT command");
r = pipeline->SetShaderEntryPoint(shader, token->AsString());
if (!r.IsSuccess())
return r;
token = tokenizer_->NextToken();
}
while (true) {
if (token->IsString() && token->AsString() == "SPECIALIZE") {
r = ParseShaderSpecialization(pipeline);
if (!r.IsSuccess())
return r;
token = tokenizer_->NextToken();
} else {
if (token->IsEOL() || token->IsEOS())
return {};
if (token->IsString())
return Result("Unknown ATTACH parameter: " + token->AsString());
return Result("extra parameters after ATTACH command");
}
}
}
Result Parser::ParseShaderSpecialization(Pipeline* pipeline) {
auto token = tokenizer_->NextToken();
if (!token->IsInteger())
return Result("specialization ID must be an integer");
auto spec_id = token->AsUint32();
token = tokenizer_->NextToken();
if (!token->IsString() || token->AsString() != "AS")
return Result("expected AS as next token");
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("expected data type in SPECIALIZE subcommand");
DatumType type;
auto r = ToDatumType(token->AsString(), &type);
if (!r.IsSuccess())
return r;
token = tokenizer_->NextToken();
uint32_t value = 0;
switch (type.GetType()) {
case DataType::kUint32:
case DataType::kInt32:
value = token->AsUint32();
break;
case DataType::kFloat: {
r = token->ConvertToDouble();
if (!r.IsSuccess())
return Result("value is not a floating point value");
union {
uint32_t u;
float f;
} u;
u.f = token->AsFloat();
value = u.u;
break;
}
default:
return Result(
"only 32-bit types are currently accepted for specialization values");
}
auto& shader = pipeline->GetShaders()[pipeline->GetShaders().size() - 1];
shader.AddSpecialization(spec_id, value);
return {};
}
Result Parser::ParsePipelineShaderOptimizations(Pipeline* pipeline) {
auto token = tokenizer_->NextToken();
if (!token->IsString())
return Result("missing shader name in SHADER_OPTIMIZATION command");
auto* shader = script_->GetShader(token->AsString());
if (!shader)
return Result("unknown shader in SHADER_OPTIMIZATION command");
token = tokenizer_->NextToken();
if (!token->IsEOL())
return Result("extra parameters after SHADER_OPTIMIZATION command");
std::vector<std::string> optimizations;
while (true) {
token = tokenizer_->NextToken();
if (token->IsEOL())
continue;
if (token->IsEOS())
return Result("SHADER_OPTIMIZATION missing END command");
if (!token->IsString())
return Result("SHADER_OPTIMIZATION options must be strings");
if (token->AsString() == "END")
break;
optimizations.push_back(token->AsString());
}
Result r = pipeline->SetShaderOptimizations(shader, optimizations);
if (!r.IsSuccess())
return r;
return ValidateEndOfStatement("SHADER_OPTIMIZATION command");
}
Result Parser::ParsePipelineShaderCompileOptions(Pipeline* pipeline) {
auto token = tokenizer_->NextToken();
if (!token->IsString())
return Result("missing shader name in COMPILE_OPTIONS command");
auto* shader = script_->GetShader(token->AsString());
if (!shader)
return Result("unknown shader in COMPILE_OPTIONS command");
if (shader->GetFormat() != kShaderFormatOpenCLC) {
return Result("COMPILE_OPTIONS currently only supports OPENCL-C shaders");
}
token = tokenizer_->NextToken();
if (!token->IsEOL())
return Result("extra parameters after COMPILE_OPTIONS command");
std::vector<std::string> options;
while (true) {
token = tokenizer_->NextToken();
if (token->IsEOL())
continue;
if (token->IsEOS())
return Result("COMPILE_OPTIONS missing END command");
if (token->AsString() == "END")
break;
options.push_back(token->AsString());
}
Result r = pipeline->SetShaderCompileOptions(shader, options);
if (!r.IsSuccess())
return r;
return ValidateEndOfStatement("COMPILE_OPTIONS command");
}
Result Parser::ParsePipelineFramebufferSize(Pipeline* pipeline) {
auto token = tokenizer_->NextToken();
if (token->IsEOL() || token->IsEOS())
return Result("missing size for FRAMEBUFFER_SIZE command");
if (!token->IsInteger())
return Result("invalid width for FRAMEBUFFER_SIZE command");
pipeline->SetFramebufferWidth(token->AsUint32());
token = tokenizer_->NextToken();
if (token->IsEOL() || token->IsEOS())
return Result("missing height for FRAMEBUFFER_SIZE command");
if (!token->IsInteger())
return Result("invalid height for FRAMEBUFFER_SIZE command");
pipeline->SetFramebufferHeight(token->AsUint32());
return ValidateEndOfStatement("FRAMEBUFFER_SIZE command");
}
Result Parser::ToBufferType(const std::string& name, BufferType* type) {
assert(type);
if (name == "uniform")
*type = BufferType::kUniform;
else if (name == "storage")
*type = BufferType::kStorage;
else
return Result("unknown buffer_type: " + name);
return {};
}
Result Parser::ParsePipelineBind(Pipeline* pipeline) {
auto token = tokenizer_->NextToken();
if (!token->IsString())
return Result("missing BUFFER in BIND command");
if (token->AsString() != "BUFFER")
return Result("missing BUFFER in BIND command");
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("missing buffer name in BIND command");
auto* buffer = script_->GetBuffer(token->AsString());
if (!buffer)
return Result("unknown buffer: " + token->AsString());
token = tokenizer_->NextToken();
if (token->IsString() && token->AsString() == "AS") {
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("invalid token for BUFFER type");
if (token->AsString() == "color") {
token = tokenizer_->NextToken();
if (!token->IsString() || token->AsString() != "LOCATION")
return Result("BIND missing LOCATION");
token = tokenizer_->NextToken();
if (!token->IsInteger())
return Result("invalid value for BIND LOCATION");
buffer->SetBufferType(BufferType::kColor);
Result r = pipeline->AddColorAttachment(buffer, token->AsUint32());
if (!r.IsSuccess())
return r;
} else if (token->AsString() == "depth_stencil") {
buffer->SetBufferType(BufferType::kDepth);
Result r = pipeline->SetDepthBuffer(buffer);
if (!r.IsSuccess())
return r;
} else if (token->AsString() == "push_constant") {
buffer->SetBufferType(BufferType::kPushConstant);
Result r = pipeline->SetPushConstantBuffer(buffer);
if (!r.IsSuccess())
return r;
} else {
BufferType type = BufferType::kColor;
Result r = ToBufferType(token->AsString(), &type);
if (!r.IsSuccess())
return r;
if (buffer->GetBufferType() == BufferType::kUnknown)
buffer->SetBufferType(type);
else if (buffer->GetBufferType() != type)
return Result("buffer type does not match intended usage");
}
}
if (buffer->GetBufferType() == BufferType::kUnknown ||
buffer->GetBufferType() == BufferType::kStorage ||
buffer->GetBufferType() == BufferType::kUniform) {
// If AS was parsed above consume the next token.
if (buffer->GetBufferType() != BufferType::kUnknown)
token = tokenizer_->NextToken();
// DESCRIPTOR_SET requires a buffer type to have been specified.
if (buffer->GetBufferType() != BufferType::kUnknown && token->IsString() &&
token->AsString() == "DESCRIPTOR_SET") {
token = tokenizer_->NextToken();
if (!token->IsInteger())
return Result("invalid value for DESCRIPTOR_SET in BIND command");
uint32_t descriptor_set = token->AsUint32();
token = tokenizer_->NextToken();
if (!token->IsString() || token->AsString() != "BINDING")
return Result("missing BINDING for BIND command");
token = tokenizer_->NextToken();
if (!token->IsInteger())
return Result("invalid value for BINDING in BIND command");
pipeline->AddBuffer(buffer, descriptor_set, token->AsUint32());
} else if (token->IsString() && token->AsString() == "KERNEL") {
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("missing kernel arg identifier");
if (token->AsString() == "ARG_NAME") {
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("expected argument identifier");
pipeline->AddBuffer(buffer, token->AsString());
} else if (token->AsString() == "ARG_NUMBER") {
token = tokenizer_->NextToken();
if (!token->IsInteger())
return Result("expected argument number");
pipeline->AddBuffer(buffer, token->AsUint32());
} else {
return Result("missing ARG_NAME or ARG_NUMBER keyword");
}
} else {
return Result("missing DESCRIPTOR_SET or KERNEL for BIND command");
}
}
return ValidateEndOfStatement("BIND command");
}
Result Parser::ParsePipelineVertexData(Pipeline* pipeline) {
auto token = tokenizer_->NextToken();
if (!token->IsString())
return Result("missing buffer name in VERTEX_DATA command");
auto* buffer = script_->GetBuffer(token->AsString());
if (!buffer)
return Result("unknown buffer: " + token->AsString());
token = tokenizer_->NextToken();
if (!token->IsString() || token->AsString() != "LOCATION")
return Result("VERTEX_DATA missing LOCATION");
token = tokenizer_->NextToken();
if (!token->IsInteger())
return Result("invalid value for VERTEX_DATA LOCATION");
buffer->SetBufferType(BufferType::kVertex);
Result r = pipeline->AddVertexBuffer(buffer, token->AsUint32());
if (!r.IsSuccess())
return r;
return ValidateEndOfStatement("VERTEX_DATA command");
}
Result Parser::ParsePipelineIndexData(Pipeline* pipeline) {
auto token = tokenizer_->NextToken();
if (!token->IsString())
return Result("missing buffer name in INDEX_DATA command");
auto* buffer = script_->GetBuffer(token->AsString());
if (!buffer)
return Result("unknown buffer: " + token->AsString());
buffer->SetBufferType(BufferType::kIndex);
Result r = pipeline->SetIndexBuffer(buffer);
if (!r.IsSuccess())
return r;
return ValidateEndOfStatement("INDEX_DATA command");
}
Result Parser::ParsePipelineSet(Pipeline* pipeline) {
if (pipeline->GetShaders().empty() ||
pipeline->GetShaders()[0].GetShader()->GetFormat() !=
kShaderFormatOpenCLC) {
return Result("SET can only be used with OPENCL-C shaders");
}
auto token = tokenizer_->NextToken();
if (!token->IsString() || token->AsString() != "KERNEL")
return Result("missing KERNEL in SET command");
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("expected ARG_NAME or ARG_NUMBER");
std::string arg_name = "";
uint32_t arg_no = std::numeric_limits<uint32_t>::max();
if (token->AsString() == "ARG_NAME") {
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("expected argument identifier");
arg_name = token->AsString();
} else if (token->AsString() == "ARG_NUMBER") {
token = tokenizer_->NextToken();
if (!token->IsInteger())
return Result("expected argument number");
arg_no = token->AsUint32();
} else {
return Result("expected ARG_NAME or ARG_NUMBER");
}
token = tokenizer_->NextToken();
if (!token->IsString() || token->AsString() != "AS")
return Result("missing AS in SET command");
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("expected data type");
DatumType arg_type;
auto r = ToDatumType(token->AsString(), &arg_type);
if (!r.IsSuccess())
return r;
token = tokenizer_->NextToken();
if (!token->IsInteger() && !token->IsDouble())
return Result("expected data value");
Value value;
if (arg_type.IsFloat() || arg_type.IsDouble())
value.SetDoubleValue(token->AsDouble());
else
value.SetIntValue(token->AsUint64());
Pipeline::ArgSetInfo info;
info.name = arg_name;
info.ordinal = arg_no;
info.fmt = arg_type.AsFormat();
info.value = value;
pipeline->SetArg(std::move(info));
return ValidateEndOfStatement("SET command");
}
Result Parser::ParseBuffer() {
auto token = tokenizer_->NextToken();
if (!token->IsString())
return Result("invalid BUFFER name provided");
auto name = token->AsString();
if (name == "DATA_TYPE" || name == "FORMAT")
return Result("missing BUFFER name");
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("invalid BUFFER command provided");
std::unique_ptr<Buffer> buffer;
auto& cmd = token->AsString();
if (cmd == "DATA_TYPE") {
buffer = MakeUnique<Buffer>();
Result r = ParseBufferInitializer(buffer.get());
if (!r.IsSuccess())
return r;
} else if (cmd == "FORMAT") {
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("BUFFER FORMAT must be a string");
buffer = MakeUnique<Buffer>();
FormatParser fmt_parser;
auto fmt = fmt_parser.Parse(token->AsString());
if (fmt == nullptr)
return Result("invalid BUFFER FORMAT");
buffer->SetFormat(std::move(fmt));
} else {
return Result("unknown BUFFER command provided: " + cmd);
}
buffer->SetName(name);
Result r = script_->AddBuffer(std::move(buffer));
if (!r.IsSuccess())
return r;
return {};
}
Result Parser::ParseBufferInitializer(Buffer* buffer) {
auto token = tokenizer_->NextToken();
if (!token->IsString())
return Result("BUFFER invalid data type");
FormatParser fp;
auto fmt = fp.Parse(token->AsString());
if (fmt != nullptr) {
buffer->SetFormat(std::move(fmt));
} else {
DatumType type;
Result r = ToDatumType(token->AsString(), &type);
if (!r.IsSuccess())
return r;
buffer->SetFormat(type.AsFormat());
}
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("BUFFER missing initializer");
if (token->AsString() == "SIZE")
return ParseBufferInitializerSize(buffer);
if (token->AsString() == "DATA")
return ParseBufferInitializerData(buffer);
return Result("unknown initializer for BUFFER");
}
Result Parser::ParseBufferInitializerSize(Buffer* buffer) {
auto token = tokenizer_->NextToken();
if (token->IsEOS() || token->IsEOL())
return Result("BUFFER size missing");
if (!token->IsInteger())
return Result("BUFFER size invalid");
uint32_t size_in_items = token->AsUint32();
buffer->SetElementCount(size_in_items);
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("BUFFER invalid initializer");
if (token->AsString() == "FILL")
return ParseBufferInitializerFill(buffer, size_in_items);
if (token->AsString() == "SERIES_FROM")
return ParseBufferInitializerSeries(buffer, size_in_items);
return Result("invalid BUFFER initializer provided");
}
Result Parser::ParseBufferInitializerFill(Buffer* buffer,
uint32_t size_in_items) {
auto token = tokenizer_->NextToken();
if (token->IsEOS() || token->IsEOL())
return Result("missing BUFFER fill value");
if (!token->IsInteger() && !token->IsDouble())
return Result("invalid BUFFER fill value");
auto fmt = buffer->GetFormat();
bool is_double_data = fmt->IsFloat() || fmt->IsDouble();
// Inflate the size because our items are multi-dimensional.
size_in_items = size_in_items * fmt->InputNeededPerElement();
std::vector<Value> values;
values.resize(size_in_items);
for (size_t i = 0; i < size_in_items; ++i) {
if (is_double_data)
values[i].SetDoubleValue(token->AsDouble());
else
values[i].SetIntValue(token->AsUint64());
}
Result r = buffer->SetData(std::move(values));
if (!r.IsSuccess())
return r;
return ValidateEndOfStatement("BUFFER fill command");
}
Result Parser::ParseBufferInitializerSeries(Buffer* buffer,
uint32_t size_in_items) {
auto token = tokenizer_->NextToken();
if (token->IsEOS() || token->IsEOL())
return Result("missing BUFFER series_from value");
if (!token->IsInteger() && !token->IsDouble())
return Result("invalid BUFFER series_from value");
auto fmt = buffer->GetFormat();
if (fmt->RowCount() > 1 || fmt->ColumnCount() > 1)
return Result("BUFFER series_from must not be multi-row/column types");
bool is_double_data = fmt->IsFloat() || fmt->IsDouble();
Value counter;
if (is_double_data)
counter.SetDoubleValue(token->AsDouble());
else
counter.SetIntValue(token->AsUint64());
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("missing BUFFER series_from inc_by");
if (token->AsString() != "INC_BY")
return Result("BUFFER series_from invalid command");
token = tokenizer_->NextToken();
if (token->IsEOS() || token->IsEOL())
return Result("missing BUFFER series_from inc_by value");
if (!token->IsInteger() && !token->IsDouble())
return Result("invalid BUFFER series_from inc_by value");
std::vector<Value> values;
values.resize(size_in_items);
for (size_t i = 0; i < size_in_items; ++i) {
if (is_double_data) {
double value = counter.AsDouble();
values[i].SetDoubleValue(value);
counter.SetDoubleValue(value + token->AsDouble());
} else {
uint64_t value = counter.AsUint64();
values[i].SetIntValue(value);
counter.SetIntValue(value + token->AsUint64());
}
}
Result r = buffer->SetData(std::move(values));
if (!r.IsSuccess())
return r;
return ValidateEndOfStatement("BUFFER series_from command");
}
Result Parser::ParseBufferInitializerData(Buffer* buffer) {
auto fmt = buffer->GetFormat();
bool is_double_type = fmt->IsFloat() || fmt->IsDouble();
std::vector<Value> values;
for (auto token = tokenizer_->NextToken();; token = tokenizer_->NextToken()) {
if (token->IsEOL())
continue;
if (token->IsEOS())
return Result("missing BUFFER END command");
if (token->IsString() && token->AsString() == "END")
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());
Value v;
if (is_double_type) {
token->ConvertToDouble();
double val = token->IsHex() ? static_cast<double>(token->AsHex())
: token->AsDouble();
v.SetDoubleValue(val);
} else {
uint64_t val = token->IsHex() ? token->AsHex() : token->AsUint64();
v.SetIntValue(val);
}
values.emplace_back(v);
}
buffer->SetValueCount(static_cast<uint32_t>(values.size()));
Result r = buffer->SetData(std::move(values));
if (!r.IsSuccess())
return r;
return ValidateEndOfStatement("BUFFER data command");
}
Result Parser::ParseRun() {
auto token = tokenizer_->NextToken();
if (!token->IsString())
return Result("missing pipeline name for RUN command");
size_t line = tokenizer_->GetCurrentLine();
auto* pipeline = script_->GetPipeline(token->AsString());
if (!pipeline)
return Result("unknown pipeline for RUN command: " + token->AsString());
token = tokenizer_->NextToken();
if (token->IsEOL() || token->IsEOS())
return Result("RUN command requires parameters");
if (token->IsInteger()) {
if (!pipeline->IsCompute())
return Result("RUN command requires compute pipeline");
auto cmd = MakeUnique<ComputeCommand>(pipeline);
cmd->SetLine(line);
cmd->SetX(token->AsUint32());
token = tokenizer_->NextToken();
if (!token->IsInteger()) {
return Result("invalid parameter for RUN command: " +
token->ToOriginalString());
}
cmd->SetY(token->AsUint32());
token = tokenizer_->NextToken();
if (!token->IsInteger()) {
return Result("invalid parameter for RUN command: " +
token->ToOriginalString());
}
cmd->SetZ(token->AsUint32());
command_list_.push_back(std::move(cmd));
return ValidateEndOfStatement("RUN command");
}
if (!token->IsString())
return Result("invalid token in RUN command: " + token->ToOriginalString());
if (token->AsString() == "DRAW_RECT") {
if (!pipeline->IsGraphics())
return Result("RUN command requires graphics pipeline");
if (pipeline->GetVertexBuffers().size() > 1) {
return Result(
"RUN DRAW_RECT is not supported in a pipeline with more than one "
"vertex buffer attached");
}
token = tokenizer_->NextToken();
if (token->IsEOS() || token->IsEOL())
return Result("RUN DRAW_RECT command requires parameters");
if (!token->IsString() || token->AsString() != "POS") {
return Result("invalid token in RUN command: " +
token->ToOriginalString() + "; expected POS");
}
token = tokenizer_->NextToken();
if (!token->IsInteger())
return Result("missing X position for RUN command");
auto cmd = MakeUnique<DrawRectCommand>(pipeline, PipelineData{});
cmd->SetLine(line);
cmd->EnableOrtho();
Result r = token->ConvertToDouble();
if (!r.IsSuccess())
return r;
cmd->SetX(token->AsFloat());
token = tokenizer_->NextToken();
if (!token->IsInteger())
return Result("missing Y position for RUN command");
r = token->ConvertToDouble();
if (!r.IsSuccess())
return r;
cmd->SetY(token->AsFloat());
token = tokenizer_->NextToken();
if (!token->IsString() || token->AsString() != "SIZE") {
return Result("invalid token in RUN command: " +
token->ToOriginalString() + "; expected SIZE");
}
token = tokenizer_->NextToken();
if (!token->IsInteger())
return Result("missing width value for RUN command");
r = token->ConvertToDouble();
if (!r.IsSuccess())
return r;
cmd->SetWidth(token->AsFloat());
token = tokenizer_->NextToken();
if (!token->IsInteger())
return Result("missing height value for RUN command");
r = token->ConvertToDouble();
if (!r.IsSuccess())
return r;
cmd->SetHeight(token->AsFloat());
command_list_.push_back(std::move(cmd));
return ValidateEndOfStatement("RUN command");
}
if (token->AsString() == "DRAW_ARRAY") {
if (!pipeline->IsGraphics())
return Result("RUN command requires graphics pipeline");
if (pipeline->GetVertexBuffers().empty())
return Result("RUN DRAW_ARRAY requires attached vertex buffer");
token = tokenizer_->NextToken();
if (!token->IsString() || token->AsString() != "AS")
return Result("missing AS for RUN command");
token = tokenizer_->NextToken();
if (!token->IsString()) {
return Result("invalid topology for RUN command: " +
token->ToOriginalString());
}
Topology topo = NameToTopology(token->AsString());
if (topo == Topology::kUnknown)
return Result("invalid topology for RUN command: " + token->AsString());
token = tokenizer_->NextToken();
bool indexed = false;
if (token->IsString() && token->AsString() == "INDEXED") {
if (!pipeline->GetIndexBuffer())
return Result("RUN DRAW_ARRAYS INDEXED requires attached index buffer");
indexed = true;
token = tokenizer_->NextToken();
}
uint32_t start_idx = 0;
uint32_t count = 0;
if (!token->IsEOS() && !token->IsEOL()) {
if (!token->IsString() || token->AsString() != "START_IDX")
return Result("missing START_IDX for RUN command");
token = tokenizer_->NextToken();
if (!token->IsInteger()) {
return Result("invalid START_IDX value for RUN command: " +
token->ToOriginalString());
}
if (token->AsInt32() < 0)
return Result("START_IDX value must be >= 0 for RUN command");
start_idx = token->AsUint32();
token = tokenizer_->NextToken();
if (!token->IsEOS() && !token->IsEOL()) {
if (!token->IsString() || token->AsString() != "COUNT")
return Result("missing COUNT for RUN command");
token = tokenizer_->NextToken();
if (!token->IsInteger()) {
return Result("invalid COUNT value for RUN command: " +
token->ToOriginalString());
}
if (token->AsInt32() <= 0)
return Result("COUNT value must be > 0 for RUN command");
count = token->AsUint32();
}
}
// If we get here then we never set count, as if count was set it must
// be > 0.
if (count == 0) {
count =
pipeline->GetVertexBuffers()[0].buffer->ElementCount() - start_idx;
}
if (start_idx + count >
pipeline->GetVertexBuffers()[0].buffer->ElementCount()) {
return Result("START_IDX plus COUNT exceeds vertex buffer data size");
}
auto cmd = MakeUnique<DrawArraysCommand>(pipeline, PipelineData{});
cmd->SetLine(line);
cmd->SetTopology(topo);
cmd->SetFirstVertexIndex(start_idx);
cmd->SetVertexCount(count);
if (indexed)
cmd->EnableIndexed();
command_list_.push_back(std::move(cmd));
return ValidateEndOfStatement("RUN command");
}
return Result("invalid token in RUN command: " + token->AsString());
}
Result Parser::ParseClear() {
auto token = tokenizer_->NextToken();
if (!token->IsString())
return Result("missing pipeline name for CLEAR command");
size_t line = tokenizer_->GetCurrentLine();
auto* pipeline = script_->GetPipeline(token->AsString());
if (!pipeline)
return Result("unknown pipeline for CLEAR command: " + token->AsString());
if (!pipeline->IsGraphics())
return Result("CLEAR command requires graphics pipeline");
auto cmd = MakeUnique<ClearCommand>(pipeline);
cmd->SetLine(line);
command_list_.push_back(std::move(cmd));
return ValidateEndOfStatement("CLEAR command");
}
Result Parser::ParseValues(const std::string& name,
Format* fmt,
std::vector<Value>* values) {
assert(values);
auto token = tokenizer_->NextToken();
while (!token->IsEOL() && !token->IsEOS()) {
Value v;
if (fmt->IsFloat() || fmt->IsDouble()) {
if (!token->IsInteger() && !token->IsDouble()) {
return Result(std::string("Invalid value provided to ") + name +
" command: " + token->ToOriginalString());
}
Result r = token->ConvertToDouble();
if (!r.IsSuccess())
return r;
v.SetDoubleValue(token->AsDouble());
} else {
if (!token->IsInteger()) {
return Result(std::string("Invalid value provided to ") + name +
" command: " + token->ToOriginalString());
}
v.SetIntValue(token->AsUint64());
}
values->push_back(v);
token = tokenizer_->NextToken();
}
return {};
}
Result Parser::ParseExpect() {
auto token = tokenizer_->NextToken();
if (!token->IsString())
return Result("invalid buffer name in EXPECT command");
if (token->AsString() == "IDX")
return Result("missing buffer name between EXPECT and IDX");
if (token->AsString() == "EQ_BUFFER")
return Result("missing buffer name between EXPECT and EQ_BUFFER");
if (token->AsString() == "RMSE_BUFFER")
return Result("missing buffer name between EXPECT and RMSE_BUFFER");
size_t line = tokenizer_->GetCurrentLine();
auto* buffer = script_->GetBuffer(token->AsString());
if (!buffer)
return Result("unknown buffer name for EXPECT command: " +
token->AsString());
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("Invalid comparator in EXPECT command");
if (token->AsString() == "EQ_BUFFER" || token->AsString() == "RMSE_BUFFER") {
auto type = token->AsString();
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("invalid buffer name in EXPECT " + type + " command");
auto* buffer_2 = script_->GetBuffer(token->AsString());
if (!buffer_2) {
return Result("unknown buffer name for EXPECT " + type +
" command: " + token->AsString());
}
if (!buffer->GetFormat()->Equal(buffer_2->GetFormat())) {
return Result("EXPECT " + type +
" command cannot compare buffers of differing format");
}
if (buffer->ElementCount() != buffer_2->ElementCount()) {
return Result("EXPECT " + type +
" command cannot compare buffers of different size: " +
std::to_string(buffer->ElementCount()) + " vs " +
std::to_string(buffer_2->ElementCount()));
}
if (buffer->GetWidth() != buffer_2->GetWidth()) {
return Result("EXPECT " + type +
" command cannot compare buffers of different width");
}
if (buffer->GetHeight() != buffer_2->GetHeight()) {
return Result("EXPECT " + type +
" command cannot compare buffers of different height");
}
auto cmd = MakeUnique<CompareBufferCommand>(buffer, buffer_2);
if (type == "RMSE_BUFFER") {
cmd->SetComparator(CompareBufferCommand::Comparator::kRmse);
token = tokenizer_->NextToken();
if (!token->IsString() && token->AsString() == "TOLERANCE")
return Result("Missing TOLERANCE for EXPECT RMSE_BUFFER");
token = tokenizer_->NextToken();
if (!token->IsInteger() && !token->IsDouble())
return Result("Invalid TOLERANCE for EXPECT RMSE_BUFFER");
Result r = token->ConvertToDouble();
if (!r.IsSuccess())
return r;
cmd->SetTolerance(token->AsFloat());
}
command_list_.push_back(std::move(cmd));
// Early return
return ValidateEndOfStatement("EXPECT " + type + " command");
}
if (token->AsString() != "IDX")
return Result("missing IDX in EXPECT command");
token = tokenizer_->NextToken();
if (!token->IsInteger() || token->AsInt32() < 0)
return Result("invalid X value in EXPECT command");
token->ConvertToDouble();
float x = token->AsFloat();
bool has_y_val = false;
float y = 0;
token = tokenizer_->NextToken();
if (token->IsInteger()) {
has_y_val = true;
if (token->AsInt32() < 0)
return Result("invalid Y value in EXPECT command");
token->ConvertToDouble();
y = token->AsFloat();
token = tokenizer_->NextToken();
}
if (token->IsString() && token->AsString() == "SIZE") {
if (!has_y_val)
return Result("invalid Y value in EXPECT command");
auto probe = MakeUnique<ProbeCommand>(buffer);
probe->SetLine(line);
probe->SetX(x);
probe->SetY(y);
probe->SetProbeRect();
token = tokenizer_->NextToken();
if (!token->IsInteger() || token->AsInt32() <= 0)
return Result("invalid width in EXPECT command");
token->ConvertToDouble();
probe->SetWidth(token->AsFloat());
token = tokenizer_->NextToken();
if (!token->IsInteger() || token->AsInt32() <= 0)
return Result("invalid height in EXPECT command");
token->ConvertToDouble();
probe->SetHeight(token->AsFloat());
token = tokenizer_->NextToken();
if (!token->IsString()) {
return Result("invalid token in EXPECT command:" +
token->ToOriginalString());
}
if (token->AsString() == "EQ_RGBA") {
probe->SetIsRGBA();
} else if (token->AsString() != "EQ_RGB") {
return Result("unknown comparator type in EXPECT: " +
token->ToOriginalString());
}
token = tokenizer_->NextToken();
if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255)
return Result("invalid R value in EXPECT command");
token->ConvertToDouble();
probe->SetR(token->AsFloat() / 255.f);
token = tokenizer_->NextToken();
if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255)
return Result("invalid G value in EXPECT command");
token->ConvertToDouble();
probe->SetG(token->AsFloat() / 255.f);
token = tokenizer_->NextToken();
if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255)
return Result("invalid B value in EXPECT command");
token->ConvertToDouble();
probe->SetB(token->AsFloat() / 255.f);
if (probe->IsRGBA()) {
token = tokenizer_->NextToken();
if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255)
return Result("invalid A value in EXPECT command");
token->ConvertToDouble();
probe->SetA(token->AsFloat() / 255.f);
}
command_list_.push_back(std::move(probe));
return ValidateEndOfStatement("EXPECT command");
}
auto probe = MakeUnique<ProbeSSBOCommand>(buffer);
probe->SetLine(line);
if (token->IsString() && token->AsString() == "TOLERANCE") {
std::vector<Probe::Tolerance> tolerances;
token = tokenizer_->NextToken();
while (!token->IsEOL() && !token->IsEOS()) {
if (!token->IsInteger() && !token->IsDouble())
break;
Result r = token->ConvertToDouble();
if (!r.IsSuccess())
return r;
double value = token->AsDouble();
token = tokenizer_->NextToken();
if (token->IsString() && token->AsString() == "%") {
tolerances.push_back(Probe::Tolerance{true, value});
token = tokenizer_->NextToken();
} else {
tolerances.push_back(Probe::Tolerance{false, value});
}
}
if (tolerances.empty())
return Result("TOLERANCE specified but no tolerances provided");
if (tolerances.size() > 4)
return Result("TOLERANCE has a maximum of 4 values");
probe->SetTolerances(std::move(tolerances));
}
if (!token->IsString() || !IsComparator(token->AsString())) {
return Result("unexpected token in EXPECT command: " +
token->ToOriginalString());
}
if (has_y_val)
return Result("Y value not needed for non-color comparator");
auto cmp = ToComparator(token->AsString());
if (probe->HasTolerances()) {
if (cmp != ProbeSSBOCommand::Comparator::kEqual)
return Result("TOLERANCE only available with EQ probes");
cmp = ProbeSSBOCommand::Comparator::kFuzzyEqual;
}
probe->SetComparator(cmp);
probe->SetFormat(MakeUnique<Format>(*buffer->GetFormat()));
probe->SetOffset(static_cast<uint32_t>(x));
std::vector<Value> values;
Result r = ParseValues("EXPECT", buffer->GetFormat(), &values);
if (!r.IsSuccess())
return r;
if (values.empty())
return Result("missing comparison values for EXPECT command");
probe->SetValues(std::move(values));
command_list_.push_back(std::move(probe));
return {};
}
Result Parser::ParseCopy() {
auto token = tokenizer_->NextToken();
if (token->IsEOL() || token->IsEOS())
return Result("missing buffer name after COPY");
if (!token->IsString())
return Result("invalid buffer name after COPY");
size_t line = tokenizer_->GetCurrentLine();
auto name = token->AsString();
if (name == "TO")
return Result("missing buffer name between COPY and TO");
Buffer* buffer_from = script_->GetBuffer(name);
if (!buffer_from)
return Result("COPY origin buffer was not declared");
token = tokenizer_->NextToken();
if (token->IsEOL() || token->IsEOS())
return Result("missing 'TO' after COPY and buffer name");
if (!token->IsString())
return Result("expected 'TO' after COPY and buffer name");
name = token->AsString();
if (name != "TO")
return Result("expected 'TO' after COPY and buffer name");
token = tokenizer_->NextToken();
if (token->IsEOL() || token->IsEOS())
return Result("missing buffer name after TO");
if (!token->IsString())
return Result("invalid buffer name after TO");
name = token->AsString();
Buffer* buffer_to = script_->GetBuffer(name);
if (!buffer_to)
return Result("COPY destination buffer was not declared");
if (buffer_to->GetBufferType() == amber::BufferType::kUnknown) {
// Set destination buffer to mirror origin buffer
buffer_to->SetBufferType(buffer_from->GetBufferType());
buffer_to->SetWidth(buffer_from->GetWidth());
buffer_to->SetHeight(buffer_from->GetHeight());
buffer_to->SetElementCount(buffer_from->ElementCount());
}
if (buffer_from->GetBufferType() != buffer_to->GetBufferType())
return Result("cannot COPY between buffers of different types");
if (buffer_from == buffer_to)
return Result("COPY origin and destination buffers are identical");
auto cmd = MakeUnique<CopyCommand>(buffer_from, buffer_to);
cmd->SetLine(line);
command_list_.push_back(std::move(cmd));
return ValidateEndOfStatement("COPY command");
}
Result Parser::ParseClearColor() {
auto token = tokenizer_->NextToken();
if (!token->IsString())
return Result("missing pipeline name for CLEAR_COLOR command");
size_t line = tokenizer_->GetCurrentLine();
auto* pipeline = script_->GetPipeline(token->AsString());
if (!pipeline) {
return Result("unknown pipeline for CLEAR_COLOR command: " +
token->AsString());
}
if (!pipeline->IsGraphics()) {
return Result("CLEAR_COLOR command requires graphics pipeline");
}
auto cmd = MakeUnique<ClearColorCommand>(pipeline);
cmd->SetLine(line);
token = tokenizer_->NextToken();
if (token->IsEOL() || token->IsEOS())
return Result("missing R value for CLEAR_COLOR command");
if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255) {
return Result("invalid R value for CLEAR_COLOR command: " +
token->ToOriginalString());
}
token->ConvertToDouble();
cmd->SetR(token->AsFloat() / 255.f);
token = tokenizer_->NextToken();
if (token->IsEOL() || token->IsEOS())
return Result("missing G value for CLEAR_COLOR command");
if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255) {
return Result("invalid G value for CLEAR_COLOR command: " +
token->ToOriginalString());
}
token->ConvertToDouble();
cmd->SetG(token->AsFloat() / 255.f);
token = tokenizer_->NextToken();
if (token->IsEOL() || token->IsEOS())
return Result("missing B value for CLEAR_COLOR command");
if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255) {
return Result("invalid B value for CLEAR_COLOR command: " +
token->ToOriginalString());
}
token->ConvertToDouble();
cmd->SetB(token->AsFloat() / 255.f);
token = tokenizer_->NextToken();
if (token->IsEOL() || token->IsEOS())
return Result("missing A value for CLEAR_COLOR command");
if (!token->IsInteger() || token->AsInt32() < 0 || token->AsInt32() > 255) {
return Result("invalid A value for CLEAR_COLOR command: " +
token->ToOriginalString());
}
token->ConvertToDouble();
cmd->SetA(token->AsFloat() / 255.f);
command_list_.push_back(std::move(cmd));
return ValidateEndOfStatement("CLEAR_COLOR command");
}
Result Parser::ParseDeviceFeature() {
auto token = tokenizer_->NextToken();
if (token->IsEOS() || token->IsEOL())
return Result("missing feature name for DEVICE_FEATURE command");
if (!token->IsString())
return Result("invalid feature name for DEVICE_FEATURE command");
if (!script_->IsKnownFeature(token->AsString()))
return Result("unknown feature name for DEVICE_FEATURE command");
script_->AddRequiredFeature(token->AsString());
return ValidateEndOfStatement("DEVICE_FEATURE command");
}
Result Parser::ParseRepeat() {
auto token = tokenizer_->NextToken();
if (token->IsEOL() || token->IsEOL())
return Result("missing count parameter for REPEAT command");
if (!token->IsInteger()) {
return Result("invalid count parameter for REPEAT command: " +
token->ToOriginalString());
}
if (token->AsInt32() <= 0)
return Result("count parameter must be > 0 for REPEAT command");
uint32_t count = token->AsUint32();
std::vector<std::unique_ptr<Command>> cur_commands;
std::swap(cur_commands, command_list_);
for (token = tokenizer_->NextToken(); !token->IsEOS();
token = tokenizer_->NextToken()) {
if (token->IsEOL())
continue;
if (!token->IsString())
return Result("expected string");
std::string tok = token->AsString();
if (tok == "END")
break;
if (!IsRepeatable(tok))
return Result("unknown token: " + tok);
Result r = ParseRepeatableCommand(tok);
if (!r.IsSuccess())
return r;
}
if (!token->IsString() || token->AsString() != "END")
return Result("missing END for REPEAT command");
auto cmd = MakeUnique<RepeatCommand>(count);
cmd->SetCommands(std::move(command_list_));
std::swap(cur_commands, command_list_);
command_list_.push_back(std::move(cmd));
return ValidateEndOfStatement("REPEAT command");
}
Result Parser::ParseDerivePipelineBlock() {
auto token = tokenizer_->NextToken();
if (!token->IsString() || token->AsString() == "FROM")
return Result("missing pipeline name for DERIVE_PIPELINE command");
std::string name = token->AsString();
if (script_->GetPipeline(name) != nullptr)
return Result("duplicate pipeline name for DERIVE_PIPELINE command");
token = tokenizer_->NextToken();
if (!token->IsString() || token->AsString() != "FROM")
return Result("missing FROM in DERIVE_PIPELINE command");
token = tokenizer_->NextToken();
if (!token->IsString())
return Result("missing parent pipeline name in DERIVE_PIPELINE command");
Pipeline* parent = script_->GetPipeline(token->AsString());
if (!parent)
return Result("unknown parent pipeline in DERIVE_PIPELINE command");
Result r = ValidateEndOfStatement("DERIVE_PIPELINE command");
if (!r.IsSuccess())
return r;
auto pipeline = parent->Clone();
pipeline->SetName(name);
return ParsePipelineBody("DERIVE_PIPELINE", std::move(pipeline));
}
Result Parser::ParseDeviceExtension() {
auto token = tokenizer_->NextToken();
if (token->IsEOL() || token->IsEOS())
return Result("DEVICE_EXTENSION missing name");
if (!token->IsString()) {
return Result("DEVICE_EXTENSION invalid name: " +
token->ToOriginalString());
}
script_->AddRequiredDeviceExtension(token->AsString());
return ValidateEndOfStatement("DEVICE_EXTENSION command");
}
Result Parser::ParseInstanceExtension() {
auto token = tokenizer_->NextToken();
if (token->IsEOL() || token->IsEOS())
return Result("INSTANCE_EXTENSION missing name");
if (!token->IsString()) {
return Result("INSTANCE_EXTENSION invalid name: " +
token->ToOriginalString());
}
script_->AddRequiredInstanceExtension(token->AsString());
return ValidateEndOfStatement("INSTANCE_EXTENSION command");
}
Result Parser::ParseSet() {
auto token = tokenizer_->NextToken();
if (!token->IsString() || token->AsString() != "ENGINE_DATA")
return Result("SET missing ENGINE_DATA");
token = tokenizer_->NextToken();
if (token->IsEOS() || token->IsEOL())
return Result("SET missing variable to be set");
if (!token->IsString())
return Result("SET invalid variable to set: " + token->ToOriginalString());
if (token->AsString() != "fence_timeout_ms")
return Result("SET unknown variable provided: " + token->AsString());
token = tokenizer_->NextToken();
if (token->IsEOS() || token->IsEOL())
return Result("SET missing value for fence_timeout_ms");
if (!token->IsInteger())
return Result("SET invalid value for fence_timeout_ms, must be uint32");
script_->GetEngineData().fence_timeout_ms = token->AsUint32();
return ValidateEndOfStatement("SET command");
}
} // namespace amberscript
} // namespace amber