aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorasuonpaa <34128694+asuonpaa@users.noreply.github.com>2021-11-18 14:09:27 +0200
committerGitHub <noreply@github.com>2021-11-18 12:09:27 +0000
commitbbb42afe676ae87138c485e004d463c97f981ad9 (patch)
tree216226a43357699312faa1f54e34edd23c7c7125
parent5bd7d35a38e410892fb73c7a54fd2a20534480cd (diff)
downloadamber-bbb42afe676ae87138c485e004d463c97f981ad9.tar.gz
Add alpha blending support for AmberScript (#971)
Alpha blending was previously only supported for VkScript. This change adds the support for AmberScript too.
-rw-r--r--docs/amber_script.md90
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/amberscript/parser.cc94
-rw-r--r--src/amberscript/parser.h1
-rw-r--r--src/amberscript/parser_blend_test.cc140
-rw-r--r--src/command_data.cc150
-rw-r--r--src/command_data.h8
-rw-r--r--src/vulkan/graphics_pipeline.cc4
-rw-r--r--tests/cases/draw_rect_blend.amber46
9 files changed, 532 insertions, 2 deletions
diff --git a/docs/amber_script.md b/docs/amber_script.md
index a0e4e54..7c78d82 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 abf5bdc..e51d1b0 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 c0f9475..d3e0eb6 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 8f59351..25d3493 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 0000000..013ccbd
--- /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 80083e1..c0b7612 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 98ec405..f7e82a2 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 9e3c7ac..138f32c 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 0000000..c47b93f
--- /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%