/* * Copyright (c) 2026, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include namespace Web::WebGL { WebGLContextProxyBase::WebGLContextProxyBase(NonnullRefPtr transport, Painting::CanvasId canvas_id, WebGLVersion webgl_version, Vector supported_extensions) : m_transport(move(transport)) , m_canvas_id(canvas_id) , m_webgl_version(webgl_version) , m_supported_extensions(move(supported_extensions)) { } WebGLContextProxyBase::~WebGLContextProxyBase() { m_transport->destroy_context(m_canvas_id); } void WebGLContextProxyBase::flush_commands() { if (m_commands.is_empty()) return; m_transport->send_commands(m_canvas_id, m_commands.buffer(), m_pending_bitmaps); m_commands.clear_with_capacity(); m_pending_bitmaps.clear_with_capacity(); } ByteBuffer WebGLContextProxyBase::send_sync_call(ByteBuffer request) { if (m_lost) return {}; flush_commands(); return m_transport->sync_call(m_canvas_id, move(request)); } ReadPixelsResult WebGLContextProxyBase::read_pixels_robust_angle_into_shared_buffer(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei buf_size, Core::AnonymousBuffer const& pixels) { flush_commands(); return m_transport->read_pixels_robust_angle(m_canvas_id, x, y, width, height, format, type, buf_size, pixels); } void WebGLContextProxyBase::set_size(Gfx::IntSize const& size) { record(Commands::SetDrawingBufferSize { .width = size.width(), .height = size.height() }); } void WebGLContextProxyBase::present_canvas_for_compositing(bool preserve_drawing_buffer) { flush_commands(); m_transport->present_canvas(m_canvas_id, preserve_drawing_buffer); } RefPtr WebGLContextProxyBase::read_back_drawing_buffer(Gfx::IntRect const& rect) { if (m_lost) return nullptr; flush_commands(); auto bitmap = m_transport->read_back_drawing_buffer(m_canvas_id, rect); if (!bitmap.is_valid()) return nullptr; return bitmap.bitmap(); } void WebGLContextProxyBase::read_pixels_into_pixel_pack_buffer(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, long long offset) { record(Commands::ReadPixelsIntoPixelPackBuffer { .x = x, .y = y, .width = width, .height = height, .format = format, .type = type, .offset = static_cast(offset), }); } void WebGLContextProxyBase::tex_image2d_from_bitmap(GLenum target, GLint level, GLint internalformat, GLenum format, GLenum type, Gfx::DecodedImageFrame frame, Optional destination_size, bool flip_y, bool premultiply_alpha) { if (m_lost) return; auto bitmap_index = static_cast(m_pending_bitmaps.size()); m_pending_bitmaps.append(move(frame)); auto has_explicit_destination_size = destination_size.has_value(); record(Commands::TexImage2DFromBitmap { .target = target, .level = level, .internalformat = internalformat, .format = format, .type = type, .bitmap_index = bitmap_index, .has_explicit_destination_size = has_explicit_destination_size, .destination_width = has_explicit_destination_size ? destination_size->width() : 0, .destination_height = has_explicit_destination_size ? destination_size->height() : 0, .flip_y = flip_y, .premultiply_alpha = premultiply_alpha, }); } void WebGLContextProxyBase::tex_sub_image2d_from_bitmap(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLenum format, GLenum type, Gfx::DecodedImageFrame frame, Optional destination_size, bool flip_y, bool premultiply_alpha) { if (m_lost) return; auto bitmap_index = static_cast(m_pending_bitmaps.size()); m_pending_bitmaps.append(move(frame)); auto has_explicit_destination_size = destination_size.has_value(); record(Commands::TexSubImage2DFromBitmap { .target = target, .level = level, .xoffset = xoffset, .yoffset = yoffset, .format = format, .type = type, .bitmap_index = bitmap_index, .has_explicit_destination_size = has_explicit_destination_size, .destination_width = has_explicit_destination_size ? destination_size->width() : 0, .destination_height = has_explicit_destination_size ? destination_size->height() : 0, .flip_y = flip_y, .premultiply_alpha = premultiply_alpha, }); } void WebGLContextProxyBase::read_buffer_sub_data(GLenum target, long long offset, Bytes destination) { if (m_lost || destination.is_empty()) return; auto shared_data_or_error = Core::AnonymousBuffer::create_with_size(destination.size()); auto shared_data = shared_data_or_error.release_value_but_fixme_should_propagate_errors(); flush_commands(); m_transport->read_buffer_sub_data(m_canvas_id, target, static_cast(offset), static_cast(destination.size()), shared_data); if (m_lost) return; __builtin_memcpy(destination.data(), shared_data.data(), destination.size()); } void WebGLContextProxy::shader_source(GLuint shader, GLsizei count, GLchar const* const* string, GLint const* length) { VERIFY(count == 1); auto source_length = length ? static_cast(length[0]) : __builtin_strlen(string[0]); ByteBuffer source_bytes = MUST(ByteBuffer::create_uninitialized(source_length + 1)); __builtin_memcpy(source_bytes.data(), string[0], source_length); source_bytes[source_length] = 0; Commands::ShaderSource command { .shader = shader, .source = {} }; command.source = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast(source_bytes.size()) }; record(command, source_bytes); } static ByteBuffer pack_strings(GLsizei count, GLchar const* const* strings) { StringBuilder builder; for (GLsizei i = 0; i < count; ++i) { builder.append({ strings[i], __builtin_strlen(strings[i]) }); builder.append('\0'); } return MUST(builder.to_byte_buffer()); } void WebGLContextProxy::transform_feedback_varyings(GLuint program, GLsizei count, GLchar const* const* varyings, GLenum bufferMode) { auto varyings_bytes = pack_strings(count, varyings); Commands::TransformFeedbackVaryings command { .program = program, .count = count, .varyings = {}, .buffer_mode = bufferMode }; command.varyings = { WebGLCommandList::first_inline_data_offset(sizeof(command)), static_cast(varyings_bytes.size()) }; record(command, varyings_bytes); } void WebGLContextProxy::read_pixels_robust_angle(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, GLsizei* length, GLsizei* columns, GLsizei* rows, void* pixels) { if (is_lost()) return; Core::AnonymousBuffer shared_pixels; if (bufSize > 0) { shared_pixels = Core::AnonymousBuffer::create_with_size(static_cast(bufSize)).release_value_but_fixme_should_propagate_errors(); } auto result = read_pixels_robust_angle_into_shared_buffer(x, y, width, height, format, type, bufSize, shared_pixels); if (is_lost()) return; if (length) *length = result.length; if (columns) *columns = result.columns; if (rows) *rows = result.rows; if (pixels && result.length > 0) { VERIFY(result.length <= bufSize); __builtin_memcpy(pixels, shared_pixels.data(), static_cast(result.length)); } } GLubyte const* WebGLContextProxy::get_string(GLenum name) { if (auto cached = m_string_cache.get(name); cached.has_value()) return cached.value()->data(); SyncCalls::GetString::Request request { .name = name }; auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request(request)); if (is_lost()) return reinterpret_cast(""); auto reply = WebGLSyncCall::decode_reply(reply_bytes); auto resolved = WebGLCommandList::resolve_string_span(reply_bytes, reply.value); auto value = make(MUST(ByteBuffer::copy(resolved))); auto const* data = value->data(); m_string_cache.set(name, move(value)); return data; } void WebGLContextProxy::get_vertex_attrib_pointerv_robust_angle(GLuint index, GLenum pname, GLsizei bufSize, GLsizei* length, void** pointer) { (void)bufSize; SyncCalls::GetVertexAttribPointervRobustANGLE::Request request { .index = index, .pname = pname }; auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request(request)); if (is_lost()) return; auto reply = WebGLSyncCall::decode_reply(reply_bytes); if (length) *length = 1; if (pointer) *pointer = reinterpret_cast(static_cast(reply.pointer)); } void WebGLContextProxy::get_uniform_indices(GLuint program, GLsizei uniformCount, GLchar const* const* uniformNames, GLuint* uniformIndices) { auto names_bytes = pack_strings(uniformCount, uniformNames); SyncCalls::GetUniformIndices::Request request { .program = program, .uniform_count = uniformCount, .uniform_names = {} }; request.uniform_names = { WebGLCommandList::first_inline_data_offset(sizeof(request)), static_cast(names_bytes.size()) }; auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request(request, names_bytes)); if (is_lost()) return; auto reply = WebGLSyncCall::decode_reply(reply_bytes); if (uniformIndices) WebGLCommandList::copy_data_span(reply_bytes, reply.uniform_indices, { uniformIndices, static_cast(uniformCount) * sizeof(GLuint) }); } void* WebGLContextProxy::map_buffer_range(GLenum, GLintptr, GLsizeiptr, GLbitfield) { // getBufferSubData() goes through read_buffer_sub_data() instead; nothing else maps. VERIFY_NOT_REACHED(); } GLboolean WebGLContextProxy::unmap_buffer(GLenum) { VERIFY_NOT_REACHED(); } }