Canvas rendering is a major remaining path where WebContent directly owns GPU-facing drawing state. Back 2D and WebGL canvas contexts with remote Compositor transports, so WebContent talks to canvas surfaces through IPC while the Compositor owns the rasterization resources. This is a large step toward GPU sandboxing because canvas GPU work now lives behind the Compositor boundary. It also gives OffscreenCanvas the process-independent canvas plumbing that HTMLCanvasElement now uses, making worker-owned canvases possible without another WebContent-local rendering path.
257 lines
10 KiB
C++
257 lines
10 KiB
C++
/*
|
|
* Copyright (c) 2026, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/StringBuilder.h>
|
|
#include <LibCore/AnonymousBuffer.h>
|
|
#include <LibGfx/Bitmap.h>
|
|
#include <LibGfx/PaintingSurface.h>
|
|
#include <LibWeb/WebGL/WebGLContextProxy.h>
|
|
#include <LibWeb/WebGL/WebGLContextProxyBase.h>
|
|
|
|
namespace Web::WebGL {
|
|
|
|
WebGLContextProxyBase::WebGLContextProxyBase(NonnullRefPtr<RemoteWebGLTransport> transport, WebGLVersion webgl_version, Vector<String> supported_extensions)
|
|
: m_transport(move(transport))
|
|
, m_webgl_version(webgl_version)
|
|
, m_supported_extensions(move(supported_extensions))
|
|
{
|
|
}
|
|
|
|
WebGLContextProxyBase::~WebGLContextProxyBase()
|
|
{
|
|
m_transport->destroy_context();
|
|
}
|
|
|
|
void WebGLContextProxyBase::flush_commands()
|
|
{
|
|
if (m_commands.is_empty())
|
|
return;
|
|
m_transport->send_commands(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(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(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(preserve_drawing_buffer);
|
|
}
|
|
|
|
RefPtr<Gfx::Bitmap> WebGLContextProxyBase::read_back_drawing_buffer(Gfx::IntRect const& rect)
|
|
{
|
|
if (m_lost)
|
|
return nullptr;
|
|
flush_commands();
|
|
auto bitmap = m_transport->read_back_drawing_buffer(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<GLintptr>(offset),
|
|
});
|
|
}
|
|
|
|
void WebGLContextProxyBase::tex_image2d_from_bitmap(GLenum target, GLint level, GLint internalformat, GLenum format, GLenum type, Gfx::DecodedImageFrame frame, Optional<Gfx::IntSize> destination_size, bool flip_y, bool premultiply_alpha)
|
|
{
|
|
if (m_lost)
|
|
return;
|
|
auto bitmap_index = static_cast<u32>(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<Gfx::IntSize> destination_size, bool flip_y, bool premultiply_alpha)
|
|
{
|
|
if (m_lost)
|
|
return;
|
|
auto bitmap_index = static_cast<u32>(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(target, static_cast<GLintptr>(offset), static_cast<GLintptr>(destination.size()), shared_data);
|
|
if (m_lost)
|
|
return;
|
|
__builtin_memcpy(destination.data(), shared_data.data<void>(), 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<size_t>(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<u32>(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<u32>(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<size_t>(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<void>(), static_cast<size_t>(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<SyncCalls::GetString>(request));
|
|
if (is_lost())
|
|
return reinterpret_cast<GLubyte const*>("");
|
|
auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetString::Reply>(reply_bytes);
|
|
auto resolved = WebGLCommandList::resolve_string_span(reply_bytes, reply.value);
|
|
auto value = make<ByteBuffer>(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<SyncCalls::GetVertexAttribPointervRobustANGLE>(request));
|
|
if (is_lost())
|
|
return;
|
|
auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetVertexAttribPointervRobustANGLE::Reply>(reply_bytes);
|
|
if (length)
|
|
*length = 1;
|
|
if (pointer)
|
|
*pointer = reinterpret_cast<void*>(static_cast<uintptr_t>(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<u32>(names_bytes.size()) };
|
|
auto reply_bytes = send_sync_call(WebGLSyncCall::encode_request<SyncCalls::GetUniformIndices>(request, names_bytes));
|
|
if (is_lost())
|
|
return;
|
|
auto reply = WebGLSyncCall::decode_reply<SyncCalls::GetUniformIndices::Reply>(reply_bytes);
|
|
if (uniformIndices)
|
|
WebGLCommandList::copy_data_span(reply_bytes, reply.uniform_indices, { uniformIndices, static_cast<size_t>(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();
|
|
}
|
|
|
|
}
|