aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordan sinclair <dj2@everburning.com>2019-02-06 20:38:12 -0500
committerJaebaek Seo <duke.acacia@gmail.com>2019-02-06 20:38:12 -0500
commit2f596671b17199fac59f0bbf82f611e9903e7302 (patch)
tree005709e7277f271f77cd8cd9e652b236292629be /src
parent371f2aea208103171de5409a2f76d3afadab0e44 (diff)
downloadamber-2f596671b17199fac59f0bbf82f611e9903e7302.tar.gz
[amberscript] Parse PIPELINE BIND calls for color and depth buffers (#256)
This CL adds parsing for the AmberScript color and depth attachments. It also updates the pipelines to create a default color or depth attachment if one is not provided.
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/amberscript/parser.cc105
-rw-r--r--src/amberscript/parser.h1
-rw-r--r--src/amberscript/parser_test.cc669
-rw-r--r--src/pipeline.cc93
-rw-r--r--src/pipeline.h42
-rw-r--r--src/pipeline_test.cc66
7 files changed, 971 insertions, 9 deletions
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());