| // 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 <map> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "src/image.h" |
| #include "src/make_unique.h" |
| #include "src/sampler.h" |
| #include "src/shader_data.h" |
| #include "src/tokenizer.h" |
| #include "src/type_parser.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; |
| } |
| |
| std::unique_ptr<type::Type> ToType(const std::string& str) { |
| TypeParser parser; |
| if (str == "int8") |
| return parser.Parse("R8_SINT"); |
| if (str == "int16") |
| return parser.Parse("R16_SINT"); |
| if (str == "int32") |
| return parser.Parse("R32_SINT"); |
| if (str == "int64") |
| return parser.Parse("R64_SINT"); |
| if (str == "uint8") |
| return parser.Parse("R8_UINT"); |
| if (str == "uint16") |
| return parser.Parse("R16_UINT"); |
| if (str == "uint32") |
| return parser.Parse("R32_UINT"); |
| if (str == "uint64") |
| return parser.Parse("R64_UINT"); |
| if (str == "float16") |
| return parser.Parse("R16_SFLOAT"); |
| if (str == "float") |
| return parser.Parse("R32_SFLOAT"); |
| if (str == "double") |
| return parser.Parse("R64_SFLOAT"); |
| |
| if (str.length() > 7 && str.substr(0, 3) == "vec") { |
| if (str[4] != '<' || str[str.length() - 1] != '>') |
| return nullptr; |
| |
| int component_count = str[3] - '0'; |
| if (component_count < 2 || component_count > 4) |
| return nullptr; |
| |
| auto type = ToType(str.substr(5, str.length() - 6)); |
| if (!type) |
| return nullptr; |
| |
| if (!type->IsNumber() || type->IsArray() || type->IsVec() || |
| type->IsMatrix()) { |
| return nullptr; |
| } |
| |
| type->SetRowCount(static_cast<uint32_t>(component_count)); |
| return type; |
| } |
| |
| if (str.length() > 9 && str.substr(0, 3) == "mat") { |
| if (str[4] != 'x' || str[6] != '<' || str[str.length() - 1] != '>') |
| return nullptr; |
| |
| int column_count = str[3] - '0'; |
| if (column_count < 2 || column_count > 4) |
| return nullptr; |
| |
| int row_count = str[5] - '0'; |
| if (row_count < 2 || row_count > 4) |
| return nullptr; |
| |
| auto type = ToType(str.substr(7, str.length() - 8)); |
| if (!type) |
| return nullptr; |
| if (!type->IsNumber() || type->IsArray() || type->IsVec() || |
| type->IsMatrix()) { |
| return nullptr; |
| } |
| |
| type->SetRowCount(static_cast<uint32_t>(row_count)); |
| type->SetColumnCount(static_cast<uint32_t>(column_count)); |
| return type; |
| } |
| return nullptr; |
| } |
| |
| AddressMode StrToAddressMode(std::string str) { |
| if (str == "repeat") |
| return AddressMode::kRepeat; |
| if (str == "mirrored_repeat") |
| return AddressMode::kMirroredRepeat; |
| if (str == "clamp_to_edge") |
| return AddressMode::kClampToEdge; |
| if (str == "clamp_to_border") |
| return AddressMode::kClampToBorder; |
| if (str == "mirror_clamp_to_edge") |
| return AddressMode::kMirrorClampToEdge; |
| |
| 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; |
| } |
| |
| } // 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->IsIdentifier()) |
| return Result(make_error("expected identifier")); |
| |
| 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 == "IMAGE") { |
| r = ParseImage(); |
| } 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 if (tok == "STRUCT") { |
| r = ParseStruct(); |
| } else if (tok == "SAMPLER") { |
| r = ParseSampler(); |
| } else if (tok == "VIRTUAL_FILE") { |
| r = ParseVirtualFile(); |
| } 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 color_buf = pipeline->GenerateDefaultColorAttachmentBuffer(); |
| buf = color_buf.get(); |
| |
| Result r = script_->AddBuffer(std::move(color_buf)); |
| if (!r.IsSuccess()) |
| return r; |
| } |
| Result r = pipeline->AddColorAttachment(buf, 0, 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" || name == "DEBUG"; |
| } |
| |
| // 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(); |
| if (name == "DEBUG") |
| return ParseDebug(); |
| |
| 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::ValidateEndOfStatement(const std::string& name) { |
| auto token = tokenizer_->NextToken(); |
| if (token->IsEOL() || token->IsEOS()) |
| return {}; |
| return Result("extra parameters after " + name + ": " + |
| token->ToOriginalString()); |
| } |
| |
| Result Parser::ParseShaderBlock() { |
| auto token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| 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->IsIdentifier()) |
| return Result("invalid token when looking for shader name"); |
| |
| shader->SetName(token->AsString()); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| 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); |
| |
| token = tokenizer_->PeekNextToken(); |
| if (token->IsIdentifier() && token->AsString() == "VIRTUAL_FILE") { |
| tokenizer_->NextToken(); // Skip VIRTUAL_FILE |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier() && !token->IsString()) |
| return Result("expected virtual file path after VIRTUAL_FILE"); |
| |
| r = ValidateEndOfStatement("SHADER command"); |
| if (!r.IsSuccess()) |
| return r; |
| |
| auto path = token->AsString(); |
| |
| std::string data; |
| r = script_->GetVirtualFile(path, &data); |
| if (!r.IsSuccess()) { |
| return r; |
| } |
| |
| shader->SetData(data); |
| } else { |
| 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->IsIdentifier() || 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->IsIdentifier()) |
| 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->IsIdentifier()) |
| 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->IsIdentifier()) |
| return Result("expected identifier"); |
| |
| 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 if (tok == "POLYGON_MODE") { |
| r = ParsePipelinePolygonMode(pipeline.get()); |
| } else { |
| r = Result("unknown token in pipeline block: " + tok); |
| } |
| if (!r.IsSuccess()) |
| return r; |
| } |
| |
| if (!token->IsIdentifier() || 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->IsIdentifier()) |
| 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->IsIdentifier()) |
| 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->IsIdentifier()) |
| 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->IsIdentifier()) |
| 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->IsIdentifier()) |
| 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->IsIdentifier() && token->AsString() == "SPECIALIZE") { |
| r = ParseShaderSpecialization(pipeline); |
| if (!r.IsSuccess()) |
| return r; |
| |
| token = tokenizer_->NextToken(); |
| } else { |
| if (token->IsEOL() || token->IsEOS()) |
| return {}; |
| if (token->IsIdentifier()) |
| return Result("unknown ATTACH parameter: " + token->AsString()); |
| return Result("extra parameters after ATTACH command: " + |
| token->ToOriginalString()); |
| } |
| } |
| } |
| |
| 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->IsIdentifier() || token->AsString() != "AS") |
| return Result("expected AS as next token"); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| return Result("expected data type in SPECIALIZE subcommand"); |
| |
| auto type = ToType(token->AsString()); |
| if (!type) |
| return Result("invalid data type '" + token->AsString() + "' provided"); |
| if (!type->IsNumber()) |
| return Result("only numeric types are accepted for specialization values"); |
| |
| auto num = type->AsNumber(); |
| |
| token = tokenizer_->NextToken(); |
| uint32_t value = 0; |
| if (type::Type::IsUint32(num->GetFormatMode(), num->NumBits()) || |
| type::Type::IsInt32(num->GetFormatMode(), num->NumBits())) { |
| value = token->AsUint32(); |
| } else if (type::Type::IsFloat32(num->GetFormatMode(), num->NumBits())) { |
| Result 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; |
| } else { |
| 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->IsIdentifier()) |
| 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: " + |
| token->ToOriginalString()); |
| |
| 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->IsIdentifier()) |
| return Result("SHADER_OPTIMIZATION options must be identifiers"); |
| 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->IsIdentifier()) |
| 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: " + |
| token->ToOriginalString()); |
| |
| 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 == "color") |
| *type = BufferType::kColor; |
| else if (name == "depth_stencil") |
| *type = BufferType::kDepth; |
| else if (name == "push_constant") |
| *type = BufferType::kPushConstant; |
| else if (name == "combined_image_sampler") |
| *type = BufferType::kCombinedImageSampler; |
| else if (name == "uniform") |
| *type = BufferType::kUniform; |
| else if (name == "storage") |
| *type = BufferType::kStorage; |
| else if (name == "storage_image") |
| *type = BufferType::kStorageImage; |
| else if (name == "sampled_image") |
| *type = BufferType::kSampledImage; |
| else if (name == "combined_image_sampler") |
| *type = BufferType::kCombinedImageSampler; |
| else |
| return Result("unknown buffer_type: " + name); |
| |
| return {}; |
| } |
| |
| Result Parser::ParsePipelineBind(Pipeline* pipeline) { |
| auto token = tokenizer_->NextToken(); |
| |
| if (!token->IsIdentifier()) |
| return Result("missing BUFFER or SAMPLER in BIND command"); |
| |
| auto object_type = token->AsString(); |
| |
| if (object_type == "BUFFER") { |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| return Result("missing buffer name in BIND command"); |
| |
| auto* buffer = script_->GetBuffer(token->AsString()); |
| if (!buffer) |
| return Result("unknown buffer: " + token->AsString()); |
| |
| BufferType buffer_type = BufferType::kUnknown; |
| token = tokenizer_->NextToken(); |
| if (token->IsIdentifier() && token->AsString() == "AS") { |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| return Result("invalid token for BUFFER type"); |
| |
| Result r = ToBufferType(token->AsString(), &buffer_type); |
| if (!r.IsSuccess()) |
| return r; |
| |
| if (buffer_type == BufferType::kColor) { |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier() || token->AsString() != "LOCATION") |
| return Result("BIND missing LOCATION"); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsInteger()) |
| return Result("invalid value for BIND LOCATION"); |
| auto location = token->AsUint32(); |
| |
| uint32_t base_mip_level = 0; |
| token = tokenizer_->PeekNextToken(); |
| if (token->IsIdentifier() && token->AsString() == "BASE_MIP_LEVEL") { |
| tokenizer_->NextToken(); |
| token = tokenizer_->NextToken(); |
| |
| if (!token->IsInteger()) |
| return Result("invalid value for BASE_MIP_LEVEL"); |
| |
| base_mip_level = token->AsUint32(); |
| |
| if (base_mip_level >= buffer->GetMipLevels()) |
| return Result( |
| "base mip level (now " + token->AsString() + |
| ") needs to be larger than the number of buffer mip maps (" + |
| std::to_string(buffer->GetMipLevels()) + ")"); |
| } |
| |
| r = pipeline->AddColorAttachment(buffer, location, base_mip_level); |
| if (!r.IsSuccess()) |
| return r; |
| |
| } else if (buffer_type == BufferType::kDepth) { |
| r = pipeline->SetDepthBuffer(buffer); |
| if (!r.IsSuccess()) |
| return r; |
| |
| } else if (buffer_type == BufferType::kPushConstant) { |
| r = pipeline->SetPushConstantBuffer(buffer); |
| if (!r.IsSuccess()) |
| return r; |
| |
| } else if (buffer_type == BufferType::kCombinedImageSampler) { |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier() || token->AsString() != "SAMPLER") |
| return Result("expecting SAMPLER for combined image sampler"); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| return Result("missing sampler name in BIND command"); |
| |
| auto* sampler = script_->GetSampler(token->AsString()); |
| if (!sampler) |
| return Result("unknown sampler: " + token->AsString()); |
| |
| buffer->SetSampler(sampler); |
| } |
| } |
| |
| // The OpenCL bindings can be typeless which allows for the kUnknown |
| // buffer type. |
| if (buffer_type == BufferType::kUnknown || |
| buffer_type == BufferType::kStorage || |
| buffer_type == BufferType::kUniform || |
| buffer_type == BufferType::kStorageImage || |
| buffer_type == BufferType::kSampledImage || |
| buffer_type == BufferType::kCombinedImageSampler) { |
| // If the buffer type is known, then we proccessed the AS block above |
| // and have to advance to the next token. Otherwise, we're already on the |
| // next token and don't want to advance. |
| if (buffer_type != BufferType::kUnknown) |
| token = tokenizer_->NextToken(); |
| |
| // DESCRIPTOR_SET requires a buffer type to have been specified. |
| if (token->IsIdentifier() && 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->IsIdentifier() || 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"); |
| |
| auto binding = token->AsUint32(); |
| uint32_t base_mip_level = 0; |
| |
| if (buffer_type == BufferType::kStorageImage || |
| buffer_type == BufferType::kSampledImage || |
| buffer_type == BufferType::kCombinedImageSampler) { |
| token = tokenizer_->PeekNextToken(); |
| if (token->IsIdentifier() && token->AsString() == "BASE_MIP_LEVEL") { |
| tokenizer_->NextToken(); |
| token = tokenizer_->NextToken(); |
| |
| if (!token->IsInteger()) |
| return Result("invalid value for BASE_MIP_LEVEL"); |
| |
| base_mip_level = token->AsUint32(); |
| |
| if (base_mip_level >= buffer->GetMipLevels()) |
| return Result( |
| "base mip level (now " + token->AsString() + |
| ") needs to be larger than the number of buffer mip maps (" + |
| std::to_string(buffer->GetMipLevels()) + ")"); |
| } |
| } |
| |
| pipeline->AddBuffer(buffer, buffer_type, descriptor_set, binding, |
| base_mip_level); |
| } else if (token->IsIdentifier() && token->AsString() == "KERNEL") { |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| return Result("missing kernel arg identifier"); |
| |
| if (token->AsString() == "ARG_NAME") { |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| return Result("expected argument identifier"); |
| |
| pipeline->AddBuffer(buffer, buffer_type, token->AsString()); |
| } else if (token->AsString() == "ARG_NUMBER") { |
| token = tokenizer_->NextToken(); |
| if (!token->IsInteger()) |
| return Result("expected argument number"); |
| |
| pipeline->AddBuffer(buffer, buffer_type, token->AsUint32()); |
| } else { |
| return Result("missing ARG_NAME or ARG_NUMBER keyword"); |
| } |
| } else { |
| return Result("missing DESCRIPTOR_SET or KERNEL for BIND command"); |
| } |
| } |
| } else if (object_type == "SAMPLER") { |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| return Result("missing sampler name in BIND command"); |
| |
| auto* sampler = script_->GetSampler(token->AsString()); |
| if (!sampler) |
| return Result("unknown sampler: " + token->AsString()); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| return Result("expected a string token for BIND command"); |
| |
| if (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->IsIdentifier() || 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->AddSampler(sampler, descriptor_set, token->AsUint32()); |
| } else if (token->AsString() == "KERNEL") { |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| return Result("missing kernel arg identifier"); |
| |
| if (token->AsString() == "ARG_NAME") { |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| return Result("expected argument identifier"); |
| |
| pipeline->AddSampler(sampler, token->AsString()); |
| } else if (token->AsString() == "ARG_NUMBER") { |
| token = tokenizer_->NextToken(); |
| if (!token->IsInteger()) |
| return Result("expected argument number"); |
| |
| pipeline->AddSampler(sampler, token->AsUint32()); |
| } else { |
| return Result("missing ARG_NAME or ARG_NUMBER keyword"); |
| } |
| } else { |
| return Result("missing DESCRIPTOR_SET or KERNEL for BIND command"); |
| } |
| } else { |
| return Result("missing BUFFER or SAMPLER in BIND command"); |
| } |
| |
| return ValidateEndOfStatement("BIND command"); |
| } |
| |
| Result Parser::ParsePipelineVertexData(Pipeline* pipeline) { |
| auto token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| 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->IsIdentifier() || token->AsString() != "LOCATION") |
| return Result("VERTEX_DATA missing LOCATION"); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsInteger()) |
| return Result("invalid value for VERTEX_DATA LOCATION"); |
| |
| 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->IsIdentifier()) |
| return Result("missing buffer name in INDEX_DATA command"); |
| |
| auto* buffer = script_->GetBuffer(token->AsString()); |
| if (!buffer) |
| return Result("unknown buffer: " + token->AsString()); |
| |
| 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->IsIdentifier() || token->AsString() != "KERNEL") |
| return Result("missing KERNEL in SET command"); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| 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->IsIdentifier()) |
| 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->IsIdentifier() || token->AsString() != "AS") |
| return Result("missing AS in SET command"); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| return Result("expected data type"); |
| |
| auto type = ToType(token->AsString()); |
| if (!type) |
| return Result("invalid data type '" + token->AsString() + "' provided"); |
| |
| if (type->IsVec() || type->IsMatrix() || type->IsArray() || type->IsStruct()) |
| return Result("data type must be a scalar type"); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsInteger() && !token->IsDouble()) |
| return Result("expected data value"); |
| |
| auto fmt = MakeUnique<Format>(type.get()); |
| Value value; |
| if (fmt->IsFloat32() || fmt->IsFloat64()) |
| value.SetDoubleValue(token->AsDouble()); |
| else |
| value.SetIntValue(token->AsUint64()); |
| |
| Pipeline::ArgSetInfo info; |
| info.name = arg_name; |
| info.ordinal = arg_no; |
| info.fmt = fmt.get(); |
| info.value = value; |
| pipeline->SetArg(std::move(info)); |
| script_->RegisterFormat(std::move(fmt)); |
| script_->RegisterType(std::move(type)); |
| |
| return ValidateEndOfStatement("SET command"); |
| } |
| |
| Result Parser::ParsePipelinePolygonMode(Pipeline* pipeline) { |
| auto token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| return Result("missing mode in POLYGON_MODE command"); |
| |
| auto mode = token->AsString(); |
| |
| if (mode == "fill") |
| pipeline->SetPolygonMode(PolygonMode::kFill); |
| else if (mode == "line") |
| pipeline->SetPolygonMode(PolygonMode::kLine); |
| else if (mode == "point") |
| pipeline->SetPolygonMode(PolygonMode::kPoint); |
| else |
| return Result("invalid polygon mode: " + mode); |
| |
| return ValidateEndOfStatement("POLYGON_MODE command"); |
| } |
| |
| Result Parser::ParseStruct() { |
| auto token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| 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->IsIdentifier()) { |
| 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->IsIdentifier()) |
| 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->IsIdentifier()) |
| 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->IsIdentifier()) { |
| 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->IsIdentifier()) |
| 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->IsIdentifier()) |
| 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->IsIdentifier()) |
| return Result("BUFFER FORMAT must be an identifier"); |
| |
| buffer = MakeUnique<Buffer>(); |
| |
| auto type = script_->ParseType(token->AsString()); |
| if (!type) |
| return Result("invalid BUFFER FORMAT"); |
| |
| auto fmt = MakeUnique<Format>(type); |
| buffer->SetFormat(fmt.get()); |
| script_->RegisterFormat(std::move(fmt)); |
| |
| token = tokenizer_->PeekNextToken(); |
| while (token->IsIdentifier()) { |
| if (token->AsString() == "MIP_LEVELS") { |
| tokenizer_->NextToken(); |
| token = tokenizer_->NextToken(); |
| |
| if (!token->IsInteger()) |
| return Result("invalid value for MIP_LEVELS"); |
| |
| buffer->SetMipLevels(token->AsUint32()); |
| } else if (token->AsString() == "FILE") { |
| tokenizer_->NextToken(); |
| token = tokenizer_->NextToken(); |
| |
| if (!token->IsIdentifier()) |
| return Result("invalid value for FILE"); |
| |
| BufferDataFileType file_type = BufferDataFileType::kPng; |
| |
| if (token->AsString() == "TEXT") { |
| file_type = BufferDataFileType::kText; |
| token = tokenizer_->NextToken(); |
| } else if (token->AsString() == "BINARY") { |
| file_type = BufferDataFileType::kBinary; |
| token = tokenizer_->NextToken(); |
| } else if (token->AsString() == "PNG") { |
| token = tokenizer_->NextToken(); |
| } |
| |
| if (!token->IsIdentifier()) |
| return Result("missing file name for FILE"); |
| |
| buffer->SetDataFile(token->AsString(), file_type); |
| } else { |
| break; |
| } |
| token = tokenizer_->PeekNextToken(); |
| } |
| } 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::ParseImage() { |
| auto token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| return Result("invalid IMAGE name provided"); |
| |
| auto name = token->AsString(); |
| 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"); |
| |
| 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"); |
| |
| 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"); |
| |
| 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)); |
| |
| token = tokenizer_->PeekNextToken(); |
| if (token->IsIdentifier() && token->AsString() == "MIP_LEVELS") { |
| tokenizer_->NextToken(); |
| 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()); |
| } |
| |
| auto dim = StrToImageDimension(token->AsString()); |
| if (dim == ImageDimension::kUnknown) |
| return Result("unknown IMAGE dimensionality"); |
| buffer->SetImageDimension(dim); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier() || token->AsString() != "WIDTH") |
| return Result("expected IMAGE WIDTH"); |
| |
| // 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); |
| |
| if (dim == ImageDimension::k2D || dim == ImageDimension::k3D) { |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier() || token->AsString() != "HEIGHT") |
| return Result("expected IMAGE HEIGHT"); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsInteger() || token->AsUint32() == 0) |
| return Result("expected positive IMAGE HEIGHT"); |
| height = token->AsUint32(); |
| buffer->SetHeight(height); |
| } |
| |
| if (dim == ImageDimension::k3D) { |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier() || token->AsString() != "DEPTH") |
| 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); |
| } |
| |
| const uint32_t size_in_items = width * height * depth; |
| buffer->SetElementCount(size_in_items); |
| |
| // Parse initializers. |
| token = tokenizer_->NextToken(); |
| if (token->IsIdentifier()) { |
| if (token->AsString() == "FILL") { |
| Result r = ParseBufferInitializerFill(buffer.get(), size_in_items); |
| if (!r.IsSuccess()) |
| return r; |
| } else if (token->AsString() == "SERIES_FROM") { |
| Result r = ParseBufferInitializerSeries(buffer.get(), size_in_items); |
| if (!r.IsSuccess()) |
| return r; |
| } else { |
| return Result("unexpected IMAGE token: " + token->AsString()); |
| } |
| } else if (!token->IsEOL() && !token->IsEOS()) { |
| return Result("unexpected IMAGE token: " + token->ToOriginalString()); |
| } |
| |
| Result r = script_->AddBuffer(std::move(buffer)); |
| if (!r.IsSuccess()) |
| return r; |
| |
| return {}; |
| } |
| |
| Result Parser::ParseBufferInitializer(Buffer* buffer) { |
| auto token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| return Result("BUFFER invalid data type"); |
| |
| 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"); |
| |
| 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)); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| return Result("BUFFER missing initializer"); |
| |
| if (token->AsString() == "STD140") { |
| buffer->GetFormat()->SetLayout(Format::Layout::kStd140); |
| token = tokenizer_->NextToken(); |
| } else if (token->AsString() == "STD430") { |
| buffer->GetFormat()->SetLayout(Format::Layout::kStd430); |
| token = tokenizer_->NextToken(); |
| } |
| |
| if (!token->IsIdentifier()) |
| return Result("BUFFER missing initializer"); |
| |
| if (token->AsString() == "SIZE") |
| return ParseBufferInitializerSize(buffer); |
| if (token->AsString() == "WIDTH") { |
| token = tokenizer_->NextToken(); |
| if (!token->IsInteger()) |
| return Result("expected an integer for WIDTH"); |
| const uint32_t width = token->AsUint32(); |
| if (width == 0) |
| return Result("expected WIDTH to be positive"); |
| buffer->SetWidth(width); |
| buffer->SetImageDimension(ImageDimension::k2D); |
| |
| token = tokenizer_->NextToken(); |
| if (token->AsString() != "HEIGHT") |
| return Result("BUFFER HEIGHT missing"); |
| token = tokenizer_->NextToken(); |
| if (!token->IsInteger()) |
| return Result("expected an integer for HEIGHT"); |
| const uint32_t height = token->AsUint32(); |
| if (height == 0) |
| return Result("expected HEIGHT to be positive"); |
| buffer->SetHeight(height); |
| |
| token = tokenizer_->NextToken(); |
| uint32_t size_in_items = width * height; |
| buffer->SetElementCount(size_in_items); |
| if (token->AsString() == "FILL") |
| return ParseBufferInitializerFill(buffer, size_in_items); |
| if (token->AsString() == "SERIES_FROM") |
| return ParseBufferInitializerSeries(buffer, size_in_items); |
| return {}; |
| } |
| 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->IsIdentifier()) |
| 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); |
| if (token->AsString() == "FILE") { |
| token = tokenizer_->NextToken(); |
| |
| if (!token->IsIdentifier()) |
| return Result("invalid value for FILE"); |
| |
| BufferDataFileType file_type = BufferDataFileType::kPng; |
| |
| if (token->AsString() == "TEXT") { |
| file_type = BufferDataFileType::kText; |
| token = tokenizer_->NextToken(); |
| } else if (token->AsString() == "BINARY") { |
| file_type = BufferDataFileType::kBinary; |
| token = tokenizer_->NextToken(); |
| } else if (token->AsString() == "PNG") { |
| token = tokenizer_->NextToken(); |
| } |
| |
| if (!token->IsIdentifier()) |
| return Result("missing file name for FILE"); |
| |
| buffer->SetDataFile(token->AsString(), file_type); |
| |
| return {}; |
| } |
| |
| 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->IsFloat32() || fmt->IsFloat64(); |
| |
| // 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 type = buffer->GetFormat()->GetType(); |
| if (type->IsMatrix() || type->IsVec()) |
| return Result("BUFFER series_from must not be multi-row/column types"); |
| |
| Value counter; |
| |
| auto n = type->AsNumber(); |
| FormatMode mode = n->GetFormatMode(); |
| uint32_t num_bits = n->NumBits(); |
| if (type::Type::IsFloat32(mode, num_bits) || |
| type::Type::IsFloat64(mode, num_bits)) { |
| counter.SetDoubleValue(token->AsDouble()); |
| } else { |
| counter.SetIntValue(token->AsUint64()); |
| } |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| 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 (type::Type::IsFloat32(mode, num_bits) || |
| type::Type::IsFloat64(mode, num_bits)) { |
| 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(); |
| 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()) { |
| if (token->IsEOL()) |
| continue; |
| if (token->IsEOS()) |
| return Result("missing BUFFER END command"); |
| if (token->IsIdentifier() && token->AsString() == "END") |
| break; |
| if (!token->IsInteger() && !token->IsDouble() && !token->IsHex()) |
| 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 (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(value_count); |
| 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->IsIdentifier()) |
| 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->IsIdentifier()) |
| 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->IsIdentifier() || 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(); |
| cmd->SetPolygonMode(pipeline->GetPolygonMode()); |
| |
| 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->IsIdentifier() || 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_GRID") { |
| if (!pipeline->IsGraphics()) |
| return Result("RUN command requires graphics pipeline"); |
| |
| if (pipeline->GetVertexBuffers().size() > 0) { |
| return Result( |
| "RUN DRAW_GRID is not supported in a pipeline with " |
| "vertex buffers attached"); |
| } |
| |
| token = tokenizer_->NextToken(); |
| if (token->IsEOS() || token->IsEOL()) |
| return Result("RUN DRAW_GRID command requires parameters"); |
| |
| if (!token->IsIdentifier() || 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<DrawGridCommand>(pipeline); |
| cmd->SetLine(line); |
| cmd->SetPolygonMode(pipeline->GetPolygonMode()); |
| |
| 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->IsIdentifier() || 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()); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier() || token->AsString() != "CELLS") { |
| return Result("invalid token in RUN command: " + |
| token->ToOriginalString() + "; expected CELLS"); |
| } |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsInteger()) |
| return Result("missing columns value for RUN command"); |
| |
| cmd->SetColumns(token->AsUint32()); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsInteger()) |
| return Result("missing rows value for RUN command"); |
| |
| cmd->SetRows(token->AsUint32()); |
| |
| 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->IsIdentifier() || token->AsString() != "AS") |
| return Result("missing AS for RUN command"); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) { |
| 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->IsIdentifier() && 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->IsIdentifier() || 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->IsIdentifier() || 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(); |
| } |
| } |
| |
| uint32_t vertex_count = |
| indexed ? pipeline->GetIndexBuffer()->ElementCount() |
| : pipeline->GetVertexBuffers()[0].buffer->ElementCount(); |
| |
| // If we get here then we never set count, as if count was set it must |
| // be > 0. |
| if (count == 0) |
| count = vertex_count - start_idx; |
| |
| if (start_idx + count > vertex_count) { |
| if (indexed) |
| return Result("START_IDX plus COUNT exceeds index buffer data size"); |
| else |
| 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); |
| cmd->SetPolygonMode(pipeline->GetPolygonMode()); |
| |
| 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::ParseDebug() { |
| // DEBUG extends a RUN with debugger test cases |
| auto res = ParseRun(); |
| if (!res.IsSuccess()) { |
| return res; |
| } |
| |
| auto dbg = debug::Script::Create(); |
| for (auto token = tokenizer_->NextToken();; token = tokenizer_->NextToken()) { |
| if (token->IsEOL()) |
| continue; |
| if (token->IsEOS()) |
| return Result("missing DEBUG END command"); |
| if (token->IsIdentifier() && token->AsString() == "END") |
| break; |
| |
| if (token->AsString() == "THREAD") { |
| res = ParseDebugThread(dbg.get()); |
| if (!res.IsSuccess()) { |
| return res; |
| } |
| } else { |
| return Result("invalid token in DEBUG command: " + token->AsString()); |
| } |
| } |
| |
| command_list_.back()->SetDebugScript(std::move(dbg)); |
| |
| return Result(); |
| } |
| |
| Result Parser::ParseDebugThread(debug::Events* dbg) { |
| auto token = tokenizer_->NextToken(); |
| if (token->AsString() == "GLOBAL_INVOCATION_ID") { |
| uint32_t invocation[3] = {}; |
| for (int i = 0; i < 3; i++) { |
| token = tokenizer_->NextToken(); |
| if (!token->IsInteger()) |
| return Result("expected invocation index"); |
| invocation[i] = token->AsUint32(); |
| } |
| |
| auto thread = debug::ThreadScript::Create(); |
| auto result = ParseDebugThreadBody(thread.get()); |
| if (!result.IsSuccess()) { |
| return result; |
| } |
| |
| dbg->BreakOnComputeGlobalInvocation(invocation[0], invocation[1], |
| invocation[2], thread); |
| } else if (token->AsString() == "VERTEX_INDEX") { |
| token = tokenizer_->NextToken(); |
| if (!token->IsInteger()) |
| return Result("expected vertex index"); |
| auto vertex_index = token->AsUint32(); |
| |
| auto thread = debug::ThreadScript::Create(); |
| auto result = ParseDebugThreadBody(thread.get()); |
| if (!result.IsSuccess()) { |
| return result; |
| } |
| |
| dbg->BreakOnVertexIndex(vertex_index, thread); |
| } else if (token->AsString() == "FRAGMENT_WINDOW_SPACE_POSITION") { |
| token = tokenizer_->NextToken(); |
| if (!token->IsInteger()) |
| return Result("expected x unsigned integer coordinate"); |
| auto x = token->AsUint32(); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsInteger()) |
| return Result("expected y unsigned integer coordinate"); |
| auto y = token->AsUint32(); |
| |
| auto thread = debug::ThreadScript::Create(); |
| auto result = ParseDebugThreadBody(thread.get()); |
| if (!result.IsSuccess()) { |
| return result; |
| } |
| |
| dbg->BreakOnFragmentWindowSpacePosition(x, y, thread); |
| } else { |
| return Result("expected GLOBAL_INVOCATION_ID or VERTEX_INDEX"); |
| } |
| |
| return Result(); |
| } |
| |
| Result Parser::ParseDebugThreadBody(debug::Thread* thread) { |
| for (auto token = tokenizer_->NextToken();; token = tokenizer_->NextToken()) { |
| if (token->IsEOL()) { |
| continue; |
| } |
| if (token->IsEOS()) { |
| return Result("missing THREAD END command"); |
| } |
| if (token->IsIdentifier() && token->AsString() == "END") { |
| break; |
| } |
| |
| if (token->AsString() == "EXPECT") { |
| token = tokenizer_->NextToken(); |
| if (token->AsString() == "LOCATION") { |
| debug::Location location; |
| token = tokenizer_->NextToken(); |
| if (!token->IsString()) { |
| return Result("expected file name string"); |
| } |
| location.file = token->AsString(); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsInteger()) { |
| return Result("expected line number"); |
| } |
| location.line = token->AsUint32(); |
| |
| std::string line_source; |
| token = tokenizer_->NextToken(); |
| if (token->IsString()) { |
| line_source = token->AsString(); |
| } |
| |
| thread->ExpectLocation(location, line_source); |
| } else if (token->AsString() == "LOCAL") { |
| auto name = tokenizer_->NextToken(); |
| if (!name->IsString()) { |
| return Result("expected variable name"); |
| } |
| |
| if (tokenizer_->NextToken()->AsString() != "EQ") { |
| return Result("expected EQ"); |
| } |
| |
| auto value = tokenizer_->NextToken(); |
| if (value->IsHex() || value->IsInteger()) { |
| thread->ExpectLocal(name->AsString(), value->AsInt64()); |
| } else if (value->IsDouble()) { |
| thread->ExpectLocal(name->AsString(), value->AsDouble()); |
| } else if (value->IsString()) { |
| thread->ExpectLocal(name->AsString(), value->AsString()); |
| } else { |
| return Result("expected variable value"); |
| } |
| } else if (token->AsString() == "CALLSTACK") { |
| std::vector<debug::StackFrame> stack; |
| for (auto tok = tokenizer_->NextToken(); tok->AsString() != "END"; |
| tok = tokenizer_->NextToken()) { |
| if (tok->IsEOL()) { |
| continue; |
| } |
| debug::StackFrame frame; |
| if (!tok->IsString()) { |
| return Result("expected stack frame name"); |
| } |
| frame.name = tok->AsString(); |
| |
| tok = tokenizer_->NextToken(); |
| if (tok->IsString()) { |
| frame.location.file = tok->AsString(); |
| tok = tokenizer_->NextToken(); |
| if (tok->IsInteger()) { |
| frame.location.line = tok->AsUint32(); |
| } else if (!tok->IsEOL()) { |
| return Result( |
| "expected end of line or stack frame location number"); |
| } |
| } else if (!tok->IsEOL()) { |
| return Result( |
| "expected end of line or stack frame location file name"); |
| } |
| |
| stack.emplace_back(frame); |
| } |
| thread->ExpectCallstack(stack); |
| } else { |
| return Result("expected LOCATION or LOCAL"); |
| } |
| } else if (token->AsString() == "STEP_IN") { |
| thread->StepIn(); |
| } else if (token->AsString() == "STEP_OUT") { |
| thread->StepOut(); |
| } else if (token->AsString() == "STEP_OVER") { |
| thread->StepOver(); |
| } else if (token->AsString() == "CONTINUE") { |
| thread->Continue(); |
| } else { |
| return Result("invalid token in THREAD block: " + token->AsString()); |
| } |
| } |
| return Result(); |
| } |
| |
| Result Parser::ParseClear() { |
| auto token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| 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(); |
| const auto& segs = fmt->GetSegments(); |
| size_t seg_idx = 0; |
| while (!token->IsEOL() && !token->IsEOS()) { |
| Value v; |
| |
| 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()); |
| } |
| |
| 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()); |
| } |
| ++seg_idx; |
| if (seg_idx >= segs.size()) |
| seg_idx = 0; |
| |
| values->push_back(v); |
| token = tokenizer_->NextToken(); |
| } |
| return {}; |
| } |
| |
| Result Parser::ParseExpect() { |
| auto token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| 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"); |
| if (token->AsString() == "EQ_HISTOGRAM_EMD_BUFFER") { |
| return Result( |
| "missing buffer name between EXPECT and EQ_HISTOGRAM_EMD_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->IsIdentifier()) |
| return Result("invalid comparator in EXPECT command"); |
| |
| if (token->AsString() == "EQ_BUFFER" || token->AsString() == "RMSE_BUFFER" || |
| token->AsString() == "EQ_HISTOGRAM_EMD_BUFFER") { |
| auto type = token->AsString(); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| 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->IsIdentifier() && 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()); |
| } else if (type == "EQ_HISTOGRAM_EMD_BUFFER") { |
| cmd->SetComparator(CompareBufferCommand::Comparator::kHistogramEmd); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier() && token->AsString() == "TOLERANCE") |
| return Result("missing TOLERANCE for EXPECT EQ_HISTOGRAM_EMD_BUFFER"); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsInteger() && !token->IsDouble()) |
| return Result("invalid TOLERANCE for EXPECT EQ_HISTOGRAM_EMD_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->IsIdentifier() && 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->IsIdentifier()) { |
| 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); |
| } |
| |
| token = tokenizer_->NextToken(); |
| if (token->IsIdentifier() && token->AsString() == "TOLERANCE") { |
| std::vector<Probe::Tolerance> tolerances; |
| |
| Result r = ParseTolerances(&tolerances); |
| |
| if (!r.IsSuccess()) |
| return r; |
| |
| if (tolerances.empty()) |
| return Result("TOLERANCE specified but no tolerances provided"); |
| |
| if (!probe->IsRGBA() && tolerances.size() > 3) { |
| return Result( |
| "TOLERANCE for an RGB comparison has a maximum of 3 values"); |
| } |
| |
| if (tolerances.size() > 4) { |
| return Result( |
| "TOLERANCE for an RGBA comparison has a maximum of 4 values"); |
| } |
| |
| probe->SetTolerances(std::move(tolerances)); |
| token = tokenizer_->NextToken(); |
| } |
| |
| if (!token->IsEOL() && !token->IsEOS()) { |
| return Result("extra parameters after EXPECT command: " + |
| token->ToOriginalString()); |
| } |
| |
| command_list_.push_back(std::move(probe)); |
| |
| return {}; |
| } |
| |
| auto probe = MakeUnique<ProbeSSBOCommand>(buffer); |
| probe->SetLine(line); |
| |
| if (token->IsIdentifier() && token->AsString() == "TOLERANCE") { |
| std::vector<Probe::Tolerance> tolerances; |
| |
| Result r = ParseTolerances(&tolerances); |
| |
| if (!r.IsSuccess()) |
| return r; |
| |
| 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)); |
| token = tokenizer_->NextToken(); |
| } |
| |
| if (!token->IsIdentifier() || !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(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->IsIdentifier()) |
| 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->IsIdentifier()) |
| 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->IsIdentifier()) |
| 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"); |
| |
| // Set destination buffer to mirror origin buffer |
| buffer_to->SetWidth(buffer_from->GetWidth()); |
| buffer_to->SetHeight(buffer_from->GetHeight()); |
| buffer_to->SetElementCount(buffer_from->ElementCount()); |
| |
| 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->IsIdentifier()) |
| 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->IsIdentifier()) |
| 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->IsIdentifier()) |
| return Result("expected identifier"); |
| |
| 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->IsIdentifier() || 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->IsIdentifier() || 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->IsIdentifier() || token->AsString() != "FROM") |
| return Result("missing FROM in DERIVE_PIPELINE command"); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| 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->IsIdentifier()) { |
| 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->IsIdentifier()) { |
| 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->IsIdentifier() || 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->IsIdentifier()) |
| 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"); |
| } |
| |
| Result Parser::ParseSampler() { |
| auto token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier()) |
| return Result("invalid token when looking for sampler name"); |
| |
| auto sampler = MakeUnique<Sampler>(); |
| sampler->SetName(token->AsString()); |
| |
| token = tokenizer_->NextToken(); |
| while (!token->IsEOS() && !token->IsEOL()) { |
| if (!token->IsIdentifier()) |
| return Result("invalid token when looking for sampler parameters"); |
| |
| auto param = token->AsString(); |
| if (param == "MAG_FILTER") { |
| token = tokenizer_->NextToken(); |
| |
| if (!token->IsIdentifier()) |
| return Result("invalid token when looking for MAG_FILTER value"); |
| |
| auto filter = token->AsString(); |
| |
| if (filter == "linear") |
| sampler->SetMagFilter(FilterType::kLinear); |
| else if (filter == "nearest") |
| sampler->SetMagFilter(FilterType::kNearest); |
| else |
| return Result("invalid MAG_FILTER value " + filter); |
| } else if (param == "MIN_FILTER") { |
| token = tokenizer_->NextToken(); |
| |
| if (!token->IsIdentifier()) |
| return Result("invalid token when looking for MIN_FILTER value"); |
| |
| auto filter = token->AsString(); |
| |
| if (filter == "linear") |
| sampler->SetMinFilter(FilterType::kLinear); |
| else if (filter == "nearest") |
| sampler->SetMinFilter(FilterType::kNearest); |
| else |
| return Result("invalid MIN_FILTER value " + filter); |
| } else if (param == "ADDRESS_MODE_U") { |
| token = tokenizer_->NextToken(); |
| |
| if (!token->IsIdentifier()) |
| return Result("invalid token when looking for ADDRESS_MODE_U value"); |
| |
| auto mode_str = token->AsString(); |
| auto mode = StrToAddressMode(mode_str); |
| |
| if (mode == AddressMode::kUnknown) |
| return Result("invalid ADDRESS_MODE_U value " + mode_str); |
| |
| sampler->SetAddressModeU(mode); |
| } else if (param == "ADDRESS_MODE_V") { |
| token = tokenizer_->NextToken(); |
| |
| if (!token->IsIdentifier()) |
| return Result("invalid token when looking for ADDRESS_MODE_V value"); |
| |
| auto mode_str = token->AsString(); |
| auto mode = StrToAddressMode(mode_str); |
| |
| if (mode == AddressMode::kUnknown) |
| return Result("invalid ADDRESS_MODE_V value " + mode_str); |
| |
| sampler->SetAddressModeV(mode); |
| } else if (param == "ADDRESS_MODE_W") { |
| token = tokenizer_->NextToken(); |
| |
| if (!token->IsIdentifier()) |
| return Result("invalid token when looking for ADDRESS_MODE_W value"); |
| |
| auto mode_str = token->AsString(); |
| auto mode = StrToAddressMode(mode_str); |
| |
| if (mode == AddressMode::kUnknown) |
| return Result("invalid ADDRESS_MODE_W value " + mode_str); |
| |
| sampler->SetAddressModeW(mode); |
| } else if (param == "BORDER_COLOR") { |
| token = tokenizer_->NextToken(); |
| |
| if (!token->IsIdentifier()) |
| return Result("invalid token when looking for BORDER_COLOR value"); |
| |
| auto color_str = token->AsString(); |
| |
| if (color_str == "float_transparent_black") |
| sampler->SetBorderColor(BorderColor::kFloatTransparentBlack); |
| else if (color_str == "int_transparent_black") |
| sampler->SetBorderColor(BorderColor::kIntTransparentBlack); |
| else if (color_str == "float_opaque_black") |
| sampler->SetBorderColor(BorderColor::kFloatOpaqueBlack); |
| else if (color_str == "int_opaque_black") |
| sampler->SetBorderColor(BorderColor::kIntOpaqueBlack); |
| else if (color_str == "float_opaque_white") |
| sampler->SetBorderColor(BorderColor::kFloatOpaqueWhite); |
| else if (color_str == "int_opaque_white") |
| sampler->SetBorderColor(BorderColor::kIntOpaqueWhite); |
| else |
| return Result("invalid BORDER_COLOR value " + color_str); |
| } else if (param == "MIN_LOD") { |
| token = tokenizer_->NextToken(); |
| |
| if (!token->IsDouble()) |
| return Result("invalid token when looking for MIN_LOD value"); |
| |
| sampler->SetMinLOD(token->AsFloat()); |
| } else if (param == "MAX_LOD") { |
| token = tokenizer_->NextToken(); |
| |
| if (!token->IsDouble()) |
| return Result("invalid token when looking for MAX_LOD value"); |
| |
| sampler->SetMaxLOD(token->AsFloat()); |
| } else if (param == "NORMALIZED_COORDS") { |
| sampler->SetNormalizedCoords(true); |
| } else if (param == "UNNORMALIZED_COORDS") { |
| sampler->SetNormalizedCoords(false); |
| sampler->SetMinLOD(0.0f); |
| sampler->SetMaxLOD(0.0f); |
| } else { |
| return Result("unexpected sampler parameter " + param); |
| } |
| |
| token = tokenizer_->NextToken(); |
| } |
| |
| if (sampler->GetMaxLOD() < sampler->GetMinLOD()) { |
| return Result("max LOD needs to be greater than or equal to min LOD"); |
| } |
| |
| return script_->AddSampler(std::move(sampler)); |
| } |
| |
| Result Parser::ParseTolerances(std::vector<Probe::Tolerance>* tolerances) { |
| auto token = tokenizer_->PeekNextToken(); |
| while (!token->IsEOL() && !token->IsEOS()) { |
| if (!token->IsInteger() && !token->IsDouble()) |
| break; |
| |
| token = tokenizer_->NextToken(); |
| Result r = token->ConvertToDouble(); |
| if (!r.IsSuccess()) |
| return r; |
| |
| double value = token->AsDouble(); |
| token = tokenizer_->PeekNextToken(); |
| if (token->IsIdentifier() && token->AsString() == "%") { |
| tolerances->push_back(Probe::Tolerance{true, value}); |
| tokenizer_->NextToken(); |
| token = tokenizer_->PeekNextToken(); |
| } else { |
| tolerances->push_back(Probe::Tolerance{false, value}); |
| } |
| } |
| |
| return {}; |
| } |
| |
| Result Parser::ParseVirtualFile() { |
| auto token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier() && !token->IsString()) |
| return Result("invalid virtual file path"); |
| |
| auto path = token->AsString(); |
| |
| auto r = ValidateEndOfStatement("VIRTUAL_FILE command"); |
| if (!r.IsSuccess()) |
| return r; |
| |
| auto data = tokenizer_->ExtractToNext("END"); |
| |
| token = tokenizer_->NextToken(); |
| if (!token->IsIdentifier() || token->AsString() != "END") |
| return Result("VIRTUAL_FILE missing END command"); |
| |
| return script_->AddVirtualFile(path, data); |
| } |
| |
| } // namespace amberscript |
| } // namespace amber |