ladybird/Libraries/LibWeb/WebGL/WebGLRenderingContextOverloads.cpp
Shannon Booth 97abc707c7 LibWeb/Bindings: Generate buffer typedefs as variants
Represent BufferSource and ArrayBufferView as ordinary IDL typedefs over
their underlying union types, instead of special casing in the IDL
generator. This allows the union conversion/return machinery handle
these types consistently with other typedefs, which removes buffer
specific paths from the IDL generator.

This necessitates changing the WebIDL::BufferSource and
WebIDL::ArrayBufferView classes as views over these variants. This
replaces the old GC backed BufferableObject wrapper structure and
provide convenience helpers to determine things such as the byte length,
byte offset, backing buffer, and typed-array APIs.
2026-05-30 11:22:08 +02:00

385 lines
14 KiB
C++

/*
* Copyright (c) 2024-2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
* Copyright (c) 2024-2025, Luke Wilde <luke@ladybird.org>
* Copyright (c) 2025, Undefine <undefine@undefine.pl>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#define GL_GLEXT_PROTOTYPES 1
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
extern "C" {
#include <GLES2/gl2ext_angle.h>
}
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/DataView.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/WebGL/OpenGLContext.h>
#include <LibWeb/WebGL/WebGLRenderingContextOverloads.h>
#include <LibWeb/WebGL/WebGLUniformLocation.h>
namespace Web::WebGL {
WebGLRenderingContextOverloads::WebGLRenderingContextOverloads(JS::Realm& realm, NonnullOwnPtr<OpenGLContext> context)
: WebGLRenderingContextImpl(realm, move(context))
{
}
void WebGLRenderingContextOverloads::buffer_data(WebIDL::UnsignedLong target, WebIDL::LongLong size, WebIDL::UnsignedLong usage)
{
m_context->make_current();
glBufferData(target, size, 0, usage);
}
void WebGLRenderingContextOverloads::buffer_data(WebIDL::UnsignedLong target, WebIDL::NullableBufferSourceVariant data, WebIDL::UnsignedLong usage)
{
m_context->make_current();
// https://registry.khronos.org/webgl/specs/latest/1.0/#5.14.5
// If the passed data is null then an INVALID_VALUE error is generated.
if (data.has<Empty>()) {
set_error(GL_INVALID_VALUE);
return;
}
auto span = MUST(get_offset_span<u8 const>(data.downcast<WebIDL::BufferSourceVariant>(), /* src_offset= */ 0));
glBufferData(target, static_cast<GLsizeiptr>(span.size()), span.data(), usage);
}
void WebGLRenderingContextOverloads::buffer_sub_data(WebIDL::UnsignedLong target, WebIDL::LongLong offset, WebIDL::BufferSource data)
{
m_context->make_current();
auto span = MUST(get_offset_span<u8 const>(data, /* src_offset= */ 0));
glBufferSubData(target, offset, span.size(), span.data());
}
void WebGLRenderingContextOverloads::compressed_tex_image2d(WebIDL::UnsignedLong target, WebIDL::Long level, WebIDL::UnsignedLong internalformat, WebIDL::Long width, WebIDL::Long height, WebIDL::Long border, WebIDL::ArrayBufferView data)
{
m_context->make_current();
if (!enabled_compressed_texture_formats().contains_slow(internalformat)) {
set_error(GL_INVALID_ENUM);
return;
}
auto span = MUST(get_offset_span<u8 const>(data, /* src_offset= */ 0));
glCompressedTexImage2DRobustANGLE(target, level, internalformat, width, height, border, span.size(), span.size(), span.data());
}
void WebGLRenderingContextOverloads::compressed_tex_sub_image2d(WebIDL::UnsignedLong target, WebIDL::Long level, WebIDL::Long xoffset, WebIDL::Long yoffset, WebIDL::Long width, WebIDL::Long height, WebIDL::UnsignedLong format, WebIDL::ArrayBufferView data)
{
m_context->make_current();
if (!enabled_compressed_texture_formats().contains_slow(format)) {
set_error(GL_INVALID_ENUM);
return;
}
auto span = MUST(get_offset_span<u8 const>(data, /* src_offset= */ 0));
glCompressedTexSubImage2DRobustANGLE(target, level, xoffset, yoffset, width, height, format, span.size(), span.size(), span.data());
}
void WebGLRenderingContextOverloads::read_pixels(WebIDL::Long x, WebIDL::Long y, WebIDL::Long width, WebIDL::Long height, WebIDL::UnsignedLong format, WebIDL::UnsignedLong type, WebIDL::NullableArrayBufferViewVariant pixels)
{
m_context->make_current();
if (pixels.has<Empty>()) {
set_error(GL_INVALID_VALUE);
return;
}
auto span = MUST(get_offset_span<u8>(pixels.downcast<WebIDL::ArrayBufferViewVariant>(), /* src_offset= */ 0));
glReadPixelsRobustANGLE(x, y, width, height, format, type, span.size(), nullptr, nullptr, nullptr, span.data());
}
void WebGLRenderingContextOverloads::tex_image2d(WebIDL::UnsignedLong target, WebIDL::Long level, WebIDL::Long internalformat, WebIDL::Long width, WebIDL::Long height, WebIDL::Long border, WebIDL::UnsignedLong format, WebIDL::UnsignedLong type, WebIDL::NullableArrayBufferViewVariant pixels)
{
m_context->make_current();
if (!pixels.has<Empty>()) {
auto span = MUST(get_offset_span<u8>(pixels.downcast<WebIDL::ArrayBufferViewVariant>(), /* src_offset= */ 0));
glTexImage2DRobustANGLE(target, level, internalformat, width, height, border, format, type, span.size(), span.data());
return;
}
Checked<size_t> bytes = 0;
if (type == GL_UNSIGNED_SHORT_5_6_5 && format != GL_RGB) {
set_error(GL_INVALID_OPERATION);
return;
}
if ((type == GL_UNSIGNED_SHORT_4_4_4_4 || type == GL_UNSIGNED_SHORT_5_5_5_1) && format != GL_RGBA) {
set_error(GL_INVALID_OPERATION);
return;
}
switch (format) {
case GL_ALPHA:
case GL_LUMINANCE:
case GL_LUMINANCE_ALPHA: {
if (type != GL_UNSIGNED_BYTE) {
set_error(GL_INVALID_ENUM);
return;
}
bytes = format == GL_LUMINANCE_ALPHA ? 2 : 1;
break;
}
case GL_RGB:
case GL_RGBA: {
switch (type) {
case GL_UNSIGNED_BYTE:
bytes = format == GL_RGB ? 3 : 4;
break;
case GL_UNSIGNED_SHORT_4_4_4_4:
case GL_UNSIGNED_SHORT_5_5_5_1:
case GL_UNSIGNED_SHORT_5_6_5:
bytes = 2;
break;
default:
set_error(GL_INVALID_ENUM);
return;
}
break;
}
default:
set_error(GL_INVALID_ENUM);
return;
}
bytes *= width;
bytes *= height;
if (bytes.has_overflow()) {
set_error(GL_INVALID_OPERATION);
return;
}
auto byte_buffer = MUST(ByteBuffer::create_zeroed(bytes.value_unchecked()));
glTexImage2DRobustANGLE(target, level, internalformat, width, height, border, format, type, byte_buffer.size(), byte_buffer.data());
}
void WebGLRenderingContextOverloads::tex_image2d(WebIDL::UnsignedLong target, WebIDL::Long level, WebIDL::Long internalformat, WebIDL::UnsignedLong format, WebIDL::UnsignedLong type, TexImageSource source)
{
m_context->make_current();
auto maybe_converted_texture = read_and_pixel_convert_texture_image_source(source, format, type);
if (!maybe_converted_texture.has_value())
return;
auto converted_texture = maybe_converted_texture.release_value();
glTexImage2DRobustANGLE(target, level, internalformat, converted_texture.width, converted_texture.height, 0, format, type, converted_texture.buffer.size(), converted_texture.buffer.data());
}
void WebGLRenderingContextOverloads::tex_sub_image2d(WebIDL::UnsignedLong target, WebIDL::Long level, WebIDL::Long xoffset, WebIDL::Long yoffset, WebIDL::Long width, WebIDL::Long height, WebIDL::UnsignedLong format, WebIDL::UnsignedLong type, WebIDL::NullableArrayBufferViewVariant pixels)
{
m_context->make_current();
if (pixels.has<Empty>()) {
set_error(GL_INVALID_VALUE);
return;
}
auto span = MUST(get_offset_span<u8>(pixels.downcast<WebIDL::ArrayBufferViewVariant>(), /* src_offset= */ 0));
glTexSubImage2DRobustANGLE(target, level, xoffset, yoffset, width, height, format, type, span.size(), span.data());
}
void WebGLRenderingContextOverloads::tex_sub_image2d(WebIDL::UnsignedLong target, WebIDL::Long level, WebIDL::Long xoffset, WebIDL::Long yoffset, WebIDL::UnsignedLong format, WebIDL::UnsignedLong type, TexImageSource source)
{
m_context->make_current();
auto maybe_converted_texture = read_and_pixel_convert_texture_image_source(source, format, type);
if (!maybe_converted_texture.has_value())
return;
auto converted_texture = maybe_converted_texture.release_value();
glTexSubImage2DRobustANGLE(target, level, xoffset, yoffset, converted_texture.width, converted_texture.height, format, type, converted_texture.buffer.size(), converted_texture.buffer.data());
}
void WebGLRenderingContextOverloads::uniform1fv(GC::Ptr<WebGLUniformLocation> location, Float32List v)
{
m_context->make_current();
if (!location)
return;
GLuint location_handle = SET_ERROR_VALUE_IF_ERROR(location->handle(m_current_program), GL_INVALID_OPERATION);
auto span = MUST(span_from_float32_list(v, /* src_offset= */ 0));
glUniform1fv(location_handle, span.size(), span.data());
}
void WebGLRenderingContextOverloads::uniform2fv(GC::Ptr<WebGLUniformLocation> location, Float32List v)
{
m_context->make_current();
if (!location)
return;
GLuint location_handle = SET_ERROR_VALUE_IF_ERROR(location->handle(m_current_program), GL_INVALID_OPERATION);
auto span = MUST(span_from_float32_list(v, /* src_offset= */ 0));
if (span.size() % 2 != 0) [[unlikely]] {
set_error(GL_INVALID_VALUE);
return;
}
glUniform2fv(location_handle, span.size() / 2, span.data());
}
void WebGLRenderingContextOverloads::uniform3fv(GC::Ptr<WebGLUniformLocation> location, Float32List v)
{
m_context->make_current();
if (!location)
return;
GLuint location_handle = SET_ERROR_VALUE_IF_ERROR(location->handle(m_current_program), GL_INVALID_OPERATION);
auto span = MUST(span_from_float32_list(v, /* src_offset= */ 0));
if (span.size() % 3 != 0) [[unlikely]] {
set_error(GL_INVALID_VALUE);
return;
}
glUniform3fv(location_handle, span.size() / 3, span.data());
}
void WebGLRenderingContextOverloads::uniform4fv(GC::Ptr<WebGLUniformLocation> location, Float32List v)
{
m_context->make_current();
if (!location)
return;
GLuint location_handle = SET_ERROR_VALUE_IF_ERROR(location->handle(m_current_program), GL_INVALID_OPERATION);
auto span = MUST(span_from_float32_list(v, /* src_offset= */ 0));
if (span.size() % 4 != 0) [[unlikely]] {
set_error(GL_INVALID_VALUE);
return;
}
glUniform4fv(location_handle, span.size() / 4, span.data());
}
void WebGLRenderingContextOverloads::uniform1iv(GC::Ptr<WebGLUniformLocation> location, Int32List v)
{
m_context->make_current();
if (!location)
return;
GLuint location_handle = SET_ERROR_VALUE_IF_ERROR(location->handle(m_current_program), GL_INVALID_OPERATION);
auto span = MUST(span_from_int32_list(v, /* src_offset= */ 0));
glUniform1iv(location_handle, span.size(), span.data());
}
void WebGLRenderingContextOverloads::uniform2iv(GC::Ptr<WebGLUniformLocation> location, Int32List v)
{
m_context->make_current();
if (!location)
return;
GLuint location_handle = SET_ERROR_VALUE_IF_ERROR(location->handle(m_current_program), GL_INVALID_OPERATION);
auto span = MUST(span_from_int32_list(v, /* src_offset= */ 0));
if (span.size() % 2 != 0) [[unlikely]] {
set_error(GL_INVALID_VALUE);
return;
}
glUniform2iv(location_handle, span.size() / 2, span.data());
}
void WebGLRenderingContextOverloads::uniform3iv(GC::Ptr<WebGLUniformLocation> location, Int32List v)
{
m_context->make_current();
if (!location)
return;
GLuint location_handle = SET_ERROR_VALUE_IF_ERROR(location->handle(m_current_program), GL_INVALID_OPERATION);
auto span = MUST(span_from_int32_list(v, /* src_offset= */ 0));
if (span.size() % 3 != 0) [[unlikely]] {
set_error(GL_INVALID_VALUE);
return;
}
glUniform3iv(location_handle, span.size() / 3, span.data());
}
void WebGLRenderingContextOverloads::uniform4iv(GC::Ptr<WebGLUniformLocation> location, Int32List v)
{
m_context->make_current();
if (!location)
return;
GLuint location_handle = SET_ERROR_VALUE_IF_ERROR(location->handle(m_current_program), GL_INVALID_OPERATION);
auto span = MUST(span_from_int32_list(v, /* src_offset= */ 0));
if (span.size() % 4 != 0) [[unlikely]] {
set_error(GL_INVALID_VALUE);
return;
}
glUniform4iv(location_handle, span.size() / 4, span.data());
}
void WebGLRenderingContextOverloads::uniform_matrix2fv(GC::Ptr<WebGLUniformLocation> location, bool transpose, Float32List value)
{
m_context->make_current();
if (!location)
return;
GLuint location_handle = SET_ERROR_VALUE_IF_ERROR(location->handle(m_current_program), GL_INVALID_OPERATION);
constexpr auto matrix_size = 2 * 2;
auto span = MUST(span_from_float32_list(value, /* src_offset= */ 0));
if (span.size() % matrix_size != 0) [[unlikely]] {
set_error(GL_INVALID_VALUE);
return;
}
glUniformMatrix2fv(location_handle, span.size() / matrix_size, transpose, span.data());
}
void WebGLRenderingContextOverloads::uniform_matrix3fv(GC::Ptr<WebGLUniformLocation> location, bool transpose, Float32List value)
{
m_context->make_current();
if (!location)
return;
GLuint location_handle = SET_ERROR_VALUE_IF_ERROR(location->handle(m_current_program), GL_INVALID_OPERATION);
constexpr auto matrix_size = 3 * 3;
auto span = MUST(span_from_float32_list(value, /* src_offset= */ 0));
if (span.size() % matrix_size != 0) [[unlikely]] {
set_error(GL_INVALID_VALUE);
return;
}
glUniformMatrix3fv(location_handle, span.size() / matrix_size, transpose, span.data());
}
void WebGLRenderingContextOverloads::uniform_matrix4fv(GC::Ptr<WebGLUniformLocation> location, bool transpose, Float32List value)
{
m_context->make_current();
if (!location)
return;
GLuint location_handle = SET_ERROR_VALUE_IF_ERROR(location->handle(m_current_program), GL_INVALID_OPERATION);
constexpr auto matrix_size = 4 * 4;
auto span = MUST(span_from_float32_list(value, /* src_offset= */ 0));
if (span.size() % matrix_size != 0) [[unlikely]] {
set_error(GL_INVALID_VALUE);
return;
}
glUniformMatrix4fv(location_handle, span.size() / matrix_size, transpose, span.data());
}
}