diff --git a/Tests/LibGL/TestRender.cpp b/Tests/LibGL/TestRender.cpp index 3c4cddeac05..ff10b32c84b 100644 --- a/Tests/LibGL/TestRender.cpp +++ b/Tests/LibGL/TestRender.cpp @@ -1,10 +1,11 @@ /* * Copyright (c) 2021, Leon Albrecht - * Copyright (c) 2022, Jelle Raaijmakers + * Copyright (c) 2022-2024, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -21,9 +22,9 @@ #endif #define SAVE_OUTPUT false -static NonnullOwnPtr create_testing_context(int width, int height) +static NonnullOwnPtr create_testing_context(int width, int height, Gfx::BitmapFormat format = Gfx::BitmapFormat::BGRx8888) { - auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, { width, height })); + auto bitmap = MUST(Gfx::Bitmap::create(format, { width, height })); auto context = MUST(GL::create_context(*bitmap)); GL::make_context_current(context); return context; @@ -317,3 +318,61 @@ TEST_CASE(0011_tex_env_combine_with_constant_color) context->present(); expect_bitmap_equals_reference(context->frontbuffer(), "0011_tex_env_combine_with_constant_color"sv); } + +TEST_CASE(0012_blend_equations) +{ + auto context = create_testing_context(64, 64, Gfx::BitmapFormat::BGRA8888); + + // Assert initial state + int actual_mode; + glGetIntegerv(GL_BLEND_EQUATION_RGB, &actual_mode); + EXPECT_EQ(actual_mode, GL_FUNC_ADD); + glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &actual_mode); + EXPECT_EQ(actual_mode, GL_FUNC_ADD); + + // Clear with alpha 0 so we get a transparent color buffer + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + + glColor4f(.8f, .2f, .3f, .7f); + glRecti(-1, -1, 1, 1); + + glColor4f(.3f, .1f, .8f, .5f); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE); + + static constexpr Array blend_modes = { + GL_FUNC_ADD, + GL_FUNC_SUBTRACT, + GL_FUNC_REVERSE_SUBTRACT, + GL_MIN, + GL_MAX, + }; + auto constexpr grid_size = blend_modes.size(); + auto constexpr cell_size = 2.f / grid_size; + for (size_t x = 0; x < grid_size; ++x) { + for (size_t y = 0; y < grid_size; ++y) { + auto rgb_mode = blend_modes[x]; + auto alpha_mode = blend_modes[y]; + + glBlendEquationSeparate(rgb_mode, alpha_mode); + + glGetIntegerv(GL_BLEND_EQUATION_RGB, &actual_mode); + EXPECT_EQ(static_cast(actual_mode), rgb_mode); + + glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &actual_mode); + EXPECT_EQ(static_cast(actual_mode), alpha_mode); + + glRectf( + -1.f + cell_size * static_cast(x), + 1.f - cell_size * static_cast(y), + -1.f + cell_size * static_cast(x + 1), + 1.f - cell_size * static_cast(y + 1)); + } + } + + EXPECT_EQ(glGetError(), 0u); + + context->present(); + expect_bitmap_equals_reference(context->frontbuffer(), "0012_blend_equations"sv); +} diff --git a/Tests/LibGL/reference-images/0012_blend_equations.qoi b/Tests/LibGL/reference-images/0012_blend_equations.qoi new file mode 100644 index 00000000000..a3ef80e7d36 Binary files /dev/null and b/Tests/LibGL/reference-images/0012_blend_equations.qoi differ diff --git a/Userland/Libraries/LibGL/ContextParameter.cpp b/Userland/Libraries/LibGL/ContextParameter.cpp index 40d40ac6fe1..94dff5809a6 100644 --- a/Userland/Libraries/LibGL/ContextParameter.cpp +++ b/Userland/Libraries/LibGL/ContextParameter.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2021, Jesse Buhagiar * Copyright (c) 2021, Stephan Unverwerth - * Copyright (c) 2022, Jelle Raaijmakers + * Copyright (c) 2022-2024, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ @@ -25,6 +25,10 @@ Optional GLContext::get_context_parameter(GLenum name) case GL_BLEND_DST: case GL_BLEND_DST_ALPHA: return ContextParameter { .type = GL_INT, .value = { .integer_value = static_cast(m_blend_destination_factor) } }; + case GL_BLEND_EQUATION_ALPHA: + return ContextParameter { .type = GL_INT, .value = { .integer_value = static_cast(m_blend_equation_alpha) } }; + case GL_BLEND_EQUATION_RGB: + return ContextParameter { .type = GL_INT, .value = { .integer_value = static_cast(m_blend_equation_rgb) } }; case GL_BLEND_SRC: case GL_BLEND_SRC_ALPHA: return ContextParameter { .type = GL_INT, .value = { .integer_value = static_cast(m_blend_source_factor) } }; diff --git a/Userland/Libraries/LibGL/GL/gl.h b/Userland/Libraries/LibGL/GL/gl.h index 85e7b3a3944..aabd1a5efdc 100644 --- a/Userland/Libraries/LibGL/GL/gl.h +++ b/Userland/Libraries/LibGL/GL/gl.h @@ -1,7 +1,7 @@ /* * Copyright (c) 2021, Jesse Buhagiar * Copyright (c) 2021, Stephan Unverwerth - * Copyright (c) 2021-2022, Jelle Raaijmakers + * Copyright (c) 2021-2024, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ @@ -380,11 +380,18 @@ extern "C" { #define GL_LIGHT6 0x4006 #define GL_LIGHT7 0x4007 -// More blend factors +// Blend factors & modes #define GL_CONSTANT_COLOR 0x8001 #define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 #define GL_CONSTANT_ALPHA 0x8003 #define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_FUNC_ADD 0x8006 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_BLEND_EQUATION_RGB 0x8009 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_BLEND_EQUATION_ALPHA 0x883D // Points #define GL_POINT_SMOOTH 0x0B10 diff --git a/Userland/Libraries/LibGL/GLAPI.json b/Userland/Libraries/LibGL/GLAPI.json index d90357f8046..558257b3219 100644 --- a/Userland/Libraries/LibGL/GLAPI.json +++ b/Userland/Libraries/LibGL/GLAPI.json @@ -50,9 +50,15 @@ }, "BlendEquation": { "arguments": [ - {"type": "GLenum", "name": "mode"} + {"type": "GLenum", "name": "mode"}, + {"expression": "mode"} ], - "unimplemented": true + "implementation": "blend_equation_separate" + }, + "BlendEquationSeparate": { + "arguments": [ + {"type": "GLenum", "name": ["modeRGB", "modeAlpha"]} + ] }, "BlendFunc": { "arguments": [ diff --git a/Userland/Libraries/LibGL/GLContext.cpp b/Userland/Libraries/LibGL/GLContext.cpp index 85a0f1ef5d0..56d812a2abd 100644 --- a/Userland/Libraries/LibGL/GLContext.cpp +++ b/Userland/Libraries/LibGL/GLContext.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2021, Jesse Buhagiar * Copyright (c) 2021, Stephan Unverwerth - * Copyright (c) 2022-2023, Jelle Raaijmakers + * Copyright (c) 2022-2024, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ @@ -261,6 +261,50 @@ void GLContext::gl_finish() // No-op since GLContext is completely synchronous at the moment } +void GLContext::gl_blend_equation_separate(GLenum rgb_mode, GLenum alpha_mode) +{ + APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_blend_equation_separate, rgb_mode, alpha_mode); + + RETURN_WITH_ERROR_IF(m_in_draw_state, GL_INVALID_OPERATION); + RETURN_WITH_ERROR_IF(!(rgb_mode == GL_FUNC_ADD + || rgb_mode == GL_FUNC_SUBTRACT + || rgb_mode == GL_FUNC_REVERSE_SUBTRACT + || rgb_mode == GL_MIN + || rgb_mode == GL_MAX), + GL_INVALID_ENUM); + RETURN_WITH_ERROR_IF(!(alpha_mode == GL_FUNC_ADD + || alpha_mode == GL_FUNC_SUBTRACT + || alpha_mode == GL_FUNC_REVERSE_SUBTRACT + || alpha_mode == GL_MIN + || alpha_mode == GL_MAX), + GL_INVALID_ENUM); + + m_blend_equation_rgb = rgb_mode; + m_blend_equation_alpha = alpha_mode; + + auto map_gl_blend_equation_to_device = [](GLenum equation) constexpr { + switch (equation) { + case GL_FUNC_ADD: + return GPU::BlendEquation::Add; + case GL_FUNC_SUBTRACT: + return GPU::BlendEquation::Subtract; + case GL_FUNC_REVERSE_SUBTRACT: + return GPU::BlendEquation::ReverseSubtract; + case GL_MIN: + return GPU::BlendEquation::Min; + case GL_MAX: + return GPU::BlendEquation::Max; + default: + VERIFY_NOT_REACHED(); + } + }; + + auto options = m_rasterizer->options(); + options.blend_equation_rgb = map_gl_blend_equation_to_device(m_blend_equation_rgb); + options.blend_equation_alpha = map_gl_blend_equation_to_device(m_blend_equation_alpha); + m_rasterizer->set_options(options); +} + void GLContext::gl_blend_func(GLenum src_factor, GLenum dst_factor) { APPEND_TO_CALL_LIST_AND_RETURN_IF_NEEDED(gl_blend_func, src_factor, dst_factor); diff --git a/Userland/Libraries/LibGL/GLContext.h b/Userland/Libraries/LibGL/GLContext.h index 3f308ba2708..6556d82f89a 100644 --- a/Userland/Libraries/LibGL/GLContext.h +++ b/Userland/Libraries/LibGL/GLContext.h @@ -1,7 +1,7 @@ /* * Copyright (c) 2021, Stephan Unverwerth * Copyright (c) 2021-2022, Jesse Buhagiar - * Copyright (c) 2022-2023, Jelle Raaijmakers + * Copyright (c) 2022-2024, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ @@ -148,6 +148,7 @@ public: GLboolean gl_is_list(GLuint list); void gl_flush(); void gl_finish(); + void gl_blend_equation_separate(GLenum rgb_mode, GLenum alpha_mode); void gl_blend_func(GLenum src_factor, GLenum dst_factor); void gl_shade_model(GLenum mode); void gl_alpha_func(GLenum func, GLclampf ref); @@ -336,6 +337,9 @@ private: GLenum m_blend_source_factor = GL_ONE; GLenum m_blend_destination_factor = GL_ZERO; + GLenum m_blend_equation_rgb = GL_FUNC_ADD; + GLenum m_blend_equation_alpha = GL_FUNC_ADD; + bool m_alpha_test_enabled = false; GLenum m_alpha_test_func = GL_ALWAYS; GLclampf m_alpha_test_ref_value = 0; @@ -469,6 +473,7 @@ private: decltype(&GLContext::gl_cull_face), decltype(&GLContext::gl_call_list), decltype(&GLContext::gl_call_lists), + decltype(&GLContext::gl_blend_equation_separate), decltype(&GLContext::gl_blend_func), decltype(&GLContext::gl_shade_model), decltype(&GLContext::gl_alpha_func), diff --git a/Userland/Libraries/LibGPU/Enums.h b/Userland/Libraries/LibGPU/Enums.h index 5cf89227611..e42af041652 100644 --- a/Userland/Libraries/LibGPU/Enums.h +++ b/Userland/Libraries/LibGPU/Enums.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Stephan Unverwerth + * Copyright (c) 2024, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ @@ -21,6 +22,14 @@ enum class AlphaTestFunction { Greater, }; +enum class BlendEquation { + Add, + Subtract, + ReverseSubtract, + Min, + Max, +}; + enum class BlendFactor { Zero, One, diff --git a/Userland/Libraries/LibGPU/RasterizerOptions.h b/Userland/Libraries/LibGPU/RasterizerOptions.h index 21bab272a84..184267587d8 100644 --- a/Userland/Libraries/LibGPU/RasterizerOptions.h +++ b/Userland/Libraries/LibGPU/RasterizerOptions.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2021, Stephan Unverwerth - * Copyright (c) 2022, Jelle Raaijmakers + * Copyright (c) 2022-2024, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ @@ -23,6 +23,8 @@ struct RasterizerOptions { AlphaTestFunction alpha_test_func { AlphaTestFunction::Always }; float alpha_test_ref_value { 0 }; bool enable_blending { false }; + BlendEquation blend_equation_rgb { BlendEquation::Add }; + BlendEquation blend_equation_alpha { BlendEquation::Add }; BlendFactor blend_source_factor { BlendFactor::One }; BlendFactor blend_destination_factor { BlendFactor::One }; u32 color_mask { 0xffffffff }; diff --git a/Userland/Libraries/LibSoftGPU/Device.cpp b/Userland/Libraries/LibSoftGPU/Device.cpp index 749dc5180b8..5e4e486ff8c 100644 --- a/Userland/Libraries/LibSoftGPU/Device.cpp +++ b/Userland/Libraries/LibSoftGPU/Device.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2021, Stephan Unverwerth * Copyright (c) 2021, Jesse Buhagiar - * Copyright (c) 2022-2023, Jelle Raaijmakers + * Copyright (c) 2022-2024, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ @@ -140,7 +140,7 @@ ALWAYS_INLINE static bool is_blend_factor_constant(GPU::BlendFactor blend_factor return (blend_factor == GPU::BlendFactor::One || blend_factor == GPU::BlendFactor::Zero); } -// OpenGL 1.5 § 4.1.8, table 4.1 +// OpenGL 2.0 § 4.1.8, table 4.2 ALWAYS_INLINE static Vector4 get_blend_factor(GPU::BlendFactor blend_factor, Vector4 const& source_color, Vector4 const& destination_color) { switch (blend_factor) { @@ -173,6 +173,42 @@ ALWAYS_INLINE static Vector4 get_blend_factor(GPU::BlendFactor blend_fact } } +// OpenGL 2.0 § 4.1.8, table 4.1 +ALWAYS_INLINE static Vector4 blend_colors( + GPU::BlendEquation rgb_mode, + GPU::BlendEquation alpha_mode, + Vector4 const& source, + Vector4 const& source_weights, + Vector4 const& destination, + Vector4 const& destination_weights) +{ + auto apply_equation = [&](GPU::BlendEquation mode, auto source, auto source_weights, auto destination, auto destination_weights) -> auto { + switch (mode) { + case GPU::BlendEquation::Add: + return source * source_weights + destination * destination_weights; + case GPU::BlendEquation::Subtract: + return source * source_weights - destination * destination_weights; + case GPU::BlendEquation::ReverseSubtract: + return destination * source_weights - source * destination_weights; + case GPU::BlendEquation::Min: + return AK::min(source, destination); + case GPU::BlendEquation::Max: + return AK::max(source, destination); + default: + VERIFY_NOT_REACHED(); + } + }; + + // Single calculation if RGB mode is the same as the alpha mode + if (rgb_mode == alpha_mode) + return apply_equation(rgb_mode, source, source_weights, destination, destination_weights); + + // Split RGB/alpha calculation + auto rgb = apply_equation(rgb_mode, source.xyz(), source_weights.xyz(), destination.xyz(), destination_weights.xyz()); + auto alpha = apply_equation(alpha_mode, source.w(), source_weights.w(), destination.w(), destination_weights.w()); + return { rgb.x(), rgb.y(), rgb.z(), alpha }; +} + template ALWAYS_INLINE void Device::rasterize(Gfx::IntRect& render_bounds, CB1 set_coverage_mask, CB2 set_quad_depth, CB3 set_quad_attributes) { @@ -239,16 +275,16 @@ ALWAYS_INLINE void Device::rasterize(Gfx::IntRect& render_bounds, CB1 set_covera auto const qy0 = render_bounds_top & ~1; auto const qy1 = render_bounds_bottom & ~1; - // Blend factors - Vector4 src_factor; - Vector4 dst_factor; - auto const src_factor_is_constant = is_blend_factor_constant(m_options.blend_source_factor); - auto const dst_factor_is_constant = is_blend_factor_constant(m_options.blend_destination_factor); + // Blend weights + Vector4 source_weights; + Vector4 destination_weights; + auto const source_weights_are_constant = is_blend_factor_constant(m_options.blend_source_factor); + auto const destination_weights_are_constant = is_blend_factor_constant(m_options.blend_destination_factor); if (m_options.enable_blending) { - if (src_factor_is_constant) - src_factor = get_blend_factor(m_options.blend_source_factor, {}, {}); - if (dst_factor_is_constant) - dst_factor = get_blend_factor(m_options.blend_destination_factor, {}, {}); + if (source_weights_are_constant) + source_weights = get_blend_factor(m_options.blend_source_factor, {}, {}); + if (destination_weights_are_constant) + destination_weights = get_blend_factor(m_options.blend_destination_factor, {}, {}); } // Rasterize all quads @@ -436,19 +472,19 @@ ALWAYS_INLINE void Device::rasterize(Gfx::IntRect& render_bounds, CB1 set_covera auto out_color = quad.get_output_vector4(SHADER_OUTPUT_FIRST_COLOR); + // Blend color values from pixel_staging into color_buffer if (m_options.enable_blending) { INCREASE_STATISTICS_COUNTER(g_num_pixels_blended, maskcount(quad.mask)); - // Blend color values from pixel_staging into color_buffer - auto const& src = out_color; - auto const dst = to_vec4(dst_u32); + auto const& source_color = out_color; + auto const destination_color = to_vec4(dst_u32); - if (!src_factor_is_constant) - src_factor = get_blend_factor(m_options.blend_source_factor, src, dst); - if (!dst_factor_is_constant) - dst_factor = get_blend_factor(m_options.blend_destination_factor, src, dst); + if (!source_weights_are_constant) + source_weights = get_blend_factor(m_options.blend_source_factor, source_color, destination_color); + if (!destination_weights_are_constant) + destination_weights = get_blend_factor(m_options.blend_destination_factor, source_color, destination_color); - out_color = src * src_factor + dst * dst_factor; + out_color = blend_colors(m_options.blend_equation_rgb, m_options.blend_equation_alpha, source_color, source_weights, destination_color, destination_weights); } auto const argb32_color = to_argb32(out_color);