mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 20:15:17 +00:00
LibAccelGfx+Meta: Introduce OpenGL painting library
This change introduces a new 2D graphics library that uses OpenGL to perform painting operations. For now, it has extremely limited functionality and supports only rectangle painting, but we have to start somewhere. Since this library is intended to be used by LibWeb, where the WebContent process does not have an associated window, painting occurs in an offscreen buffer created using EGL. For now it is only possible to compile this library on linux. Offscreen context creation on SerenityOS and MacOS will have to be implemented separately in the future. Co-Authored-By: Andreas Kling <awesomekling@gmail.com>
This commit is contained in:
parent
56e8f52cb3
commit
95c154d9bd
Notes:
sideshowbarker
2024-07-17 18:38:54 +09:00
Author: https://github.com/kalenikaliaksandr Commit: https://github.com/SerenityOS/serenity/commit/95c154d9bd Pull-request: https://github.com/SerenityOS/serenity/pull/21662
11 changed files with 463 additions and 2 deletions
2
.github/workflows/cmake.yml
vendored
2
.github/workflows/cmake.yml
vendored
|
@ -59,7 +59,7 @@ jobs:
|
|||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-16 main'
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang-format-16 ccache e2fsprogs gcc-12 g++-12 libstdc++-12-dev libmpfr-dev libmpc-dev ninja-build optipng qemu-utils qemu-system-i386 unzip generate-ninja
|
||||
sudo apt-get install -y clang-format-16 ccache e2fsprogs gcc-12 g++-12 libstdc++-12-dev libmpfr-dev libmpc-dev ninja-build optipng qemu-utils qemu-system-i386 unzip generate-ninja libegl1-mesa-dev
|
||||
if ${{ matrix.arch == 'aarch64' }}; then
|
||||
# FIXME: Remove this when we no longer build our own Qemu binary.
|
||||
sudo apt-get install libgtk-3-dev libpixman-1-dev libsdl2-dev libslirp-dev
|
||||
|
|
|
@ -21,7 +21,7 @@ steps:
|
|||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main'
|
||||
sudo apt-get update
|
||||
sudo apt-get install ccache gcc-12 g++-12 clang-15 libstdc++-12-dev ninja-build unzip qt6-base-dev qt6-tools-dev-tools libqt6svg6-dev qt6-multimedia-dev libgl1-mesa-dev libpulse-dev libssl-dev
|
||||
sudo apt-get install ccache gcc-12 g++-12 clang-15 libstdc++-12-dev ninja-build unzip qt6-base-dev qt6-tools-dev-tools libqt6svg6-dev qt6-multimedia-dev libgl1-mesa-dev libpulse-dev libssl-dev libegl1-mesa-dev
|
||||
|
||||
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-15 100
|
||||
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-15 100
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
add_subdirectory(LibAccelGfx)
|
||||
add_subdirectory(LibArchive)
|
||||
add_subdirectory(LibAudio)
|
||||
add_subdirectory(LibC)
|
||||
|
|
10
Userland/Libraries/LibAccelGfx/CMakeLists.txt
Normal file
10
Userland/Libraries/LibAccelGfx/CMakeLists.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
if (LINUX)
|
||||
set(SOURCES
|
||||
Canvas.cpp
|
||||
Context.cpp
|
||||
Painter.cpp
|
||||
)
|
||||
|
||||
serenity_lib(LibAccelGfx accelgfx)
|
||||
target_link_libraries(LibAccelGfx PRIVATE LibGfx GL EGL)
|
||||
endif()
|
45
Userland/Libraries/LibAccelGfx/Canvas.cpp
Normal file
45
Userland/Libraries/LibAccelGfx/Canvas.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "Canvas.h"
|
||||
#include <GL/gl.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
|
||||
namespace AccelGfx {
|
||||
|
||||
Canvas Canvas::create(Context& context, NonnullRefPtr<Gfx::Bitmap> bitmap)
|
||||
{
|
||||
VERIFY(bitmap->format() == Gfx::BitmapFormat::BGRA8888);
|
||||
Canvas canvas { move(bitmap), context };
|
||||
canvas.initialize();
|
||||
return canvas;
|
||||
}
|
||||
|
||||
Canvas::Canvas(NonnullRefPtr<Gfx::Bitmap> bitmap, Context& context)
|
||||
: m_bitmap(move(bitmap))
|
||||
, m_context(context)
|
||||
{
|
||||
}
|
||||
|
||||
void Canvas::initialize()
|
||||
{
|
||||
m_surface = m_context.create_surface(width(), height());
|
||||
m_context.set_active_surface(m_surface);
|
||||
glViewport(0, 0, width(), height());
|
||||
}
|
||||
|
||||
void Canvas::flush()
|
||||
{
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
glReadPixels(0, 0, width(), height(), GL_BGRA, GL_UNSIGNED_BYTE, m_bitmap->scanline(0));
|
||||
}
|
||||
|
||||
Canvas::~Canvas()
|
||||
{
|
||||
m_context.destroy_surface(m_surface);
|
||||
}
|
||||
|
||||
}
|
41
Userland/Libraries/LibAccelGfx/Canvas.h
Normal file
41
Userland/Libraries/LibAccelGfx/Canvas.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibAccelGfx/Context.h>
|
||||
#include <LibAccelGfx/Forward.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
|
||||
namespace AccelGfx {
|
||||
|
||||
class Canvas {
|
||||
public:
|
||||
static Canvas create(Context& context, NonnullRefPtr<Gfx::Bitmap> bitmap);
|
||||
|
||||
[[nodiscard]] Gfx::IntSize size() const { return m_bitmap->size(); }
|
||||
[[nodiscard]] int width() const { return m_bitmap->width(); }
|
||||
[[nodiscard]] int height() const { return m_bitmap->height(); }
|
||||
|
||||
void flush();
|
||||
|
||||
[[nodiscard]] Gfx::Bitmap const& bitmap() const { return *m_bitmap; }
|
||||
|
||||
~Canvas();
|
||||
|
||||
private:
|
||||
explicit Canvas(NonnullRefPtr<Gfx::Bitmap>, Context&);
|
||||
|
||||
void initialize();
|
||||
|
||||
NonnullRefPtr<Gfx::Bitmap> m_bitmap;
|
||||
|
||||
Context& m_context;
|
||||
Context::Surface m_surface;
|
||||
};
|
||||
|
||||
}
|
75
Userland/Libraries/LibAccelGfx/Context.cpp
Normal file
75
Userland/Libraries/LibAccelGfx/Context.cpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibAccelGfx/Context.h>
|
||||
|
||||
namespace AccelGfx {
|
||||
|
||||
Context& Context::the()
|
||||
{
|
||||
static OwnPtr<Context> s_the;
|
||||
if (!s_the)
|
||||
s_the = Context::create();
|
||||
return *s_the;
|
||||
}
|
||||
|
||||
Context::Surface Context::create_surface(int width, int height)
|
||||
{
|
||||
EGLint const pbuffer_attributes[] = {
|
||||
EGL_WIDTH,
|
||||
width,
|
||||
EGL_HEIGHT,
|
||||
height,
|
||||
EGL_NONE,
|
||||
};
|
||||
|
||||
auto egl_surface = eglCreatePbufferSurface(m_egl_display, m_egl_config, pbuffer_attributes);
|
||||
return { egl_surface };
|
||||
}
|
||||
|
||||
void Context::destroy_surface(Surface surface)
|
||||
{
|
||||
if (surface.egl_surface)
|
||||
eglDestroySurface(m_egl_display, surface.egl_surface);
|
||||
}
|
||||
|
||||
void Context::set_active_surface(Surface surface)
|
||||
{
|
||||
VERIFY(eglMakeCurrent(m_egl_display, surface.egl_surface, surface.egl_surface, m_egl_context));
|
||||
}
|
||||
|
||||
OwnPtr<Context> Context::create()
|
||||
{
|
||||
EGLDisplay egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
|
||||
EGLint major;
|
||||
EGLint minor;
|
||||
eglInitialize(egl_display, &major, &minor);
|
||||
|
||||
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_CLIENT_VERSION, 2,
|
||||
EGL_NONE
|
||||
};
|
||||
EGLContext egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, context_attributes);
|
||||
|
||||
return make<Context>(egl_display, egl_context, egl_config);
|
||||
}
|
||||
|
||||
}
|
45
Userland/Libraries/LibAccelGfx/Context.h
Normal file
45
Userland/Libraries/LibAccelGfx/Context.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
#ifdef AK_OS_LINUX
|
||||
# include <EGL/egl.h>
|
||||
#endif
|
||||
|
||||
namespace AccelGfx {
|
||||
|
||||
class Context {
|
||||
public:
|
||||
static Context& the();
|
||||
|
||||
struct Surface {
|
||||
EGLSurface egl_surface { 0 };
|
||||
};
|
||||
|
||||
Surface create_surface(int width, int height);
|
||||
void destroy_surface(Surface surface);
|
||||
void set_active_surface(Surface surface);
|
||||
|
||||
static OwnPtr<Context> create();
|
||||
|
||||
Context(EGLDisplay egl_display, EGLContext egl_context, EGLConfig egl_config)
|
||||
: m_egl_display(egl_display)
|
||||
, m_egl_context(egl_context)
|
||||
, m_egl_config(egl_config)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
EGLDisplay m_egl_display { nullptr };
|
||||
EGLContext m_egl_context { nullptr };
|
||||
EGLConfig m_egl_config { nullptr };
|
||||
};
|
||||
|
||||
}
|
14
Userland/Libraries/LibAccelGfx/Forward.h
Normal file
14
Userland/Libraries/LibAccelGfx/Forward.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace AccelGfx {
|
||||
|
||||
class Canvas;
|
||||
class Painter;
|
||||
|
||||
}
|
177
Userland/Libraries/LibAccelGfx/Painter.cpp
Normal file
177
Userland/Libraries/LibAccelGfx/Painter.cpp
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
|
||||
#include "Painter.h"
|
||||
#include "Canvas.h"
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
#include <LibGfx/Color.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_canvas.width() - 1.0f;
|
||||
float y = -1.0f + 2.0f * screen_rect.y() / m_canvas.height();
|
||||
|
||||
float width = 2.0f * screen_rect.width() / m_canvas.width();
|
||||
float height = 2.0f * screen_rect.height() / m_canvas.height();
|
||||
|
||||
return { x, y, width, height };
|
||||
}
|
||||
|
||||
Painter::Painter(Canvas& canvas)
|
||||
: m_canvas(canvas)
|
||||
{
|
||||
m_state_stack.empend(State());
|
||||
}
|
||||
|
||||
Painter::~Painter()
|
||||
{
|
||||
flush();
|
||||
}
|
||||
|
||||
void Painter::clear(Gfx::Color color)
|
||||
{
|
||||
auto [red, green, blue, alpha] = gfx_color_to_opengl_color(color);
|
||||
glClearColor(red, green, blue, alpha);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void Painter::fill_rect(Gfx::IntRect rect, Gfx::Color color)
|
||||
{
|
||||
fill_rect(rect.to_type<float>(), color);
|
||||
}
|
||||
|
||||
static GLuint create_shader(GLenum type, char const* source)
|
||||
{
|
||||
GLuint shader = glCreateShader(type);
|
||||
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();
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// 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)));
|
||||
|
||||
char const* vertex_shader_source = R"(
|
||||
attribute vec2 position;
|
||||
void main() {
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
char const* fragment_shader_source = R"(
|
||||
precision mediump float;
|
||||
uniform vec4 uColor;
|
||||
void main() {
|
||||
gl_FragColor = uColor;
|
||||
}
|
||||
)";
|
||||
|
||||
auto [red, green, blue, alpha] = gfx_color_to_opengl_color(color);
|
||||
|
||||
GLuint vertex_shader = create_shader(GL_VERTEX_SHADER, vertex_shader_source);
|
||||
GLuint fragment_shader = create_shader(GL_FRAGMENT_SHADER, fragment_shader_source);
|
||||
|
||||
GLuint program = glCreateProgram();
|
||||
|
||||
glAttachShader(program, vertex_shader);
|
||||
glAttachShader(program, fragment_shader);
|
||||
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();
|
||||
}
|
||||
|
||||
glUseProgram(program);
|
||||
|
||||
GLuint position_attribute = glGetAttribLocation(program, "position");
|
||||
GLuint color_uniform = glGetUniformLocation(program, "uColor");
|
||||
|
||||
glUniform4f(color_uniform, red, green, blue, alpha);
|
||||
|
||||
glVertexAttribPointer(position_attribute, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), vertices.data());
|
||||
glEnableVertexAttribArray(position_attribute);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
|
||||
glDeleteShader(vertex_shader);
|
||||
glDeleteShader(fragment_shader);
|
||||
glDeleteProgram(program);
|
||||
}
|
||||
|
||||
void Painter::save()
|
||||
{
|
||||
m_state_stack.append(state());
|
||||
}
|
||||
|
||||
void Painter::restore()
|
||||
{
|
||||
VERIFY(!m_state_stack.is_empty());
|
||||
m_state_stack.take_last();
|
||||
}
|
||||
|
||||
void Painter::flush()
|
||||
{
|
||||
m_canvas.flush();
|
||||
}
|
||||
|
||||
}
|
53
Userland/Libraries/LibAccelGfx/Painter.h
Normal file
53
Userland/Libraries/LibAccelGfx/Painter.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibAccelGfx/Forward.h>
|
||||
#include <LibGfx/AffineTransform.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
|
||||
namespace AccelGfx {
|
||||
|
||||
class Painter {
|
||||
AK_MAKE_NONCOPYABLE(Painter);
|
||||
AK_MAKE_NONMOVABLE(Painter);
|
||||
|
||||
public:
|
||||
Painter(Canvas&);
|
||||
~Painter();
|
||||
|
||||
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 fill_rect(Gfx::FloatRect, Gfx::Color);
|
||||
void fill_rect(Gfx::IntRect, Gfx::Color);
|
||||
|
||||
private:
|
||||
void flush();
|
||||
|
||||
Canvas& m_canvas;
|
||||
|
||||
struct State {
|
||||
Gfx::AffineTransform transform;
|
||||
};
|
||||
|
||||
[[nodiscard]] State& state() { return m_state_stack.last(); }
|
||||
[[nodiscard]] State const& state() const { return m_state_stack.last(); }
|
||||
|
||||
[[nodiscard]] Gfx::FloatRect to_clip_space(Gfx::FloatRect const& screen_rect) const;
|
||||
|
||||
Vector<State, 1> m_state_stack;
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue