From bbb42afe676ae87138c485e004d463c97f981ad9 Mon Sep 17 00:00:00 2001 From: asuonpaa <34128694+asuonpaa@users.noreply.github.com> Date: Thu, 18 Nov 2021 14:09:27 +0200 Subject: [PATCH] Add alpha blending support for AmberScript (#971) Alpha blending was previously only supported for VkScript. This change adds the support for AmberScript too. --- docs/amber_script.md | 90 ++++++++++++++++ src/CMakeLists.txt | 1 + src/amberscript/parser.cc | 94 +++++++++++++++++ src/amberscript/parser.h | 1 + src/amberscript/parser_blend_test.cc | 140 +++++++++++++++++++++++++ src/command_data.cc | 150 +++++++++++++++++++++++++++ src/command_data.h | 8 +- src/vulkan/graphics_pipeline.cc | 4 + tests/cases/draw_rect_blend.amber | 46 ++++++++ 9 files changed, 532 insertions(+), 2 deletions(-) create mode 100644 src/amberscript/parser_blend_test.cc create mode 100644 tests/cases/draw_rect_blend.amber diff --git a/docs/amber_script.md b/docs/amber_script.md index a0e4e544e..7c78d822b 100644 --- a/docs/amber_script.md +++ b/docs/amber_script.md @@ -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. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index abf5bdcdb..e51d1b08c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/amberscript/parser.cc b/src/amberscript/parser.cc index c0f9475de..d3e0eb63c 100644 --- a/src/amberscript/parser.cc +++ b/src/amberscript/parser.cc @@ -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); } @@ -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()) diff --git a/src/amberscript/parser.h b/src/amberscript/parser.h index 8f593519b..25d3493b0 100644 --- a/src/amberscript/parser.h +++ b/src/amberscript/parser.h @@ -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); diff --git a/src/amberscript/parser_blend_test.cc b/src/amberscript/parser_blend_test.cc new file mode 100644 index 000000000..013ccbd00 --- /dev/null +++ b/src/amberscript/parser_blend_test.cc @@ -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 diff --git a/src/command_data.cc b/src/command_data.cc index 80083e100..c0b76123c 100644 --- a/src/command_data.cc +++ b/src/command_data.cc @@ -54,4 +54,154 @@ Topology NameToTopology(const std::string& name) { return Topology::kUnknown; } +BlendFactor NameToBlendFactor(const std::string& name) { + if (name == "zero") + return BlendFactor::kZero; + else if (name == "one") + return BlendFactor::kOne; + else if (name == "src_color") + return BlendFactor::kSrcColor; + else if (name == "one_minus_src_color") + return BlendFactor::kOneMinusSrcColor; + else if (name == "dst_color") + return BlendFactor::kDstColor; + else if (name == "one_minus_dst_color") + return BlendFactor::kOneMinusDstColor; + else if (name == "src_alpha") + return BlendFactor::kSrcAlpha; + else if (name == "one_minus_src_alpha") + return BlendFactor::kOneMinusSrcAlpha; + else if (name == "dst_alpha") + return BlendFactor::kDstAlpha; + else if (name == "one_minus_dst_alpha") + return BlendFactor::kOneMinusDstAlpha; + else if (name == "constant_color") + return BlendFactor::kConstantColor; + else if (name == "one_minus_constant_color") + return BlendFactor::kOneMinusConstantColor; + else if (name == "costant_alpha") + return BlendFactor::kConstantAlpha; + else if (name == "one_minus_constant_alpha") + return BlendFactor::kOneMinusConstantAlpha; + else if (name == "src_alpha_saturate") + return BlendFactor::kSrcAlphaSaturate; + else if (name == "src1_color") + return BlendFactor::kSrc1Color; + else if (name == "one_minus_src1_color") + return BlendFactor::kOneMinusSrc1Color; + else if (name == "src1_alpha") + return BlendFactor::kSrc1Alpha; + else if (name == "one_minus_src1_alpha") + return BlendFactor::kOneMinusSrc1Alpha; + else + return BlendFactor::kUnknown; +} + +BlendOp NameToBlendOp(const std::string& name) { + if (name == "add") + return BlendOp::kAdd; + else if (name == "substract") + return BlendOp::kSubtract; + else if (name == "reverse_substract") + return BlendOp::kReverseSubtract; + else if (name == "min") + return BlendOp::kMin; + else if (name == "max") + return BlendOp::kMax; + else if (name == "zero") + return BlendOp::kZero; + else if (name == "src") + return BlendOp::kSrc; + else if (name == "dst") + return BlendOp::kDst; + else if (name == "src_over") + return BlendOp::kSrcOver; + else if (name == "dst_over") + return BlendOp::kDstOver; + else if (name == "src_in") + return BlendOp::kSrcIn; + else if (name == "dst_in") + return BlendOp::kDstIn; + else if (name == "src_out") + return BlendOp::kSrcOut; + else if (name == "dst_out") + return BlendOp::kDstOut; + else if (name == "src_atop") + return BlendOp::kSrcAtop; + else if (name == "dst_atop") + return BlendOp::kDstAtop; + else if (name == "xor") + return BlendOp::kXor; + else if (name == "multiply") + return BlendOp::kMultiply; + else if (name == "screen") + return BlendOp::kScreen; + else if (name == "overlay") + return BlendOp::kOverlay; + else if (name == "darken") + return BlendOp::kDarken; + else if (name == "lighten") + return BlendOp::kLighten; + else if (name == "color_dodge") + return BlendOp::kColorDodge; + else if (name == "color_burn") + return BlendOp::kColorBurn; + else if (name == "hard_light") + return BlendOp::kHardLight; + else if (name == "soft_light") + return BlendOp::kSoftLight; + else if (name == "difference") + return BlendOp::kDifference; + else if (name == "exclusion") + return BlendOp::kExclusion; + else if (name == "invert") + return BlendOp::kInvert; + else if (name == "invert_rgb") + return BlendOp::kInvertRGB; + else if (name == "linear_dodge") + return BlendOp::kLinearDodge; + else if (name == "linear_burn") + return BlendOp::kLinearBurn; + else if (name == "vivid_light") + return BlendOp::kVividLight; + else if (name == "linear_light") + return BlendOp::kLinearLight; + else if (name == "pin_light") + return BlendOp::kPinLight; + else if (name == "hard_mix") + return BlendOp::kHardMix; + else if (name == "hsl_hue") + return BlendOp::kHslHue; + else if (name == "hsl_saturation") + return BlendOp::kHslSaturation; + else if (name == "hsl_color") + return BlendOp::kHslColor; + else if (name == "hsl_luminosity") + return BlendOp::kHslLuminosity; + else if (name == "plus") + return BlendOp::kPlus; + else if (name == "plus_clamped") + return BlendOp::kPlusClamped; + else if (name == "plus_clamped_alpha") + return BlendOp::kPlusClampedAlpha; + else if (name == "plus_darker") + return BlendOp::kPlusDarker; + else if (name == "minus") + return BlendOp::kMinus; + else if (name == "minus_clamped") + return BlendOp::kMinusClamped; + else if (name == "contrast") + return BlendOp::kContrast; + else if (name == "invert_ovg") + return BlendOp::kInvertOvg; + else if (name == "red") + return BlendOp::kRed; + else if (name == "green") + return BlendOp::kGreen; + else if (name == "blue") + return BlendOp::kBlue; + else + return BlendOp::kUnknown; +} + } // namespace amber diff --git a/src/command_data.h b/src/command_data.h index 98ec4053d..f7e82a254 100644 --- a/src/command_data.h +++ b/src/command_data.h @@ -104,7 +104,8 @@ enum class LogicOp : uint8_t { }; enum class BlendOp : uint8_t { - kAdd = 0, + kUnknown = 0, + kAdd, kSubtract, kReverseSubtract, kMin, @@ -158,7 +159,8 @@ enum class BlendOp : uint8_t { }; enum class BlendFactor : uint8_t { - kZero = 0, + kUnknown = 0, + kZero, kOne, kSrcColor, kOneMinusSrcColor, @@ -180,6 +182,8 @@ enum class BlendFactor : uint8_t { }; Topology NameToTopology(const std::string& name); +BlendFactor NameToBlendFactor(const std::string& name); +BlendOp NameToBlendOp(const std::string& name); } // namespace amber diff --git a/src/vulkan/graphics_pipeline.cc b/src/vulkan/graphics_pipeline.cc index 9e3c7acb2..138f32c26 100644 --- a/src/vulkan/graphics_pipeline.cc +++ b/src/vulkan/graphics_pipeline.cc @@ -234,6 +234,8 @@ VkBlendFactor ToVkBlendFactor(BlendFactor factor) { return VK_BLEND_FACTOR_SRC1_ALPHA; case BlendFactor::kOneMinusSrc1Alpha: return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA; + case BlendFactor::kUnknown: + break; } assert(false && "Vulkan::Unknown BlendFactor"); return VK_BLEND_FACTOR_ZERO; @@ -343,6 +345,8 @@ VkBlendOp ToVkBlendOp(BlendOp op) { return VK_BLEND_OP_GREEN_EXT; case BlendOp::kBlue: return VK_BLEND_OP_BLUE_EXT; + case BlendOp::kUnknown: + break; } assert(false && "Vulkan::Unknown BlendOp"); return VK_BLEND_OP_ADD; diff --git a/tests/cases/draw_rect_blend.amber b/tests/cases/draw_rect_blend.amber new file mode 100644 index 000000000..c47b93f5d --- /dev/null +++ b/tests/cases/draw_rect_blend.amber @@ -0,0 +1,46 @@ +#!amber +# 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 +# +# https://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. + +SHADER vertex vert_shader PASSTHROUGH +SHADER fragment frag_shader GLSL +#version 430 +layout(location = 0) out vec4 color_out; +void main() +{ + color_out = vec4(1.0, 0.0, 0.0, 0.5); +} +END + +BUFFER framebuffer FORMAT B8G8R8A8_UNORM + +PIPELINE graphics pipeline + ATTACH vert_shader + ATTACH frag_shader + BIND BUFFER framebuffer 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 one + ALPHA_OP max + END +END + +CLEAR_COLOR pipeline 0 255 0 255 +CLEAR pipeline +RUN pipeline DRAW_RECT POS 0 0 SIZE 250 250 +EXPECT framebuffer IDX 0 0 SIZE 250 250 EQ_RGBA 128 128 0 255 TOLERANCE 5%