Skip to content

Commit

Permalink
Add alpha blending support for AmberScript (google#971)
Browse files Browse the repository at this point in the history
Alpha blending was previously only supported for VkScript. This change
adds the support for AmberScript too.
  • Loading branch information
asuonpaa authored Nov 18, 2021
1 parent 5bd7d35 commit bbb42af
Show file tree
Hide file tree
Showing 9 changed files with 532 additions and 2 deletions.
90 changes: 90 additions & 0 deletions docs/amber_script.md
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,96 @@ The following commands are all specified within the `PIPELINE` command.
END
```

#### Blend factors
* `zero`
* `one`
* `src_color`
* `one_minus_src_color`
* `dst_color`
* `one_minus_dst_color`
* `src_alpha`
* `one_minus_src_alpha`
* `dst_alpha`
* `one_minus_dst_alpha`
* `constant_color`
* `one_minus_constant_color`
* `constant_alpha`
* `one_minus_constant_alpha`
* `src_alpha_saturate`
* `src1_color`
* `one_minus_src1_color`
* `src1_alpha`
* `one_minus_src1_alpha`

#### Blend operations
* `add`
* `substract`
* `reverse_substract`
* `min`
* `max`

The following operations also require VK_EXT_blend_operation_advanced
when using a Vulkan backend.
* `zero`
* `src`
* `dst`
* `src_over`
* `dst_over`
* `src_in`
* `dst_in`
* `src_out`
* `dst_out`
* `src_atop`
* `dst_atop`
* `xor`
* `multiply`
* `screen`
* `overlay`
* `darken`
* `lighten`
* `color_dodge`
* `color_burn`
* `hard_light`
* `soft_light`
* `difference`
* `exclusion`
* `invert`
* `invert_rgb`
* `linear_dodge`
* `linear_burn`
* `vivid_light`
* `linear_light`
* `pin_light`
* `hard_mix`
* `hsl_hue`
* `hsl_saturation`
* `hsl_color`
* `hsl_luminosity`
* `plus`
* `plus_clamped`
* `plus_clamped_alpha`
* `plus_darker`
* `minus`
* `minus_clamped`
* `contrast`
* `invert_org`
* `red`
* `green`
* `blue`

```groovy
# Enable alpha blending and set blend factors and operations. Available
# blend factors and operations are listed above.
BLEND
SRC_COLOR_FACTOR {src_color_factor}
DST_COLOR_FACTOR {dst_color_factor}
COLOR_OP {color_op}
SRC_ALPHA_FACTOR {src_alpha_factor}
DST_ALPHA_FACTOR {dst_alpha_factor}
ALPHA_OP {alpha_op}
END
```

```groovy
# Set the size of the render buffers. |width| and |height| are integers and
# default to 250x250.
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ if (${AMBER_ENABLE_TESTS})
amberscript/parser_shader_opt_test.cc
amberscript/parser_shader_test.cc
amberscript/parser_stencil_test.cc
amberscript/parser_blend_test.cc
amberscript/parser_struct_test.cc
amberscript/parser_subgroup_size_control_test.cc
amberscript/parser_test.cc
Expand Down
94 changes: 94 additions & 0 deletions src/amberscript/parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,8 @@ Result Parser::ParsePipelineBody(const std::string& cmd_name,
r = ParsePipelineSubgroup(pipeline.get());
} else if (tok == "PATCH_CONTROL_POINTS") {
r = ParsePipelinePatchControlPoints(pipeline.get());
} else if (tok == "BLEND") {
r = ParsePipelineBlend(pipeline.get());
} else {
r = Result("unknown token in pipeline block: " + tok);
}
Expand Down Expand Up @@ -1863,6 +1865,98 @@ Result Parser::ParsePipelineStencil(Pipeline* pipeline) {
return ValidateEndOfStatement("STENCIL command");
}

Result Parser::ParsePipelineBlend(Pipeline* pipeline) {
pipeline->GetPipelineData()->SetEnableBlend(true);

while (true) {
auto token = tokenizer_->NextToken();
if (token->IsEOL())
continue;
if (token->IsEOS())
return Result("BLEND missing END command");
if (!token->IsIdentifier())
return Result("BLEND options must be identifiers");
if (token->AsString() == "END")
break;

if (token->AsString() == "SRC_COLOR_FACTOR") {
token = tokenizer_->NextToken();

if (!token->IsIdentifier())
return Result("BLEND invalid value for SRC_COLOR_FACTOR");

const auto factor = NameToBlendFactor(token->AsString());
if (factor == BlendFactor::kUnknown)
return Result("BLEND invalid value for SRC_COLOR_FACTOR: " +
token->AsString());
pipeline->GetPipelineData()->SetSrcColorBlendFactor(
NameToBlendFactor(token->AsString()));
} else if (token->AsString() == "DST_COLOR_FACTOR") {
token = tokenizer_->NextToken();

if (!token->IsIdentifier())
return Result("BLEND invalid value for DST_COLOR_FACTOR");

const auto factor = NameToBlendFactor(token->AsString());
if (factor == BlendFactor::kUnknown)
return Result("BLEND invalid value for DST_COLOR_FACTOR: " +
token->AsString());
pipeline->GetPipelineData()->SetDstColorBlendFactor(
NameToBlendFactor(token->AsString()));
} else if (token->AsString() == "SRC_ALPHA_FACTOR") {
token = tokenizer_->NextToken();

if (!token->IsIdentifier())
return Result("BLEND invalid value for SRC_ALPHA_FACTOR");

const auto factor = NameToBlendFactor(token->AsString());
if (factor == BlendFactor::kUnknown)
return Result("BLEND invalid value for SRC_ALPHA_FACTOR: " +
token->AsString());
pipeline->GetPipelineData()->SetSrcAlphaBlendFactor(
NameToBlendFactor(token->AsString()));
} else if (token->AsString() == "DST_ALPHA_FACTOR") {
token = tokenizer_->NextToken();

if (!token->IsIdentifier())
return Result("BLEND invalid value for DST_ALPHA_FACTOR");

const auto factor = NameToBlendFactor(token->AsString());
if (factor == BlendFactor::kUnknown)
return Result("BLEND invalid value for DST_ALPHA_FACTOR: " +
token->AsString());
pipeline->GetPipelineData()->SetDstAlphaBlendFactor(
NameToBlendFactor(token->AsString()));
} else if (token->AsString() == "COLOR_OP") {
token = tokenizer_->NextToken();

if (!token->IsIdentifier())
return Result("BLEND invalid value for COLOR_OP");

const auto op = NameToBlendOp(token->AsString());
if (op == BlendOp::kUnknown)
return Result("BLEND invalid value for COLOR_OP: " + token->AsString());
pipeline->GetPipelineData()->SetColorBlendOp(
NameToBlendOp(token->AsString()));
} else if (token->AsString() == "ALPHA_OP") {
token = tokenizer_->NextToken();

if (!token->IsIdentifier())
return Result("BLEND invalid value for ALPHA_OP");

const auto op = NameToBlendOp(token->AsString());
if (op == BlendOp::kUnknown)
return Result("BLEND invalid value for ALPHA_OP: " + token->AsString());
pipeline->GetPipelineData()->SetAlphaBlendOp(
NameToBlendOp(token->AsString()));
} else {
return Result("BLEND invalid value for BLEND: " + token->AsString());
}
}

return ValidateEndOfStatement("BLEND command");
}

Result Parser::ParseStruct() {
auto token = tokenizer_->NextToken();
if (!token->IsIdentifier())
Expand Down
1 change: 1 addition & 0 deletions src/amberscript/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class Parser : public amber::Parser {
Result ParsePipelinePolygonMode(Pipeline*);
Result ParsePipelineDepth(Pipeline* pipeline);
Result ParsePipelineStencil(Pipeline* pipeline);
Result ParsePipelineBlend(Pipeline* pipeline);
Result ParseRun();
Result ParseDebug();
Result ParseDebugThread(debug::Events*, Pipeline* pipeline);
Expand Down
140 changes: 140 additions & 0 deletions src/amberscript/parser_blend_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright 2021 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 parseried.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "gtest/gtest.h"
#include "src/amberscript/parser.h"

namespace amber {
namespace amberscript {

using AmberScriptParserTest = testing::Test;

TEST_F(AmberScriptParserTest, BlendAllValues) {
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
BLEND
SRC_COLOR_FACTOR src_alpha
DST_COLOR_FACTOR one_minus_src_alpha
COLOR_OP add
SRC_ALPHA_FACTOR one
DST_ALPHA_FACTOR zero
ALPHA_OP max
END
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());

auto* pipeline = pipelines[0].get();

ASSERT_TRUE(pipeline->GetPipelineData()->GetEnableBlend());
ASSERT_EQ(BlendFactor::kSrcAlpha,
pipeline->GetPipelineData()->GetSrcColorBlendFactor());
ASSERT_EQ(BlendFactor::kOneMinusSrcAlpha,
pipeline->GetPipelineData()->GetDstColorBlendFactor());
ASSERT_EQ(BlendOp::kAdd,
pipeline->GetPipelineData()->GetColorBlendOp());

ASSERT_EQ(BlendFactor::kOne,
pipeline->GetPipelineData()->GetSrcAlphaBlendFactor());
ASSERT_EQ(BlendFactor::kZero,
pipeline->GetPipelineData()->GetDstAlphaBlendFactor());
ASSERT_EQ(BlendOp::kMax,
pipeline->GetPipelineData()->GetAlphaBlendOp());
}

TEST_F(AmberScriptParserTest, BlendDefaultValues) {
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
BLEND
END
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());

auto* pipeline = pipelines[0].get();

ASSERT_TRUE(pipeline->GetPipelineData()->GetEnableBlend());
ASSERT_EQ(BlendFactor::kOne,
pipeline->GetPipelineData()->GetSrcColorBlendFactor());
ASSERT_EQ(BlendFactor::kZero,
pipeline->GetPipelineData()->GetDstColorBlendFactor());
ASSERT_EQ(BlendOp::kAdd,
pipeline->GetPipelineData()->GetColorBlendOp());

ASSERT_EQ(BlendFactor::kOne,
pipeline->GetPipelineData()->GetSrcAlphaBlendFactor());
ASSERT_EQ(BlendFactor::kZero,
pipeline->GetPipelineData()->GetDstAlphaBlendFactor());
ASSERT_EQ(BlendOp::kAdd,
pipeline->GetPipelineData()->GetAlphaBlendOp());
}

TEST_F(AmberScriptParserTest, BlendInvalidColorFactor) {
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
BLEND
SRC_COLOR_FACTOR foo
END
END)";

Parser parser;
Result r = parser.Parse(in);
ASSERT_FALSE(r.IsSuccess()) << r.Error();
EXPECT_EQ("14: BLEND invalid value for SRC_COLOR_FACTOR: foo", r.Error());
}

} // namespace amberscript
} // namespace amber
Loading

0 comments on commit bbb42af

Please sign in to comment.