/* * Copyright (c) 2022, Luke Wilde * Copyright (c) 2023, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Web::WebGL { GC_DEFINE_ALLOCATOR(WebGLRenderingContext); // https://www.khronos.org/registry/webgl/specs/latest/1.0/#fire-a-webgl-context-event void fire_webgl_context_event(HTML::HTMLCanvasElement& canvas_element, FlyString const& type) { // To fire a WebGL context event named e means that an event using the WebGLContextEvent interface, with its type attribute [DOM4] initialized to e, its cancelable attribute initialized to true, and its isTrusted attribute [DOM4] initialized to true, is to be dispatched at the given object. // FIXME: Consider setting a status message. auto event = WebGLContextEvent::create(canvas_element.realm(), type, WebGLContextEventInit {}); event->set_is_trusted(true); event->set_cancelable(true); canvas_element.dispatch_event(*event); } // https://www.khronos.org/registry/webgl/specs/latest/1.0/#fire-a-webgl-context-creation-error void fire_webgl_context_creation_error(HTML::HTMLCanvasElement& canvas_element) { // 1. Fire a WebGL context event named "webglcontextcreationerror" at canvas, optionally with its statusMessage attribute set to a platform dependent string about the nature of the failure. fire_webgl_context_event(canvas_element, EventNames::webglcontextcreationerror); } JS::ThrowCompletionOr> WebGLRenderingContext::create(JS::Realm& realm, HTML::HTMLCanvasElement& canvas_element, JS::Value options) { // We should be coming here from getContext being called on a wrapped element. auto context_attributes = TRY(convert_value_to_context_attributes_dictionary(canvas_element.vm(), options)); auto skia_backend_context = canvas_element.navigable()->traversable_navigable()->skia_backend_context(); if (!skia_backend_context) { fire_webgl_context_creation_error(canvas_element); return GC::Ptr { nullptr }; } auto context = OpenGLContext::create(*skia_backend_context); if (!context) { fire_webgl_context_creation_error(canvas_element); return GC::Ptr { nullptr }; } context->set_size(canvas_element.bitmap_size_for_canvas(1, 1)); return realm.create(realm, canvas_element, context.release_nonnull(), context_attributes, context_attributes); } WebGLRenderingContext::WebGLRenderingContext(JS::Realm& realm, HTML::HTMLCanvasElement& canvas_element, NonnullOwnPtr context, WebGLContextAttributes context_creation_parameters, WebGLContextAttributes actual_context_parameters) : PlatformObject(realm) , WebGLRenderingContextImpl(realm, move(context)) , m_canvas_element(canvas_element) , m_context_creation_parameters(context_creation_parameters) , m_actual_context_parameters(actual_context_parameters) { } WebGLRenderingContext::~WebGLRenderingContext() = default; void WebGLRenderingContext::initialize(JS::Realm& realm) { Base::initialize(realm); WEB_SET_PROTOTYPE_FOR_INTERFACE(WebGLRenderingContext); } void WebGLRenderingContext::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); WebGLRenderingContextImpl::visit_edges(visitor); visitor.visit(m_canvas_element); visitor.visit(m_angle_instanced_arrays_extension); visitor.visit(m_oes_vertex_array_object_extension); visitor.visit(m_webgl_compressed_texture_s3tc_extension); visitor.visit(m_webgl_draw_buffers_extension); } void WebGLRenderingContext::present() { if (!m_should_present) return; m_should_present = false; // "Before the drawing buffer is presented for compositing the implementation shall ensure that all rendering operations have been flushed to the drawing buffer." glFlush(); // "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 (!m_context_creation_parameters.preserve_drawing_buffer) { context().clear_buffer_to_default_values(); } } GC::Ref WebGLRenderingContext::canvas_for_binding() const { return *m_canvas_element; } void WebGLRenderingContext::needs_to_present() { m_should_present = true; if (!m_canvas_element->paintable()) return; m_canvas_element->paintable()->set_needs_display(); } void WebGLRenderingContext::set_error(GLenum error) { auto context_error = glGetError(); if (context_error != GL_NO_ERROR) m_error = context_error; else m_error = error; } bool WebGLRenderingContext::is_context_lost() const { dbgln_if(WEBGL_CONTEXT_DEBUG, "WebGLRenderingContext::is_context_lost()"); return m_context_lost; } Optional WebGLRenderingContext::get_context_attributes() { if (is_context_lost()) return {}; return m_actual_context_parameters; } void WebGLRenderingContext::set_size(Gfx::IntSize const& size) { Gfx::IntSize final_size; final_size.set_width(max(size.width(), 1)); final_size.set_height(max(size.height(), 1)); context().set_size(final_size); } void WebGLRenderingContext::reset_to_default_state() { } RefPtr WebGLRenderingContext::surface() { return context().surface(); } void WebGLRenderingContext::allocate_painting_surface_if_needed() { context().allocate_painting_surface_if_needed(); } Optional> WebGLRenderingContext::get_supported_extensions() { return context().get_supported_extensions(); } JS::Object* WebGLRenderingContext::get_extension(String const& name) { // Returns an object if, and only if, name is an ASCII case-insensitive match [HTML] for one of the names returned // from getSupportedExtensions; otherwise, returns null. The object returned from getExtension contains any constants // or functions provided by the extension. A returned object may have no constants or functions if the extension does // not define any, but a unique object must still be returned. That object is used to indicate that the extension has // been enabled. auto supported_extensions = get_supported_extensions(); auto supported_extension_iterator = supported_extensions->find_if([&name](String const& supported_extension) { return Infra::is_ascii_case_insensitive_match(supported_extension, name); }); if (supported_extension_iterator == supported_extensions->end()) return nullptr; if (Infra::is_ascii_case_insensitive_match(name, "ANGLE_instanced_arrays"sv)) { if (!m_angle_instanced_arrays_extension) { m_angle_instanced_arrays_extension = MUST(Extensions::ANGLEInstancedArrays::create(realm(), *this)); } VERIFY(m_angle_instanced_arrays_extension); return m_angle_instanced_arrays_extension; } if (Infra::is_ascii_case_insensitive_match(name, "OES_vertex_array_object"sv)) { if (!m_oes_vertex_array_object_extension) { m_oes_vertex_array_object_extension = MUST(Extensions::OESVertexArrayObject::create(realm(), *this)); } VERIFY(m_oes_vertex_array_object_extension); return m_oes_vertex_array_object_extension; } if (Infra::is_ascii_case_insensitive_match(name, "WEBGL_compressed_texture_s3tc"sv)) { if (!m_webgl_compressed_texture_s3tc_extension) { m_webgl_compressed_texture_s3tc_extension = MUST(Extensions::WebGLCompressedTextureS3tc::create(realm(), this)); } VERIFY(m_webgl_compressed_texture_s3tc_extension); return m_webgl_compressed_texture_s3tc_extension; } if (Infra::is_ascii_case_insensitive_match(name, "WEBGL_draw_buffers"sv)) { if (!m_webgl_draw_buffers_extension) { m_webgl_draw_buffers_extension = MUST(Extensions::WebGLDrawBuffers::create(realm(), *this)); } VERIFY(m_webgl_draw_buffers_extension); return m_webgl_draw_buffers_extension; } return nullptr; } WebIDL::Long WebGLRenderingContext::drawing_buffer_width() const { auto size = canvas_for_binding()->bitmap_size_for_canvas(); return size.width(); } WebIDL::Long WebGLRenderingContext::drawing_buffer_height() const { auto size = canvas_for_binding()->bitmap_size_for_canvas(); return size.height(); } }