diff options
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | docs/amber_script.md | 14 | ||||
-rw-r--r-- | src/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/amberscript/parser.cc | 105 | ||||
-rw-r--r-- | src/amberscript/parser.h | 1 | ||||
-rw-r--r-- | src/amberscript/parser_test.cc | 669 | ||||
-rw-r--r-- | src/pipeline.cc | 93 | ||||
-rw-r--r-- | src/pipeline.h | 42 | ||||
-rw-r--r-- | src/pipeline_test.cc | 66 | ||||
-rw-r--r-- | tools/amber-syntax.vim | 5 | ||||
-rw-r--r-- | tools/amber.sublime-syntax | 2 |
11 files changed, 984 insertions, 18 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index c66352e..71439b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,6 +110,7 @@ if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") -Wextra -Wno-c++98-compat -Wno-c++98-compat-pedantic + -Wno-format-pedantic -Wno-padded -Wno-switch-enum -Wno-unknown-pragmas diff --git a/docs/amber_script.md b/docs/amber_script.md index 6ebb504..5106311 100644 --- a/docs/amber_script.md +++ b/docs/amber_script.md @@ -180,14 +180,16 @@ A `pipeline` can have buffers bound. This includes buffers to contain image attachment content, depth/stencil content, uniform buffers, etc. ``` - # Attach |buffer_name| as an output colour attachment at location |idx|. - # The provided buffer must be a `FORMAT` buffer. If no colour attachments are - # provided a single attachment with format `R8G8B8A8_UINT` will be created. + # Attach |buffer_name| as an output color attachment at location |idx|. + # The provided buffer must be a `FORMAT` buffer. If no color attachments are + # provided a single attachment with format `B8G8R8A8_UNORM` will be created + # for graphics pipelines. BIND BUFFER <buffer_name> AS color LOCATION <idx> # Attach |buffer_name| as the depth/stencil buffer. The provided buffer must # be a `FORMAT` buffer. If no depth/stencil buffer is specified a default - # buffer of format `D32_SFLOAT_S8_UINT` will be created. + # buffer of format `D32_SFLOAT_S8_UINT` will be created for graphics + # pipelines. BIND BUFFER <buffer_name> AS depth_stencil # Bind the buffer of the given |buffer_type| at the given descriptor set @@ -284,8 +286,8 @@ RUN <pipeline_name> DRAW_ARRAY INDEXED AS <topology> \ ### Commands ``` -# Sets the clear colour to use for |pipeline| which must be a `graphics` -# pipeline. The colours are integers from 0 - 255. +# Sets the clear color to use for |pipeline| which must be a `graphics` +# pipeline. The colors are integers from 0 - 255. # TODO(dsinclair): Do we need to allow different types here to handle different # buffer formats? CLEAR_COLOR <pipeline> <r (0 - 255)> <g (0 - 255)> <b (0 - 255)> <a (0 - 255)> diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 62e6f63..c1d57d9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -102,7 +102,9 @@ if (${AMBER_ENABLE_TESTS}) if (NOT MSVC) target_compile_options(amber_unittests PRIVATE - -Wno-global-constructors) + -Wno-global-constructors + -Wno-weak-vtables + ) endif() target_include_directories(amber_unittests PRIVATE diff --git a/src/amberscript/parser.cc b/src/amberscript/parser.cc index d84bf12..a5484bf 100644 --- a/src/amberscript/parser.cc +++ b/src/amberscript/parser.cc @@ -60,6 +60,53 @@ Result Parser::Parse(const std::string& data) { if (!r.IsSuccess()) return Result(make_error(r.Error())); } + + // Generate any needed color and depth attachments. This is done before + // validating in case one of the pipelines specifies the framebuffer size + // it needs to be verified against all other pipelines. + for (const auto& pipeline : script_->GetPipelines()) { + // Add a color attachment if needed + if (pipeline->GetColorAttachments().empty()) { + auto* buf = script_->GetBuffer(Pipeline::kGeneratedColorBuffer); + if (!buf) { + auto new_buf = pipeline->GenerateDefaultColorAttachmentBuffer(); + buf = new_buf.get(); + + Result r = script_->AddBuffer(std::move(new_buf)); + if (!r.IsSuccess()) + return r; + } + Result r = pipeline->AddColorAttachment(buf, 0); + if (!r.IsSuccess()) + return r; + } + + // Add a depth buffer if needed + if (pipeline->GetDepthBuffer().buffer == nullptr) { + auto* buf = script_->GetBuffer(Pipeline::kGeneratedDepthBuffer); + if (!buf) { + auto new_buf = pipeline->GenerateDefaultDepthAttachmentBuffer(); + buf = new_buf.get(); + + Result r = script_->AddBuffer(std::move(new_buf)); + if (!r.IsSuccess()) + return r; + } + + Result r = pipeline->SetDepthBuffer(buf); + 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 {}; } @@ -307,6 +354,8 @@ Result Parser::ParsePipelineBlock() { r = ParsePipelineShaderOptimizations(pipeline.get()); } else if (tok == "FRAMEBUFFER_SIZE") { r = ParsePipelineFramebufferSize(pipeline.get()); + } else if (tok == "BIND") { + r = ParsePipelineBind(pipeline.get()); } else { r = Result("unknown token in pipeline block: " + tok); } @@ -317,10 +366,6 @@ Result Parser::ParsePipelineBlock() { if (!token->IsString() || token->AsString() != "END") return Result("PIPELINE missing END command"); - r = pipeline->Validate(); - if (!r.IsSuccess()) - return r; - r = script_->AddPipeline(std::move(pipeline)); if (!r.IsSuccess()) return r; @@ -446,6 +491,58 @@ Result Parser::ParsePipelineFramebufferSize(Pipeline* pipeline) { return ValidateEndOfStatement("FRAMEBUFFER_SIZE command"); } +Result Parser::ParsePipelineBind(Pipeline* pipeline) { + auto token = tokenizer_->NextToken(); + if (!token->IsString()) + return Result("missing BUFFER in BIND command"); + if (token->AsString() != "BUFFER") + return Result("missing BUFFER in BIND command"); + + token = tokenizer_->NextToken(); + if (!token->IsString()) + return Result("missing buffer name in BIND command"); + + auto* buffer = script_->GetBuffer(token->AsString()); + if (!buffer) + return Result("unknown buffer: " + token->AsString()); + + token = tokenizer_->NextToken(); + if (!token->IsString() || token->AsString() != "AS") + return Result("BUFFER command missing AS keyword"); + + token = tokenizer_->NextToken(); + if (!token->IsString()) + return Result("invalid token for BUFFER type"); + + if (token->AsString() == "color") { + if (!buffer->IsFormatBuffer()) + return Result("color buffer must be a FORMAT buffer"); + + token = tokenizer_->NextToken(); + if (!token->IsString() || token->AsString() != "LOCATION") + return Result("BIND missing LOCATION"); + + token = tokenizer_->NextToken(); + if (!token->IsInteger()) + return Result("invalid value for BIND LOCATION"); + + Result r = pipeline->AddColorAttachment(buffer, token->AsUint32()); + if (!r.IsSuccess()) + return r; + } else if (token->AsString() == "depth_stencil") { + if (!buffer->IsFormatBuffer()) + return Result("depth buffer must be a FORMAT buffer"); + + Result r = pipeline->SetDepthBuffer(buffer); + if (!r.IsSuccess()) + return r; + } else { + return Result("unknown BUFFER type: " + token->AsString()); + } + + return ValidateEndOfStatement("BIND command"); +} + Result Parser::ToBufferType(const std::string& str, BufferType* type) { assert(type); diff --git a/src/amberscript/parser.h b/src/amberscript/parser.h index 5347e04..64ba6c2 100644 --- a/src/amberscript/parser.h +++ b/src/amberscript/parser.h @@ -57,6 +57,7 @@ class Parser : public amber::Parser { Result ParsePipelineAttach(Pipeline*); Result ParsePipelineShaderOptimizations(Pipeline*); Result ParsePipelineFramebufferSize(Pipeline*); + Result ParsePipelineBind(Pipeline*); std::unique_ptr<Tokenizer> tokenizer_; }; diff --git a/src/amberscript/parser_test.cc b/src/amberscript/parser_test.cc index ae2cf31..a22da2f 100644 --- a/src/amberscript/parser_test.cc +++ b/src/amberscript/parser_test.cc @@ -499,7 +499,7 @@ TEST_F(AmberScriptParserTest, PipelineEmpty) { Parser parser; Result r = parser.Parse(in); ASSERT_FALSE(r.IsSuccess()); - EXPECT_EQ("2: compute pipeline requires a compute shader", r.Error()); + EXPECT_EQ("compute pipeline requires a compute shader", r.Error()); } TEST_F(AmberScriptParserTest, PipelineWithUnknownShader) { @@ -1622,5 +1622,672 @@ END EXPECT_EQ("9: invalid height for FRAMEBUFFER_SIZE command", r.Error()); } +TEST_F(AmberScriptParserTest, BindColorBuffer) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_fb FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_fb AS color LOCATION 0 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& pipelines = script->GetPipelines(); + ASSERT_EQ(1U, pipelines.size()); + + const auto* pipeline = pipelines[0].get(); + const auto& color_buffers = pipeline->GetColorAttachments(); + ASSERT_EQ(1U, color_buffers.size()); + + const auto& buf_info = color_buffers[0]; + ASSERT_TRUE(buf_info.buffer != nullptr); + EXPECT_EQ(0, buf_info.location); + EXPECT_EQ(250 * 250, buf_info.buffer->GetSize()); + EXPECT_EQ(250 * 250 * 4 * sizeof(float), buf_info.buffer->GetSizeInBytes()); +} + +TEST_F(AmberScriptParserTest, BindColorBufferTwice) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_fb FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_fb AS color LOCATION 0 + BIND BUFFER my_fb AS color LOCATION 1 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: color buffer may only be bound to a PIPELINE once", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindColorBufferMissingBuffer) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_fb FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER AS color LOCATION 0 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("12: unknown buffer: AS", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindColorBufferNonDeclaredBuffer) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_fb AS color LOCATION 0 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("11: unknown buffer: my_fb", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindColorBufferMissingLocation) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_fb FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_fb AS color +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: BIND missing LOCATION", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindColorBufferMissingLocationIndex) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_fb FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_fb AS color LOCATION +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: invalid value for BIND LOCATION", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindColorBufferInvalidLocationIndex) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_fb FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_fb AS color LOCATION INVALID +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("12: invalid value for BIND LOCATION", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindColorBufferExtraParams) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_fb FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_fb AS color LOCATION 0 EXTRA +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("12: extra parameters after BIND command", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindColorBufferNonFormatBuffer) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_fb DATA_TYPE int32 SIZE 500 FILL 0 + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_fb AS color LOCATION 0 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("12: color buffer must be a FORMAT buffer", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindColorBufferDuplicateLocation) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_fb FORMAT R32G32B32A32_SFLOAT +BUFFER sec_fb FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_fb AS color LOCATION 0 + BIND BUFFER sec_fb AS color LOCATION 0 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("14: can not bind two color buffers to the same LOCATION", + r.Error()); +} + +TEST_F(AmberScriptParserTest, BindColorToTwoPipelinesRequiresMatchingSize) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_fb FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_fb AS color LOCATION 0 +END +PIPELINE graphics second_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_fb AS color LOCATION 0 + FRAMEBUFFER_SIZE 256 300 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("shared framebuffer must have same size over all PIPELINES", + r.Error()); +} + +TEST_F(AmberScriptParserTest, BindColorTwoPipelines) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_fb FORMAT R32G32B32A32_SFLOAT +BUFFER second_fb FORMAT R8G8B8A8_UINT +BUFFER depth_1 FORMAT D32_SFLOAT_S8_UINT +BUFFER depth_2 FORMAT D32_SFLOAT_S8_UINT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_fb AS color LOCATION 0 + BIND BUFFER depth_1 AS depth_stencil + FRAMEBUFFER_SIZE 90 180 +END +PIPELINE graphics second_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER second_fb AS color LOCATION 9 + BIND BUFFER depth_2 AS depth_stencil + FRAMEBUFFER_SIZE 256 300 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& pipelines = script->GetPipelines(); + ASSERT_EQ(2U, pipelines.size()); + + const auto* pipeline = pipelines[0].get(); + const auto& color_buffers1 = pipeline->GetColorAttachments(); + ASSERT_EQ(1U, color_buffers1.size()); + + const auto& buf1 = color_buffers1[0]; + ASSERT_TRUE(buf1.buffer != nullptr); + EXPECT_EQ(0, buf1.location); + EXPECT_EQ(90 * 180, buf1.buffer->GetSize()); + EXPECT_EQ(90 * 180 * 4 * sizeof(float), buf1.buffer->GetSizeInBytes()); + + pipeline = pipelines[1].get(); + const auto& color_buffers2 = pipeline->GetColorAttachments(); + const auto& buf2 = color_buffers2[0]; + ASSERT_TRUE(buf2.buffer != nullptr); + EXPECT_EQ(9, buf2.location); + EXPECT_EQ(256 * 300, buf2.buffer->GetSize()); + EXPECT_EQ(256 * 300 * sizeof(uint32_t), buf2.buffer->GetSizeInBytes()); +} + +TEST_F(AmberScriptParserTest, BindColorFBSizeSetBeforeBuffer) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_fb FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + FRAMEBUFFER_SIZE 90 180 + BIND BUFFER my_fb AS color LOCATION 0 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& pipelines = script->GetPipelines(); + ASSERT_EQ(1U, pipelines.size()); + + const auto* pipeline = pipelines[0].get(); + const auto& color_buffers1 = pipeline->GetColorAttachments(); + ASSERT_EQ(1U, color_buffers1.size()); + + const auto& buf1 = color_buffers1[0]; + ASSERT_TRUE(buf1.buffer != nullptr); + EXPECT_EQ(0, buf1.location); + EXPECT_EQ(90 * 180, buf1.buffer->GetSize()); + EXPECT_EQ(90 * 180 * 4 * sizeof(float), buf1.buffer->GetSizeInBytes()); +} + +TEST_F(AmberScriptParserTest, BindColorFBSizeSetAfterBuffer) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_fb FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_fb AS color LOCATION 0 + FRAMEBUFFER_SIZE 90 180 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& pipelines = script->GetPipelines(); + ASSERT_EQ(1U, pipelines.size()); + + const auto* pipeline = pipelines[0].get(); + const auto& color_buffers1 = pipeline->GetColorAttachments(); + ASSERT_EQ(1U, color_buffers1.size()); + + const auto& buf1 = color_buffers1[0]; + ASSERT_TRUE(buf1.buffer != nullptr); + EXPECT_EQ(0, buf1.location); + EXPECT_EQ(90 * 180, buf1.buffer->GetSize()); + EXPECT_EQ(90 * 180 * 4 * sizeof(float), buf1.buffer->GetSizeInBytes()); +} + +TEST_F(AmberScriptParserTest, PipelineDefaultColorBuffer) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment +END +PIPELINE graphics my_pipeline2 + ATTACH my_shader + ATTACH my_fragment +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& pipelines = script->GetPipelines(); + ASSERT_EQ(2U, pipelines.size()); + + ASSERT_EQ(1U, pipelines[0]->GetColorAttachments().size()); + const auto& buf1 = pipelines[0]->GetColorAttachments()[0]; + ASSERT_TRUE(buf1.buffer != nullptr); + + Buffer* buffer1 = buf1.buffer; + ASSERT_TRUE(buffer1->IsFormatBuffer()); + EXPECT_EQ(FormatType::kB8G8R8A8_UNORM, + buffer1->AsFormatBuffer()->GetFormat().GetFormatType()); + EXPECT_EQ(0, buf1.location); + EXPECT_EQ(250 * 250, buffer1->GetSize()); + EXPECT_EQ(250 * 250 * sizeof(uint32_t), buffer1->GetSizeInBytes()); + + ASSERT_EQ(1U, pipelines[1]->GetColorAttachments().size()); + const auto& buf2 = pipelines[1]->GetColorAttachments()[0]; + ASSERT_TRUE(buf2.buffer != nullptr); + ASSERT_EQ(buffer1, buf2.buffer); + EXPECT_EQ(0, buf2.location); + EXPECT_EQ(FormatType::kB8G8R8A8_UNORM, + buf2.buffer->AsFormatBuffer()->GetFormat().GetFormatType()); + EXPECT_EQ(250 * 250, buf2.buffer->GetSize()); + EXPECT_EQ(250 * 250 * sizeof(uint32_t), buf2.buffer->GetSizeInBytes()); +} + +TEST_F(AmberScriptParserTest, PipelineDefaultColorBufferMismatchSize) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment +END +PIPELINE graphics my_pipeline2 + ATTACH my_shader + ATTACH my_fragment + FRAMEBUFFER_SIZE 256 256 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + + EXPECT_EQ("shared framebuffer must have same size over all PIPELINES", + r.Error()); +} + +TEST_F(AmberScriptParserTest, PipelineDefaultDepthBuffer) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& pipelines = script->GetPipelines(); + ASSERT_EQ(1U, pipelines.size()); + + const auto* pipeline = pipelines[0].get(); + const auto& buf = pipeline->GetDepthBuffer(); + + ASSERT_TRUE(buf.buffer != nullptr); + EXPECT_EQ(FormatType::kD32_SFLOAT_S8_UINT, + buf.buffer->AsFormatBuffer()->GetFormat().GetFormatType()); + EXPECT_EQ(250 * 250, buf.buffer->GetSize()); + EXPECT_EQ(250 * 250 * (sizeof(float) + sizeof(uint8_t)), + buf.buffer->GetSizeInBytes()); +} + +TEST_F(AmberScriptParserTest, BindDepthBuffer) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_buf FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_buf AS depth_stencil + FRAMEBUFFER_SIZE 90 180 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& pipelines = script->GetPipelines(); + ASSERT_EQ(1U, pipelines.size()); + + const auto* pipeline = pipelines[0].get(); + const auto& buf = pipeline->GetDepthBuffer(); + ASSERT_TRUE(buf.buffer != nullptr); + EXPECT_EQ(90 * 180, buf.buffer->GetSize()); + EXPECT_EQ(90 * 180 * 4 * sizeof(float), buf.buffer->GetSizeInBytes()); +} + +TEST_F(AmberScriptParserTest, BindDepthBufferNonFormatBuffer) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_buf DATA_TYPE int32 SIZE 500 FILL 0 + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_buf AS depth_stencil +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("12: depth buffer must be a FORMAT buffer", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindDepthBufferExtraParams) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_buf FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_buf AS depth_stencil EXTRA + FRAMEBUFFER_SIZE 90 180 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("12: extra parameters after BIND command", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindBufferMissingBufferName) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_buf FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER AS depth_stencil + FRAMEBUFFER_SIZE 90 180 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("12: unknown buffer: AS", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindBufferAsMissingType) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_buf FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_buf AS + FRAMEBUFFER_SIZE 90 180 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: invalid token for BUFFER type", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindBufferAsInvalidType) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_buf FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_buf AS + FRAMEBUFFER_SIZE 90 180 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: invalid token for BUFFER type", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindDepthBufferUnknownBuffer) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_buf AS depth_stencil + FRAMEBUFFER_SIZE 90 180 +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("11: unknown buffer: my_buf", r.Error()); +} + +TEST_F(AmberScriptParserTest, BindBufferMultipleDepthBuffers) { + std::string in = R"( +SHADER vertex my_shader PASSTHROUGH +SHADER fragment my_fragment GLSL +# GLSL Shader +END +BUFFER my_buf FORMAT R32G32B32A32_SFLOAT +BUFFER my_buf2 FORMAT R32G32B32A32_SFLOAT + +PIPELINE graphics my_pipeline + ATTACH my_shader + ATTACH my_fragment + + BIND BUFFER my_buf AS depth_stencil + BIND BUFFER my_buf AS depth_stencil +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("14: can only bind one depth buffer in a PIPELINE", r.Error()); +} + } // namespace amberscript } // namespace amber diff --git a/src/pipeline.cc b/src/pipeline.cc index 04e8e5d..590abb0 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -17,6 +17,9 @@ #include <algorithm> #include <set> +#include "src/format_parser.h" +#include "src/make_unique.h" + namespace amber { Pipeline::ShaderInfo::ShaderInfo(const Shader* shader, ShaderType type) @@ -26,6 +29,11 @@ Pipeline::ShaderInfo::ShaderInfo(const ShaderInfo&) = default; Pipeline::ShaderInfo::~ShaderInfo() = default; +const char* Pipeline::kDefaultColorBufferFormat = "B8G8R8A8_UNORM"; +const char* Pipeline::kDefaultDepthBufferFormat = "D32_SFLOAT_S8_UINT"; +const char* Pipeline::kGeneratedColorBuffer = "amber_default_framebuffer"; +const char* Pipeline::kGeneratedDepthBuffer = "amber_default_depth_buffer"; + Pipeline::Pipeline(PipelineType type) : pipeline_type_(type) {} Pipeline::~Pipeline() = default; @@ -117,6 +125,22 @@ Result Pipeline::SetShaderType(const Shader* shader, ShaderType type) { } Result Pipeline::Validate() const { + if (color_attachments_.empty()) + return Result("PIPELINE missing color attachment"); + + size_t fb_size = fb_width_ * fb_height_; + for (const auto& attachment : color_attachments_) { + if (attachment.buffer->GetSize() != fb_size) { + return Result( + "shared framebuffer must have same size over all PIPELINES"); + } + } + + if (depth_buffer_.buffer == nullptr) + return Result("PIPELINE missing depth buffer"); + if (depth_buffer_.buffer->GetSize() != fb_size) + return Result("shared depth buffer must have same size over all PIPELINES"); + if (pipeline_type_ == PipelineType::kGraphics) return ValidateGraphics(); return ValidateCompute(); @@ -144,6 +168,7 @@ Result Pipeline::ValidateGraphics() const { return Result("graphics pipeline requires a vertex shader"); if (!found_fragment) return Result("graphics pipeline requires a fragment shader"); + return {}; } @@ -154,4 +179,72 @@ Result Pipeline::ValidateCompute() const { return {}; } +void Pipeline::UpdateFramebufferSizes() { + size_t size = fb_width_ * fb_height_; + if (size == 0) + return; + + for (auto& attachment : color_attachments_) { + attachment.buffer->SetSize(size); + attachment.width = fb_width_; + attachment.height = fb_height_; + } + + if (depth_buffer_.buffer) { + depth_buffer_.buffer->SetSize(size); + depth_buffer_.width = fb_width_; + depth_buffer_.height = fb_height_; + } +} + +Result Pipeline::AddColorAttachment(Buffer* buf, uint32_t location) { + for (const auto& attachment : color_attachments_) { + if (attachment.location == location) + return Result("can not bind two color buffers to the same LOCATION"); + if (attachment.buffer == buf) + return Result("color buffer may only be bound to a PIPELINE once"); + } + + color_attachments_.push_back(BufferInfo{buf}); + + auto& info = color_attachments_.back(); + info.location = location; + info.width = fb_width_; + info.height = fb_height_; + + buf->SetSize(fb_width_ * fb_height_); + return {}; +} + +Result Pipeline::SetDepthBuffer(Buffer* buf) { + if (depth_buffer_.buffer != nullptr) + return Result("can only bind one depth buffer in a PIPELINE"); + + depth_buffer_.buffer = buf; + depth_buffer_.width = fb_width_; + depth_buffer_.height = fb_height_; + + buf->SetSize(fb_width_ * fb_height_); + + return {}; +} + +std::unique_ptr<Buffer> Pipeline::GenerateDefaultColorAttachmentBuffer() const { + FormatParser fp; + + std::unique_ptr<Buffer> buf = MakeUnique<FormatBuffer>(BufferType::kColor); + buf->SetName(kGeneratedColorBuffer); + buf->AsFormatBuffer()->SetFormat(fp.Parse(kDefaultColorBufferFormat)); + return buf; +} + +std::unique_ptr<Buffer> Pipeline::GenerateDefaultDepthAttachmentBuffer() const { + FormatParser fp; + + std::unique_ptr<Buffer> buf = MakeUnique<FormatBuffer>(BufferType::kDepth); + buf->SetName(kGeneratedDepthBuffer); + buf->AsFormatBuffer()->SetFormat(fp.Parse(kDefaultDepthBufferFormat)); + return buf; +} + } // namespace amber diff --git a/src/pipeline.h b/src/pipeline.h index 54f4f42..08e8e21 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -15,10 +15,12 @@ #ifndef SRC_PIPELINE_H_ #define SRC_PIPELINE_H_ +#include <memory> #include <string> #include <vector> #include "amber/result.h" +#include "src/buffer.h" #include "src/shader.h" namespace amber { @@ -55,6 +57,21 @@ class Pipeline { std::string entry_point_; }; + struct BufferInfo { + BufferInfo() = default; + explicit BufferInfo(Buffer* buf) : buffer(buf) {} + + Buffer* buffer = nullptr; + uint32_t location = 0; + uint32_t width = 0; + uint32_t height = 0; + }; + + static const char* kDefaultColorBufferFormat; + static const char* kDefaultDepthBufferFormat; + static const char* kGeneratedColorBuffer; + static const char* kGeneratedDepthBuffer; + explicit Pipeline(PipelineType type); ~Pipeline(); @@ -63,10 +80,16 @@ class Pipeline { void SetName(const std::string& name) { name_ = name; } const std::string& GetName() const { return name_; } - void SetFramebufferWidth(uint32_t fb_width) { fb_width_ = fb_width; } + void SetFramebufferWidth(uint32_t fb_width) { + fb_width_ = fb_width; + UpdateFramebufferSizes(); + } uint32_t GetFramebufferWidth() const { return fb_width_; } - void SetFramebufferHeight(uint32_t fb_height) { fb_height_ = fb_height; } + void SetFramebufferHeight(uint32_t fb_height) { + fb_height_ = fb_height; + UpdateFramebufferSizes(); + } uint32_t GetFramebufferHeight() const { return fb_height_; } Result AddShader(const Shader*, ShaderType); @@ -77,16 +100,31 @@ class Pipeline { Result SetShaderOptimizations(const Shader* shader, const std::vector<std::string>& opts); + const std::vector<BufferInfo>& GetColorAttachments() const { + return color_attachments_; + } + Result AddColorAttachment(Buffer* buf, uint32_t location); + + Result SetDepthBuffer(Buffer* buf); + const BufferInfo& GetDepthBuffer() const { return depth_buffer_; } + // Validates that the pipeline has been created correctly. Result Validate() const; + std::unique_ptr<Buffer> GenerateDefaultColorAttachmentBuffer() const; + std::unique_ptr<Buffer> GenerateDefaultDepthAttachmentBuffer() const; + private: + void UpdateFramebufferSizes(); + Result ValidateGraphics() const; Result ValidateCompute() const; PipelineType pipeline_type_ = PipelineType::kCompute; std::string name_; std::vector<ShaderInfo> shaders_; + std::vector<BufferInfo> color_attachments_; + BufferInfo depth_buffer_; uint32_t fb_width_ = 250; uint32_t fb_height_ = 250; diff --git a/src/pipeline_test.cc b/src/pipeline_test.cc index 7a54a45..0e6bb7b 100644 --- a/src/pipeline_test.cc +++ b/src/pipeline_test.cc @@ -13,6 +13,7 @@ // limitations under the License. #include "src/pipeline.h" + #include "gtest/gtest.h" namespace amber { @@ -24,7 +25,31 @@ struct ShaderTypeData { } // namespace -using AmberScriptPipelineTest = testing::Test; +class AmberScriptPipelineTest : public testing::Test { + public: + void TearDown() override { + color_buffer_ = nullptr; + depth_buffer_ = nullptr; + } + + void SetupColorAttachment(Pipeline* p, uint32_t location) { + if (!color_buffer_) + color_buffer_ = p->GenerateDefaultColorAttachmentBuffer(); + + p->AddColorAttachment(color_buffer_.get(), location); + } + + void SetupDepthAttachment(Pipeline* p) { + if (!depth_buffer_) + depth_buffer_ = p->GenerateDefaultDepthAttachmentBuffer(); + + p->SetDepthBuffer(depth_buffer_.get()); + } + + private: + std::unique_ptr<Buffer> color_buffer_; + std::unique_ptr<Buffer> depth_buffer_; +}; TEST_F(AmberScriptPipelineTest, AddShader) { Shader v(kShaderTypeVertex); @@ -169,6 +194,24 @@ TEST_F(AmberScriptPipelineTest, SetOptimizationForInvalidShader) { EXPECT_EQ("unknown shader specified for optimizations: my_shader", r.Error()); } +TEST_F(AmberScriptPipelineTest, GraphicsPipelineRequiresColorAttachment) { + Pipeline p(PipelineType::kGraphics); + SetupDepthAttachment(&p); + + Result r = p.Validate(); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("PIPELINE missing color attachment", r.Error()); +} + +TEST_F(AmberScriptPipelineTest, GraphicsPipelineRequiresDepthAttachment) { + Pipeline p(PipelineType::kGraphics); + SetupColorAttachment(&p, 0); + + Result r = p.Validate(); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("PIPELINE missing depth buffer", r.Error()); +} + TEST_F(AmberScriptPipelineTest, GraphicsPipelineRequiresVertexAndFragmentShader) { Shader v(kShaderTypeVertex); @@ -176,6 +219,9 @@ TEST_F(AmberScriptPipelineTest, Shader g(kShaderTypeGeometry); Pipeline p(PipelineType::kGraphics); + SetupColorAttachment(&p, 0); + SetupDepthAttachment(&p); + Result r = p.AddShader(&v, kShaderTypeVertex); EXPECT_TRUE(r.IsSuccess()) << r.Error(); @@ -194,6 +240,9 @@ TEST_F(AmberScriptPipelineTest, GraphicsPipelineMissingFragmentShader) { Shader g(kShaderTypeGeometry); Pipeline p(PipelineType::kGraphics); + SetupColorAttachment(&p, 0); + SetupDepthAttachment(&p); + Result r = p.AddShader(&v, kShaderTypeVertex); EXPECT_TRUE(r.IsSuccess()) << r.Error(); @@ -210,6 +259,9 @@ TEST_F(AmberScriptPipelineTest, GraphicsPipelineMissingVertexShader) { Shader g(kShaderTypeGeometry); Pipeline p(PipelineType::kGraphics); + SetupColorAttachment(&p, 0); + SetupDepthAttachment(&p); + Result r = p.AddShader(&g, kShaderTypeGeometry); EXPECT_TRUE(r.IsSuccess()) << r.Error(); @@ -226,6 +278,9 @@ TEST_F(AmberScriptPipelineTest, Shader g(kShaderTypeGeometry); Pipeline p(PipelineType::kGraphics); + SetupColorAttachment(&p, 0); + SetupDepthAttachment(&p); + Result r = p.AddShader(&g, kShaderTypeGeometry); EXPECT_TRUE(r.IsSuccess()) << r.Error(); @@ -237,6 +292,9 @@ TEST_F(AmberScriptPipelineTest, TEST_F(AmberScriptPipelineTest, GraphicsPipelineWihoutShaders) { Pipeline p(PipelineType::kGraphics); + SetupColorAttachment(&p, 0); + SetupDepthAttachment(&p); + Result r = p.Validate(); EXPECT_FALSE(r.IsSuccess()) << r.Error(); EXPECT_EQ("graphics pipeline requires vertex and fragment shaders", @@ -247,6 +305,9 @@ TEST_F(AmberScriptPipelineTest, ComputePipelineRequiresComputeShader) { Shader c(kShaderTypeCompute); Pipeline p(PipelineType::kCompute); + SetupColorAttachment(&p, 0); + SetupDepthAttachment(&p); + Result r = p.AddShader(&c, kShaderTypeCompute); EXPECT_TRUE(r.IsSuccess()) << r.Error(); @@ -256,6 +317,9 @@ TEST_F(AmberScriptPipelineTest, ComputePipelineRequiresComputeShader) { TEST_F(AmberScriptPipelineTest, ComputePipelineWithoutShader) { Pipeline p(PipelineType::kCompute); + SetupColorAttachment(&p, 0); + SetupDepthAttachment(&p); + Result r = p.Validate(); EXPECT_FALSE(r.IsSuccess()) << r.Error(); EXPECT_EQ("compute pipeline requires a compute shader", r.Error()); diff --git a/tools/amber-syntax.vim b/tools/amber-syntax.vim index 360463d..d197b65 100644 --- a/tools/amber-syntax.vim +++ b/tools/amber-syntax.vim @@ -33,8 +33,9 @@ syn keyword amberBlockCmd DESCRIPTOR_SET BINDING IDX TO EXPECT PASSTHROUGH syn keyword amberBlockCmd DATA_TYPE DATA SERIES_FROM DRAW_ARRAY IN START_IDX syn keyword amberBlockCmd COUNT CLEAR CLEAR_COLOR AS POS DRAW_RECT INC_BY syn keyword amberBlockCmd FRAMEBUFFER ENTRY_POINT SHADER_OPTIMIZATION -syn keyword amberBlockCmd FORMAT FRAMEBUFFER_SIZE BIND SAMPLER VERTEX_DATA INDEX_DATA -syn keyword amberBlockCmd INDEXED IMAGE_ATTACHMENT DEPTH_STENCIL_ATTACHMENT +syn keyword amberBlockCmd FORMAT FRAMEBUFFER_SIZE LOCATION BIND SAMPLER +syn keyword amberBlockCmd VERTEX_DATA INDEX_DATA INDEXED IMAGE_ATTACHMENT +syn keyword amberBlockCmd DEPTH_STENCIL_ATTACHMENT syn keyword amberComparator EQ NE LT LE GT GE EQ_RGB EQ_RGBA diff --git a/tools/amber.sublime-syntax b/tools/amber.sublime-syntax index 613782b..89e339a 100644 --- a/tools/amber.sublime-syntax +++ b/tools/amber.sublime-syntax @@ -27,7 +27,7 @@ contexts: scope: keyword.control.amber - match: '\b(FORMAT|FRAMEBUFFER_SIZE|BIND|SAMPLER|VERTEX_DATA|INDEX_DATA|INDEXED)\b' scope: keyword.control.amber - - match : '\b(IMAGE_ATTACHMENT|DEPTH_STENCIL_ATTACHMENT)\b' + - match : '\b(IMAGE_ATTACHMENT|DEPTH_STENCIL_ATTACHMENT|LOCATION)\b' scope: keyword.control.amber - match: '\b(vertex|fragment|compute|geometry|tessellation_evaluation|tessellation_control)\b' |