/* * Copyright (c) 2024-2025, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include #define SET_ERROR_VALUE_IF_ERROR(expression, error_value) \ ({ \ auto maybe_error = expression; \ if (maybe_error.is_error()) [[unlikely]] { \ set_error(error_value); \ return; \ } \ maybe_error.release_value(); \ }) namespace Web::WebGL { static constexpr int COMPRESSED_TEXTURE_FORMATS = 0x86A3; static constexpr int UNPACK_FLIP_Y_WEBGL = 0x9240; static constexpr int UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241; static constexpr int UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243; static constexpr int BROWSER_DEFAULT_WEBGL = 0x9244; static constexpr int MAX_CLIENT_WAIT_TIMEOUT_WEBGL = 0x9247; // NOTE: This is the Variant created by the IDL wrapper generator, and needs to be updated accordingly. using TexImageSource = Variant, GC::Root, GC::Root, GC::Root, GC::Root, GC::Root>; class WebGLRenderingContextBase : public Bindings::PlatformObject { WEB_NON_IDL_PLATFORM_OBJECT(WebGLRenderingContextBase, Bindings::PlatformObject); public: using Float32List = Variant, Vector>; using Int32List = Variant, Vector>; using Uint32List = Variant, Vector>; virtual OpenGLContext& context() = 0; bool is_context_lost() const; bool xr_compatible() const { return m_xr_compatible; } void set_xr_compatible(bool xr_compatible) { m_xr_compatible = xr_compatible; } // https://immersive-web.github.io/webxr/#dom-webglrenderingcontextbase-makexrcompatible GC::Ref make_xr_compatible(); Optional> get_supported_extensions(); JS::Object* get_extension(String const& name); void enable_compressed_texture_format(WebIDL::UnsignedLong format); protected: WebGLRenderingContextBase(JS::Realm&); virtual void visit_edges(Cell::Visitor&) override; // FIXME: Make this and any another instance of extension names a FlyString, similarly to HTML::TagNames bool extension_enabled(StringView extension) const; ReadonlySpan enabled_compressed_texture_formats() const; template static ErrorOr> get_offset_span(Span src_span, WebIDL::UnsignedLongLong src_offset, WebIDL::UnsignedLong src_length_override = 0) { Checked length = src_offset; length += src_length_override; if (length.has_overflow() || length.value_unchecked() > src_span.size()) [[unlikely]] return Error::from_errno(EINVAL); if (src_length_override == 0) return src_span.slice(src_offset, src_span.size() - src_offset); return src_span.slice(src_offset, src_length_override); } template static ErrorOr> get_offset_span(GC::Ref src_data, WebIDL::UnsignedLongLong src_offset, WebIDL::UnsignedLong src_length_override = 0) { auto buffer_size = src_data->byte_length(); if (buffer_size % sizeof(T) != 0) [[unlikely]] return Error::from_errno(EINVAL); auto raw_object = src_data->raw_object(); if (auto* array_buffer = as_if(*raw_object)) { return TRY(get_offset_span(array_buffer->buffer().span(), src_offset, src_length_override)).reinterpret(); } if (auto* data_view = as_if(*raw_object)) { return TRY(get_offset_span(data_view->viewed_array_buffer()->buffer().span(), src_offset, src_length_override)).reinterpret(); } // NOTE: This has to be done because src_offset is the number of elements to offset by, not the number of bytes. #define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, Type) \ if (auto* typed_array = as_if(*raw_object)) { \ return TRY(get_offset_span(typed_array->data(), src_offset, src_length_override)).reinterpret(); \ } JS_ENUMERATE_TYPED_ARRAYS #undef __JS_ENUMERATE VERIFY_NOT_REACHED(); } static ErrorOr> span_from_float32_list(Float32List& float32_list, WebIDL::UnsignedLongLong src_offset, WebIDL::UnsignedLong src_length_override = 0) { if (float32_list.has>()) { auto& vector = float32_list.get>(); return get_offset_span(vector.span(), src_offset, src_length_override); } auto& buffer = float32_list.get>(); return get_offset_span(buffer->data(), src_offset, src_length_override); } static ErrorOr> span_from_int32_list(Int32List& int32_list, WebIDL::UnsignedLongLong src_offset, WebIDL::UnsignedLong src_length_override = 0) { if (int32_list.has>()) { auto& vector = int32_list.get>(); return get_offset_span(vector.span(), src_offset, src_length_override); } auto& buffer = int32_list.get>(); return get_offset_span(buffer->data(), src_offset, src_length_override); } static ErrorOr> span_from_uint32_list(Uint32List& uint32_list, WebIDL::UnsignedLongLong src_offset, WebIDL::UnsignedLong src_length_override = 0) { if (uint32_list.has>()) { auto& vector = uint32_list.get>(); return get_offset_span(vector.span(), src_offset, src_length_override); } auto& buffer = uint32_list.get>(); return get_offset_span(buffer->data(), src_offset, src_length_override); } Optional read_and_pixel_convert_texture_image_source(TexImageSource const& source, WebIDL::UnsignedLong format, WebIDL::UnsignedLong type, Optional destination_width = OptionalNone {}, Optional destination_height = OptionalNone {}); static Vector null_terminated_string(StringView string) { Vector result; result.ensure_capacity(string.length() + 1); for (auto c : string.bytes()) result.append(c); result.append('\0'); return result; } GLenum get_error_value(); void set_error(GLenum error); // UNPACK_FLIP_Y_WEBGL of type boolean // If set, then during any subsequent calls to texImage2D or texSubImage2D, the source data is flipped along // the vertical axis, so that conceptually the last row is the first one transferred. The initial value is false. // Any non-zero value is interpreted as true. bool m_unpack_flip_y { false }; // UNPACK_PREMULTIPLY_ALPHA_WEBGL of type boolean // If set, then during any subsequent calls to texImage2D or texSubImage2D, the alpha channel of the source data, // if present, is multiplied into the color channels during the data transfer. The initial value is false. // Any non-zero value is interpreted as true. bool m_unpack_premultiply_alpha { false }; // UNPACK_COLORSPACE_CONVERSION_WEBGL of type unsigned long // If set to BROWSER_DEFAULT_WEBGL, then the browser's default colorspace conversion (e.g. converting a display-p3 // image to srgb) is applied during subsequent texture data upload calls (e.g. texImage2D and texSubImage2D) that // take an argument of TexImageSource. The precise conversions may be specific to both the browser and file type. // If set to NONE, no colorspace conversion is applied, other than conversion to RGBA. (For example, a rec709 YUV // video is still converted to rec709 RGB data, but not then converted to e.g. srgb RGB data) The initial value is // BROWSER_DEFAULT_WEBGL. GLenum m_unpack_colorspace_conversion { BROWSER_DEFAULT_WEBGL }; private: GLenum m_error { 0 }; // https://registry.khronos.org/webgl/specs/latest/2.0/#webgl-context-lost-flag // Each WebGLRenderingContext and WebGL2RenderingContext has a webgl context lost flag, which is initially unset. bool m_context_lost { false }; // https://immersive-web.github.io/webxr/#xr-compatible bool m_xr_compatible { false }; Vector m_enabled_compressed_texture_formats; // Extensions // "Multiple calls to getExtension with the same extension string, taking into account case-insensitive comparison, must return the same object as long as the extension is enabled." HashMap, AK::ASCIICaseInsensitiveStringTraits> m_enabled_extensions; }; }