diff --git a/docs/amber_script.md b/docs/amber_script.md index 59d20e4d..cfcf6599 100644 --- a/docs/amber_script.md +++ b/docs/amber_script.md @@ -802,6 +802,11 @@ when using a Vulkan backend. END ``` +```groovy + # Enable or disable alpha-to-coverage for the pipeline. Defaults to off. + ALPHA_TO_COVERAGE {on | off} +``` + ```groovy # Set the size of the render buffers. |width| and |height| are integers and # default to 250x250. diff --git a/src/amberscript/parser.cc b/src/amberscript/parser.cc index ec8ad1e8..ceb58f56 100644 --- a/src/amberscript/parser.cc +++ b/src/amberscript/parser.cc @@ -744,6 +744,8 @@ Result Parser::ParsePipelineBody(const std::string& cmd_name, r = ParsePipelinePatchControlPoints(pipeline.get()); } else if (tok == "BLEND") { r = ParsePipelineBlend(pipeline.get()); + } else if (tok == "ALPHA_TO_COVERAGE") { + r = ParsePipelineAlphaToCoverage(pipeline.get()); } else if (tok == "SHADER_GROUP") { r = ParsePipelineShaderGroup(pipeline.get()); } else if (tok == "SHADER_BINDING_TABLE") { @@ -2328,6 +2330,24 @@ Result Parser::ParsePipelineBlend(Pipeline* pipeline) { return ValidateEndOfStatement("BLEND command"); } +Result Parser::ParsePipelineAlphaToCoverage(Pipeline* pipeline) { + auto token = tokenizer_->NextToken(); + if (!token->IsIdentifier()) { + return Result("missing mode in ALPHA_TO_COVERAGE command"); + } + + auto mode = token->AsString(); + if (mode == "on") { + pipeline->GetPipelineData()->SetEnableAlphaToCoverage(true); + } else if (mode == "off") { + pipeline->GetPipelineData()->SetEnableAlphaToCoverage(false); + } else { + return Result("invalid value for ALPHA_TO_COVERAGE: " + mode); + } + + return ValidateEndOfStatement("ALPHA_TO_COVERAGE command"); +} + Result Parser::ParsePipelineShaderGroup(Pipeline* pipeline) { std::unique_ptr token = tokenizer_->NextToken(); if (!token->IsIdentifier()) { diff --git a/src/amberscript/parser.h b/src/amberscript/parser.h index 83c8c99f..1066e411 100644 --- a/src/amberscript/parser.h +++ b/src/amberscript/parser.h @@ -76,6 +76,7 @@ class Parser : public amber::Parser { Result ParsePipelineDepth(Pipeline* pipeline); Result ParsePipelineStencil(Pipeline* pipeline); Result ParsePipelineBlend(Pipeline* pipeline); + Result ParsePipelineAlphaToCoverage(Pipeline* pipeline); Result ParsePipelineShaderGroup(Pipeline* pipeline); Result ParseRun(); Result ParseClear(); diff --git a/src/amberscript/parser_pipeline_test.cc b/src/amberscript/parser_pipeline_test.cc index 4707dd98..653bc910 100644 --- a/src/amberscript/parser_pipeline_test.cc +++ b/src/amberscript/parser_pipeline_test.cc @@ -623,5 +623,176 @@ END EXPECT_EQ(pipelines[1]->GetPipelineData()->GetPatchControlPoints(), 4u); } +TEST_F(AmberScriptParserTest, PipelineAlphaToCoverageDefault) { + 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()); + + EXPECT_FALSE(pipelines[0]->GetPipelineData()->GetEnableAlphaToCoverage()); +} + +TEST_F(AmberScriptParserTest, PipelineAlphaToCoverageOn) { + 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 + + ALPHA_TO_COVERAGE on +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()); + + EXPECT_TRUE(pipelines[0]->GetPipelineData()->GetEnableAlphaToCoverage()); +} + +TEST_F(AmberScriptParserTest, PipelineAlphaToCoverageOff) { + 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 + + ALPHA_TO_COVERAGE on + ALPHA_TO_COVERAGE off +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()); + + EXPECT_FALSE(pipelines[0]->GetPipelineData()->GetEnableAlphaToCoverage()); +} + +TEST_F(AmberScriptParserTest, PipelineDeriveAlphaToCoverage) { + 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 + + ALPHA_TO_COVERAGE on +END + +DERIVE_PIPELINE child_pipeline FROM my_pipeline +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()); + + EXPECT_TRUE(pipelines[0]->GetPipelineData()->GetEnableAlphaToCoverage()); + EXPECT_TRUE(pipelines[1]->GetPipelineData()->GetEnableAlphaToCoverage()); +} + +TEST_F(AmberScriptParserTest, PipelineAlphaToCoverageMissingMode) { + 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 + + ALPHA_TO_COVERAGE +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("12: missing mode in ALPHA_TO_COVERAGE command", r.Error()); +} + +TEST_F(AmberScriptParserTest, PipelineAlphaToCoverageInvalidMode) { + 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 + + ALPHA_TO_COVERAGE invalid +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("11: invalid value for ALPHA_TO_COVERAGE: invalid", r.Error()); +} + +TEST_F(AmberScriptParserTest, PipelineAlphaToCoverageExtraParams) { + 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 + + ALPHA_TO_COVERAGE on EXTRA +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("11: extra parameters after ALPHA_TO_COVERAGE command: EXTRA", + r.Error()); +} + } // namespace amberscript } // namespace amber diff --git a/src/dawn/engine_dawn.cc b/src/dawn/engine_dawn.cc index 0f66eeb6..e6ede3f5 100644 --- a/src/dawn/engine_dawn.cc +++ b/src/dawn/engine_dawn.cc @@ -1053,6 +1053,10 @@ Result DawnPipelineHelper::CreateRenderPipelineDescriptor( primitiveState.frontFace = GetDawnFrontFace(pipeline_data->GetFrontFace()); primitiveState.cullMode = GetDawnCullMode(pipeline_data->GetCullMode()); renderPipelineDescriptor.primitive = primitiveState; + + multisampleState.alphaToCoverageEnabled = + pipeline_data->GetEnableAlphaToCoverage(); + renderPipelineDescriptor.multisample = multisampleState; } // Set defaults for the color state descriptors. diff --git a/src/pipeline.cc b/src/pipeline.cc index 1c261478..1f71eab7 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -69,6 +69,7 @@ std::unique_ptr Pipeline::Clone() const { auto clone = std::make_unique(pipeline_type_); clone->shaders_ = shaders_; clone->color_attachments_ = color_attachments_; + clone->resolve_targets_ = resolve_targets_; clone->vertex_buffers_ = vertex_buffers_; clone->buffers_ = buffers_; clone->depth_stencil_buffer_ = depth_stencil_buffer_; diff --git a/src/pipeline_data.h b/src/pipeline_data.h index c763f378..3af6e32e 100644 --- a/src/pipeline_data.h +++ b/src/pipeline_data.h @@ -132,6 +132,9 @@ class PipelineData { void SetEnableDepthBoundsTest(bool v) { enable_depth_bounds_test_ = v; } bool GetEnableDepthBoundsTest() const { return enable_depth_bounds_test_; } + void SetEnableAlphaToCoverage(bool v) { enable_alpha_to_coverage_ = v; } + bool GetEnableAlphaToCoverage() const { return enable_alpha_to_coverage_; } + void SetDepthBiasConstantFactor(float f) { depth_bias_constant_factor_ = f; } float GetDepthBiasConstantFactor() const { return depth_bias_constant_factor_; @@ -228,6 +231,7 @@ class PipelineData { bool enable_primitive_restart_ = false; bool enable_rasterizer_discard_ = false; bool enable_logic_op_ = false; + bool enable_alpha_to_coverage_ = false; float line_width_ = 1.0f; float depth_bias_constant_factor_ = 0.0f; diff --git a/src/vulkan/frame_buffer.cc b/src/vulkan/frame_buffer.cc index 3e89ef57..43075d7b 100644 --- a/src/vulkan/frame_buffer.cc +++ b/src/vulkan/frame_buffer.cc @@ -99,7 +99,8 @@ Result FrameBuffer::Initialize(VkRenderPass render_pass) { depth_stencil_image_ = std::make_unique( device_, *depth_stencil_attachment_.buffer->GetFormat(), aspect, - VK_IMAGE_TYPE_2D, usage_flags, width_, height_, depth_, 1u, 0u, 1u, 1u); + VK_IMAGE_TYPE_2D, usage_flags, width_, height_, depth_, 1u, 0u, 1u, + depth_stencil_attachment_.buffer->GetSamples()); Result r = depth_stencil_image_->Initialize(); if (!r.IsSuccess()) { diff --git a/src/vulkan/graphics_pipeline.cc b/src/vulkan/graphics_pipeline.cc index af9e13ce..a7318a7c 100644 --- a/src/vulkan/graphics_pipeline.cc +++ b/src/vulkan/graphics_pipeline.cc @@ -458,6 +458,8 @@ Result GraphicsPipeline::CreateRenderPass() { VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; attachment_desc.back().finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + attachment_desc.back().samples = static_cast( + depth_stencil_buffer_.buffer->GetSamples()); depth_refer.attachment = static_cast(attachment_desc.size() - 1); depth_refer.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; @@ -653,8 +655,10 @@ Result GraphicsPipeline::CreateVkGraphicsPipeline( VK_FALSE, /* sampleShadingEnable */ 0, /* minSampleShading */ &kSampleMask, /* pSampleMask */ - VK_FALSE, /* alphaToCoverageEnable */ - VK_FALSE, /* alphaToOneEnable */ + pipeline_data->GetEnableAlphaToCoverage() + ? VK_TRUE + : VK_FALSE, /* alphaToCoverageEnable */ + VK_FALSE, /* alphaToOneEnable */ }; // Search for multisampled color buffers and adjust the rasterization samples @@ -666,6 +670,14 @@ Result GraphicsPipeline::CreateVkGraphicsPipeline( multisampleInfo.rasterizationSamples = static_cast(samples); } + if (depth_stencil_buffer_.buffer && + depth_stencil_buffer_.buffer->GetFormat()->IsFormatKnown()) { + uint32_t samples = depth_stencil_buffer_.buffer->GetSamples(); + assert(static_cast(samples) >= + multisampleInfo.rasterizationSamples); + multisampleInfo.rasterizationSamples = + static_cast(samples); + } VkGraphicsPipelineCreateInfo pipeline_info = VkGraphicsPipelineCreateInfo(); pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; diff --git a/tests/cases/alpha_to_coverage.amber b/tests/cases/alpha_to_coverage.amber new file mode 100644 index 00000000..fc1ac656 --- /dev/null +++ b/tests/cases/alpha_to_coverage.amber @@ -0,0 +1,101 @@ +#!amber +# Copyright 2026 Google LLC +# +# 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 GLSL +#version 440 +layout(location = 0) in vec3 in_pos; +layout(location = 1) in vec4 in_color; +layout(location = 0) out vec4 frag_color; + +void main(void) { + gl_Position = vec4(in_pos, 1.0); + frag_color = in_color; +} +END + +SHADER fragment frag_shader GLSL +#version 440 +layout(location = 0) in vec4 frag_color; +layout(location = 0) out vec4 out_color; + +void main(void) { + out_color = frag_color; +} +END + +BUFFER position_buf DATA_TYPE R32G32B32_SFLOAT DATA +# Near quad at z = 0.2 +-1.0 -1.0 0.2 + 1.0 -1.0 0.2 +-1.0 1.0 0.2 +-1.0 1.0 0.2 + 1.0 -1.0 0.2 + 1.0 1.0 0.2 +# Far quad at z = 0.8 +-1.0 -1.0 0.8 + 1.0 -1.0 0.8 +-1.0 1.0 0.8 +-1.0 1.0 0.8 + 1.0 -1.0 0.8 + 1.0 1.0 0.8 +END + +BUFFER color_buf DATA_TYPE R32G32B32A32_SFLOAT DATA +# Near quad: red with alpha = 0.5 (covers 2 of 4 MSAA samples) +1.0 0.0 0.0 0.5 +1.0 0.0 0.0 0.5 +1.0 0.0 0.0 0.5 +1.0 0.0 0.0 0.5 +1.0 0.0 0.0 0.5 +1.0 0.0 0.0 0.5 +# Far quad: green with alpha = 1.0 (covers all 4 MSAA samples, passes depth on uncovered 2) +0.0 1.0 0.0 1.0 +0.0 1.0 0.0 1.0 +0.0 1.0 0.0 1.0 +0.0 1.0 0.0 1.0 +0.0 1.0 0.0 1.0 +0.0 1.0 0.0 1.0 +END + +IMAGE framebuffer_ms FORMAT R8G8B8A8_UNORM DIM_2D WIDTH 64 HEIGHT 64 SAMPLES 4 +IMAGE depth_ms FORMAT D32_SFLOAT DIM_2D WIDTH 64 HEIGHT 64 SAMPLES 4 +IMAGE framebuffer FORMAT R8G8B8A8_UNORM DIM_2D WIDTH 64 HEIGHT 64 + +PIPELINE graphics pipeline + ATTACH vert_shader + ATTACH frag_shader + FRAMEBUFFER_SIZE 64 64 + VERTEX_DATA position_buf LOCATION 0 + VERTEX_DATA color_buf LOCATION 1 + BIND BUFFER framebuffer_ms AS color LOCATION 0 + BIND BUFFER depth_ms AS depth_stencil + BIND BUFFER framebuffer AS resolve + ALPHA_TO_COVERAGE on + DEPTH + TEST on + WRITE on + COMPARE_OP less + END +END + +CLEAR_COLOR pipeline 0 0 0 0 +CLEAR_DEPTH pipeline 1.0 +CLEAR pipeline + +# Draw near red quad (vertices 0..5, z = 0.2, alpha = 0.5) followed by +# far green quad (vertices 6..11, z = 0.8, alpha = 1.0) in the same pipeline. +RUN pipeline DRAW_ARRAY AS TRIANGLE_LIST START_IDX 0 COUNT 12 + +EXPECT framebuffer IDX 0 0 SIZE 64 64 EQ_RGBA 128 128 0 191 TOLERANCE 5%