ladybird/Libraries/LibWeb/WebGL/OpenGLContext.cpp
Erik Kurzinger 8fd1df4f8b
Some checks are pending
CI / macOS, arm64, Sanitizer, Clang (push) Waiting to run
CI / Linux, x86_64, Fuzzers, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer, Clang (push) Waiting to run
Package the js repl as a binary artifact / Linux, arm64 (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run
LibGfx: Enable WebGL on Linux
This enabled WebGL on Linux. It uses ANGLE's OpenGL backend running atop
EGL_PLATFORM_SURFACELESS_MESA. Eventually we should probably switch to
the Vulkan backend but that doesn't seem to be enabled in the vcpkg
angle package. Anyway, switching later should be trivial.

The painting surface is allocated through Vulkan and then imported into
EGL as a dma-buf. The DRM format modifier mechanism, along with Vulkan
initializing the image with VK_IMAGE_LAYOUT_GENERAL, should ensure
surface compatibility across the two APIs.

For now, we will synchronize rendering and presentation using glFinish,
although this is admittedly suboptimal. Really we should grab an
EGLSync, export that to an fd, import it into Skia and have it wait for
it before reading from the image. That can be implemented in a future
change, though.
2025-08-19 00:30:22 +02:00

534 lines
22 KiB
C++

/*
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/HashMap.h>
#include <AK/OwnPtr.h>
#include <AK/String.h>
#include <LibGfx/PaintingSurface.h>
#include <LibWeb/WebGL/OpenGLContext.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#define EGL_EGLEXT_PROTOTYPES 1
extern "C" {
#include <EGL/eglext_angle.h>
}
#define GL_GLEXT_PROTOTYPES 1
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
extern "C" {
#include <GLES2/gl2ext_angle.h>
}
namespace Web::WebGL {
struct OpenGLContext::Impl {
EGLDisplay display { nullptr };
EGLConfig config { nullptr };
EGLContext context { nullptr };
EGLSurface surface { nullptr };
GLuint framebuffer { 0 };
GLuint color_buffer { 0 };
GLuint depth_buffer { 0 };
#ifdef USE_VULKAN
EGLImage egl_image { EGL_NO_IMAGE };
struct {
PFNEGLQUERYDMABUFFORMATSEXTPROC query_dma_buf_formats { nullptr };
PFNEGLQUERYDMABUFMODIFIERSEXTPROC query_dma_buf_modifiers { nullptr };
} ext_procs;
#endif
};
OpenGLContext::OpenGLContext(NonnullRefPtr<Gfx::SkiaBackendContext> skia_backend_context, Impl impl, WebGLVersion webgl_version)
: m_skia_backend_context(move(skia_backend_context))
, m_impl(make<Impl>(impl))
, m_webgl_version(webgl_version)
{
}
OpenGLContext::~OpenGLContext()
{
free_surface_resources();
eglMakeCurrent(m_impl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(m_impl->display, m_impl->context);
}
void OpenGLContext::free_surface_resources()
{
if (m_impl->framebuffer) {
glDeleteFramebuffers(1, &m_impl->framebuffer);
m_impl->framebuffer = 0;
}
if (m_impl->color_buffer) {
glDeleteTextures(1, &m_impl->color_buffer);
m_impl->color_buffer = 0;
}
if (m_impl->depth_buffer) {
glDeleteRenderbuffers(1, &m_impl->depth_buffer);
m_impl->depth_buffer = 0;
}
#ifdef USE_VULKAN
if (m_impl->egl_image != EGL_NO_IMAGE) {
eglDestroyImage(m_impl->display, m_impl->egl_image);
m_impl->egl_image = EGL_NO_IMAGE;
}
#endif
if (m_impl->surface != EGL_NO_SURFACE) {
#ifdef AK_OS_MACOS
eglReleaseTexImage(m_impl->display, m_impl->surface, EGL_BACK_BUFFER);
#endif
eglDestroySurface(m_impl->display, m_impl->surface);
m_impl->surface = EGL_NO_SURFACE;
}
}
static EGLConfig get_egl_config(EGLDisplay display)
{
EGLint const config_attribs[] = {
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 24,
EGL_STENCIL_SIZE, 8,
EGL_NONE
};
EGLint number_of_configs;
eglChooseConfig(display, config_attribs, NULL, 0, &number_of_configs);
Vector<EGLConfig> configs;
configs.resize(number_of_configs);
eglChooseConfig(display, config_attribs, configs.data(), number_of_configs, &number_of_configs);
return number_of_configs > 0 ? configs[0] : EGL_NO_CONFIG_KHR;
}
OwnPtr<OpenGLContext> OpenGLContext::create(NonnullRefPtr<Gfx::SkiaBackendContext> skia_backend_context, WebGLVersion webgl_version)
{
#if !defined(AK_OS_MACOS) && !defined(USE_VULKAN)
(void)skia_backend_context;
(void)webgl_version;
return {};
#endif
EGLAttrib display_attributes[] = {
EGL_PLATFORM_ANGLE_TYPE_ANGLE,
#if defined(AK_OS_MACOS)
EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE,
#elif defined(USE_VULKAN)
EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE,
EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE,
EGL_PLATFORM_SURFACELESS_MESA,
#endif
EGL_NONE,
};
EGLDisplay display = eglGetPlatformDisplay(EGL_PLATFORM_ANGLE_ANGLE, reinterpret_cast<void*>(EGL_DEFAULT_DISPLAY), display_attributes);
if (display == EGL_NO_DISPLAY) {
dbgln("Failed to get EGL display");
return {};
}
EGLint major, minor;
if (!eglInitialize(display, &major, &minor)) {
dbgln("Failed to initialize EGL");
return {};
}
auto* config = get_egl_config(display);
if (config == EGL_NO_CONFIG_KHR) {
dbgln("Failed to find EGLConfig");
return {};
}
EGLint context_attributes[] = {
EGL_CONTEXT_CLIENT_VERSION,
webgl_version == WebGLVersion::WebGL1 ? 2 : 3,
EGL_CONTEXT_WEBGL_COMPATIBILITY_ANGLE,
EGL_TRUE,
EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE,
EGL_TRUE,
EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE,
EGL_FALSE,
#ifdef USE_VULKAN
// we need GL_OES_EGL_image
EGL_EXTENSIONS_ENABLED_ANGLE,
EGL_TRUE,
#endif
EGL_NONE,
EGL_NONE,
};
EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attributes);
if (context == EGL_NO_CONTEXT) {
dbgln("Failed to create EGL context");
return {};
}
#ifdef USE_VULKAN
PFNEGLQUERYDMABUFFORMATSEXTPROC pfn_egl_query_dma_buf_formats_ext = reinterpret_cast<PFNEGLQUERYDMABUFFORMATSEXTPROC>(eglGetProcAddress("eglQueryDmaBufFormatsEXT"));
if (!pfn_egl_query_dma_buf_formats_ext) {
dbgln("eglQueryDmaBufFormatsEXT unavailable");
return {};
}
PFNEGLQUERYDMABUFMODIFIERSEXTPROC pfn_egl_query_dma_buf_modifiers_ext = reinterpret_cast<PFNEGLQUERYDMABUFMODIFIERSEXTPROC>(eglGetProcAddress("eglQueryDmaBufModifiersEXT"));
if (!pfn_egl_query_dma_buf_modifiers_ext) {
dbgln("eglQueryDmaBufModifiersEXT unavailable");
return {};
}
#endif
return make<OpenGLContext>(skia_backend_context, Impl {
.display = display,
.config = config,
.context = context,
#ifdef USE_VULKAN
.ext_procs = {
.query_dma_buf_formats = pfn_egl_query_dma_buf_formats_ext,
.query_dma_buf_modifiers = pfn_egl_query_dma_buf_modifiers_ext,
},
#endif
},
webgl_version);
}
void OpenGLContext::notify_content_will_change()
{
m_painting_surface->notify_content_will_change();
}
void OpenGLContext::clear_buffer_to_default_values()
{
Array<GLfloat, 4> current_clear_color;
glGetFloatv(GL_COLOR_CLEAR_VALUE, current_clear_color.data());
GLfloat current_clear_depth;
glGetFloatv(GL_DEPTH_CLEAR_VALUE, &current_clear_depth);
GLint current_clear_stencil;
glGetIntegerv(GL_STENCIL_CLEAR_VALUE, &current_clear_stencil);
// The implicit clear value for the color buffer is (0, 0, 0, 0)
glClearColor(0, 0, 0, 0);
// The implicit clear value for the depth buffer is 1.0.
glClearDepthf(1.0f);
// The implicit clear value for the stencil buffer is 0.
glClearStencil(0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// Restore the clear values.
glClearColor(current_clear_color[0], current_clear_color[1], current_clear_color[2], current_clear_color[3]);
glClearDepthf(current_clear_depth);
glClearStencil(current_clear_stencil);
}
void OpenGLContext::allocate_painting_surface_if_needed()
{
if (m_painting_surface)
return;
free_surface_resources();
VERIFY(!m_size.is_empty());
auto width = m_size.width();
auto height = m_size.height();
auto* display = m_impl->display;
EGLint texture_target_name = 0;
#if defined(AK_OS_MACOS)
auto iosurface = Core::IOSurfaceHandle::create(m_size.width(), m_size.height());
m_painting_surface = Gfx::PaintingSurface::create_from_iosurface(move(iosurface), m_skia_backend_context, Gfx::PaintingSurface::Origin::BottomLeft);
auto* config = m_impl->config;
EGLint target = 0;
eglGetConfigAttrib(display, config, EGL_BIND_TO_TEXTURE_TARGET_ANGLE, &target);
EGLint const surface_attributes[] = {
EGL_WIDTH,
width,
EGL_HEIGHT,
height,
EGL_IOSURFACE_PLANE_ANGLE,
0,
EGL_TEXTURE_TARGET,
target,
EGL_TEXTURE_INTERNAL_FORMAT_ANGLE,
GL_BGRA_EXT,
EGL_TEXTURE_FORMAT,
EGL_TEXTURE_RGBA,
EGL_TEXTURE_TYPE_ANGLE,
GL_UNSIGNED_BYTE,
EGL_NONE,
EGL_NONE,
};
m_impl->surface = eglCreatePbufferFromClientBuffer(display, EGL_IOSURFACE_ANGLE, iosurface.core_foundation_pointer(), config, surface_attributes);
eglMakeCurrent(m_impl->display, m_impl->surface, m_impl->surface, m_impl->context);
eglGetConfigAttrib(display, config, EGL_BIND_TO_TEXTURE_TARGET_ANGLE, &texture_target_name);
VERIFY(texture_target_name == EGL_TEXTURE_RECTANGLE_ANGLE || texture_target_name == EGL_TEXTURE_2D);
glGenTextures(1, &m_impl->color_buffer);
glBindTexture(texture_target_name == EGL_TEXTURE_RECTANGLE_ANGLE ? GL_TEXTURE_RECTANGLE_ANGLE : GL_TEXTURE_2D, m_impl->color_buffer);
auto result = eglBindTexImage(display, m_impl->surface, EGL_BACK_BUFFER);
VERIFY(result == EGL_TRUE);
#elif defined(USE_VULKAN)
VkFormat vulkan_format = VK_FORMAT_B8G8R8A8_UNORM;
uint32_t drm_format = Gfx::vk_format_to_drm_format(vulkan_format);
// Ensure that our format is supported by the implementation.
// FIXME: try other formats if not?
EGLint num_formats = 0;
m_impl->ext_procs.query_dma_buf_formats(display, 0, nullptr, &num_formats);
Vector<EGLint> egl_formats;
egl_formats.resize(num_formats);
m_impl->ext_procs.query_dma_buf_formats(display, num_formats, egl_formats.data(), &num_formats);
VERIFY(egl_formats.find(drm_format) != egl_formats.end());
EGLint num_modifiers = 0;
m_impl->ext_procs.query_dma_buf_modifiers(display, drm_format, 0, nullptr, nullptr, &num_modifiers);
Vector<uint64_t> egl_modifiers;
egl_modifiers.resize(num_modifiers);
Vector<EGLBoolean> external_only;
external_only.resize(num_modifiers);
m_impl->ext_procs.query_dma_buf_modifiers(display, drm_format, num_modifiers, egl_modifiers.data(), external_only.data(), &num_modifiers);
Vector<uint64_t> renderable_modifiers;
for (int i = 0; i < num_modifiers; ++i) {
if (!external_only[i]) {
renderable_modifiers.append(egl_modifiers[i]);
}
}
NonnullRefPtr<Gfx::VulkanImage> vulkan_image = Gfx::create_shared_vulkan_image(m_skia_backend_context->vulkan_context(), width, height, vulkan_format,
renderable_modifiers.size(), renderable_modifiers.data())
.value();
m_painting_surface = Gfx::PaintingSurface::create_from_vkimage(m_skia_backend_context, vulkan_image, Gfx::PaintingSurface::Origin::BottomLeft);
EGLAttrib attribs[] = {
EGL_WIDTH,
width,
EGL_HEIGHT,
height,
EGL_LINUX_DRM_FOURCC_EXT,
drm_format,
EGL_DMA_BUF_PLANE0_FD_EXT,
vulkan_image->get_dma_buf_fd(), // EGL takes ownership of the fd
EGL_DMA_BUF_PLANE0_OFFSET_EXT,
0,
EGL_DMA_BUF_PLANE0_PITCH_EXT,
static_cast<uint32_t>(vulkan_image->info.row_pitch),
EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
static_cast<uint32_t>(vulkan_image->info.modifier & 0xffffffff),
EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
static_cast<uint32_t>(vulkan_image->info.modifier >> 32),
EGL_NONE,
};
m_impl->egl_image = eglCreateImage(display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs);
VERIFY(m_impl->egl_image != EGL_NO_IMAGE);
m_impl->surface = EGL_NO_SURFACE;
eglMakeCurrent(m_impl->display, m_impl->surface, m_impl->surface, m_impl->context);
glGenTextures(1, &m_impl->color_buffer);
glBindTexture(GL_TEXTURE_2D, m_impl->color_buffer);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_impl->egl_image);
texture_target_name = EGL_TEXTURE_2D;
glViewport(0, 0, width, height);
#endif
glGenFramebuffers(1, &m_impl->framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, m_impl->framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture_target_name == EGL_TEXTURE_RECTANGLE_ANGLE ? GL_TEXTURE_RECTANGLE_ANGLE : GL_TEXTURE_2D, m_impl->color_buffer, 0);
// NOTE: ANGLE doesn't allocate depth buffer for us, so we need to do it manually
// FIXME: Depth buffer only needs to be allocated if it's configured in WebGL context attributes
glGenRenderbuffers(1, &m_impl->depth_buffer);
glBindRenderbuffer(GL_RENDERBUFFER, m_impl->depth_buffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_impl->depth_buffer);
VERIFY(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
}
void OpenGLContext::set_size(Gfx::IntSize const& size)
{
if (m_size != size) {
m_painting_surface = nullptr;
}
m_size = size;
}
void OpenGLContext::make_current()
{
allocate_painting_surface_if_needed();
eglMakeCurrent(m_impl->display, m_impl->surface, m_impl->surface, m_impl->context);
}
void OpenGLContext::present(bool preserve_drawing_buffer)
{
make_current();
// "Before the drawing buffer is presented for compositing the implementation shall ensure that all rendering operations have been flushed to the drawing buffer."
// With Metal, glFlush flushes the command buffer, but without waiting for it to be scheduled or completed.
// eglWaitUntilWorkScheduledANGLE flushes the command buffer, and waits until it has been scheduled, hence the name.
// eglWaitUntilWorkScheduledANGLE only has an effect on CGL and Metal backends, so we only use it on macOS.
#if defined(AK_OS_MACOS)
eglWaitUntilWorkScheduledANGLE(m_impl->display);
#elif defined(USE_VULKAN)
// FIXME: CPU sync for now, but it would be better to export a fence and have Skia wait for it before reading from the surface
glFinish();
#endif
// "By default, after compositing the contents of the drawing buffer shall be cleared to their default values, as shown in the table above.
// This default behavior can be changed by setting the preserveDrawingBuffer attribute of the WebGLContextAttributes object.
// If this flag is true, the contents of the drawing buffer shall be preserved until the author either clears or overwrites them."
if (!preserve_drawing_buffer) {
// FIXME: we're assuming the clear operation won't actually be submitted to the GPU
clear_buffer_to_default_values();
}
}
RefPtr<Gfx::PaintingSurface> OpenGLContext::surface()
{
return m_painting_surface;
}
u32 OpenGLContext::default_renderbuffer() const
{
return m_impl->depth_buffer;
}
u32 OpenGLContext::default_framebuffer() const
{
return m_impl->framebuffer;
}
struct Extension {
String webgl_extension_name;
Vector<StringView> required_angle_extensions;
Optional<OpenGLContext::WebGLVersion> only_for_webgl_version { OptionalNone {} };
};
Vector<Extension> s_available_webgl_extensions {
// Khronos ratified WebGL Extensions
{ "ANGLE_instanced_arrays"_string, { "GL_ANGLE_instanced_arrays"sv }, OpenGLContext::WebGLVersion::WebGL1 },
{ "EXT_blend_minmax"_string, { "GL_EXT_blend_minmax"sv }, OpenGLContext::WebGLVersion::WebGL1 },
{ "EXT_frag_depth"_string, { "GL_EXT_frag_depth"sv }, OpenGLContext::WebGLVersion::WebGL1 },
{ "EXT_shader_texture_lod"_string, { "GL_EXT_shader_texture_lod"sv }, OpenGLContext::WebGLVersion::WebGL1 },
{ "EXT_texture_filter_anisotropic"_string, { "GL_EXT_texture_filter_anisotropic"sv } },
{ "OES_element_index_uint"_string, { "GL_OES_element_index_uint"sv }, OpenGLContext::WebGLVersion::WebGL1 },
{ "OES_standard_derivatives"_string, { "GL_OES_standard_derivatives"sv }, OpenGLContext::WebGLVersion::WebGL1 },
{ "OES_texture_float"_string, { "GL_OES_texture_float"sv }, OpenGLContext::WebGLVersion::WebGL1 },
{ "OES_texture_float_linear"_string, { "GL_OES_texture_float_linear"sv } },
{ "OES_texture_half_float"_string, { "GL_OES_texture_half_float"sv }, OpenGLContext::WebGLVersion::WebGL1 },
{ "OES_texture_half_float_linear"_string, { "GL_OES_texture_half_float_linear"sv }, OpenGLContext::WebGLVersion::WebGL1 },
{ "OES_vertex_array_object"_string, { "GL_OES_vertex_array_object"sv }, OpenGLContext::WebGLVersion::WebGL1 },
{ "WEBGL_compressed_texture_s3tc"_string, { "GL_EXT_texture_compression_dxt1"sv, "GL_ANGLE_texture_compression_dxt3"sv, "GL_ANGLE_texture_compression_dxt5"sv } },
{ "WEBGL_debug_renderer_info"_string, {} },
{ "WEBGL_debug_shaders"_string, {} },
{ "WEBGL_depth_texture"_string, { "GL_ANGLE_depth_texture"sv }, OpenGLContext::WebGLVersion::WebGL1 },
{ "WEBGL_draw_buffers"_string, { "GL_EXT_draw_buffers"sv }, OpenGLContext::WebGLVersion::WebGL1 },
{ "WEBGL_lose_context"_string, {} },
// Community approved WebGL Extensions
{ "EXT_clip_control"_string, { "GL_EXT_clip_control"sv } },
{ "EXT_color_buffer_float"_string, { "GL_EXT_color_buffer_float"sv }, OpenGLContext::WebGLVersion::WebGL2 },
{ "EXT_color_buffer_half_float"_string, { "GL_EXT_color_buffer_half_float"sv } },
{ "EXT_conservative_depth"_string, { "GL_EXT_conservative_depth"sv }, OpenGLContext::WebGLVersion::WebGL2 },
{ "EXT_depth_clamp"_string, { "GL_EXT_depth_clamp"sv } },
{ "EXT_disjoint_timer_query"_string, { "GL_EXT_disjoint_timer_query"sv }, OpenGLContext::WebGLVersion::WebGL1 },
{ "EXT_disjoint_timer_query_webgl2"_string, { "GL_EXT_disjoint_timer_query"sv }, OpenGLContext::WebGLVersion::WebGL2 },
{ "EXT_float_blend"_string, { "GL_EXT_float_blend"sv } },
{ "EXT_polygon_offset_clamp"_string, { "GL_EXT_polygon_offset_clamp"sv } },
{ "EXT_render_snorm"_string, { "GL_EXT_render_snorm"sv }, OpenGLContext::WebGLVersion::WebGL2 },
{ "EXT_sRGB"_string, { "GL_EXT_sRGB"sv }, OpenGLContext::WebGLVersion::WebGL1 },
{ "EXT_texture_compression_bptc"_string, { "GL_EXT_texture_compression_bptc"sv } },
{ "EXT_texture_compression_rgtc"_string, { "GL_EXT_texture_compression_rgtc"sv } },
{ "EXT_texture_mirror_clamp_to_edge"_string, { "GL_EXT_texture_mirror_clamp_to_edge"sv } },
{ "EXT_texture_norm16"_string, { "GL_EXT_texture_norm16"sv }, OpenGLContext::WebGLVersion::WebGL2 },
{ "KHR_parallel_shader_compile"_string, { "GL_KHR_parallel_shader_compile"sv } },
{ "NV_shader_noperspective_interpolation"_string, { "GL_NV_shader_noperspective_interpolation"sv }, OpenGLContext::WebGLVersion::WebGL2 },
{ "OES_draw_buffers_indexed"_string, { "GL_OES_draw_buffers_indexed"sv } },
{ "OES_fbo_render_mipmap"_string, { "GL_OES_fbo_render_mipmap"sv }, OpenGLContext::WebGLVersion::WebGL1 },
{ "OES_sample_variables"_string, { "GL_OES_sample_variables"sv }, OpenGLContext::WebGLVersion::WebGL2 },
{ "OES_shader_multisample_interpolation"_string, { "GL_OES_shader_multisample_interpolation"sv }, OpenGLContext::WebGLVersion::WebGL2 },
{ "OVR_multiview2"_string, { "GL_OVR_multiview2"sv }, OpenGLContext::WebGLVersion::WebGL2 },
{ "WEBGL_blend_func_extended"_string, { "GL_EXT_blend_func_extended"sv } },
{ "WEBGL_clip_cull_distance"_string, { "GL_EXT_clip_cull_distance"sv }, OpenGLContext::WebGLVersion::WebGL2 },
{ "WEBGL_color_buffer_float"_string, { "EXT_color_buffer_half_float"sv, "OES_texture_float"sv }, OpenGLContext::WebGLVersion::WebGL1 },
{ "WEBGL_compressed_texture_astc"_string, { "KHR_texture_compression_astc_hdr"sv, "KHR_texture_compression_astc_ldr"sv } },
{ "WEBGL_compressed_texture_etc"_string, { "GL_ANGLE_compressed_texture_etc"sv } },
{ "WEBGL_compressed_texture_etc1"_string, { "GL_OES_compressed_ETC1_RGB8_texture"sv } },
{ "WEBGL_compressed_texture_pvrtc"_string, { "GL_IMG_texture_compression_pvrtc"sv } },
{ "WEBGL_compressed_texture_s3tc_srgb"_string, { "GL_EXT_texture_compression_s3tc_srgb"sv } },
{ "WEBGL_multi_draw"_string, { "GL_ANGLE_multi_draw"sv } },
{ "WEBGL_polygon_mode"_string, { "GL_ANGLE_polygon_mode"sv } },
{ "WEBGL_provoking_vertex"_string, { "GL_ANGLE_provoking_vertex"sv }, OpenGLContext::WebGLVersion::WebGL2 },
{ "WEBGL_render_shared_exponent"_string, { "GL_QCOM_render_shared_exponent"sv }, OpenGLContext::WebGLVersion::WebGL2 },
{ "WEBGL_stencil_texturing"_string, { "GL_ANGLE_stencil_texturing"sv }, OpenGLContext::WebGLVersion::WebGL2 },
};
Vector<String> OpenGLContext::get_supported_extensions()
{
if (m_requestable_extensions.has_value())
return m_requestable_extensions.value();
make_current();
auto const* requestable_extensions_string = reinterpret_cast<char const*>(glGetString(GL_REQUESTABLE_EXTENSIONS_ANGLE));
StringView requestable_extensions_view(requestable_extensions_string, strlen(requestable_extensions_string));
auto requestable_extensions = requestable_extensions_view.split_view(' ');
Vector<String> extensions;
for (auto const& available_extension : s_available_webgl_extensions) {
bool supported = !available_extension.only_for_webgl_version.has_value()
|| m_webgl_version == available_extension.only_for_webgl_version;
if (supported) {
for (auto const& required_extension : available_extension.required_angle_extensions) {
auto maybe_required_extension = requestable_extensions.find_if([&](StringView requestable_extension) {
return required_extension == requestable_extension;
});
if (maybe_required_extension == requestable_extensions.end()) {
supported = false;
break;
}
}
}
if (supported)
extensions.append(available_extension.webgl_extension_name);
}
// We must cache this, because once extensions have been requested, they're no longer requestable extensions and would
// not appear in this list. However, we must always report every supported extension, regardless of what has already
// been requested.
m_requestable_extensions = extensions;
return extensions;
}
void OpenGLContext::request_extension(char const* extension_name)
{
make_current();
glRequestExtensionANGLE(extension_name);
}
}