Everywhere: Remove GPU painter and AccelGfx

GPU painter that uses AccelGfx is slower and way less complete compared
to both default Gfx::Painter and Skia painter. It does not make much
sense to keep it, considering Skia painter already uses Metal backend on
macOS by default and there is an option to enable GPU-accelerated
backend on linux.
This commit is contained in:
Aliaksandr Kalenik 2024-07-03 20:15:58 +02:00 committed by Andreas Kling
parent 9dd14c6a7f
commit 830b287c46
Notes: sideshowbarker 2024-07-17 00:37:23 +09:00
37 changed files with 2 additions and 2760 deletions

View file

@ -30,7 +30,6 @@ There are some optional features that can be enabled during compilation that are
- `INCLUDE_FLAC_SPEC_TESTS`: downloads and includes the xiph.org FLAC test suite.
- `SERENITY_CACHE_DIR`: sets the location of a shared cache of downloaded files. Should not need to be set manually unless managing a distribution package.
- `ENABLE_NETWORK_DOWNLOADS`: allows downloading files from the internet during the build. Default on, turning off enables offline builds. For offline builds, the structure of the SERENITY_CACHE_DIR must be set up the way that the build expects.
- `ENABLE_ACCELERATED_GRAPHICS`: builds features that use accelerated graphics APIs to speed up painting and drawing using native graphics libraries.
Many parts of the codebase have debug functionality, mostly consisting of additional messages printed to the debug console. This is done via the `<component_name>_DEBUG` macros, which can be enabled individually at build time. They are listed in [this file](../Meta/CMake/all_the_debug_macros.cmake).

View file

@ -83,7 +83,6 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
Vector<ByteString> raw_urls;
Vector<ByteString> certificates;
StringView webdriver_content_ipc_path;
bool use_gpu_painting = false;
bool use_skia_painting = false;
bool debug_web_content = false;
bool log_all_js_exceptions = false;
@ -94,7 +93,6 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
args_parser.set_general_help("The Ladybird web browser");
args_parser.add_positional_argument(raw_urls, "URLs to open", "url", Core::ArgsParser::Required::No);
args_parser.add_option(webdriver_content_ipc_path, "Path to WebDriver IPC for WebContent", "webdriver-content-path", 0, "path", Core::ArgsParser::OptionHideMode::CommandLineAndMarkdown);
args_parser.add_option(use_gpu_painting, "Enable GPU painting", "enable-gpu-painting");
args_parser.add_option(use_skia_painting, "Enable Skia painting", "enable-skia-painting");
args_parser.add_option(debug_web_content, "Wait for debugger to attach to WebContent", "debug-web-content");
args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate");
@ -140,7 +138,6 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
Ladybird::WebContentOptions web_content_options {
.command_line = MUST(command_line_builder.to_string()),
.executable_path = MUST(String::from_byte_string(MUST(Core::System::current_executable_path()))),
.enable_gpu_painting = use_gpu_painting ? Ladybird::EnableGPUPainting::Yes : Ladybird::EnableGPUPainting::No,
.enable_skia_painting = use_skia_painting ? Ladybird::EnableSkiaPainting::Yes : Ladybird::EnableSkiaPainting::No,
.wait_for_debugger = debug_web_content ? Ladybird::WaitForDebugger::Yes : Ladybird::WaitForDebugger::No,
.log_all_js_exceptions = log_all_js_exceptions ? Ladybird::LogAllJSExceptions::Yes : Ladybird::LogAllJSExceptions::No,

View file

@ -84,8 +84,6 @@ ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(
arguments.append("--layout-test-mode"sv);
if (web_content_options.use_lagom_networking == Ladybird::UseLagomNetworking::Yes)
arguments.append("--use-lagom-networking"sv);
if (web_content_options.enable_gpu_painting == Ladybird::EnableGPUPainting::Yes)
arguments.append("--use-gpu-painting"sv);
if (web_content_options.enable_skia_painting == Ladybird::EnableSkiaPainting::Yes)
arguments.append("--use-skia-painting"sv);
if (web_content_options.wait_for_debugger == Ladybird::WaitForDebugger::Yes)

View file

@ -91,7 +91,6 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
bool disable_sql_database = false;
bool enable_qt_networking = false;
bool expose_internals_object = false;
bool use_gpu_painting = false;
bool use_skia_painting = false;
bool debug_web_content = false;
bool log_all_js_exceptions = false;
@ -108,7 +107,6 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
args_parser.add_option(enable_callgrind_profiling, "Enable Callgrind profiling", "enable-callgrind-profiling", 'P');
args_parser.add_option(disable_sql_database, "Disable SQL database", "disable-sql-database");
args_parser.add_option(enable_qt_networking, "Enable Qt as the backend networking service", "enable-qt-networking");
args_parser.add_option(use_gpu_painting, "Enable GPU painting", "enable-gpu-painting");
args_parser.add_option(use_skia_painting, "Enable Skia painting", "enable-skia-painting");
args_parser.add_option(debug_web_content, "Wait for debugger to attach to WebContent", "debug-web-content");
args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate");
@ -175,7 +173,6 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
.command_line = MUST(command_line_builder.to_string()),
.executable_path = MUST(String::from_byte_string(MUST(Core::System::current_executable_path()))),
.enable_callgrind_profiling = enable_callgrind_profiling ? Ladybird::EnableCallgrindProfiling::Yes : Ladybird::EnableCallgrindProfiling::No,
.enable_gpu_painting = use_gpu_painting ? Ladybird::EnableGPUPainting::Yes : Ladybird::EnableGPUPainting::No,
.enable_skia_painting = use_skia_painting ? Ladybird::EnableSkiaPainting::Yes : Ladybird::EnableSkiaPainting::No,
.use_lagom_networking = enable_qt_networking ? Ladybird::UseLagomNetworking::No : Ladybird::UseLagomNetworking::Yes,
.wait_for_debugger = debug_web_content ? Ladybird::WaitForDebugger::Yes : Ladybird::WaitForDebugger::No,

View file

@ -15,11 +15,6 @@ enum class EnableCallgrindProfiling {
Yes
};
enum class EnableGPUPainting {
No,
Yes
};
enum class EnableSkiaPainting {
No,
Yes
@ -64,7 +59,6 @@ struct WebContentOptions {
String command_line;
String executable_path;
EnableCallgrindProfiling enable_callgrind_profiling { EnableCallgrindProfiling::No };
EnableGPUPainting enable_gpu_painting { EnableGPUPainting::No };
EnableSkiaPainting enable_skia_painting { EnableSkiaPainting::No };
IsLayoutTestMode is_layout_test_mode { IsLayoutTestMode::No };
UseLagomNetworking use_lagom_networking { UseLagomNetworking::Yes };

View file

@ -1,4 +1,3 @@
include(accelerated_graphics)
include(fontconfig)
set(WEBCONTENT_SOURCE_DIR ${LADYBIRD_SOURCE_DIR}/Userland/Services/WebContent/)
@ -44,11 +43,6 @@ if (HAS_FONTCONFIG)
target_link_libraries(webcontent PRIVATE Fontconfig::Fontconfig)
endif()
if (HAS_ACCELERATED_GRAPHICS)
target_compile_definitions(webcontent PUBLIC HAS_ACCELERATED_GRAPHICS)
target_link_libraries(webcontent PUBLIC LibAccelGfx)
endif()
if (ENABLE_QT)
qt_add_executable(WebContent
../Qt/EventLoopImplementationQt.cpp

View file

@ -99,7 +99,6 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
bool is_layout_test_mode = false;
bool expose_internals_object = false;
bool use_lagom_networking = false;
bool use_gpu_painting = false;
bool use_skia_painter = false;
bool wait_for_debugger = false;
bool log_all_js_exceptions = false;
@ -114,7 +113,6 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
args_parser.add_option(is_layout_test_mode, "Is layout test mode", "layout-test-mode");
args_parser.add_option(expose_internals_object, "Expose internals object", "expose-internals-object");
args_parser.add_option(use_lagom_networking, "Enable Lagom servers for networking", "use-lagom-networking");
args_parser.add_option(use_gpu_painting, "Enable GPU painting", "use-gpu-painting");
args_parser.add_option(use_skia_painter, "Enable Skia painter", "use-skia-painting");
args_parser.add_option(wait_for_debugger, "Wait for debugger", "wait-for-debugger");
args_parser.add_option(mach_server_name, "Mach server name", "mach-server-name", 0, "mach_server_name");
@ -134,9 +132,6 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
Web::set_chrome_process_command_line(command_line);
Web::set_chrome_process_executable_path(executable_path);
if (use_gpu_painting) {
WebContent::PageClient::set_use_gpu_painter();
}
if (use_skia_painter) {
WebContent::PageClient::set_use_skia_painter();

View file

@ -1,19 +0,0 @@
if (NOT ENABLE_ACCELERATED_GRAPHICS OR EMSCRIPTEN)
# FIXME: Enable GL emulation in emscripten: https://emscripten.org/docs/porting/multimedia_and_graphics/OpenGL-support.html
set(HAS_ACCELERATED_GRAPHICS OFF CACHE BOOL "" FORCE)
return()
endif()
if (APPLE AND NOT IOS)
set(HAS_ACCELERATED_GRAPHICS ON CACHE BOOL "" FORCE)
set(ACCEL_GFX_LIBS "-framework OpenGL" CACHE STRING "" FORCE)
else()
find_package(OpenGL QUIET COMPONENTS OpenGL EGL)
if (OPENGL_FOUND)
set(HAS_ACCELERATED_GRAPHICS ON CACHE BOOL "" FORCE)
set(ACCEL_GFX_LIBS OpenGL::OpenGL OpenGL::EGL CACHE STRING "" FORCE)
else()
set(HAS_ACCELERATED_GRAPHICS OFF CACHE BOOL "" FORCE)
endif()
endif()

View file

@ -20,7 +20,6 @@ serenity_option(ENABLE_PUBLIC_SUFFIX_DOWNLOAD ON CACHE BOOL "Enable download of
serenity_option(INCLUDE_WASM_SPEC_TESTS OFF CACHE BOOL "Download and include the WebAssembly spec testsuite")
serenity_option(INCLUDE_FLAC_SPEC_TESTS OFF CACHE BOOL "Download and include the FLAC spec testsuite")
serenity_option(ENABLE_CACERT_DOWNLOAD ON CACHE BOOL "Enable download of cacert.pem at build time")
serenity_option(ENABLE_ACCELERATED_GRAPHICS ON CACHE BOOL "Enable use of accelerated graphics APIs")
serenity_option(SERENITY_CACHE_DIR "${PROJECT_BINARY_DIR}/../caches" CACHE PATH "Location of shared cache of downloaded files")
serenity_option(ENABLE_NETWORK_DOWNLOADS ON CACHE BOOL "Allow downloads of required files. If OFF, required files must already be present in SERENITY_CACHE_DIR")

View file

@ -384,7 +384,6 @@ endif()
# Lagom Libraries
set(lagom_standard_libraries
AccelGfx
Archive
Audio
Compress

View file

@ -359,7 +359,6 @@ if (current_os != "mac") {
bundle_data("ladybird_bundle_libs") {
public_deps = [
"//AK",
"//Userland/Libraries/LibAccelGfx",
"//Userland/Libraries/LibAudio",
"//Userland/Libraries/LibCompress",
"//Userland/Libraries/LibCore",
@ -389,7 +388,6 @@ if (current_os != "mac") {
"//Userland/Libraries/LibXML",
]
sources = [
"$root_out_dir/lib/liblagom-accelgfx.dylib",
"$root_out_dir/lib/liblagom-ak.dylib",
"$root_out_dir/lib/liblagom-audio.dylib",
"$root_out_dir/lib/liblagom-compress.dylib",

View file

@ -94,9 +94,5 @@ executable("WebContent") {
deps += [ ":generate_moc" ]
}
if (current_os == "linux" || current_os == "mac") {
cflags_cc = [ "-DHAS_ACCELERATED_GRAPHICS" ]
deps += [ "//Userland/Libraries/LibAccelGfx" ]
}
output_dir = "$root_out_dir/libexec"
}

View file

@ -1,33 +0,0 @@
shared_library("LibAccelGfx") {
output_name = "accelgfx"
include_dirs = [ "//Userland/Libraries" ]
deps = [
"//AK",
"//Userland/Libraries/LibCore",
"//Userland/Libraries/LibGfx",
]
if (current_os == "linux") {
libs = [
"GL",
"EGL",
]
} else if (current_os == "mac") {
frameworks = [ "OpenGL.framework" ]
}
sources = [
"Canvas.h",
"Context.cpp",
"Context.h",
"Forward.h",
"GL.cpp",
"GL.h",
"GlyphAtlas.cpp",
"Painter.cpp",
"Painter.h",
"Program.cpp",
"Program.h",
]
}

View file

@ -46,9 +46,4 @@ source_set("Painting") {
"VideoPaintable.cpp",
"ViewportPaintable.cpp",
]
if (current_os == "linux" || current_os == "mac") {
sources += [ "DisplayListPlayerGPU.cpp" ]
public_deps = [ "//Userland/Libraries/LibAccelGfx" ]
}
}

View file

@ -1,14 +0,0 @@
include(accelerated_graphics)
if (HAS_ACCELERATED_GRAPHICS)
set(SOURCES
GL.cpp
GlyphAtlas.cpp
Context.cpp
Painter.cpp
Program.cpp
)
serenity_lib(LibAccelGfx accelgfx)
target_link_libraries(LibAccelGfx PRIVATE LibGfx ${ACCEL_GFX_LIBS})
endif()

View file

@ -1,46 +0,0 @@
/*
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/RefCounted.h>
#include <LibAccelGfx/GL.h>
#include <LibGfx/Rect.h>
namespace AccelGfx {
class Canvas : public RefCounted<Canvas> {
public:
static NonnullRefPtr<Canvas> create(Gfx::IntSize const& size)
{
return adopt_ref(*new Canvas(size));
}
Gfx::IntSize const& size() const { return m_size; }
GL::Framebuffer const& framebuffer() const { return m_framebuffer; }
void bind()
{
GL::bind_framebuffer(m_framebuffer);
}
virtual ~Canvas()
{
GL::delete_framebuffer(m_framebuffer);
}
private:
Canvas(Gfx::IntSize const& size)
: m_size(size)
, m_framebuffer(GL::create_framebuffer(size))
{
}
Gfx::IntSize m_size;
GL::Framebuffer m_framebuffer;
};
}

View file

@ -1,195 +0,0 @@
/*
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Error.h>
#include <LibAccelGfx/Context.h>
#ifdef AK_OS_MACOS
# define GL_SILENCE_DEPRECATION
# include <OpenGL/CGLRenderers.h>
# include <OpenGL/CGLTypes.h>
# include <OpenGL/OpenGL.h>
# include <OpenGL/gl3.h>
#endif
namespace AccelGfx {
#ifdef AK_OS_MACOS
class CGLContextWrapper : public Context {
public:
CGLContextWrapper(CGLContextObj context)
: m_context(context)
{
}
virtual void activate() override
{
CGLSetCurrentContext(m_context);
}
~CGLContextWrapper()
{
CGLReleaseContext(m_context);
}
private:
CGLContextObj m_context;
};
#else
class EGLContextWrapper : public Context {
public:
EGLContextWrapper(EGLContext context)
: m_context(context)
{
}
~EGLContextWrapper()
{
eglDestroyContext(eglGetCurrentDisplay(), m_context);
}
virtual void activate() override
{
eglMakeCurrent(eglGetCurrentDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE, m_context);
}
private:
EGLContext m_context;
};
#endif
#ifdef AK_OS_MACOS
static ErrorOr<NonnullOwnPtr<CGLContextWrapper>> make_context_cgl()
{
CGLContextObj context = NULL;
CGLPixelFormatAttribute attributes[4] = {
kCGLPFAOpenGLProfile,
(CGLPixelFormatAttribute)kCGLOGLPVersion_3_2_Core,
kCGLPFAAccelerated,
(CGLPixelFormatAttribute)0
};
CGLPixelFormatObj pixelFormat = NULL;
GLint numPixelFormats = 0;
CGLError error = CGLChoosePixelFormat(attributes, &pixelFormat, &numPixelFormats);
if (error) {
auto const* cgl_error_string = CGLErrorString(error);
return Error::from_string_view(StringView { cgl_error_string, strlen(cgl_error_string) });
}
error = CGLCreateContext(pixelFormat, NULL, &context);
if (error) {
auto const* cgl_error_string = CGLErrorString(error);
return Error::from_string_view(StringView { cgl_error_string, strlen(cgl_error_string) });
}
error = CGLSetCurrentContext(context);
if (error) {
auto const* cgl_error_string = CGLErrorString(error);
return Error::from_string_view(StringView { cgl_error_string, strlen(cgl_error_string) });
}
VERIFY(glGetError() == GL_NO_ERROR);
return make<CGLContextWrapper>(context);
}
#else
static StringView format_egl_error(EGLint error)
{
switch (error) {
case EGL_SUCCESS:
return "EGL_SUCCESS"sv;
case EGL_NOT_INITIALIZED:
return "EGL_NOT_INITIALIZED"sv;
case EGL_BAD_ACCESS:
return "EGL_BAD_ACCESS"sv;
case EGL_BAD_ALLOC:
return "EGL_BAD_ALLOC"sv;
case EGL_BAD_ATTRIBUTE:
return "EGL_BAD_ATTRIBUTE"sv;
case EGL_BAD_CONTEXT:
return "EGL_BAD_CONTEXT"sv;
case EGL_BAD_CONFIG:
return "EGL_BAD_CONFIG"sv;
case EGL_BAD_CURRENT_SURFACE:
return "EGL_BAD_CURRENT_SURFACE"sv;
case EGL_BAD_DISPLAY:
return "EGL_BAD_DISPLAY"sv;
case EGL_BAD_SURFACE:
return "EGL_BAD_SURFACE"sv;
case EGL_BAD_MATCH:
return "EGL_BAD_MATCH"sv;
case EGL_BAD_PARAMETER:
return "EGL_BAD_PARAMETER"sv;
case EGL_BAD_NATIVE_PIXMAP:
return "EGL_BAD_NATIVE_PIXMAP"sv;
case EGL_BAD_NATIVE_WINDOW:
return "EGL_BAD_NATIVE_WINDOW"sv;
case EGL_CONTEXT_LOST:
return "EGL_CONTEXT_LOST"sv;
default:
return "Unknown error"sv;
}
}
static ErrorOr<NonnullOwnPtr<EGLContextWrapper>> make_context_egl()
{
EGLDisplay egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EGLint major;
EGLint minor;
eglInitialize(egl_display, &major, &minor);
EGLBoolean ok = eglBindAPI(EGL_OPENGL_API);
if (ok == EGL_FALSE) {
dbgln("eglBindAPI failed");
VERIFY_NOT_REACHED();
}
static EGLint const config_attributes[] = {
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_DEPTH_SIZE, 8,
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
EGL_NONE
};
EGLConfig egl_config;
EGLint num_configs;
eglChooseConfig(egl_display, config_attributes, &egl_config, 1, &num_configs);
static EGLint const context_attributes[] = {
EGL_CONTEXT_MAJOR_VERSION, 3,
EGL_CONTEXT_MINOR_VERSION, 3,
EGL_NONE
};
EGLContext egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, context_attributes);
if (egl_context == EGL_FALSE) {
return Error::from_string_view(format_egl_error(eglGetError()));
}
EGLBoolean result = eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context);
if (result == EGL_FALSE) {
return Error::from_string_view(format_egl_error(eglGetError()));
}
return make<EGLContextWrapper>(egl_context);
}
#endif
ErrorOr<NonnullOwnPtr<Context>> Context::create()
{
#ifdef AK_OS_MACOS
return make_context_cgl();
#else
return make_context_egl();
#endif
}
}

View file

@ -1,36 +0,0 @@
/*
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Assertions.h>
#include <AK/OwnPtr.h>
#ifndef AK_OS_MACOS
// Make sure egl.h doesn't give us definitions from X11 headers
# define EGL_NO_X11
# include <EGL/egl.h>
# undef EGL_NO_X11
#endif
namespace AccelGfx {
class Context {
public:
static ErrorOr<NonnullOwnPtr<Context>> create();
Context()
{
}
virtual ~Context()
{
}
virtual void activate() = 0;
};
}

View file

@ -1,13 +0,0 @@
/*
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
namespace AccelGfx {
class Painter;
}

View file

@ -1,299 +0,0 @@
/*
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#define GL_GLEXT_PROTOTYPES
#include <LibAccelGfx/GL.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Rect.h>
namespace AccelGfx::GL {
static void verify_no_error()
{
VERIFY(glGetError() == GL_NO_ERROR);
}
void set_viewport(Gfx::IntRect rect)
{
glViewport(rect.left(), rect.top(), rect.width(), rect.height());
verify_no_error();
}
static GLenum to_gl_enum(BlendFactor factor)
{
switch (factor) {
case BlendFactor::SrcAlpha:
return GL_SRC_ALPHA;
case BlendFactor::One:
return GL_ONE;
case BlendFactor::Zero:
return GL_ZERO;
case BlendFactor::OneMinusSrcAlpha:
return GL_ONE_MINUS_SRC_ALPHA;
}
VERIFY_NOT_REACHED();
}
void enable_blending(BlendFactor source, BlendFactor destination, BlendFactor source_alpha, BlendFactor destination_alpha)
{
glEnable(GL_BLEND);
glBlendFuncSeparate(to_gl_enum(source), to_gl_enum(destination), to_gl_enum(source_alpha), to_gl_enum(destination_alpha));
verify_no_error();
}
void read_pixels(Gfx::IntRect rect, Gfx::Bitmap& bitmap)
{
VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRA8888);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(rect.left(), rect.top(), rect.width(), rect.height(), GL_BGRA, GL_UNSIGNED_BYTE, bitmap.scanline(0));
verify_no_error();
}
Shader create_shader(ShaderType type, char const* source)
{
GLuint shader = glCreateShader(type == ShaderType::Vertex ? GL_VERTEX_SHADER : GL_FRAGMENT_SHADER);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
int success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
char buffer[512];
glGetShaderInfoLog(shader, sizeof(buffer), nullptr, buffer);
dbgln("GLSL shader compilation failed: {}", buffer);
VERIFY_NOT_REACHED();
}
verify_no_error();
return { shader };
}
Program create_program(Shader const& vertex_shader, Shader const& fragment_shader)
{
GLuint program = glCreateProgram();
glAttachShader(program, vertex_shader.id);
glAttachShader(program, fragment_shader.id);
glLinkProgram(program);
int linked;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (!linked) {
char buffer[512];
glGetProgramInfoLog(program, sizeof(buffer), nullptr, buffer);
dbgln("GLSL program linking failed: {}", buffer);
VERIFY_NOT_REACHED();
}
glDeleteShader(vertex_shader.id);
glDeleteShader(fragment_shader.id);
verify_no_error();
return { program };
}
void use_program(Program const& program)
{
glUseProgram(program.id);
verify_no_error();
}
VertexAttribute get_attribute_location(Program const& program, char const* name)
{
auto id = glGetAttribLocation(program.id, name);
verify_no_error();
return { id };
}
Uniform get_uniform_location(Program const& program, char const* name)
{
auto id = glGetUniformLocation(program.id, name);
verify_no_error();
return { id };
}
void delete_program(Program const& program)
{
glDeleteProgram(program.id);
verify_no_error();
}
Texture create_texture()
{
GLuint texture;
glGenTextures(1, &texture);
verify_no_error();
return { texture, {} };
}
void bind_texture(Texture const& texture)
{
glBindTexture(GL_TEXTURE_2D, texture.id);
verify_no_error();
}
void upload_texture_data(Texture& texture, Gfx::Bitmap const& bitmap)
{
VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRx8888 || bitmap.format() == Gfx::BitmapFormat::BGRA8888);
bind_texture(texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap.width(), bitmap.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, bitmap.scanline(0));
texture.size = bitmap.size();
verify_no_error();
}
void delete_texture(Texture const& texture)
{
glDeleteTextures(1, &texture.id);
verify_no_error();
}
void set_uniform(Uniform const& uniform, int value)
{
glUniform1i(uniform.id, value);
verify_no_error();
}
void set_uniform(Uniform const& uniform, float value1, float value2)
{
glUniform2f(uniform.id, value1, value2);
verify_no_error();
}
void set_uniform(Uniform const& uniform, float value1, float value2, float value3, float value4)
{
glUniform4f(uniform.id, value1, value2, value3, value4);
verify_no_error();
}
void set_vertex_attribute(VertexAttribute const& attribute, u32 offset, int number_of_components)
{
glVertexAttribPointer(attribute.id, number_of_components, GL_FLOAT, GL_FALSE, number_of_components * sizeof(float), reinterpret_cast<void*>(offset));
glEnableVertexAttribArray(attribute.id);
verify_no_error();
}
void set_texture_scale_mode(ScalingMode scaling_mode)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, scaling_mode == ScalingMode::Nearest ? GL_NEAREST : GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, scaling_mode == ScalingMode::Nearest ? GL_NEAREST : GL_LINEAR);
verify_no_error();
}
void clear_color(Gfx::Color const& color)
{
glClearColor(color.red() / 255.0f, color.green() / 255.0f, color.blue() / 255.0f, color.alpha() / 255.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
verify_no_error();
}
void draw_arrays(DrawPrimitive draw_primitive, size_t count)
{
GLenum mode = GL_TRIANGLES;
if (draw_primitive == DrawPrimitive::TriangleFan)
mode = GL_TRIANGLE_FAN;
glDrawArrays(mode, 0, count);
verify_no_error();
}
Buffer create_buffer()
{
GLuint buffer;
glGenBuffers(1, &buffer);
verify_no_error();
return { buffer };
}
void bind_buffer(Buffer const& buffer)
{
glBindBuffer(GL_ARRAY_BUFFER, buffer.id);
verify_no_error();
}
void upload_to_buffer(Buffer const& buffer, Span<float> values)
{
glBindBuffer(GL_ARRAY_BUFFER, buffer.id);
glBufferData(GL_ARRAY_BUFFER, values.size() * sizeof(float), values.data(), GL_STATIC_DRAW);
verify_no_error();
}
void delete_buffer(Buffer const& buffer)
{
glDeleteBuffers(1, &buffer.id);
verify_no_error();
}
VertexArray create_vertex_array()
{
GLuint vertex_array;
glGenVertexArrays(1, &vertex_array);
verify_no_error();
return { vertex_array };
}
void bind_vertex_array(VertexArray const& vertex_array)
{
glBindVertexArray(vertex_array.id);
verify_no_error();
}
void delete_vertex_array(VertexArray const& vertex_array)
{
glDeleteVertexArrays(1, &vertex_array.id);
verify_no_error();
}
Framebuffer create_framebuffer(Gfx::IntSize size)
{
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
VERIFY_NOT_REACHED();
}
verify_no_error();
return { fbo, Texture { texture, size } };
}
void bind_framebuffer(Framebuffer const& framebuffer)
{
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.fbo_id);
verify_no_error();
}
void delete_framebuffer(Framebuffer const& framebuffer)
{
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.fbo_id);
glDeleteFramebuffers(1, &framebuffer.fbo_id);
delete_texture(framebuffer.texture);
verify_no_error();
}
void enable_scissor_test(Gfx::IntRect rect)
{
glEnable(GL_SCISSOR_TEST);
glScissor(rect.left(), rect.top(), rect.width(), rect.height());
verify_no_error();
}
void disable_scissor_test()
{
glDisable(GL_SCISSOR_TEST);
verify_no_error();
}
}

View file

@ -1,121 +0,0 @@
/*
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Vector.h>
#ifdef AK_OS_MACOS
# define GL_SILENCE_DEPRECATION
# include <OpenGL/OpenGL.h>
# include <OpenGL/gl3.h>
#else
# include <GL/gl.h>
#endif
#include <LibGfx/Forward.h>
#include <LibGfx/Rect.h>
namespace AccelGfx::GL {
enum class ShaderType {
Vertex,
Fragment,
};
struct Shader {
GLuint id;
};
struct Program {
GLuint id;
};
struct VertexAttribute {
GLint id;
};
struct Uniform {
GLint id;
};
struct Texture {
GLuint id;
Optional<Gfx::IntSize> size;
};
struct Buffer {
GLuint id;
};
struct VertexArray {
GLuint id;
};
struct Framebuffer {
GLuint fbo_id;
GL::Texture texture;
};
void set_viewport(Gfx::IntRect);
enum class BlendFactor {
Zero,
One,
OneMinusSrcAlpha,
SrcAlpha,
};
void enable_blending(BlendFactor source, BlendFactor destination, BlendFactor source_alpha, BlendFactor destination_alpha);
void read_pixels(Gfx::IntRect, Gfx::Bitmap&);
Shader create_shader(ShaderType type, char const* source);
Program create_program(Shader const& vertex_shader, Shader const& fragment_shader);
void use_program(Program const&);
VertexAttribute get_attribute_location(Program const&, char const* name);
Uniform get_uniform_location(Program const&, char const* name);
void delete_program(Program const&);
Texture create_texture();
void bind_texture(Texture const&);
void upload_texture_data(Texture& texture, Gfx::Bitmap const& bitmap);
void delete_texture(Texture const&);
void set_uniform(Uniform const& uniform, int);
void set_uniform(Uniform const& uniform, float, float);
void set_uniform(Uniform const& uniform, float, float, float, float);
void set_vertex_attribute(VertexAttribute const& attribute, u32 offset, int number_of_components);
enum class ScalingMode {
Nearest,
Linear,
};
void set_texture_scale_mode(ScalingMode);
void clear_color(Gfx::Color const&);
enum class DrawPrimitive {
Triangles,
TriangleFan,
};
void draw_arrays(DrawPrimitive, size_t count);
Buffer create_buffer();
void bind_buffer(Buffer const&);
void upload_to_buffer(Buffer const&, Span<float> values);
void delete_buffer(Buffer const&);
VertexArray create_vertex_array();
void bind_vertex_array(VertexArray const&);
void delete_vertex_array(VertexArray const&);
Framebuffer create_framebuffer(Gfx::IntSize);
void bind_framebuffer(Framebuffer const& framebuffer);
void delete_framebuffer(Framebuffer const& framebuffer);
void enable_scissor_test(Gfx::IntRect);
void disable_scissor_test();
}

View file

@ -1,86 +0,0 @@
/*
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/QuickSort.h>
#include <LibAccelGfx/GlyphAtlas.h>
#include <LibGfx/Painter.h>
namespace AccelGfx {
GlyphAtlas& GlyphAtlas::the()
{
static OwnPtr<GlyphAtlas> s_the;
if (!s_the)
s_the = make<GlyphAtlas>();
return *s_the;
}
void GlyphAtlas::update(HashMap<Gfx::Font const*, HashTable<u32>> const& unique_glyphs)
{
auto need_to_rebuild_texture = false;
HashMap<GlyphsTextureKey, NonnullRefPtr<Gfx::Bitmap>> glyph_bitmaps;
for (auto const& [font, code_points] : unique_glyphs) {
for (auto const& code_point : code_points) {
auto glyph = font->glyph(code_point);
if (!glyph.has_value())
continue;
auto atlas_key = GlyphsTextureKey { font, code_point };
if (!m_glyphs_texture_map.contains(atlas_key))
need_to_rebuild_texture = true;
glyph_bitmaps.set(atlas_key, *glyph->bitmap());
}
}
if (!need_to_rebuild_texture || glyph_bitmaps.is_empty())
return;
m_glyphs_texture_map.clear();
Vector<GlyphsTextureKey> glyphs_sorted_by_height;
glyphs_sorted_by_height.ensure_capacity(glyph_bitmaps.size());
for (auto const& [atlas_key, bitmap] : glyph_bitmaps) {
glyphs_sorted_by_height.append(atlas_key);
}
quick_sort(glyphs_sorted_by_height, [&](auto const& a, auto const& b) {
auto const* bitmap_a = *glyph_bitmaps.get(a);
auto const* bitmap_b = *glyph_bitmaps.get(b);
return bitmap_a->height() > bitmap_b->height();
});
int current_x = 0;
int current_y = 0;
int row_height = 0;
int const texture_width = 512;
int const padding = 1;
for (auto const& glyphs_texture_key : glyphs_sorted_by_height) {
auto const* bitmap = *glyph_bitmaps.get(glyphs_texture_key);
if (current_x + bitmap->width() > texture_width) {
current_x = 0;
current_y += row_height + padding;
row_height = 0;
}
m_glyphs_texture_map.set(glyphs_texture_key, { current_x, current_y, bitmap->width(), bitmap->height() });
current_x += bitmap->width() + padding;
row_height = max(row_height, bitmap->height());
}
auto glyphs_texture_bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { texture_width, current_y + row_height }));
auto glyphs_texture_painter = Gfx::Painter(*glyphs_texture_bitmap);
for (auto const& [glyphs_texture_key, glyph_bitmap] : glyph_bitmaps) {
auto rect = m_glyphs_texture_map.get(glyphs_texture_key).value();
glyphs_texture_painter.blit({ rect.x(), rect.y() }, glyph_bitmap, glyph_bitmap->rect());
}
GL::upload_texture_data(m_texture, *glyphs_texture_bitmap);
}
Optional<Gfx::IntRect> GlyphAtlas::get_glyph_rect(Gfx::Font const* font, u32 code_point) const
{
auto atlas_key = GlyphsTextureKey { font, code_point };
return m_glyphs_texture_map.get(atlas_key);
}
}

View file

@ -1,64 +0,0 @@
/*
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/HashMap.h>
#include <AK/Noncopyable.h>
#include <LibAccelGfx/GL.h>
#include <LibGfx/Font/Font.h>
namespace AccelGfx {
class GlyphAtlas {
AK_MAKE_NONCOPYABLE(GlyphAtlas);
public:
GlyphAtlas()
: m_texture(GL::create_texture())
{
}
~GlyphAtlas()
{
GL::delete_texture(m_texture);
}
static GlyphAtlas& the();
struct GlyphsTextureKey {
Gfx::Font const* font;
u32 code_point;
bool operator==(GlyphsTextureKey const& other) const
{
return font == other.font && code_point == other.code_point;
}
};
void update(HashMap<Gfx::Font const*, HashTable<u32>> const& unique_glyphs);
Optional<Gfx::IntRect> get_glyph_rect(Gfx::Font const*, u32 code_point) const;
GL::Texture const& texture() const { return m_texture; }
private:
GL::Texture m_texture;
HashMap<GlyphsTextureKey, Gfx::IntRect> m_glyphs_texture_map;
};
}
namespace AK {
template<>
struct Traits<AccelGfx::GlyphAtlas::GlyphsTextureKey> : public DefaultTraits<AccelGfx::GlyphAtlas::GlyphsTextureKey> {
static unsigned hash(AccelGfx::GlyphAtlas::GlyphsTextureKey const& key)
{
return pair_int_hash(ptr_hash(key.font), key.code_point);
}
};
}

View file

@ -1,801 +0,0 @@
/*
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibAccelGfx/GL.h>
#include <LibAccelGfx/Painter.h>
#include <LibGfx/ImmutableBitmap.h>
#include <LibGfx/Painter.h>
namespace AccelGfx {
struct ColorComponents {
float red;
float green;
float blue;
float alpha;
};
static ColorComponents gfx_color_to_opengl_color(Gfx::Color color)
{
ColorComponents components;
components.red = static_cast<float>(color.red()) / 255.0f;
components.green = static_cast<float>(color.green()) / 255.0f;
components.blue = static_cast<float>(color.blue()) / 255.0f;
components.alpha = static_cast<float>(color.alpha()) / 255.0f;
return components;
}
Gfx::FloatRect Painter::to_clip_space(Gfx::FloatRect const& screen_rect) const
{
float x = 2.0f * screen_rect.x() / m_target_canvas->size().width() - 1.0f;
float y = -1.0f + 2.0f * screen_rect.y() / m_target_canvas->size().height();
float width = 2.0f * screen_rect.width() / m_target_canvas->size().width();
float height = 2.0f * screen_rect.height() / m_target_canvas->size().height();
return { x, y, width, height };
}
char const* vertex_shader_source = R"(
#version 330 core
in vec2 aVertexPosition;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
}
)";
char const* rect_with_rounded_corners_fragment_shader_source = R"(
#version 330 core
uniform vec2 uRectCenter;
uniform vec2 uRectCorner;
uniform vec2 uTopLeftRadius;
uniform vec2 uTopRightRadius;
uniform vec2 uBottomLeftRadius;
uniform vec2 uBottomRightRadius;
uniform vec4 uColor;
out vec4 fragColor;
bool isPointWithinEllipse(vec2 point, vec2 radius) {
vec2 normalizedPoint = point / radius;
return dot(normalizedPoint, normalizedPoint) <= 1.0;
}
void main() {
vec2 p = gl_FragCoord.xy - uRectCenter;
vec2 cornerRadius = vec2(0.0, 0.0);
if (p.x < 0.0 && p.y < 0.0) {
cornerRadius = uTopLeftRadius;
} else if (p.x > 0.0 && p.y < 0.0) {
cornerRadius = uTopRightRadius;
} else if (p.x < 0.0 && p.y > 0.0) {
cornerRadius = uBottomLeftRadius;
} else if (p.x > 0.0 && p.y > 0.0) {
cornerRadius = uBottomRightRadius;
}
vec2 q = abs(p) - (uRectCorner - cornerRadius);
if (q.x < 0 || q.y < 0 || isPointWithinEllipse(q, cornerRadius)) {
fragColor = uColor;
} else {
discard;
}
}
)";
char const* solid_color_fragment_shader_source = R"(
#version 330 core
uniform vec4 uColor;
out vec4 fragColor;
void main() {
fragColor = uColor;
}
)";
char const* blit_vertex_shader_source = R"(
#version 330 core
in vec4 aVertexPosition;
out vec2 vTextureCoord;
void main() {
gl_Position = vec4(aVertexPosition.xy, 0.0, 1.0);
vTextureCoord = aVertexPosition.zw;
}
)";
char const* blit_fragment_shader_source = R"(
#version 330 core
uniform vec4 uColor;
in vec2 vTextureCoord;
uniform sampler2D uSampler;
out vec4 fragColor;
void main() {
fragColor = texture(uSampler, vTextureCoord) * uColor;
}
)";
char const* linear_gradient_vertex_shader_source = R"(
#version 330 core
layout (location = 0) in vec2 aVertexPosition;
layout (location = 1) in vec4 aColor;
out vec4 vColor;
void main() {
gl_Position = vec4(aVertexPosition, 0.0, 1.0);
vColor = aColor;
}
)";
char const* linear_gradient_fragment_shader_source = R"(
#version 330 core
out vec4 FragColor;
in vec4 vColor;
void main() {
FragColor = vec4(vColor);
}
)";
char const* blur_fragment_shader_source = R"(
#version 330 core
uniform vec2 uResolution;
uniform int uRadius;
uniform int uHorizontal;
uniform sampler2D uSampler;
in vec2 vTextureCoord;
out vec4 fragColor;
#define pow2(x) (x * x)
const float pi = atan(1.0) * 4.0;
float gaussian(vec2 i, float sigma) {
return 1.0 / (2.0 * pi * pow2(sigma)) * exp(-((pow2(i.x) + pow2(i.y)) / (2.0 * pow2(sigma))));
}
void main() {
vec2 scale = vec2(1.0) / uResolution.xy;
float sigma = float(uRadius);
vec4 col = vec4(0.0);
float accum = 0.0;
float weight = 0.0;
for (int i = -uRadius; i <= uRadius; i++) {
vec2 offset = vec2(i * uHorizontal, i * (1 - uHorizontal));
weight = gaussian(offset, sigma);
col += texture(uSampler, vTextureCoord + scale * offset) * weight;
accum += weight;
}
fragColor = col / accum;
}
)";
static void set_blending_mode(Painter::BlendingMode blending_mode)
{
switch (blending_mode) {
case Painter::BlendingMode::AlphaAdd:
GL::enable_blending(GL::BlendFactor::SrcAlpha, GL::BlendFactor::OneMinusSrcAlpha, GL::BlendFactor::One, GL::BlendFactor::One);
break;
case Painter::BlendingMode::AlphaOverride:
GL::enable_blending(GL::BlendFactor::SrcAlpha, GL::BlendFactor::OneMinusSrcAlpha, GL::BlendFactor::One, GL::BlendFactor::Zero);
break;
case Painter::BlendingMode::AlphaPreserve:
GL::enable_blending(GL::BlendFactor::SrcAlpha, GL::BlendFactor::OneMinusSrcAlpha, GL::BlendFactor::Zero, GL::BlendFactor::One);
break;
default:
VERIFY_NOT_REACHED();
}
}
HashMap<u32, GL::Texture> s_immutable_bitmap_texture_cache;
NonnullOwnPtr<Painter> Painter::create(Context& context, NonnullRefPtr<Canvas> canvas)
{
return make<Painter>(context, canvas);
}
Painter::Painter(Context& context, NonnullRefPtr<Canvas> canvas)
: m_context(context)
, m_target_canvas(canvas)
, m_rectangle_program(Program::create(Program::Name::RectangleProgram, vertex_shader_source, solid_color_fragment_shader_source))
, m_rounded_rectangle_program(Program::create(Program::Name::RoundedRectangleProgram, vertex_shader_source, rect_with_rounded_corners_fragment_shader_source))
, m_blit_program(Program::create(Program::Name::BlitProgram, blit_vertex_shader_source, blit_fragment_shader_source))
, m_linear_gradient_program(Program::create(Program::Name::LinearGradientProgram, linear_gradient_vertex_shader_source, linear_gradient_fragment_shader_source))
, m_blur_program(Program::create(Program::Name::BlurProgram, blit_vertex_shader_source, blur_fragment_shader_source))
{
m_state_stack.empend(State());
state().clip_rect = { { 0, 0 }, m_target_canvas->size() };
bind_target_canvas();
}
Painter::~Painter()
{
}
void Painter::clear(Gfx::Color color)
{
bind_target_canvas();
GL::clear_color(color);
}
void Painter::fill_rect(Gfx::IntRect rect, Gfx::Color color)
{
fill_rect(rect.to_type<float>(), color);
}
static Array<GLfloat, 8> rect_to_vertices(Gfx::FloatRect const& rect)
{
return {
rect.left(),
rect.top(),
rect.left(),
rect.bottom(),
rect.right(),
rect.bottom(),
rect.right(),
rect.top(),
};
}
void Painter::fill_rect(Gfx::FloatRect rect, Gfx::Color color)
{
bind_target_canvas();
// Draw a filled rect (with `color`) using OpenGL after mapping it through the current transform.
auto vertices = rect_to_vertices(to_clip_space(transform().map(rect)));
auto vbo = GL::create_buffer();
GL::upload_to_buffer(vbo, vertices);
auto vao = GL::create_vertex_array();
GL::bind_vertex_array(vao);
GL::bind_buffer(vbo);
auto [red, green, blue, alpha] = gfx_color_to_opengl_color(color);
m_rectangle_program.use();
auto position_attribute = m_rectangle_program.get_attribute_location("aVertexPosition");
auto color_uniform = m_rectangle_program.get_uniform_location("uColor");
GL::set_uniform(color_uniform, red, green, blue, alpha);
GL::set_vertex_attribute(position_attribute, 0, 2);
GL::enable_blending(GL::BlendFactor::SrcAlpha, GL::BlendFactor::OneMinusSrcAlpha, GL::BlendFactor::One, GL::BlendFactor::One);
GL::draw_arrays(GL::DrawPrimitive::TriangleFan, 4);
GL::delete_buffer(vbo);
GL::delete_vertex_array(vao);
}
void Painter::fill_rect_with_rounded_corners(Gfx::IntRect const& rect, Color const& color, CornerRadius const& top_left_radius, CornerRadius const& top_right_radius, CornerRadius const& bottom_left_radius, CornerRadius const& bottom_right_radius, BlendingMode blending_mode)
{
fill_rect_with_rounded_corners(rect.to_type<float>(), color, top_left_radius, top_right_radius, bottom_left_radius, bottom_right_radius, blending_mode);
}
void Painter::fill_rect_with_rounded_corners(Gfx::FloatRect const& rect, Color const& color, CornerRadius const& top_left_radius, CornerRadius const& top_right_radius, CornerRadius const& bottom_left_radius, CornerRadius const& bottom_right_radius, BlendingMode blending_mode)
{
bind_target_canvas();
auto transformed_rect = transform().map(rect);
auto vertices = rect_to_vertices(to_clip_space(transformed_rect));
auto vbo = GL::create_buffer();
GL::upload_to_buffer(vbo, vertices);
auto vao = GL::create_vertex_array();
GL::bind_vertex_array(vao);
GL::bind_buffer(vbo);
auto [red, green, blue, alpha] = gfx_color_to_opengl_color(color);
m_rounded_rectangle_program.use();
auto position_attribute = m_rounded_rectangle_program.get_attribute_location("aVertexPosition");
GL::set_vertex_attribute(position_attribute, 0, 2);
auto color_uniform = m_rounded_rectangle_program.get_uniform_location("uColor");
GL::set_uniform(color_uniform, red, green, blue, alpha);
auto rect_center_uniform = m_rounded_rectangle_program.get_uniform_location("uRectCenter");
GL::set_uniform(rect_center_uniform, transformed_rect.center().x(), transformed_rect.center().y());
auto rect_corner_uniform = m_rounded_rectangle_program.get_uniform_location("uRectCorner");
GL::set_uniform(rect_corner_uniform, rect.width() / 2, rect.height() / 2);
auto top_left_corner_radius_uniform = m_rounded_rectangle_program.get_uniform_location("uTopLeftRadius");
GL::set_uniform(top_left_corner_radius_uniform, top_left_radius.horizontal_radius, top_left_radius.vertical_radius);
auto top_right_corner_radius_uniform = m_rounded_rectangle_program.get_uniform_location("uTopRightRadius");
GL::set_uniform(top_right_corner_radius_uniform, top_right_radius.horizontal_radius, top_right_radius.vertical_radius);
auto bottom_left_corner_radius_uniform = m_rounded_rectangle_program.get_uniform_location("uBottomLeftRadius");
GL::set_uniform(bottom_left_corner_radius_uniform, bottom_left_radius.horizontal_radius, bottom_left_radius.vertical_radius);
auto bottom_right_corner_radius_uniform = m_rounded_rectangle_program.get_uniform_location("uBottomRightRadius");
GL::set_uniform(bottom_right_corner_radius_uniform, bottom_right_radius.horizontal_radius, bottom_right_radius.vertical_radius);
set_blending_mode(blending_mode);
GL::draw_arrays(GL::DrawPrimitive::TriangleFan, 4);
GL::delete_buffer(vbo);
GL::delete_vertex_array(vao);
}
void Painter::draw_line(Gfx::IntPoint a, Gfx::IntPoint b, float thickness, Gfx::Color color)
{
draw_line(a.to_type<float>(), b.to_type<float>(), thickness, color);
}
void Painter::draw_line(Gfx::FloatPoint a, Gfx::FloatPoint b, float thickness, Color color)
{
bind_target_canvas();
auto midpoint = (a + b) / 2.0f;
auto length = a.distance_from(b);
auto angle = AK::atan2(b.y() - a.y(), b.x() - a.x());
auto offset = Gfx::FloatPoint {
(length / 2) * AK::cos(angle) - (thickness / 2) * AK::sin(angle),
(length / 2) * AK::sin(angle) + (thickness / 2) * AK::cos(angle),
};
auto rect = Gfx::FloatRect(midpoint - offset, { length, thickness });
auto vertices = rect_to_vertices(to_clip_space(transform().map(rect)));
auto vbo = GL::create_buffer();
GL::upload_to_buffer(vbo, vertices);
auto vao = GL::create_vertex_array();
GL::bind_vertex_array(vao);
GL::bind_buffer(vbo);
auto [red, green, blue, alpha] = gfx_color_to_opengl_color(color);
m_rectangle_program.use();
auto position_attribute = m_rectangle_program.get_attribute_location("aVertexPosition");
auto color_uniform = m_rectangle_program.get_uniform_location("uColor");
GL::set_uniform(color_uniform, red, green, blue, alpha);
GL::set_vertex_attribute(position_attribute, 0, 2);
GL::enable_blending(GL::BlendFactor::SrcAlpha, GL::BlendFactor::OneMinusSrcAlpha, GL::BlendFactor::One, GL::BlendFactor::One);
GL::draw_arrays(GL::DrawPrimitive::TriangleFan, 4);
GL::delete_buffer(vbo);
GL::delete_vertex_array(vao);
}
void Painter::draw_scaled_bitmap(Gfx::IntRect const& dest_rect, Gfx::Bitmap const& bitmap, Gfx::IntRect const& src_rect, ScalingMode scaling_mode)
{
draw_scaled_bitmap(dest_rect.to_type<float>(), bitmap, src_rect.to_type<float>(), scaling_mode);
}
void Painter::draw_scaled_immutable_bitmap(Gfx::IntRect const& dst_rect, Gfx::ImmutableBitmap const& immutable_bitmap, Gfx::IntRect const& src_rect, ScalingMode scaling_mode)
{
draw_scaled_immutable_bitmap(dst_rect.to_type<float>(), immutable_bitmap, src_rect.to_type<float>(), scaling_mode);
}
void Painter::draw_scaled_immutable_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const& immutable_bitmap, Gfx::FloatRect const& src_rect, ScalingMode scaling_mode)
{
auto texture = s_immutable_bitmap_texture_cache.get(immutable_bitmap.id());
VERIFY(texture.has_value());
blit_scaled_texture(dst_rect, texture.value(), src_rect, scaling_mode);
}
static Gfx::FloatRect to_texture_space(Gfx::FloatRect rect, Gfx::IntSize image_size)
{
auto x = rect.x() / image_size.width();
auto y = rect.y() / image_size.height();
auto width = rect.width() / image_size.width();
auto height = rect.height() / image_size.height();
return { x, y, width, height };
}
static GL::ScalingMode to_gl_scaling_mode(Painter::ScalingMode scaling_mode)
{
switch (scaling_mode) {
case Painter::ScalingMode::NearestNeighbor:
return GL::ScalingMode::Nearest;
case Painter::ScalingMode::Bilinear:
return GL::ScalingMode::Linear;
default:
VERIFY_NOT_REACHED();
}
}
void Painter::draw_scaled_bitmap(Gfx::FloatRect const& dst_rect, Gfx::Bitmap const& bitmap, Gfx::FloatRect const& src_rect, ScalingMode scaling_mode)
{
// FIXME: We should reuse textures across repaints if possible.
auto texture = GL::create_texture();
GL::upload_texture_data(texture, bitmap);
blit_scaled_texture(dst_rect, texture, src_rect, scaling_mode);
GL::delete_texture(texture);
}
void Painter::draw_glyph_run(Span<Gfx::DrawGlyphOrEmoji const> glyph_run, Gfx::Font const& font, Color const& color)
{
bind_target_canvas();
Vector<GLfloat> vertices;
vertices.ensure_capacity(glyph_run.size() * 24);
auto const& glyph_atlas = GlyphAtlas::the();
for (auto const& glyph_or_emoji : glyph_run) {
if (glyph_or_emoji.has<Gfx::DrawGlyph>()) {
auto const& glyph = glyph_or_emoji.get<Gfx::DrawGlyph>();
auto code_point = glyph.code_point;
auto point = glyph.position;
auto maybe_texture_rect = glyph_atlas.get_glyph_rect(&font, code_point);
if (!maybe_texture_rect.has_value()) {
continue;
}
auto texture_rect = to_texture_space(maybe_texture_rect.value().to_type<float>(), *glyph_atlas.texture().size);
auto glyph_position = point + Gfx::FloatPoint(font.glyph_left_bearing(code_point), 0);
auto glyph_size = maybe_texture_rect->size().to_type<float>();
auto glyph_rect = transform().map(Gfx::FloatRect { glyph_position, glyph_size });
auto rect_in_clip_space = to_clip_space(glyph_rect);
// p0 --- p1
// | \ |
// | \ |
// | \ |
// p2 --- p3
auto p0 = rect_in_clip_space.top_left();
auto p1 = rect_in_clip_space.top_right();
auto p2 = rect_in_clip_space.bottom_left();
auto p3 = rect_in_clip_space.bottom_right();
auto s0 = texture_rect.top_left();
auto s1 = texture_rect.top_right();
auto s2 = texture_rect.bottom_left();
auto s3 = texture_rect.bottom_right();
auto add_triangle = [&](auto& p1, auto& p2, auto& p3, auto& s1, auto& s2, auto& s3) {
vertices.unchecked_append(p1.x());
vertices.unchecked_append(p1.y());
vertices.unchecked_append(s1.x());
vertices.unchecked_append(s1.y());
vertices.unchecked_append(p2.x());
vertices.unchecked_append(p2.y());
vertices.unchecked_append(s2.x());
vertices.unchecked_append(s2.y());
vertices.unchecked_append(p3.x());
vertices.unchecked_append(p3.y());
vertices.unchecked_append(s3.x());
vertices.unchecked_append(s3.y());
};
add_triangle(p0, p1, p3, s0, s1, s3);
add_triangle(p0, p3, p2, s0, s3, s2);
}
}
auto vbo = GL::create_buffer();
GL::upload_to_buffer(vbo, vertices);
auto vao = GL::create_vertex_array();
GL::bind_vertex_array(vao);
GL::bind_buffer(vbo);
auto [red, green, blue, alpha] = gfx_color_to_opengl_color(color);
m_blit_program.use();
GL::bind_texture(glyph_atlas.texture());
GL::set_texture_scale_mode(GL::ScalingMode::Nearest);
auto position_attribute = m_blit_program.get_attribute_location("aVertexPosition");
auto color_uniform = m_blit_program.get_uniform_location("uColor");
GL::set_uniform(color_uniform, red, green, blue, alpha);
GL::set_vertex_attribute(position_attribute, 0, 4);
GL::enable_blending(GL::BlendFactor::SrcAlpha, GL::BlendFactor::OneMinusSrcAlpha, GL::BlendFactor::One, GL::BlendFactor::One);
GL::draw_arrays(GL::DrawPrimitive::Triangles, vertices.size() / 4);
GL::delete_buffer(vbo);
GL::delete_vertex_array(vao);
}
void Painter::fill_rect_with_linear_gradient(Gfx::IntRect const& rect, ReadonlySpan<Gfx::ColorStop> stops, float angle, Optional<float> repeat_length)
{
fill_rect_with_linear_gradient(rect.to_type<float>(), stops, angle, repeat_length);
}
void Painter::fill_rect_with_linear_gradient(Gfx::FloatRect const& rect, ReadonlySpan<Gfx::ColorStop> stops, float angle, Optional<float> repeat_length)
{
bind_target_canvas();
// FIXME: Implement support for angle and repeat_length
(void)angle;
(void)repeat_length;
Vector<GLfloat> vertices;
Vector<GLfloat> colors;
for (size_t stop_index = 0; stop_index < stops.size() - 1; stop_index++) {
auto const& stop_start = stops[stop_index];
auto const& stop_end = stops[stop_index + 1];
// The gradient is divided into segments that represent linear gradients between adjacent pairs of stops.
auto segment_rect_location = rect.location();
segment_rect_location.set_x(segment_rect_location.x() + stop_start.position * rect.width());
auto segment_rect_width = (stop_end.position - stop_start.position) * rect.width();
auto segment_rect_height = rect.height();
auto segment_rect = transform().map(Gfx::FloatRect { segment_rect_location.x(), segment_rect_location.y(), segment_rect_width, segment_rect_height });
auto rect_in_clip_space = to_clip_space(segment_rect);
// p0 --- p1
// | \ |
// | \ |
// | \ |
// p2 --- p3
auto p0 = rect_in_clip_space.top_left();
auto p1 = rect_in_clip_space.top_right();
auto p2 = rect_in_clip_space.bottom_left();
auto p3 = rect_in_clip_space.bottom_right();
auto c0 = gfx_color_to_opengl_color(stop_start.color);
auto c1 = gfx_color_to_opengl_color(stop_end.color);
auto c2 = gfx_color_to_opengl_color(stop_start.color);
auto c3 = gfx_color_to_opengl_color(stop_end.color);
auto add_triangle = [&](auto& p1, auto& p2, auto& p3, auto& c1, auto& c2, auto& c3) {
vertices.append(p1.x());
vertices.append(p1.y());
colors.append(c1.red * c1.alpha);
colors.append(c1.green * c1.alpha);
colors.append(c1.blue * c1.alpha);
colors.append(c1.alpha);
vertices.append(p2.x());
vertices.append(p2.y());
colors.append(c2.red * c2.alpha);
colors.append(c2.green * c2.alpha);
colors.append(c2.blue * c2.alpha);
colors.append(c2.alpha);
vertices.append(p3.x());
vertices.append(p3.y());
colors.append(c3.red * c3.alpha);
colors.append(c3.green * c3.alpha);
colors.append(c3.blue * c3.alpha);
colors.append(c3.alpha);
};
add_triangle(p0, p1, p3, c0, c1, c3);
add_triangle(p0, p3, p2, c0, c3, c2);
}
auto vao = GL::create_vertex_array();
GL::bind_vertex_array(vao);
auto vbo_vertices = GL::create_buffer();
GL::upload_to_buffer(vbo_vertices, vertices);
auto vbo_colors = GL::create_buffer();
GL::upload_to_buffer(vbo_colors, colors);
m_linear_gradient_program.use();
auto position_attribute = m_linear_gradient_program.get_attribute_location("aVertexPosition");
auto color_attribute = m_linear_gradient_program.get_attribute_location("aColor");
GL::bind_buffer(vbo_vertices);
GL::set_vertex_attribute(position_attribute, 0, 2);
GL::bind_buffer(vbo_colors);
GL::set_vertex_attribute(color_attribute, 0, 4);
GL::enable_blending(GL::BlendFactor::One, GL::BlendFactor::OneMinusSrcAlpha, GL::BlendFactor::One, GL::BlendFactor::One);
GL::draw_arrays(GL::DrawPrimitive::Triangles, vertices.size() / 2);
GL::delete_buffer(vbo_vertices);
GL::delete_buffer(vbo_colors);
GL::delete_vertex_array(vao);
}
void Painter::save()
{
m_state_stack.append(state());
}
void Painter::restore()
{
VERIFY(!m_state_stack.is_empty());
m_state_stack.take_last();
}
void Painter::set_clip_rect(Gfx::IntRect rect)
{
state().clip_rect = transform().map(rect);
GL::enable_scissor_test(transform().map(rect));
}
void Painter::clear_clip_rect()
{
state().clip_rect = { { 0, 0 }, m_target_canvas->size() };
GL::disable_scissor_test();
}
void Painter::bind_target_canvas()
{
m_target_canvas->bind();
GL::set_viewport({ 0, 0, m_target_canvas->size().width(), m_target_canvas->size().height() });
GL::enable_scissor_test(state().clip_rect);
}
void Painter::flush(Gfx::Bitmap& bitmap)
{
m_target_canvas->bind();
GL::read_pixels({ 0, 0, bitmap.width(), bitmap.height() }, bitmap);
}
void Painter::blit_canvas(Gfx::IntRect const& dst_rect, Canvas const& canvas, float opacity, Optional<Gfx::AffineTransform> affine_transform)
{
blit_canvas(dst_rect.to_type<float>(), canvas, opacity, move(affine_transform));
}
void Painter::blit_canvas(Gfx::FloatRect const& dst_rect, Canvas const& canvas, float opacity, Optional<Gfx::AffineTransform> affine_transform)
{
auto texture = GL::Texture(canvas.framebuffer().texture);
blit_scaled_texture(dst_rect, texture, { { 0, 0 }, canvas.size() }, ScalingMode::NearestNeighbor, opacity, move(affine_transform));
}
void Painter::blit_canvas(Gfx::FloatRect const& dst_rect, Canvas const& canvas, Gfx::FloatRect const& src_rect, float opacity, Optional<Gfx::AffineTransform> affine_transform, BlendingMode blending_mode)
{
auto texture = GL::Texture(canvas.framebuffer().texture);
blit_scaled_texture(dst_rect, texture, src_rect, ScalingMode::NearestNeighbor, opacity, move(affine_transform), blending_mode);
}
void Painter::blit_scaled_texture(Gfx::FloatRect const& dst_rect, GL::Texture const& texture, Gfx::FloatRect const& src_rect, ScalingMode scaling_mode, float opacity, Optional<Gfx::AffineTransform> affine_transform, BlendingMode blending_mode)
{
bind_target_canvas();
m_blit_program.use();
auto dst_rect_rotated = dst_rect;
Array<Gfx::FloatPoint, 4> dst_rect_vertices = {
dst_rect_rotated.top_left(),
dst_rect_rotated.bottom_left(),
dst_rect_rotated.bottom_right(),
dst_rect_rotated.top_right(),
};
if (affine_transform.has_value()) {
for (auto& point : dst_rect_vertices)
point = affine_transform->map(point);
}
auto const viewport_width = static_cast<float>(m_target_canvas->size().width());
auto const viewport_height = static_cast<float>(m_target_canvas->size().height());
for (auto& point : dst_rect_vertices) {
point = transform().map(point);
point.set_x(2.0f * point.x() / viewport_width - 1.0f);
point.set_y(-1.0f + 2.0f * point.y() / viewport_height);
}
auto src_rect_in_texture_space = to_texture_space(src_rect, *texture.size);
Vector<GLfloat> vertices;
vertices.ensure_capacity(16);
auto add_vertex = [&](auto const& p, auto const& s) {
vertices.append(p.x());
vertices.append(p.y());
vertices.append(s.x());
vertices.append(s.y());
};
add_vertex(dst_rect_vertices[0], src_rect_in_texture_space.top_left());
add_vertex(dst_rect_vertices[1], src_rect_in_texture_space.bottom_left());
add_vertex(dst_rect_vertices[2], src_rect_in_texture_space.bottom_right());
add_vertex(dst_rect_vertices[3], src_rect_in_texture_space.top_right());
auto vbo = GL::create_buffer();
GL::upload_to_buffer(vbo, vertices);
auto vao = GL::create_vertex_array();
GL::bind_vertex_array(vao);
GL::bind_buffer(vbo);
auto vertex_position_attribute = m_blit_program.get_attribute_location("aVertexPosition");
GL::set_vertex_attribute(vertex_position_attribute, 0, 4);
auto color_uniform = m_blit_program.get_uniform_location("uColor");
GL::set_uniform(color_uniform, 1, 1, 1, opacity);
GL::bind_texture(texture);
auto scaling_mode_gl = to_gl_scaling_mode(scaling_mode);
GL::set_texture_scale_mode(scaling_mode_gl);
set_blending_mode(blending_mode);
GL::draw_arrays(GL::DrawPrimitive::TriangleFan, 4);
GL::delete_buffer(vbo);
GL::delete_vertex_array(vao);
}
void Painter::blit_blurred_texture(Gfx::FloatRect const& dst_rect, GL::Texture const& texture, Gfx::FloatRect const& src_rect, int radius, BlurDirection direction, ScalingMode scaling_mode)
{
bind_target_canvas();
m_blur_program.use();
auto dst_rect_in_clip_space = to_clip_space(transform().map(dst_rect));
auto src_rect_in_texture_space = to_texture_space(src_rect, *texture.size);
Vector<GLfloat> vertices;
vertices.ensure_capacity(16);
auto add_vertex = [&](auto const& p, auto const& s) {
vertices.append(p.x());
vertices.append(p.y());
vertices.append(s.x());
vertices.append(s.y());
};
add_vertex(dst_rect_in_clip_space.top_left(), src_rect_in_texture_space.top_left());
add_vertex(dst_rect_in_clip_space.bottom_left(), src_rect_in_texture_space.bottom_left());
add_vertex(dst_rect_in_clip_space.bottom_right(), src_rect_in_texture_space.bottom_right());
add_vertex(dst_rect_in_clip_space.top_right(), src_rect_in_texture_space.top_right());
auto vbo = GL::create_buffer();
GL::upload_to_buffer(vbo, vertices);
auto vao = GL::create_vertex_array();
GL::bind_vertex_array(vao);
GL::bind_buffer(vbo);
auto vertex_position_attribute = m_blur_program.get_attribute_location("aVertexPosition");
GL::set_vertex_attribute(vertex_position_attribute, 0, 4);
auto resolution_uniform = m_blur_program.get_uniform_location("uResolution");
GL::set_uniform(resolution_uniform, dst_rect.width(), dst_rect.height());
auto radius_uniform = m_blur_program.get_uniform_location("uRadius");
GL::set_uniform(radius_uniform, radius);
auto direction_uniform = m_blur_program.get_uniform_location("uHorizontal");
GL::set_uniform(direction_uniform, direction == BlurDirection::Horizontal ? 1 : 0);
GL::bind_texture(texture);
auto scaling_mode_gl = to_gl_scaling_mode(scaling_mode);
GL::set_texture_scale_mode(scaling_mode_gl);
GL::enable_blending(GL::BlendFactor::SrcAlpha, GL::BlendFactor::OneMinusSrcAlpha, GL::BlendFactor::One, GL::BlendFactor::One);
GL::draw_arrays(GL::DrawPrimitive::TriangleFan, 4);
GL::delete_buffer(vbo);
GL::delete_vertex_array(vao);
}
void Painter::blit_blurred_canvas(Gfx::FloatRect const& dst_rect, Canvas const& canvas, int radius, BlurDirection direction, ScalingMode scaling_mode)
{
blit_blurred_texture(dst_rect, canvas.framebuffer().texture, { { 0, 0 }, canvas.size() }, radius, direction, scaling_mode);
}
void Painter::update_immutable_bitmap_texture_cache(HashMap<u32, Gfx::ImmutableBitmap const*>& immutable_bitmaps)
{
for (auto immutable_bitmap_id : s_immutable_bitmap_texture_cache.keys()) {
if (!immutable_bitmaps.contains(immutable_bitmap_id)) {
auto texture = s_immutable_bitmap_texture_cache.get(immutable_bitmap_id).value();
GL::delete_texture(texture);
s_immutable_bitmap_texture_cache.remove(immutable_bitmap_id);
}
}
for (auto const& [id, immutable_bitmap] : immutable_bitmaps) {
if (s_immutable_bitmap_texture_cache.contains(id))
continue;
auto texture = GL::create_texture();
GL::upload_texture_data(texture, immutable_bitmap->bitmap());
s_immutable_bitmap_texture_cache.set(id, texture);
}
}
}

View file

@ -1,130 +0,0 @@
/*
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/HashMap.h>
#include <AK/Noncopyable.h>
#include <AK/Vector.h>
#include <LibAccelGfx/Canvas.h>
#include <LibAccelGfx/Context.h>
#include <LibAccelGfx/Forward.h>
#include <LibAccelGfx/GL.h>
#include <LibAccelGfx/GlyphAtlas.h>
#include <LibAccelGfx/Program.h>
#include <LibGfx/AffineTransform.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Gradients.h>
#include <LibGfx/TextLayout.h>
namespace AccelGfx {
class Painter {
AK_MAKE_NONCOPYABLE(Painter);
AK_MAKE_NONMOVABLE(Painter);
public:
static NonnullOwnPtr<Painter> create(Context&, NonnullRefPtr<Canvas>);
Painter(Context&, NonnullRefPtr<Canvas>);
~Painter();
Canvas const& canvas() { return *m_target_canvas; }
void clear(Gfx::Color);
void save();
void restore();
[[nodiscard]] Gfx::AffineTransform const& transform() const { return state().transform; }
void set_transform(Gfx::AffineTransform const& transform) { state().transform = transform; }
void translate(Gfx::FloatPoint translation) { state().transform.translate(translation); }
Gfx::IntRect const& clip_rect() const { return state().clip_rect; }
void fill_rect(Gfx::FloatRect, Gfx::Color);
void fill_rect(Gfx::IntRect, Gfx::Color);
enum class ScalingMode {
NearestNeighbor,
Bilinear,
};
enum class BlendingMode {
AlphaAdd,
AlphaOverride,
AlphaPreserve,
};
void draw_line(Gfx::IntPoint a, Gfx::IntPoint b, float thickness, Gfx::Color color);
void draw_line(Gfx::FloatPoint a, Gfx::FloatPoint b, float thickness, Gfx::Color color);
void draw_scaled_bitmap(Gfx::FloatRect const& dst_rect, Gfx::Bitmap const&, Gfx::FloatRect const& src_rect, ScalingMode = ScalingMode::NearestNeighbor);
void draw_scaled_bitmap(Gfx::IntRect const& dst_rect, Gfx::Bitmap const&, Gfx::IntRect const& src_rect, ScalingMode = ScalingMode::NearestNeighbor);
void draw_scaled_immutable_bitmap(Gfx::IntRect const& dst_rect, Gfx::ImmutableBitmap const&, Gfx::IntRect const& src_rect, ScalingMode = ScalingMode::NearestNeighbor);
void draw_scaled_immutable_bitmap(Gfx::FloatRect const& dst_rect, Gfx::ImmutableBitmap const&, Gfx::FloatRect const& src_rect, ScalingMode = ScalingMode::NearestNeighbor);
void draw_glyph_run(Span<Gfx::DrawGlyphOrEmoji const> glyph_run, Gfx::Font const&, Color const& color);
void set_clip_rect(Gfx::IntRect);
void clear_clip_rect();
void flush(Gfx::Bitmap&);
void fill_rect_with_linear_gradient(Gfx::IntRect const&, ReadonlySpan<Gfx::ColorStop>, float angle, Optional<float> repeat_length = {});
void fill_rect_with_linear_gradient(Gfx::FloatRect const&, ReadonlySpan<Gfx::ColorStop>, float angle, Optional<float> repeat_length = {});
struct CornerRadius {
float horizontal_radius;
float vertical_radius;
};
void fill_rect_with_rounded_corners(Gfx::IntRect const& rect, Color const& color, CornerRadius const& top_left_radius, CornerRadius const& top_right_radius, CornerRadius const& bottom_left_radius, CornerRadius const& bottom_right_radius, BlendingMode = BlendingMode::AlphaAdd);
void fill_rect_with_rounded_corners(Gfx::FloatRect const& rect, Color const& color, CornerRadius const& top_left_radius, CornerRadius const& top_right_radius, CornerRadius const& bottom_left_radius, CornerRadius const& bottom_right_radius, BlendingMode = BlendingMode::AlphaAdd);
void blit_canvas(Gfx::IntRect const& dst_rect, Canvas const&, float opacity = 1.0f, Optional<Gfx::AffineTransform> affine_transform = {});
void blit_canvas(Gfx::FloatRect const& dst_rect, Canvas const&, float opacity = 1.0f, Optional<Gfx::AffineTransform> affine_transform = {});
void blit_canvas(Gfx::FloatRect const& dst_rect, Canvas const&, Gfx::FloatRect const& src_rect, float opacity = 1.0f, Optional<Gfx::AffineTransform> affine_transform = {}, BlendingMode = BlendingMode::AlphaAdd);
enum class BlurDirection {
Horizontal,
Vertical,
};
void blit_blurred_canvas(Gfx::FloatRect const& dst_rect, Canvas const&, int radius, BlurDirection direction, ScalingMode = ScalingMode::NearestNeighbor);
void update_immutable_bitmap_texture_cache(HashMap<u32, Gfx::ImmutableBitmap const*>&);
private:
Context& m_context;
struct State {
Gfx::AffineTransform transform;
Gfx::IntRect clip_rect;
};
[[nodiscard]] State& state() { return m_state_stack.last(); }
[[nodiscard]] State const& state() const { return m_state_stack.last(); }
void blit_scaled_texture(Gfx::FloatRect const& dst_rect, GL::Texture const&, Gfx::FloatRect const& src_rect, ScalingMode, float opacity = 1.0f, Optional<Gfx::AffineTransform> affine_transform = {}, BlendingMode = BlendingMode::AlphaAdd);
void blit_blurred_texture(Gfx::FloatRect const& dst_rect, GL::Texture const&, Gfx::FloatRect const& src_rect, int radius, BlurDirection direction, ScalingMode = ScalingMode::NearestNeighbor);
void bind_target_canvas();
[[nodiscard]] Gfx::FloatRect to_clip_space(Gfx::FloatRect const& screen_rect) const;
Vector<State, 1> m_state_stack;
NonnullRefPtr<Canvas> m_target_canvas;
Program m_rectangle_program;
Program m_rounded_rectangle_program;
Program m_blit_program;
Program m_linear_gradient_program;
Program m_blur_program;
};
}

View file

@ -1,46 +0,0 @@
/*
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/Format.h>
#include <LibAccelGfx/GL.h>
#include <LibAccelGfx/Program.h>
namespace AccelGfx {
Optional<GL::Program> programs_cache[to_underlying(Program::Name::ProgramCount)];
Program Program::create(Name name, char const* vertex_shader_source, char const* fragment_shader_source)
{
if (programs_cache[to_underlying(name)].has_value()) {
return Program { *programs_cache[to_underlying(name)] };
}
auto vertex_shader = GL::create_shader(GL::ShaderType::Vertex, vertex_shader_source);
auto fragment_shader = GL::create_shader(GL::ShaderType::Fragment, fragment_shader_source);
auto program = GL::create_program(vertex_shader, fragment_shader);
programs_cache[to_underlying(name)] = program;
return Program { program };
}
void Program::use()
{
GL::use_program(m_program);
}
GL::VertexAttribute Program::get_attribute_location(char const* name)
{
return GL::get_attribute_location(m_program, name);
}
GL::Uniform Program::get_uniform_location(char const* name)
{
return GL::get_uniform_location(m_program, name);
}
}

View file

@ -1,43 +0,0 @@
/*
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Noncopyable.h>
#include <LibAccelGfx/GL.h>
namespace AccelGfx {
class Program {
AK_MAKE_NONCOPYABLE(Program);
public:
enum class Name {
RectangleProgram,
RoundedRectangleProgram,
BlitProgram,
LinearGradientProgram,
BlurProgram,
ProgramCount,
};
static Program create(Name name, char const* vertex_shader_source, char const* fragment_shader_source);
void use();
GL::VertexAttribute get_attribute_location(char const* name);
GL::Uniform get_uniform_location(char const* name);
private:
Program(GL::Program program)
: m_program(program)
{
}
GL::Program m_program;
};
}

View file

@ -1,5 +1,4 @@
include(libweb_generators)
include(accelerated_graphics)
set(SOURCES
Animations/Animatable.cpp
@ -759,13 +758,6 @@ serenity_lib(LibWeb web)
target_link_libraries(LibWeb PRIVATE LibCore LibCrypto LibJS LibHTTP LibGfx LibIPC LibRegex LibSyntax LibTextCodec LibUnicode LibAudio LibMedia LibWasm LibXML LibIDL LibURL LibTLS unofficial::skia::skia)
if (HAS_ACCELERATED_GRAPHICS)
target_link_libraries(LibWeb PRIVATE ${ACCEL_GFX_LIBS})
target_sources(LibWeb PRIVATE Painting/DisplayListPlayerGPU.cpp)
target_link_libraries(LibWeb PRIVATE LibAccelGfx)
target_compile_definitions(LibWeb PRIVATE HAS_ACCELERATED_GRAPHICS)
endif()
generate_js_bindings(LibWeb)
# Note: If you're looking for the calls to "libweb_js_bindings()",

View file

@ -20,10 +20,6 @@
#include <LibWeb/Painting/DisplayListPlayerCPU.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
#ifdef HAS_ACCELERATED_GRAPHICS
# include <LibWeb/Painting/DisplayListPlayerGPU.h>
#endif
namespace Web::HTML {
JS_DEFINE_ALLOCATOR(TraversableNavigable);
@ -1192,19 +1188,7 @@ void TraversableNavigable::paint(DevicePixelRect const& content_rect, Painting::
record_display_list(display_list_recorder, paint_config);
auto display_list_player_type = page().client().display_list_player_type();
if (display_list_player_type == DisplayListPlayerType::GPU) {
#ifdef HAS_ACCELERATED_GRAPHICS
Painting::DisplayListPlayerGPU player(*paint_options.accelerated_graphics_context, target.bitmap());
display_list.execute(player);
#else
static bool has_warned_about_configuration = false;
if (!has_warned_about_configuration) {
warnln("\033[31;1mConfigured to use GPU painter, but current platform does not have accelerated graphics\033[0m");
has_warned_about_configuration = true;
}
#endif
} else if (display_list_player_type == DisplayListPlayerType::Skia) {
if (display_list_player_type == DisplayListPlayerType::Skia) {
#ifdef AK_OS_MACOS
if (m_metal_context && m_skia_backend_context && is<Painting::IOSurfaceBackingStore>(target)) {
auto& iosurface_backing_store = static_cast<Painting::IOSurfaceBackingStore&>(target);

View file

@ -42,10 +42,6 @@
#include <LibWeb/PixelUnits.h>
#include <LibWeb/UIEvents/KeyCode.h>
#ifdef HAS_ACCELERATED_GRAPHICS
# include <LibAccelGfx/Context.h>
#endif
namespace Web {
class PageClient;
@ -278,15 +274,10 @@ struct PaintOptions {
PaintOverlay paint_overlay { PaintOverlay::Yes };
bool should_show_line_box_borders { false };
bool has_focus { false };
#ifdef HAS_ACCELERATED_GRAPHICS
AccelGfx::Context* accelerated_graphics_context { nullptr };
#endif
};
enum class DisplayListPlayerType {
CPU,
GPU,
Skia
};

View file

@ -1,397 +0,0 @@
/*
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibAccelGfx/GlyphAtlas.h>
#include <LibWeb/Painting/BorderRadiusCornerClipper.h>
#include <LibWeb/Painting/DisplayListPlayerGPU.h>
namespace Web::Painting {
DisplayListPlayerGPU::DisplayListPlayerGPU(AccelGfx::Context& context, Gfx::Bitmap& bitmap)
: m_target_bitmap(bitmap)
, m_context(context)
{
m_context.activate();
auto canvas = AccelGfx::Canvas::create(bitmap.size());
auto painter = AccelGfx::Painter::create(m_context, canvas);
m_stacking_contexts.append({ .canvas = canvas,
.painter = move(painter),
.opacity = 1.0f,
.destination = {},
.transform = {} });
}
DisplayListPlayerGPU::~DisplayListPlayerGPU()
{
m_context.activate();
VERIFY(m_stacking_contexts.size() == 1);
painter().flush(m_target_bitmap);
}
CommandResult DisplayListPlayerGPU::draw_glyph_run(DrawGlyphRun const& command)
{
Vector<Gfx::DrawGlyphOrEmoji> transformed_glyph_run;
auto const& glyphs = command.glyph_run->glyphs();
transformed_glyph_run.ensure_capacity(glyphs.size());
auto const& font = command.glyph_run->font();
auto scaled_font = font.with_size(font.point_size() * static_cast<float>(command.scale));
for (auto& glyph : glyphs) {
auto transformed_glyph = glyph;
transformed_glyph.visit([&](auto& glyph) {
glyph.position = glyph.position.scaled(command.scale).translated(command.translation);
});
transformed_glyph_run.append(transformed_glyph);
}
painter().draw_glyph_run(transformed_glyph_run, scaled_font, command.color);
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::fill_rect(FillRect const& command)
{
// FIXME: Support clip paths
painter().fill_rect(command.rect, command.color);
return CommandResult::Continue;
}
static AccelGfx::Painter::ScalingMode to_accelgfx_scaling_mode(Gfx::ScalingMode scaling_mode)
{
switch (scaling_mode) {
case Gfx::ScalingMode::NearestNeighbor:
case Gfx::ScalingMode::BoxSampling:
case Gfx::ScalingMode::SmoothPixels:
case Gfx::ScalingMode::None:
return AccelGfx::Painter::ScalingMode::NearestNeighbor;
case Gfx::ScalingMode::BilinearBlend:
return AccelGfx::Painter::ScalingMode::Bilinear;
default:
VERIFY_NOT_REACHED();
}
}
CommandResult DisplayListPlayerGPU::draw_scaled_bitmap(DrawScaledBitmap const& command)
{
painter().draw_scaled_bitmap(command.dst_rect, command.bitmap, command.src_rect, to_accelgfx_scaling_mode(command.scaling_mode));
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::draw_scaled_immutable_bitmap(DrawScaledImmutableBitmap const& command)
{
// TODO: Support clip paths
painter().draw_scaled_immutable_bitmap(command.dst_rect, command.bitmap, command.src_rect, to_accelgfx_scaling_mode(command.scaling_mode));
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::save(Save const&)
{
painter().save();
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::restore(Restore const&)
{
painter().restore();
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::add_clip_rect(AddClipRect const& command)
{
painter().set_clip_rect(command.rect);
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::push_stacking_context(PushStackingContext const& command)
{
if (command.source_paintable_rect.is_empty())
return CommandResult::SkipStackingContext;
m_stacking_contexts.last().stacking_context_depth++;
painter().save();
if (command.is_fixed_position) {
auto const& translation = painter().transform().translation();
painter().translate(-translation);
}
auto stacking_context_transform = Gfx::extract_2d_affine_transform(command.transform.matrix);
Gfx::AffineTransform inverse_origin_translation;
inverse_origin_translation.translate(-command.transform.origin);
Gfx::AffineTransform origin_translation;
origin_translation.translate(command.transform.origin);
Gfx::AffineTransform final_transform = origin_translation;
final_transform.multiply(stacking_context_transform);
final_transform.multiply(inverse_origin_translation);
if (command.opacity < 1 || !stacking_context_transform.is_identity_or_translation()) {
// If, due to layout mistakes, we encounter an excessively large rectangle here, it must be skipped to prevent
// framebuffer allocation failure.
if (command.source_paintable_rect.width() > 10000 || command.source_paintable_rect.height() > 10000) {
dbgln("FIXME: Skipping stacking context with excessively large paintable rect: {}", command.source_paintable_rect);
return CommandResult::SkipStackingContext;
}
auto canvas = AccelGfx::Canvas::create(command.source_paintable_rect.size());
auto painter = AccelGfx::Painter::create(m_context, canvas);
painter->translate(-command.source_paintable_rect.location().to_type<float>());
painter->clear(Color::Transparent);
m_stacking_contexts.append({ .canvas = canvas,
.painter = move(painter),
.opacity = command.opacity,
.destination = command.source_paintable_rect,
.transform = final_transform });
} else {
painter().translate(stacking_context_transform.translation() + command.post_transform_translation.to_type<float>());
m_stacking_contexts.append({ .canvas = {},
.painter = MaybeOwned(painter()),
.opacity = command.opacity,
.destination = {},
.transform = final_transform });
}
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::pop_stacking_context(PopStackingContext const&)
{
auto stacking_context = m_stacking_contexts.take_last();
VERIFY(stacking_context.stacking_context_depth == 0);
if (stacking_context.painter.is_owned()) {
painter().blit_canvas(stacking_context.destination, *stacking_context.canvas, stacking_context.opacity, stacking_context.transform);
}
painter().restore();
m_stacking_contexts.last().stacking_context_depth--;
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::paint_linear_gradient(PaintLinearGradient const& command)
{
// FIXME: Support clip paths
auto const& linear_gradient_data = command.linear_gradient_data;
painter().fill_rect_with_linear_gradient(command.gradient_rect, linear_gradient_data.color_stops.list, linear_gradient_data.gradient_angle, linear_gradient_data.color_stops.repeat_length);
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::paint_outer_box_shadow(PaintOuterBoxShadow const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::paint_inner_box_shadow(PaintInnerBoxShadow const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::paint_text_shadow(PaintTextShadow const& command)
{
auto text_shadow_canvas = AccelGfx::Canvas::create(command.shadow_bounding_rect.size());
auto text_shadow_painter = AccelGfx::Painter::create(m_context, text_shadow_canvas);
text_shadow_painter->clear(command.color.with_alpha(0));
Gfx::FloatRect const shadow_location { command.draw_location, command.shadow_bounding_rect.size() };
Gfx::IntPoint const baseline_start(command.text_rect.x(), command.text_rect.y());
text_shadow_painter->translate(baseline_start.to_type<float>());
text_shadow_painter->draw_glyph_run(command.glyph_run->glyphs(), command.glyph_run->font(), command.color);
if (command.blur_radius == 0) {
painter().blit_canvas(shadow_location, *text_shadow_canvas);
return CommandResult::Continue;
}
auto horizontal_blur_canvas = AccelGfx::Canvas::create(command.shadow_bounding_rect.size());
auto horizontal_blur_painter = AccelGfx::Painter::create(m_context, horizontal_blur_canvas);
horizontal_blur_painter->clear(command.color.with_alpha(0));
horizontal_blur_painter->blit_blurred_canvas(command.shadow_bounding_rect.to_type<float>(), *text_shadow_canvas, command.blur_radius, AccelGfx::Painter::BlurDirection::Horizontal);
painter().blit_blurred_canvas(shadow_location, *horizontal_blur_canvas, command.blur_radius, AccelGfx::Painter::BlurDirection::Vertical);
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::fill_rect_with_rounded_corners(FillRectWithRoundedCorners const& command)
{
// FIXME: Support clip paths
painter().fill_rect_with_rounded_corners(
command.rect, command.color,
{ static_cast<float>(command.corner_radii.top_left.horizontal_radius), static_cast<float>(command.corner_radii.top_left.vertical_radius) },
{ static_cast<float>(command.corner_radii.top_right.horizontal_radius), static_cast<float>(command.corner_radii.top_right.vertical_radius) },
{ static_cast<float>(command.corner_radii.bottom_left.horizontal_radius), static_cast<float>(command.corner_radii.bottom_left.vertical_radius) },
{ static_cast<float>(command.corner_radii.bottom_right.horizontal_radius), static_cast<float>(command.corner_radii.bottom_right.vertical_radius) });
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::fill_path_using_color(FillPathUsingColor const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::fill_path_using_paint_style(FillPathUsingPaintStyle const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::stroke_path_using_color(StrokePathUsingColor const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::stroke_path_using_paint_style(StrokePathUsingPaintStyle const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::draw_ellipse(DrawEllipse const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::fill_ellipse(FillEllipse const& command)
{
auto horizontal_radius = static_cast<float>(command.rect.width() / 2);
auto vertical_radius = static_cast<float>(command.rect.height() / 2);
painter().fill_rect_with_rounded_corners(
command.rect, command.color,
{ horizontal_radius, vertical_radius },
{ horizontal_radius, vertical_radius },
{ horizontal_radius, vertical_radius },
{ horizontal_radius, vertical_radius });
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::draw_line(DrawLine const& command)
{
// FIXME: Pass line style and alternate color once AccelGfx::Painter supports it
painter().draw_line(command.from, command.to, command.thickness, command.color);
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::apply_backdrop_filter(ApplyBackdropFilter const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::draw_rect(DrawRect const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::paint_radial_gradient(PaintRadialGradient const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::paint_conic_gradient(PaintConicGradient const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::draw_triangle_wave(DrawTriangleWave const&)
{
// FIXME
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::sample_under_corners(SampleUnderCorners const& command)
{
m_corner_clippers.resize(command.id + 1);
m_corner_clippers[command.id] = make<BorderRadiusCornerClipper>();
auto& corner_clipper = *m_corner_clippers[command.id];
auto const& top_left = command.corner_radii.top_left;
auto const& top_right = command.corner_radii.top_right;
auto const& bottom_right = command.corner_radii.bottom_right;
auto const& bottom_left = command.corner_radii.bottom_left;
auto sampling_config = calculate_border_radius_sampling_config(command.corner_radii, command.border_rect);
auto const& page_locations = sampling_config.page_locations;
auto const& bitmap_locations = sampling_config.bitmap_locations;
auto top_left_corner_size = Gfx::IntSize { top_left.horizontal_radius, top_left.vertical_radius };
auto top_right_corner_size = Gfx::IntSize { top_right.horizontal_radius, top_right.vertical_radius };
auto bottom_right_corner_size = Gfx::IntSize { bottom_right.horizontal_radius, bottom_right.vertical_radius };
auto bottom_left_corner_size = Gfx::IntSize { bottom_left.horizontal_radius, bottom_left.vertical_radius };
corner_clipper.page_top_left_rect = { page_locations.top_left, top_left_corner_size };
corner_clipper.page_top_right_rect = { page_locations.top_right, top_right_corner_size };
corner_clipper.page_bottom_right_rect = { page_locations.bottom_right, bottom_right_corner_size };
corner_clipper.page_bottom_left_rect = { page_locations.bottom_left, bottom_left_corner_size };
corner_clipper.sample_canvas_top_left_rect = { bitmap_locations.top_left, top_left_corner_size };
corner_clipper.sample_canvas_top_right_rect = { bitmap_locations.top_right, top_right_corner_size };
corner_clipper.sample_canvas_bottom_right_rect = { bitmap_locations.bottom_right, bottom_right_corner_size };
corner_clipper.sample_canvas_bottom_left_rect = { bitmap_locations.bottom_left, bottom_left_corner_size };
corner_clipper.corners_sample_canvas = AccelGfx::Canvas::create(sampling_config.corners_bitmap_size);
auto corner_painter = AccelGfx::Painter::create(m_context, *corner_clipper.corners_sample_canvas);
corner_painter->clear(Color::White);
corner_painter->fill_rect_with_rounded_corners(
Gfx::IntRect { { 0, 0 }, sampling_config.corners_bitmap_size },
Color::Transparent,
{ static_cast<float>(top_left.horizontal_radius), static_cast<float>(top_left.vertical_radius) },
{ static_cast<float>(top_right.horizontal_radius), static_cast<float>(top_right.vertical_radius) },
{ static_cast<float>(bottom_right.horizontal_radius), static_cast<float>(bottom_right.vertical_radius) },
{ static_cast<float>(bottom_left.horizontal_radius), static_cast<float>(bottom_left.vertical_radius) },
AccelGfx::Painter::BlendingMode::AlphaOverride);
auto const& target_canvas = painter().canvas();
if (!corner_clipper.sample_canvas_top_left_rect.is_empty())
corner_painter->blit_canvas(corner_clipper.sample_canvas_top_left_rect, target_canvas, painter().transform().map(corner_clipper.page_top_left_rect), 1.0f, {}, AccelGfx::Painter::BlendingMode::AlphaPreserve);
if (!corner_clipper.sample_canvas_top_right_rect.is_empty())
corner_painter->blit_canvas(corner_clipper.sample_canvas_top_right_rect, target_canvas, painter().transform().map(corner_clipper.page_top_right_rect), 1.0f, {}, AccelGfx::Painter::BlendingMode::AlphaPreserve);
if (!corner_clipper.sample_canvas_bottom_right_rect.is_empty())
corner_painter->blit_canvas(corner_clipper.sample_canvas_bottom_right_rect, target_canvas, painter().transform().map(corner_clipper.page_bottom_right_rect), 1.0f, {}, AccelGfx::Painter::BlendingMode::AlphaPreserve);
if (!corner_clipper.sample_canvas_bottom_left_rect.is_empty())
corner_painter->blit_canvas(corner_clipper.sample_canvas_bottom_left_rect, target_canvas, painter().transform().map(corner_clipper.page_bottom_left_rect), 1.0f, {}, AccelGfx::Painter::BlendingMode::AlphaPreserve);
return CommandResult::Continue;
}
CommandResult DisplayListPlayerGPU::blit_corner_clipping(BlitCornerClipping const& command)
{
auto const& corner_clipper = *m_corner_clippers[command.id];
auto const& corner_sample_canvas = *corner_clipper.corners_sample_canvas;
if (!corner_clipper.sample_canvas_top_left_rect.is_empty())
painter().blit_canvas(corner_clipper.page_top_left_rect, corner_sample_canvas, corner_clipper.sample_canvas_top_left_rect);
if (!corner_clipper.sample_canvas_top_right_rect.is_empty())
painter().blit_canvas(corner_clipper.page_top_right_rect, corner_sample_canvas, corner_clipper.sample_canvas_top_right_rect);
if (!corner_clipper.sample_canvas_bottom_right_rect.is_empty())
painter().blit_canvas(corner_clipper.page_bottom_right_rect, corner_sample_canvas, corner_clipper.sample_canvas_bottom_right_rect);
if (!corner_clipper.sample_canvas_bottom_left_rect.is_empty())
painter().blit_canvas(corner_clipper.page_bottom_left_rect, corner_sample_canvas, corner_clipper.sample_canvas_bottom_left_rect);
m_corner_clippers[command.id].clear();
return CommandResult::Continue;
}
bool DisplayListPlayerGPU::would_be_fully_clipped_by_painter(Gfx::IntRect rect) const
{
auto translation = painter().transform().translation().to_type<int>();
return !painter().clip_rect().intersects(rect.translated(translation));
}
void DisplayListPlayerGPU::prepare_glyph_texture(HashMap<Gfx::Font const*, HashTable<u32>> const& unique_glyphs)
{
AccelGfx::GlyphAtlas::the().update(unique_glyphs);
}
void DisplayListPlayerGPU::prepare_to_execute([[maybe_unused]] size_t corner_clip_max_depth)
{
m_context.activate();
}
void DisplayListPlayerGPU::update_immutable_bitmap_texture_cache(HashMap<u32, Gfx::ImmutableBitmap const*>& immutable_bitmaps)
{
painter().update_immutable_bitmap_texture_cache(immutable_bitmaps);
}
}

View file

@ -1,93 +0,0 @@
/*
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/MaybeOwned.h>
#include <LibAccelGfx/Painter.h>
#include <LibWeb/Painting/DisplayListRecorder.h>
namespace Web::Painting {
class DisplayListPlayerGPU : public DisplayListPlayer {
public:
CommandResult draw_glyph_run(DrawGlyphRun const&) override;
CommandResult fill_rect(FillRect const&) override;
CommandResult draw_scaled_bitmap(DrawScaledBitmap const&) override;
CommandResult draw_scaled_immutable_bitmap(DrawScaledImmutableBitmap const&) override;
CommandResult save(Save const&) override;
CommandResult restore(Restore const&) override;
CommandResult add_clip_rect(AddClipRect const&) override;
CommandResult push_stacking_context(PushStackingContext const&) override;
CommandResult pop_stacking_context(PopStackingContext const&) override;
CommandResult paint_linear_gradient(PaintLinearGradient const&) override;
CommandResult paint_outer_box_shadow(PaintOuterBoxShadow const&) override;
CommandResult paint_inner_box_shadow(PaintInnerBoxShadow const&) override;
CommandResult paint_text_shadow(PaintTextShadow const&) override;
CommandResult fill_rect_with_rounded_corners(FillRectWithRoundedCorners const&) override;
CommandResult fill_path_using_color(FillPathUsingColor const&) override;
CommandResult fill_path_using_paint_style(FillPathUsingPaintStyle const&) override;
CommandResult stroke_path_using_color(StrokePathUsingColor const&) override;
CommandResult stroke_path_using_paint_style(StrokePathUsingPaintStyle const&) override;
CommandResult draw_ellipse(DrawEllipse const&) override;
CommandResult fill_ellipse(FillEllipse const&) override;
CommandResult draw_line(DrawLine const&) override;
CommandResult apply_backdrop_filter(ApplyBackdropFilter const&) override;
CommandResult draw_rect(DrawRect const&) override;
CommandResult paint_radial_gradient(PaintRadialGradient const&) override;
CommandResult paint_conic_gradient(PaintConicGradient const&) override;
CommandResult draw_triangle_wave(DrawTriangleWave const&) override;
CommandResult sample_under_corners(SampleUnderCorners const&) override;
CommandResult blit_corner_clipping(BlitCornerClipping const&) override;
bool would_be_fully_clipped_by_painter(Gfx::IntRect) const override;
virtual bool needs_prepare_glyphs_texture() const override { return true; }
void prepare_glyph_texture(HashMap<Gfx::Font const*, HashTable<u32>> const&) override;
virtual void prepare_to_execute(size_t corner_clip_max_depth) override;
bool needs_update_immutable_bitmap_texture_cache() const override { return true; }
void update_immutable_bitmap_texture_cache(HashMap<u32, Gfx::ImmutableBitmap const*>&) override;
DisplayListPlayerGPU(AccelGfx::Context&, Gfx::Bitmap& bitmap);
~DisplayListPlayerGPU() override;
private:
Gfx::Bitmap& m_target_bitmap;
AccelGfx::Context& m_context;
struct StackingContext {
RefPtr<AccelGfx::Canvas> canvas;
MaybeOwned<AccelGfx::Painter> painter;
float opacity;
Gfx::IntRect destination;
Gfx::AffineTransform transform;
int stacking_context_depth { 0 };
};
struct BorderRadiusCornerClipper {
RefPtr<AccelGfx::Canvas> corners_sample_canvas;
Gfx::FloatRect page_top_left_rect;
Gfx::FloatRect page_top_right_rect;
Gfx::FloatRect page_bottom_right_rect;
Gfx::FloatRect page_bottom_left_rect;
Gfx::FloatRect sample_canvas_top_left_rect;
Gfx::FloatRect sample_canvas_top_right_rect;
Gfx::FloatRect sample_canvas_bottom_right_rect;
Gfx::FloatRect sample_canvas_bottom_left_rect;
};
[[nodiscard]] AccelGfx::Painter const& painter() const { return *m_stacking_contexts.last().painter; }
[[nodiscard]] AccelGfx::Painter& painter() { return *m_stacking_contexts.last().painter; }
Vector<StackingContext> m_stacking_contexts;
Vector<OwnPtr<BorderRadiusCornerClipper>> m_corner_clippers;
};
}

View file

@ -100,8 +100,7 @@ RefPtr<Gfx::Bitmap> SVGDecodedImageData::render(Gfx::IntSize size) const
auto painting_command_executor_type = m_page_client->display_list_player_type();
switch (painting_command_executor_type) {
case DisplayListPlayerType::CPU:
case DisplayListPlayerType::GPU: { // GPU painter does not have any path rasterization support so we always fall back to CPU painter
case DisplayListPlayerType::CPU: {
Painting::DisplayListPlayerCPU executor { *bitmap };
display_list.execute(executor);
break;

View file

@ -8,222 +8,16 @@
#include <LibGfx/Bitmap.h>
#include <LibWeb/WebGL/OpenGLContext.h>
#ifdef HAS_ACCELERATED_GRAPHICS
# include <LibAccelGfx/Canvas.h>
# include <LibAccelGfx/Context.h>
#endif
namespace Web::WebGL {
#ifdef HAS_ACCELERATED_GRAPHICS
class AccelGfxContext : public OpenGLContext {
public:
void activate()
{
m_context->activate();
}
virtual void present(Gfx::Bitmap& bitmap) override
{
VERIFY(bitmap.format() == Gfx::BitmapFormat::BGRA8888);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, bitmap.width(), bitmap.height(), GL_BGRA, GL_UNSIGNED_BYTE, bitmap.scanline(0));
}
virtual GLenum gl_get_error() override
{
activate();
return glGetError();
}
virtual void gl_get_doublev(GLenum pname, GLdouble* params) override
{
activate();
glGetDoublev(pname, params);
}
virtual void gl_get_integerv(GLenum pname, GLint* params) override
{
activate();
glGetIntegerv(pname, params);
}
virtual void gl_clear(GLbitfield mask) override
{
activate();
glClear(mask);
}
virtual void gl_clear_color(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) override
{
activate();
glClearColor(red, green, blue, alpha);
}
virtual void gl_clear_depth(GLdouble depth) override
{
activate();
glClearDepth(depth);
}
virtual void gl_clear_stencil(GLint s) override
{
activate();
glClearStencil(s);
}
virtual void gl_active_texture(GLenum texture) override
{
activate();
glActiveTexture(texture);
}
virtual void gl_viewport(GLint x, GLint y, GLsizei width, GLsizei height) override
{
activate();
glViewport(x, y, width, height);
}
virtual void gl_line_width(GLfloat width) override
{
activate();
glLineWidth(width);
}
virtual void gl_polygon_offset(GLfloat factor, GLfloat units) override
{
activate();
glPolygonOffset(factor, units);
}
virtual void gl_scissor(GLint x, GLint y, GLsizei width, GLsizei height) override
{
activate();
glScissor(x, y, width, height);
}
virtual void gl_depth_mask(GLboolean mask) override
{
activate();
glDepthMask(mask);
}
virtual void gl_depth_func(GLenum func) override
{
activate();
glDepthFunc(func);
}
virtual void gl_depth_range(GLdouble z_near, GLdouble z_far) override
{
activate();
glDepthRange(z_near, z_far);
}
virtual void gl_cull_face(GLenum mode) override
{
activate();
glCullFace(mode);
}
virtual void gl_color_mask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) override
{
activate();
glColorMask(red, green, blue, alpha);
}
virtual void gl_front_face(GLenum mode) override
{
activate();
glFrontFace(mode);
}
virtual void gl_finish() override
{
activate();
glFinish();
}
virtual void gl_flush() override
{
activate();
glFlush();
}
virtual void gl_stencil_op_separate(GLenum, GLenum, GLenum, GLenum) override
{
TODO();
}
AccelGfxContext(NonnullOwnPtr<AccelGfx::Context> context, NonnullRefPtr<AccelGfx::Canvas> canvas)
: m_context(move(context))
, m_canvas(move(canvas))
{
}
~AccelGfxContext()
{
activate();
}
private:
NonnullOwnPtr<AccelGfx::Context> m_context;
NonnullRefPtr<AccelGfx::Canvas> m_canvas;
};
#endif
#ifdef HAS_ACCELERATED_GRAPHICS
static OwnPtr<AccelGfxContext> make_accelgfx_context(Gfx::Bitmap& bitmap)
{
auto context = AccelGfx::Context::create();
if (context.is_error()) {
dbgln("Failed to create AccelGfx context: {}", context.error().string_literal());
return {};
}
auto canvas = AccelGfx::Canvas::create(bitmap.size());
canvas->bind();
return make<AccelGfxContext>(context.release_value(), move(canvas));
}
#endif
OwnPtr<OpenGLContext> OpenGLContext::create(Gfx::Bitmap& bitmap)
{
#ifdef HAS_ACCELERATED_GRAPHICS
return make_accelgfx_context(bitmap);
#endif
(void)bitmap;
return {};
}
void OpenGLContext::clear_buffer_to_default_values()
{
#if defined(HAS_ACCELERATED_GRAPHICS)
Array<GLdouble, 4> current_clear_color;
gl_get_doublev(GL_COLOR_CLEAR_VALUE, current_clear_color.data());
GLdouble current_clear_depth;
gl_get_doublev(GL_DEPTH_CLEAR_VALUE, &current_clear_depth);
GLint current_clear_stencil;
gl_get_integerv(GL_STENCIL_CLEAR_VALUE, &current_clear_stencil);
// The implicit clear value for the color buffer is (0, 0, 0, 0)
gl_clear_color(0, 0, 0, 0);
// The implicit clear value for the depth buffer is 1.0.
gl_clear_depth(1.0);
// The implicit clear value for the stencil buffer is 0.
gl_clear_stencil(0);
gl_clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// Restore the clear values.
gl_clear_color(current_clear_color[0], current_clear_color[1], current_clear_color[2], current_clear_color[3]);
gl_clear_depth(current_clear_depth);
gl_clear_stencil(current_clear_stencil);
#endif
}
}

View file

@ -1,5 +1,3 @@
include(accelerated_graphics)
compile_ipc(WebContentServer.ipc WebContentServerEndpoint.h)
compile_ipc(WebContentClient.ipc WebContentClientEndpoint.h)
@ -26,8 +24,3 @@ set(GENERATED_SOURCES
serenity_bin(WebContent)
target_link_libraries(WebContent PRIVATE LibCore LibFileSystem LibIPC LibGfx LibAudio LibImageDecoderClient LibJS LibWebView LibWeb LibUnicode LibMain LibURL)
if (HAS_ACCELERATED_GRAPHICS)
target_compile_definitions(WebContent PRIVATE HAS_ACCELERATED_GRAPHICS)
target_link_libraries(WebContent PRIVATE LibAccelGfx)
endif()

View file

@ -25,22 +25,12 @@
#include <WebContent/WebContentClientEndpoint.h>
#include <WebContent/WebDriverConnection.h>
#ifdef HAS_ACCELERATED_GRAPHICS
# include <LibWeb/Painting/DisplayListPlayerGPU.h>
#endif
namespace WebContent {
static bool s_use_gpu_painter = false;
static bool s_use_skia_painter = false;
JS_DEFINE_ALLOCATOR(PageClient);
void PageClient::set_use_gpu_painter()
{
s_use_gpu_painter = true;
}
void PageClient::set_use_skia_painter()
{
s_use_skia_painter = true;
@ -58,17 +48,6 @@ PageClient::PageClient(PageHost& owner, u64 id)
, m_backing_store_manager(*this)
{
setup_palette();
#ifdef HAS_ACCELERATED_GRAPHICS
if (s_use_gpu_painter) {
auto context = AccelGfx::Context::create();
if (context.is_error()) {
dbgln("Failed to create AccelGfx context: {}", context.error());
VERIFY_NOT_REACHED();
}
m_accelerated_graphics_context = context.release_value();
}
#endif
}
PageClient::~PageClient() = default;
@ -233,9 +212,6 @@ void PageClient::paint(Web::DevicePixelRect const& content_rect, Web::Painting::
{
paint_options.should_show_line_box_borders = m_should_show_line_box_borders;
paint_options.has_focus = m_has_focus;
#ifdef HAS_ACCELERATED_GRAPHICS
paint_options.accelerated_graphics_context = m_accelerated_graphics_context.ptr();
#endif
page().top_level_traversable()->paint(content_rect, target, paint_options);
}
@ -756,8 +732,6 @@ void PageClient::did_get_js_console_messages(i32 start_index, Vector<ByteString>
Web::DisplayListPlayerType PageClient::display_list_player_type() const
{
if (s_use_gpu_painter)
return Web::DisplayListPlayerType::GPU;
if (s_use_skia_painter)
return Web::DisplayListPlayerType::Skia;
return Web::DisplayListPlayerType::CPU;

View file

@ -8,7 +8,6 @@
#pragma once
#include <LibAccelGfx/Forward.h>
#include <LibGfx/Rect.h>
#include <LibWeb/HTML/AudioPlayState.h>
#include <LibWeb/HTML/FileFilter.h>
@ -17,10 +16,6 @@
#include <WebContent/BackingStoreManager.h>
#include <WebContent/Forward.h>
#ifdef HAS_ACCELERATED_GRAPHICS
# include <LibAccelGfx/Context.h>
#endif
namespace WebContent {
class PageClient final : public Web::PageClient {
@ -32,7 +27,6 @@ public:
virtual ~PageClient() override;
static void set_use_gpu_painter();
static void set_use_skia_painter();
virtual void schedule_repaint() override;
@ -204,10 +198,6 @@ private:
RefPtr<WebDriverConnection> m_webdriver;
#ifdef HAS_ACCELERATED_GRAPHICS
OwnPtr<AccelGfx::Context> m_accelerated_graphics_context;
#endif
BackingStoreManager m_backing_store_manager;
// NOTE: These documents are not visited, but manually removed from the map on document finalization.