Moving WebGL execution into the Compositor needs a serializable command stream and a client-side proxy that can queue commands before sending them over IPC. The existing generator metadata only described direct GL wrappers, so generated code could not distinguish async commands from sync calls or object factory methods. This teaches the WebGL metadata and generators about command streams and adds the unused LibWeb proxy/list types. No rendering behavior changes yet; the later host wiring can build on these generated interfaces without mixing the metadata churn into that commit.
258 lines
10 KiB
C++
258 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, Painting::CanvasId canvas_id, WebGLVersion webgl_version, Vector<String> 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<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(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<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(m_canvas_id, 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();
|
|
}
|
|
|
|
}
|