/* * Copyright (c) 2024, Aliaksandr Kalenik * Copyright (c) 2024, Luke Wilde * * SPDX-License-Identifier: BSD-2-Clause */ #include "BindingsGenerator/IDLGenerators.h" #include #include #include #include #include #include #include static bool is_webgl_object_type(StringView type_name) { return type_name == "WebGLBuffer"sv || type_name == "WebGLFramebuffer"sv || type_name == "WebGLProgram"sv || type_name == "WebGLRenderbuffer"sv || type_name == "WebGLSampler"sv || type_name == "WebGLShader"sv || type_name == "WebGLTexture"sv || type_name == "WebGLVertexArrayObject"sv; } static bool gl_function_modifies_framebuffer(StringView function_name) { return function_name == "clear"sv || function_name == "clearBufferfv"sv || function_name == "clearBufferiv"sv || function_name == "clearBufferuiv"sv || function_name == "clearBufferfi"sv || function_name == "drawArrays"sv || function_name == "drawElements"sv || function_name == "drawElementsInstanced"sv || function_name == "blitFramebuffer"sv || function_name == "invalidateFramebuffer"sv; } static ByteString to_cpp_type(const IDL::Type& type, const IDL::Interface& interface) { if (type.name() == "undefined"sv) return "void"sv; if (type.name() == "object"sv) { if (type.is_nullable()) return "JS::Object*"sv; return "JS::Object&"sv; } if (type.name() == "DOMString"sv) { if (type.is_nullable()) return "Optional"sv; return "String"sv; } auto cpp_type = idl_type_name_to_cpp_type(type, interface); return cpp_type.name; } static ByteString idl_to_gl_function_name(StringView function_name) { StringBuilder gl_function_name_builder; gl_function_name_builder.append("gl"sv); for (size_t i = 0; i < function_name.length(); ++i) { if (i == 0) { gl_function_name_builder.append(to_ascii_uppercase(function_name[i])); } else { gl_function_name_builder.append(function_name[i]); } } if (function_name == "clearDepth"sv || function_name == "depthRange"sv) { gl_function_name_builder.append("f"sv); } return gl_function_name_builder.to_byte_string(); } struct NameAndType { StringView name; struct { StringView type; int element_count { 0 }; } return_type; Optional webgl_version { OptionalNone {} }; }; static void generate_get_parameter(SourceGenerator& generator, int webgl_version) { Vector const name_to_type = { { "ACTIVE_TEXTURE"sv, { "GLenum"sv } }, { "ALIASED_LINE_WIDTH_RANGE"sv, { "Float32Array"sv, 2 } }, { "ALIASED_POINT_SIZE_RANGE"sv, { "Float32Array"sv, 2 } }, { "ALPHA_BITS"sv, { "GLint"sv } }, { "ARRAY_BUFFER_BINDING"sv, { "WebGLBuffer"sv } }, { "BLEND"sv, { "GLboolean"sv } }, { "BLEND_COLOR"sv, { "Float32Array"sv, 4 } }, { "BLEND_DST_ALPHA"sv, { "GLenum"sv } }, { "BLEND_DST_RGB"sv, { "GLenum"sv } }, { "BLEND_EQUATION_ALPHA"sv, { "GLenum"sv } }, { "BLEND_EQUATION_RGB"sv, { "GLenum"sv } }, { "BLEND_SRC_ALPHA"sv, { "GLenum"sv } }, { "BLEND_SRC_RGB"sv, { "GLenum"sv } }, { "BLUE_BITS"sv, { "GLint"sv } }, { "COLOR_CLEAR_VALUE"sv, { "Float32Array"sv, 4 } }, // FIXME: { "COLOR_WRITEMASK"sv, { "sequence"sv, 4 } }, // FIXME: { "COMPRESSED_TEXTURE_FORMATS"sv, { "Uint32Array"sv } }, { "CULL_FACE"sv, { "GLboolean"sv } }, { "CULL_FACE_MODE"sv, { "GLenum"sv } }, { "CURRENT_PROGRAM"sv, { "WebGLProgram"sv } }, { "DEPTH_BITS"sv, { "GLint"sv } }, { "DEPTH_CLEAR_VALUE"sv, { "GLfloat"sv } }, { "DEPTH_FUNC"sv, { "GLenum"sv } }, { "DEPTH_RANGE"sv, { "Float32Array"sv, 2 } }, { "DEPTH_TEST"sv, { "GLboolean"sv } }, { "DEPTH_WRITEMASK"sv, { "GLboolean"sv } }, { "DITHER"sv, { "GLboolean"sv } }, { "ELEMENT_ARRAY_BUFFER_BINDING"sv, { "WebGLBuffer"sv } }, { "FRAMEBUFFER_BINDING"sv, { "WebGLFramebuffer"sv } }, { "FRONT_FACE"sv, { "GLenum"sv } }, { "GENERATE_MIPMAP_HINT"sv, { "GLenum"sv } }, { "GREEN_BITS"sv, { "GLint"sv } }, { "IMPLEMENTATION_COLOR_READ_FORMAT"sv, { "GLenum"sv } }, { "IMPLEMENTATION_COLOR_READ_TYPE"sv, { "GLenum"sv } }, { "LINE_WIDTH"sv, { "GLfloat"sv } }, { "MAX_COMBINED_TEXTURE_IMAGE_UNITS"sv, { "GLint"sv } }, { "MAX_CUBE_MAP_TEXTURE_SIZE"sv, { "GLint"sv } }, { "MAX_FRAGMENT_UNIFORM_VECTORS"sv, { "GLint"sv } }, { "MAX_RENDERBUFFER_SIZE"sv, { "GLint"sv } }, { "MAX_TEXTURE_IMAGE_UNITS"sv, { "GLint"sv } }, { "MAX_TEXTURE_SIZE"sv, { "GLint"sv } }, { "MAX_VARYING_VECTORS"sv, { "GLint"sv } }, { "MAX_VERTEX_ATTRIBS"sv, { "GLint"sv } }, { "MAX_VERTEX_TEXTURE_IMAGE_UNITS"sv, { "GLint"sv } }, { "MAX_VERTEX_UNIFORM_VECTORS"sv, { "GLint"sv } }, { "MAX_VIEWPORT_DIMS"sv, { "Int32Array"sv, 2 } }, { "PACK_ALIGNMENT"sv, { "GLint"sv } }, { "POLYGON_OFFSET_FACTOR"sv, { "GLfloat"sv } }, { "POLYGON_OFFSET_FILL"sv, { "GLboolean"sv } }, { "POLYGON_OFFSET_UNITS"sv, { "GLfloat"sv } }, { "RED_BITS"sv, { "GLint"sv } }, { "RENDERBUFFER_BINDING"sv, { "WebGLRenderbuffer"sv } }, { "RENDERER"sv, { "DOMString"sv } }, { "SAMPLE_ALPHA_TO_COVERAGE"sv, { "GLboolean"sv } }, { "SAMPLE_BUFFERS"sv, { "GLint"sv } }, { "SAMPLE_COVERAGE"sv, { "GLboolean"sv } }, { "SAMPLE_COVERAGE_INVERT"sv, { "GLboolean"sv } }, { "SAMPLE_COVERAGE_VALUE"sv, { "GLfloat"sv } }, { "SAMPLES"sv, { "GLint"sv } }, { "SCISSOR_BOX"sv, { "Int32Array"sv, 4 } }, { "SCISSOR_TEST"sv, { "GLboolean"sv } }, { "SHADING_LANGUAGE_VERSION"sv, { "DOMString"sv } }, { "STENCIL_BACK_FAIL"sv, { "GLenum"sv } }, { "STENCIL_BACK_FUNC"sv, { "GLenum"sv } }, { "STENCIL_BACK_PASS_DEPTH_FAIL"sv, { "GLenum"sv } }, { "STENCIL_BACK_PASS_DEPTH_PASS"sv, { "GLenum"sv } }, { "STENCIL_BACK_REF"sv, { "GLint"sv } }, { "STENCIL_BACK_VALUE_MASK"sv, { "GLuint"sv } }, { "STENCIL_BACK_WRITEMASK"sv, { "GLuint"sv } }, { "STENCIL_BITS"sv, { "GLint"sv } }, { "STENCIL_CLEAR_VALUE"sv, { "GLint"sv } }, { "STENCIL_FAIL"sv, { "GLenum"sv } }, { "STENCIL_FUNC"sv, { "GLenum"sv } }, { "STENCIL_PASS_DEPTH_FAIL"sv, { "GLenum"sv } }, { "STENCIL_PASS_DEPTH_PASS"sv, { "GLenum"sv } }, { "STENCIL_REF"sv, { "GLint"sv } }, { "STENCIL_TEST"sv, { "GLboolean"sv } }, { "STENCIL_VALUE_MASK"sv, { "GLuint"sv } }, { "STENCIL_WRITEMASK"sv, { "GLuint"sv } }, { "SUBPIXEL_BITS"sv, { "GLint"sv } }, { "TEXTURE_BINDING_2D"sv, { "WebGLTexture"sv } }, { "TEXTURE_BINDING_CUBE_MAP"sv, { "WebGLTexture"sv } }, { "UNPACK_ALIGNMENT"sv, { "GLint"sv } }, // FIXME: { "UNPACK_COLORSPACE_CONVERSION_WEBGL"sv, { "GLenum"sv } }, // FIXME: { "UNPACK_FLIP_Y_WEBGL"sv, { "GLboolean"sv } }, // FIXME: { "UNPACK_PREMULTIPLY_ALPHA_WEBGL"sv, { "GLboolean"sv } }, { "VENDOR"sv, { "DOMString"sv } }, { "VERSION"sv, { "DOMString"sv } }, { "VIEWPORT"sv, { "Int32Array"sv, 4 } }, { "MAX_SAMPLES"sv, { "GLint"sv }, 2 }, { "MAX_3D_TEXTURE_SIZE"sv, { "GLint"sv }, 2 }, { "MAX_ARRAY_TEXTURE_LAYERS"sv, { "GLint"sv }, 2 }, { "MAX_COLOR_ATTACHMENTS"sv, { "GLint"sv }, 2 }, { "MAX_VERTEX_UNIFORM_COMPONENTS"sv, { "GLint"sv }, 2 }, { "MAX_UNIFORM_BLOCK_SIZE"sv, { "GLint64"sv }, 2 }, { "MAX_UNIFORM_BUFFER_BINDINGS"sv, { "GLint"sv }, 2 }, { "UNIFORM_BUFFER_OFFSET_ALIGNMENT"sv, { "GLint"sv }, 2 }, { "MAX_DRAW_BUFFERS"sv, { "GLint"sv }, 2 }, { "MAX_VERTEX_UNIFORM_BLOCKS"sv, { "GLint"sv }, 2 }, { "MAX_FRAGMENT_INPUT_COMPONENTS"sv, { "GLint"sv }, 2 }, { "MAX_COMBINED_UNIFORM_BLOCKS"sv, { "GLint"sv }, 2 }, { "UNIFORM_BUFFER_BINDING"sv, { "WebGLBuffer"sv }, 2 }, { "TEXTURE_BINDING_2D_ARRAY"sv, { "WebGLTexture"sv }, 2 }, { "COPY_READ_BUFFER_BINDING"sv, { "WebGLBuffer"sv }, 2 }, { "COPY_WRITE_BUFFER_BINDING"sv, { "WebGLBuffer"sv }, 2 }, { "MAX_ELEMENT_INDEX"sv, { "GLint64"sv }, 2 }, { "MAX_FRAGMENT_UNIFORM_BLOCKS"sv, { "GLint"sv }, 2 }, { "MAX_VARYING_COMPONENTS"sv, { "GLint"sv }, 2 }, }; auto is_primitive_type = [](StringView type) { return type == "GLboolean"sv || type == "GLint"sv || type == "GLfloat"sv || type == "GLenum"sv || type == "GLuint"sv; }; generator.append(" switch (pname) {"); for (auto const& name_and_type : name_to_type) { if (name_and_type.webgl_version.has_value() && name_and_type.webgl_version.value() != webgl_version) continue; auto const& parameter_name = name_and_type.name; auto const& type_name = name_and_type.return_type.type; StringBuilder string_builder; SourceGenerator impl_generator { string_builder }; impl_generator.set("parameter_name", parameter_name); impl_generator.set("type_name", type_name); impl_generator.append(R"~~~( case GL_@parameter_name@: {)~~~"); if (is_primitive_type(type_name)) { impl_generator.append(R"~~~( GLint result; glGetIntegerv(GL_@parameter_name@, &result); return JS::Value(result); )~~~"); } else if (type_name == "GLint64"sv) { impl_generator.append(R"~~~( GLint64 result; glGetInteger64v(GL_@parameter_name@, &result); return JS::Value(static_cast(result)); )~~~"); } else if (type_name == "DOMString"sv) { impl_generator.append(R"~~~( auto result = reinterpret_cast(glGetString(GL_@parameter_name@)); return JS::PrimitiveString::create(m_realm->vm(), ByteString { result });)~~~"); } else if (type_name == "Float32Array"sv || type_name == "Int32Array"sv) { auto element_count = name_and_type.return_type.element_count; impl_generator.set("element_count", MUST(String::formatted("{}", element_count))); if (type_name == "Int32Array"sv) { impl_generator.set("gl_function_name", "glGetIntegerv"sv); impl_generator.set("element_type", "GLint"sv); } else if (type_name == "Float32Array"sv) { impl_generator.set("gl_function_name", "glGetFloatv"sv); impl_generator.set("element_type", "GLfloat"sv); } else { VERIFY_NOT_REACHED(); } impl_generator.append(R"~~~( Array<@element_type@, @element_count@> result; @gl_function_name@(GL_@parameter_name@, result.data()); auto byte_buffer = MUST(ByteBuffer::copy(result.data(), @element_count@ * sizeof(@element_type@))); auto array_buffer = JS::ArrayBuffer::create(m_realm, move(byte_buffer)); return JS::@type_name@::create(m_realm, @element_count@, array_buffer); )~~~"); } else if (type_name == "WebGLProgram"sv || type_name == "WebGLBuffer"sv || type_name == "WebGLTexture"sv || type_name == "WebGLFramebuffer"sv || type_name == "WebGLRenderbuffer"sv) { impl_generator.set("stored_name", name_and_type.name.to_lowercase_string()); impl_generator.append(R"~~~( if (!m_@stored_name@) return JS::js_null(); return JS::Value(m_@stored_name@); )~~~"); } else { VERIFY_NOT_REACHED(); } impl_generator.append(" }"); generator.append(string_builder.string_view()); } generator.appendln(R"~~~( default: dbgln("Unknown WebGL parameter name: {:x}", pname); set_error(GL_INVALID_ENUM); return JS::js_null(); })~~~"); } static void generate_get_buffer_parameter(SourceGenerator& generator) { Vector const name_to_type = { { "BUFFER_SIZE"sv, { "GLint"sv } }, { "BUFFER_USAGE"sv, { "GLenum"sv } }, }; generator.append(" switch (pname) {"); for (auto const& name_and_type : name_to_type) { auto const& parameter_name = name_and_type.name; auto const& type_name = name_and_type.return_type.type; StringBuilder string_builder; SourceGenerator impl_generator { string_builder }; impl_generator.set("parameter_name", parameter_name); impl_generator.set("type_name", type_name); impl_generator.append(R"~~~( case GL_@parameter_name@: { GLint result; glGetBufferParameteriv(target, GL_@parameter_name@, &result); return JS::Value(result); } )~~~"); generator.append(string_builder.string_view()); } generator.appendln(R"~~~( default: dbgln("Unknown WebGL buffer parameter name: {:x}", pname); set_error(GL_INVALID_ENUM); return JS::js_null(); })~~~"); } static void generate_get_internal_format_parameter(SourceGenerator& generator) { generator.append(R"~~~( switch (pname) { case GL_SAMPLES: { GLint num_sample_counts { 0 }; glGetInternalformativ(target, internalformat, GL_NUM_SAMPLE_COUNTS, 1, &num_sample_counts); auto samples_buffer = MUST(ByteBuffer::create_zeroed(num_sample_counts * sizeof(GLint))); glGetInternalformativ(target, internalformat, GL_SAMPLES, num_sample_counts, reinterpret_cast(samples_buffer.data())); auto array_buffer = JS::ArrayBuffer::create(m_realm, move(samples_buffer)); return JS::Int32Array::create(m_realm, num_sample_counts, array_buffer); } default: dbgln("Unknown WebGL internal format parameter name: {:x}", pname); set_error(GL_INVALID_ENUM); return JS::js_null(); } )~~~"); } static void generate_webgl_object_handle_unwrap(SourceGenerator& generator, StringView object_name, StringView early_return_value) { StringBuilder string_builder; SourceGenerator unwrap_generator { string_builder }; unwrap_generator.set("object_name", object_name); unwrap_generator.set("early_return_value", early_return_value); unwrap_generator.append(R"~~~( GLuint @object_name@_handle = 0; if (@object_name@) { auto handle_or_error = @object_name@->handle(this); if (handle_or_error.is_error()) { set_error(GL_INVALID_OPERATION); return @early_return_value@; } @object_name@_handle = handle_or_error.release_value(); } )~~~"); generator.append(unwrap_generator.as_string_view()); } static void generate_get_active_uniform_block_parameter(SourceGenerator& generator) { generate_webgl_object_handle_unwrap(generator, "program"sv, "JS::js_null()"sv); generator.append(R"~~~( switch (pname) { case GL_UNIFORM_BLOCK_BINDING: case GL_UNIFORM_BLOCK_DATA_SIZE: case GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS: { GLint result = 0; glGetActiveUniformBlockiv(program_handle, uniform_block_index, pname, &result); return JS::Value(result); } case GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES: { GLint num_active_uniforms = 0; glGetActiveUniformBlockiv(program_handle, uniform_block_index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &num_active_uniforms); auto active_uniform_indices_buffer = MUST(ByteBuffer::create_zeroed(num_active_uniforms * sizeof(GLint))); glGetActiveUniformBlockiv(program_handle, uniform_block_index, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, reinterpret_cast(active_uniform_indices_buffer.data())); auto array_buffer = JS::ArrayBuffer::create(m_realm, move(active_uniform_indices_buffer)); return JS::Uint32Array::create(m_realm, num_active_uniforms, array_buffer); } case GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER: case GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER: { GLint result = 0; glGetActiveUniformBlockiv(program_handle, uniform_block_index, pname, &result); return JS::Value(result == GL_TRUE); } default: dbgln("Unknown WebGL active uniform block parameter name: {:x}", pname); set_error(GL_INVALID_ENUM); return JS::js_null(); } )~~~"); } ErrorOr serenity_main(Main::Arguments arguments) { StringView generated_header_path; StringView generated_implementation_path; Vector base_paths; StringView webgl_context_idl_path; Core::ArgsParser args_parser; args_parser.add_option(webgl_context_idl_path, "Path to the WebGLRenderingContext idl file", "webgl-idl-path", 'i', "webgl-idl-path"); args_parser.add_option(Core::ArgsParser::Option { .argument_mode = Core::ArgsParser::OptionArgumentMode::Required, .help_string = "Path to root of IDL file tree(s)", .long_name = "base-path", .short_name = 'b', .value_name = "base-path", .accept_value = [&](StringView s) { base_paths.append(s); return true; }, }); args_parser.add_option(generated_header_path, "Path to the header file to generate", "generated-header-path", 'h', "generated-header-path"); args_parser.add_option(generated_implementation_path, "Path to the implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path"); args_parser.parse(arguments); auto generated_header_file = TRY(Core::File::open(generated_header_path, Core::File::OpenMode::Write)); auto generated_implementation_file = TRY(Core::File::open(generated_implementation_path, Core::File::OpenMode::Write)); auto idl_file = MUST(Core::File::open(webgl_context_idl_path, Core::File::OpenMode::Read)); auto webgl_context_idl_file_content = MUST(idl_file->read_until_eof()); Vector import_base_paths; for (auto const& base_path : base_paths) { VERIFY(!base_path.is_empty()); import_base_paths.append(base_path); } IDL::Parser parser(webgl_context_idl_path, StringView(webgl_context_idl_file_content), import_base_paths); auto const& interface = parser.parse(); auto path = LexicalPath(generated_header_path); auto title = path.title(); auto first_dot = title.find('.'); ByteString class_name = title; if (first_dot.has_value()) class_name = title.substring_view(0, *first_dot); StringBuilder header_file_string_builder; SourceGenerator header_file_generator { header_file_string_builder }; header_file_generator.set("class_name", class_name); StringBuilder implementation_file_string_builder; SourceGenerator implementation_file_generator { implementation_file_string_builder }; implementation_file_generator.set("class_name", class_name); auto webgl_version = class_name == "WebGLRenderingContextImpl" ? 1 : 2; if (webgl_version == 1) { implementation_file_generator.append(R"~~~( #include #include )~~~"); } else { implementation_file_generator.append(R"~~~( #include )~~~"); } implementation_file_generator.append(R"~~~( #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Web::WebGL { static Vector null_terminated_string(StringView string) { Vector result; for (auto c : string.bytes()) result.append(c); result.append('\\0'); return result; } @class_name@::@class_name@(JS::Realm& realm, NonnullOwnPtr context) : m_realm(realm) , m_context(move(context)) { } )~~~"); header_file_generator.append(R"~~~( #pragma once #include #include #include #include #include #include #include namespace Web::WebGL { using namespace Web::HTML; class @class_name@ : public WebGLRenderingContextBase { public: @class_name@(JS::Realm&, NonnullOwnPtr); OpenGLContext& context() { return *m_context; } virtual void present() = 0; virtual void needs_to_present() = 0; virtual void set_error(GLenum) = 0; )~~~"); for (auto const& function : interface.functions) { if (function.extended_attributes.contains("FIXME")) { continue; } if (function.name == "getSupportedExtensions"sv || function.name == "getExtension"sv || function.name == "getContextAttributes"sv || function.name == "isContextLost"sv) { // Implemented in WebGLRenderingContext continue; } StringBuilder function_declaration; StringBuilder function_parameters; for (size_t i = 0; i < function.parameters.size(); ++i) { auto const& parameter = function.parameters[i]; function_parameters.append(to_cpp_type(*parameter.type, interface)); function_parameters.append(" "sv); function_parameters.append(parameter.name.to_snakecase()); if (i != function.parameters.size() - 1) { function_parameters.append(", "sv); } } auto function_name = function.name.to_snakecase(); function_declaration.append(to_cpp_type(*function.return_type, interface)); function_declaration.append(" "sv); function_declaration.append(function_name); function_declaration.append("("sv); function_declaration.append(function_parameters.string_view()); function_declaration.append(");"sv); header_file_generator.append(" "sv); header_file_generator.append(function_declaration.string_view()); header_file_generator.append("\n"sv); StringBuilder function_impl; SourceGenerator function_impl_generator { function_impl }; function_impl_generator.set("class_name", class_name); ScopeGuard function_guard { [&] { function_impl_generator.append("}\n"sv); implementation_file_generator.append(function_impl_generator.as_string_view().bytes()); } }; function_impl_generator.set("function_name", function_name); function_impl_generator.set("function_parameters", function_parameters.string_view()); function_impl_generator.set("function_return_type", to_cpp_type(*function.return_type, interface)); function_impl_generator.append(R"~~~( @function_return_type@ @class_name@::@function_name@(@function_parameters@) { m_context->make_current(); )~~~"); if (gl_function_modifies_framebuffer(function.name)) { function_impl_generator.append(" m_context->notify_content_will_change();\n"sv); } if (function.name == "getUniformLocation"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "program"sv, "{}"sv); function_impl_generator.append(R"~~~( auto name_null_terminated = null_terminated_string(name); return WebGLUniformLocation::create(m_realm, glGetUniformLocation(program_handle, name_null_terminated.data())); )~~~"); continue; } if (function.name == "createBuffer"sv) { function_impl_generator.append(R"~~~( GLuint handle = 0; glGenBuffers(1, &handle); return WebGLBuffer::create(m_realm, *this, handle); )~~~"); continue; } if (function.name == "createTexture"sv) { function_impl_generator.append(R"~~~( GLuint handle = 0; glGenTextures(1, &handle); return WebGLTexture::create(m_realm, *this, handle); )~~~"); continue; } if (function.name == "createFramebuffer"sv) { function_impl_generator.append(R"~~~( GLuint handle = 0; glGenFramebuffers(1, &handle); return WebGLFramebuffer::create(m_realm, *this, handle); )~~~"); continue; } if (function.name == "createRenderbuffer"sv) { function_impl_generator.append(R"~~~( GLuint handle = 0; glGenRenderbuffers(1, &handle); return WebGLRenderbuffer::create(m_realm, *this, handle); )~~~"); continue; } if (function.name == "invalidateFramebuffer"sv) { function_impl_generator.append(R"~~~( glInvalidateFramebuffer(target, attachments.size(), attachments.data()); needs_to_present(); )~~~"); continue; } if (function.name == "createVertexArray"sv) { function_impl_generator.append(R"~~~( GLuint handle = 0; glGenVertexArrays(1, &handle); return WebGLVertexArrayObject::create(m_realm, *this, handle); )~~~"); continue; } if (function.name == "createSampler"sv) { function_impl_generator.append(R"~~~( GLuint handle = 0; glGenSamplers(1, &handle); return WebGLSampler::create(m_realm, *this, handle); )~~~"); continue; } if (function.name == "fenceSync"sv) { function_impl_generator.append(R"~~~( GLsync handle = glFenceSync(condition, flags); return WebGLSync::create(m_realm, *this, handle); )~~~"); continue; } if (function.name == "shaderSource"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "shader"sv, ""sv); function_impl_generator.append(R"~~~( Vector strings; auto string = null_terminated_string(source); strings.append(string.data()); Vector length; length.append(source.bytes().size()); glShaderSource(shader_handle, 1, strings.data(), length.data()); )~~~"); continue; } if (function.name == "vertexAttribPointer"sv) { function_impl_generator.append(R"~~~( glVertexAttribPointer(index, size, type, normalized, stride, reinterpret_cast(offset)); )~~~"); continue; } if (function.name == "texStorage2D") { function_impl_generator.append(R"~~~( glTexStorage2D(target, levels, internalformat, width, height); )~~~"); continue; } if (function.name == "texImage2D"sv && function.overload_index == 0) { function_impl_generator.append(R"~~~( void const* pixels_ptr = nullptr; if (pixels) { auto const& viewed_array_buffer = pixels->viewed_array_buffer(); auto const& byte_buffer = viewed_array_buffer->buffer(); pixels_ptr = byte_buffer.data(); } glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_ptr); )~~~"); continue; } if (function.name == "texImage3D"sv && function.overload_index == 0) { // FIXME: If a WebGLBuffer is bound to the PIXEL_UNPACK_BUFFER target, generates an INVALID_OPERATION error. // FIXME: If srcData is null, a buffer of sufficient size initialized to 0 is passed. // FIXME: If type is specified as FLOAT_32_UNSIGNED_INT_24_8_REV, srcData must be null; otherwise, generates an INVALID_OPERATION error. // FIXME: If srcData is non-null, the type of srcData must match the type according to the above table; otherwise, generate an INVALID_OPERATION error. // FIXME: If an attempt is made to call this function with no WebGLTexture bound (see above), generates an INVALID_OPERATION error. // FIXME: If there's not enough data in srcData starting at srcOffset, generate INVALID_OPERATION. function_impl_generator.append(R"~~~( void const* src_data_ptr = nullptr; if (src_data) { auto const& viewed_array_buffer = src_data->viewed_array_buffer(); auto const& byte_buffer = viewed_array_buffer->buffer(); src_data_ptr = byte_buffer.data(); } glTexImage3D(target, level, internalformat, width, height, depth, border, format, type, src_data_ptr); )~~~"); continue; } if (function.name == "texImage3D"sv && function.overload_index == 1) { // FIXME: If a WebGLBuffer is bound to the PIXEL_UNPACK_BUFFER target, generates an INVALID_OPERATION error. // FIXME: If srcData is null, a buffer of sufficient size initialized to 0 is passed. // FIXME: If type is specified as FLOAT_32_UNSIGNED_INT_24_8_REV, srcData must be null; otherwise, generates an INVALID_OPERATION error. // FIXME: If srcData is non-null, the type of srcData must match the type according to the above table; otherwise, generate an INVALID_OPERATION error. // FIXME: If an attempt is made to call this function with no WebGLTexture bound (see above), generates an INVALID_OPERATION error. // FIXME: If there's not enough data in srcData starting at srcOffset, generate INVALID_OPERATION. function_impl_generator.append(R"~~~( void const* src_data_ptr = nullptr; if (src_data) { auto const& viewed_array_buffer = src_data->viewed_array_buffer(); auto const& byte_buffer = viewed_array_buffer->buffer(); src_data_ptr = byte_buffer.data() + src_offset; } glTexImage3D(target, level, internalformat, width, height, depth, border, format, type, src_data_ptr); )~~~"); continue; } if (function.name == "texImage2D"sv && (function.overload_index == 1 || (webgl_version == 2 && function.overload_index == 2))) { // FIXME: If this function is called with an ImageData whose data attribute has been neutered, // an INVALID_VALUE error is generated. // FIXME: If this function is called with an ImageBitmap that has been neutered, an INVALID_VALUE // error is generated. // FIXME: If this function is called with an HTMLImageElement or HTMLVideoElement whose origin // differs from the origin of the containing Document, or with an HTMLCanvasElement, // ImageBitmap or OffscreenCanvas whose bitmap's origin-clean flag is set to false, // a SECURITY_ERR exception must be thrown. See Origin Restrictions. // FIXME: If source is null then an INVALID_VALUE error is generated. function_impl_generator.append(R"~~~( auto bitmap = source.visit( [](GC::Root const& source) -> RefPtr { return source->immutable_bitmap(); }, [](GC::Root const& source) -> RefPtr { auto surface = source->surface(); if (!surface) return {}; auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA8888, Gfx::AlphaType::Premultiplied, surface->size())); surface->read_into_bitmap(*bitmap); return Gfx::ImmutableBitmap::create(*bitmap); }, [](GC::Root const& source) -> RefPtr { return Gfx::ImmutableBitmap::create(*source->bitmap()); }, [](GC::Root const& source) -> RefPtr { return Gfx::ImmutableBitmap::create(*source->bitmap()); }, [](GC::Root const& source) -> RefPtr { return Gfx::ImmutableBitmap::create(source->bitmap()); }); if (!bitmap) return; void const* pixels_ptr = bitmap->bitmap()->begin(); )~~~"); if (webgl_version == 2 && function.overload_index == 2) { function_impl_generator.append(R"~~~( glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_ptr); )~~~"); } else { function_impl_generator.append(R"~~~( int width = bitmap->width(); int height = bitmap->height(); glTexImage2D(target, level, internalformat, width, height, 0, format, type, pixels_ptr); )~~~"); } continue; } if (webgl_version == 2 && function.name == "texImage2D"sv && function.overload_index == 3) { function_impl_generator.append(R"~~~( void const* pixels_ptr = nullptr; if (src_data) { auto const& viewed_array_buffer = src_data->viewed_array_buffer(); auto const& byte_buffer = viewed_array_buffer->buffer(); pixels_ptr = byte_buffer.data() + src_offset; } glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_ptr); )~~~"); continue; } if (function.name == "texSubImage2D"sv && function.overload_index == 0) { function_impl_generator.append(R"~~~( void const* pixels_ptr = nullptr; if (pixels) { auto const& viewed_array_buffer = pixels->viewed_array_buffer(); auto const& byte_buffer = viewed_array_buffer->buffer(); pixels_ptr = byte_buffer.data(); } glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_ptr); )~~~"); continue; } if (function.name == "texSubImage2D" && (function.overload_index == 1 || (webgl_version == 2 && function.overload_index == 2))) { // FIXME: If this function is called with an ImageData whose data attribute has been neutered, // an INVALID_VALUE error is generated. // FIXME: If this function is called with an ImageBitmap that has been neutered, an INVALID_VALUE // error is generated. // FIXME: If this function is called with an HTMLImageElement or HTMLVideoElement whose origin // differs from the origin of the containing Document, or with an HTMLCanvasElement, // ImageBitmap or OffscreenCanvas whose bitmap's origin-clean flag is set to false, // a SECURITY_ERR exception must be thrown. See Origin Restrictions. // FIXME: If source is null then an INVALID_VALUE error is generated. function_impl_generator.append(R"~~~( auto bitmap = source.visit( [](GC::Root const& source) -> RefPtr { return source->immutable_bitmap(); }, [](GC::Root const& source) -> RefPtr { auto surface = source->surface(); if (!surface) return {}; auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA8888, Gfx::AlphaType::Premultiplied, surface->size())); surface->read_into_bitmap(*bitmap); return Gfx::ImmutableBitmap::create(*bitmap); }, [](GC::Root const& source) -> RefPtr { return Gfx::ImmutableBitmap::create(*source->bitmap()); }, [](GC::Root const& source) -> RefPtr { return Gfx::ImmutableBitmap::create(*source->bitmap()); }, [](GC::Root const& source) -> RefPtr { return Gfx::ImmutableBitmap::create(source->bitmap()); }); if (!bitmap) return; void const* pixels_ptr = bitmap->bitmap()->begin(); )~~~"); if (webgl_version == 2 && function.overload_index == 2) { function_impl_generator.append(R"~~~( glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_ptr); )~~~"); } else { function_impl_generator.append(R"~~~( int width = bitmap->width(); int height = bitmap->height(); glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_ptr); )~~~"); } continue; } if (webgl_version == 2 && function.name == "texSubImage2D"sv && function.overload_index == 3) { function_impl_generator.append(R"~~~( void const* pixels_ptr = nullptr; if (src_data) { auto const& viewed_array_buffer = src_data->viewed_array_buffer(); auto const& byte_buffer = viewed_array_buffer->buffer(); pixels_ptr = byte_buffer.data() + src_offset; } glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_ptr); )~~~"); continue; } if (function.name == "texSubImage3D"sv && function.overload_index == 0) { function_impl_generator.append(R"~~~( void const* pixels_ptr = nullptr; if (src_data) { auto const& viewed_array_buffer = src_data->viewed_array_buffer(); auto const& byte_buffer = viewed_array_buffer->buffer(); pixels_ptr = byte_buffer.data() + src_offset; } glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels_ptr); )~~~"); continue; } if (function.name == "getShaderParameter"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "shader"sv, "JS::js_null()"sv); function_impl_generator.append(R"~~~( GLint result = 0; glGetShaderiv(shader_handle, pname, &result); return JS::Value(result); )~~~"); continue; } if (function.name == "getProgramParameter"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "program"sv, "JS::js_null()"sv); function_impl_generator.append(R"~~~( GLint result = 0; glGetProgramiv(program_handle, pname, &result); return JS::Value(result); )~~~"); continue; } if (function.name == "getActiveUniformBlockParameter"sv) { generate_get_active_uniform_block_parameter(function_impl_generator); continue; } if (function.name == "bufferData"sv && function.overload_index == 0) { function_impl_generator.append(R"~~~( glBufferData(target, size, 0, usage); )~~~"); continue; } if (webgl_version == 2 && function.name == "bufferData"sv && function.overload_index == 2) { function_impl_generator.append(R"~~~( VERIFY(src_data); auto const& viewed_array_buffer = src_data->viewed_array_buffer(); auto const& byte_buffer = viewed_array_buffer->buffer(); auto src_data_length = src_data->byte_length(); auto src_data_element_size = src_data->element_size(); u8 const* buffer_ptr = byte_buffer.data(); u64 copy_length = length == 0 ? src_data_length - src_offset : length; copy_length *= src_data_element_size; if (src_offset > src_data_length) { set_error(GL_INVALID_VALUE); return; } if (src_offset + copy_length > src_data_length) { set_error(GL_INVALID_VALUE); return; } buffer_ptr += src_offset * src_data_element_size; glBufferData(target, copy_length, buffer_ptr, usage); )~~~"); continue; } if (webgl_version == 2 && function.name == "bufferSubData"sv && function.overload_index == 1) { function_impl_generator.append(R"~~~( VERIFY(src_data); auto const& viewed_array_buffer = src_data->viewed_array_buffer(); auto const& byte_buffer = viewed_array_buffer->buffer(); auto src_data_length = src_data->byte_length(); auto src_data_element_size = src_data->element_size(); u8 const* buffer_ptr = byte_buffer.data(); u64 copy_length = length == 0 ? src_data_length - src_offset : length; copy_length *= src_data_element_size; if (src_offset > src_data_length) { set_error(GL_INVALID_VALUE); return; } if (src_offset + copy_length > src_data_length) { set_error(GL_INVALID_VALUE); return; } buffer_ptr += src_offset * src_data_element_size; glBufferSubData(target, dst_byte_offset, copy_length, buffer_ptr); )~~~"); continue; } if (function.name == "readPixels"sv) { function_impl_generator.append(R"~~~( if (!pixels) { return; } void *ptr = nullptr; if (pixels->is_data_view()) { auto& data_view = static_cast(*pixels->raw_object()); ptr = data_view.viewed_array_buffer()->buffer().data(); } else if (pixels->is_typed_array_base()) { auto& typed_array_base = static_cast(*pixels->raw_object()); ptr = typed_array_base.viewed_array_buffer()->buffer().data(); } else { VERIFY_NOT_REACHED(); } glReadPixels(x, y, width, height, format, type, ptr); )~~~"); continue; } if (function.name == "drawElements"sv) { function_impl_generator.append(R"~~~( glDrawElements(mode, count, type, reinterpret_cast(offset)); needs_to_present(); )~~~"); continue; } if (function.name == "drawElementsInstanced"sv) { function_impl_generator.append(R"~~~( glDrawElementsInstanced(mode, count, type, reinterpret_cast(offset), instance_count); needs_to_present(); )~~~"); continue; } if (function.name == "drawBuffers"sv) { function_impl_generator.append(R"~~~( glDrawBuffers(buffers.size(), buffers.data()); )~~~"); continue; } if (function.name.starts_with("uniformMatrix"sv)) { auto number_of_matrix_elements = function.name.substring_view(13, 1); function_impl_generator.set("number_of_matrix_elements", number_of_matrix_elements); if (webgl_version == 1) { function_impl_generator.set("array_argument_name", "value"); } else { function_impl_generator.set("array_argument_name", "data"); } function_impl_generator.append(R"~~~( auto matrix_size = @number_of_matrix_elements@ * @number_of_matrix_elements@; float const* raw_data = nullptr; u64 count = 0; if (@array_argument_name@.has>()) { auto& vector_data = @array_argument_name@.get>(); raw_data = vector_data.data(); count = vector_data.size() / matrix_size; } else { auto& typed_array_base = static_cast(*@array_argument_name@.get>()->raw_object()); auto& float32_array = verify_cast(typed_array_base); raw_data = float32_array.data().data(); count = float32_array.array_length().length() / matrix_size; } )~~~"); if (webgl_version == 2) { function_impl_generator.append(R"~~~( if (src_offset + src_length > (count * matrix_size)) { set_error(GL_INVALID_VALUE); return; } raw_data += src_offset; if (src_length == 0) { count -= src_offset; } else { count = src_length; } )~~~"); } function_impl_generator.append(R"~~~( glUniformMatrix@number_of_matrix_elements@fv(location->handle(), count, transpose, raw_data); )~~~"); continue; } if (function.name == "uniform1fv"sv || function.name == "uniform2fv"sv || function.name == "uniform3fv"sv || function.name == "uniform4fv"sv || function.name == "uniform1iv"sv || function.name == "uniform2iv"sv || function.name == "uniform3iv"sv || function.name == "uniform4iv"sv) { auto number_of_vector_elements = function.name.substring_view(7, 1); auto element_type = function.name.substring_view(8, 1); if (element_type == "f"sv) { function_impl_generator.set("cpp_element_type", "float"sv); function_impl_generator.set("typed_array_type", "Float32Array"sv); function_impl_generator.set("gl_postfix", "f"sv); } else if (element_type == "i"sv) { function_impl_generator.set("cpp_element_type", "int"sv); function_impl_generator.set("typed_array_type", "Int32Array"sv); function_impl_generator.set("gl_postfix", "i"sv); } else { VERIFY_NOT_REACHED(); } function_impl_generator.set("number_of_vector_elements", number_of_vector_elements); function_impl_generator.append(R"~~~( @cpp_element_type@ const* data = nullptr; size_t count = 0; if (v.has>()) { auto& vector = v.get>(); data = vector.data(); count = vector.size(); } else if (v.has>()) { auto& typed_array_base = static_cast(*v.get>()->raw_object()); auto& typed_array = verify_cast(typed_array_base); data = typed_array.data().data(); count = typed_array.array_length().length(); } else { VERIFY_NOT_REACHED(); } )~~~"); if (webgl_version == 2) { function_impl_generator.append(R"~~~( if (src_offset + src_length > count) { set_error(GL_INVALID_VALUE); return; } data += src_offset; if (src_length == 0) { count -= src_offset; } else { count = src_length; } )~~~"); } function_impl_generator.append(R"~~~( glUniform@number_of_vector_elements@@gl_postfix@v(location->handle(), count / @number_of_vector_elements@, data); )~~~"); continue; } if (function.name == "vertexAttrib1fv"sv || function.name == "vertexAttrib2fv"sv || function.name == "vertexAttrib3fv"sv || function.name == "vertexAttrib4fv"sv) { auto number_of_vector_elements = function.name.substring_view(12, 1); function_impl_generator.set("number_of_vector_elements", number_of_vector_elements); function_impl_generator.append(R"~~~( if (values.has>()) { auto& data = values.get>(); glVertexAttrib@number_of_vector_elements@fv(index, data.data()); return; } auto& typed_array_base = static_cast(*values.get>()->raw_object()); auto& float32_array = verify_cast(typed_array_base); float const* data = float32_array.data().data(); glVertexAttrib@number_of_vector_elements@fv(index, data); )~~~"); continue; } if (function.name == "vertexAttribIPointer"sv) { function_impl_generator.append(R"~~~( glVertexAttribIPointer(index, size, type, stride, reinterpret_cast(offset)); )~~~"); continue; } if (function.name == "getParameter"sv) { generate_get_parameter(function_impl_generator, webgl_version); continue; } if (function.name == "getBufferParameter"sv) { generate_get_buffer_parameter(function_impl_generator); continue; } if (function.name == "getInternalformatParameter") { generate_get_internal_format_parameter(function_impl_generator); continue; } if (function.name == "getActiveUniform"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "program"sv, "{}"sv); function_impl_generator.append(R"~~~( GLint size = 0; GLenum type = 0; GLsizei buf_size = 256; GLsizei length = 0; GLchar name[256]; glGetActiveUniform(program_handle, index, buf_size, &length, &size, &type, name); auto readonly_bytes = ReadonlyBytes { name, static_cast(length) }; return WebGLActiveInfo::create(m_realm, String::from_utf8_without_validation(readonly_bytes), type, size); )~~~"); continue; } if (function.name == "getActiveAttrib"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "program"sv, "{}"sv); function_impl_generator.append(R"~~~( GLint size = 0; GLenum type = 0; GLsizei buf_size = 256; GLsizei length = 0; GLchar name[256]; glGetActiveAttrib(program_handle, index, buf_size, &length, &size, &type, name); auto readonly_bytes = ReadonlyBytes { name, static_cast(length) }; return WebGLActiveInfo::create(m_realm, String::from_utf8_without_validation(readonly_bytes), type, size); )~~~"); continue; } if (function.name == "getShaderInfoLog"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "shader"sv, "{}"sv); function_impl_generator.append(R"~~~( GLint info_log_length = 0; glGetShaderiv(shader_handle, GL_INFO_LOG_LENGTH, &info_log_length); Vector info_log; info_log.resize(info_log_length); if (!info_log_length) return String {}; glGetShaderInfoLog(shader_handle, info_log_length, nullptr, info_log.data()); return String::from_utf8_without_validation(ReadonlyBytes { info_log.data(), static_cast(info_log_length - 1) }); )~~~"); continue; } if (function.name == "getProgramInfoLog"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "program"sv, "{}"sv); function_impl_generator.append(R"~~~( GLint info_log_length = 0; glGetProgramiv(program_handle, GL_INFO_LOG_LENGTH, &info_log_length); Vector info_log; info_log.resize(info_log_length); if (!info_log_length) return String {}; glGetProgramInfoLog(program_handle, info_log_length, nullptr, info_log.data()); return String::from_utf8_without_validation(ReadonlyBytes { info_log.data(), static_cast(info_log_length - 1) }); )~~~"); continue; } if (function.name == "getShaderPrecisionFormat"sv) { function_impl_generator.append(R"~~~( GLint range[2]; GLint precision; glGetShaderPrecisionFormat(shadertype, precisiontype, range, &precision); return WebGLShaderPrecisionFormat::create(m_realm, range[0], range[1], precision); )~~~"); continue; } if (function.name == "getActiveUniformBlockName"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "program"sv, "OptionalNone {}"sv); function_impl_generator.append(R"~~~( GLint uniform_block_name_length = 0; glGetActiveUniformBlockiv(program_handle, uniform_block_index, GL_UNIFORM_BLOCK_NAME_LENGTH, &uniform_block_name_length); Vector uniform_block_name; uniform_block_name.resize(uniform_block_name_length); if (!uniform_block_name_length) return String {}; glGetActiveUniformBlockName(program_handle, uniform_block_index, uniform_block_name_length, nullptr, uniform_block_name.data()); return String::from_utf8_without_validation(ReadonlyBytes { uniform_block_name.data(), static_cast(uniform_block_name_length - 1) }); )~~~"); continue; } if (function.name == "deleteBuffer"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "buffer"sv, ""sv); function_impl_generator.append(R"~~~( glDeleteBuffers(1, &buffer_handle); )~~~"); continue; } if (function.name == "deleteFramebuffer"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "framebuffer"sv, ""sv); function_impl_generator.append(R"~~~( glDeleteFramebuffers(1, &framebuffer_handle); )~~~"); continue; } if (function.name == "deleteRenderbuffer"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "renderbuffer"sv, ""sv); function_impl_generator.append(R"~~~( glDeleteRenderbuffers(1, &renderbuffer_handle); )~~~"); continue; } if (function.name == "deleteTexture"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "texture"sv, ""sv); function_impl_generator.append(R"~~~( glDeleteTextures(1, &texture_handle); )~~~"); continue; } if (function.name == "deleteVertexArray"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "vertex_array"sv, ""sv); function_impl_generator.append(R"~~~( glDeleteVertexArrays(1, &vertex_array_handle); )~~~"); continue; } if (function.name == "deleteSampler"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "sampler"sv, ""sv); function_impl_generator.append(R"~~~( glDeleteSamplers(1, &sampler_handle); )~~~"); continue; } if (function.name == "bindBuffer"sv) { // FIXME: Implement Buffer Object Binding restrictions. generate_webgl_object_handle_unwrap(function_impl_generator, "buffer"sv, ""sv); function_impl_generator.append(R"~~~( switch (target) { case GL_ELEMENT_ARRAY_BUFFER: m_element_array_buffer_binding = buffer; break; case GL_ARRAY_BUFFER: m_array_buffer_binding = buffer; break; )~~~"); if (webgl_version == 2) { function_impl_generator.append(R"~~~( case GL_UNIFORM_BUFFER: m_uniform_buffer_binding = buffer; break; case GL_COPY_READ_BUFFER: m_copy_read_buffer_binding = buffer; break; case GL_COPY_WRITE_BUFFER: m_copy_write_buffer_binding = buffer; break; )~~~"); } function_impl_generator.append(R"~~~( default: dbgln("Unknown WebGL buffer object binding target for storing current binding: 0x{:04x}", target); break; } glBindBuffer(target, buffer_handle); )~~~"); continue; } if (function.name == "useProgram"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "program"sv, ""sv); function_impl_generator.append(R"~~~( glUseProgram(program_handle); m_current_program = program; )~~~"); continue; } if (function.name == "bindFramebuffer"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "framebuffer"sv, ""sv); function_impl_generator.append(R"~~~( glBindFramebuffer(target, framebuffer_handle); m_framebuffer_binding = framebuffer; )~~~"); continue; } if (function.name == "bindRenderbuffer"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "renderbuffer"sv, ""sv); function_impl_generator.append(R"~~~( glBindRenderbuffer(target, renderbuffer_handle); m_renderbuffer_binding = renderbuffer; )~~~"); continue; } if (function.name == "bindTexture"sv) { generate_webgl_object_handle_unwrap(function_impl_generator, "texture"sv, ""sv); function_impl_generator.append(R"~~~( switch (target) { case GL_TEXTURE_2D: m_texture_binding_2d = texture; break; case GL_TEXTURE_CUBE_MAP: m_texture_binding_cube_map = texture; break; )~~~"); if (webgl_version == 2) { function_impl_generator.append(R"~~~( case GL_TEXTURE_2D_ARRAY: m_texture_binding_2d_array = texture; break; case GL_TEXTURE_3D: m_texture_binding_3d = texture; break; )~~~"); } function_impl_generator.append(R"~~~( default: dbgln("Unknown WebGL texture target for storing current binding: 0x{:04x}", target); break; } glBindTexture(target, texture_handle); )~~~"); continue; } if (function.name == "renderbufferStorage"sv) { // To be backward compatible with WebGL 1, also accepts internal format DEPTH_STENCIL, which should be // mapped to DEPTH24_STENCIL8 by implementations. if (webgl_version == 2) { function_impl_generator.append(R"~~~( if (internalformat == GL_DEPTH_STENCIL) internalformat = GL_DEPTH24_STENCIL8; )~~~"); } function_impl_generator.append(R"~~~( glRenderbufferStorage(target, internalformat, width, height); )~~~"); continue; } if (function.name.starts_with("samplerParameter"sv)) { generate_webgl_object_handle_unwrap(function_impl_generator, "sampler"sv, ""sv); function_impl_generator.set("param_type", function.name.substring_view(16, 1)); // pname is given in the following table: // - TEXTURE_COMPARE_FUNC // - TEXTURE_COMPARE_MODE // - TEXTURE_MAG_FILTER // - TEXTURE_MAX_LOD // - TEXTURE_MIN_FILTER // - TEXTURE_MIN_LOD // - TEXTURE_WRAP_R // - TEXTURE_WRAP_S // - TEXTURE_WRAP_T // If pname is not in the table above, generates an INVALID_ENUM error. // NOTE: We have to do this ourselves, as OpenGL does not. function_impl_generator.append(R"~~~( switch (pname) { case GL_TEXTURE_COMPARE_FUNC: case GL_TEXTURE_COMPARE_MODE: case GL_TEXTURE_MAG_FILTER: case GL_TEXTURE_MAX_LOD: case GL_TEXTURE_MIN_FILTER: case GL_TEXTURE_MIN_LOD: case GL_TEXTURE_WRAP_R: case GL_TEXTURE_WRAP_S: case GL_TEXTURE_WRAP_T: break; default: dbgln("Unknown WebGL sampler parameter name: 0x{:04x}", pname); set_error(GL_INVALID_ENUM); return; } glSamplerParameter@param_type@(sampler_handle, pname, param); )~~~"); continue; } if (function.name.starts_with("clearBuffer"sv) && function.name.ends_with('v')) { auto element_type = function.name.substring_view(11, 2); if (element_type == "fv"sv) { function_impl_generator.set("cpp_element_type", "float"sv); function_impl_generator.set("typed_array_type", "Float32Array"sv); function_impl_generator.set("gl_postfix", "f"sv); } else if (element_type == "iv"sv) { function_impl_generator.set("cpp_element_type", "int"sv); function_impl_generator.set("typed_array_type", "Int32Array"sv); function_impl_generator.set("gl_postfix", "i"sv); } else if (element_type == "ui"sv) { function_impl_generator.set("cpp_element_type", "u32"sv); function_impl_generator.set("typed_array_type", "Uint32Array"sv); function_impl_generator.set("gl_postfix", "ui"sv); } else { VERIFY_NOT_REACHED(); } function_impl_generator.append(R"~~~( @cpp_element_type@ const* data = nullptr; size_t count = 0; if (values.has>()) { auto& vector = values.get>(); data = vector.data(); count = vector.size(); } else if (values.has>()) { auto& typed_array_base = static_cast(*values.get>()->raw_object()); auto& typed_array = verify_cast(typed_array_base); data = typed_array.data().data(); count = typed_array.array_length().length(); } else { VERIFY_NOT_REACHED(); } switch (buffer) { case GL_COLOR: if (src_offset + 4 > count) { set_error(GL_INVALID_VALUE); return; } break; case GL_DEPTH: case GL_STENCIL: if (src_offset + 1 > count) { set_error(GL_INVALID_VALUE); return; } break; default: dbgln("Unknown WebGL buffer target for buffer clearing: 0x{:04x}", buffer); set_error(GL_INVALID_ENUM); return; } data += src_offset; glClearBuffer@gl_postfix@v(buffer, drawbuffer, data); needs_to_present(); )~~~"); continue; } Vector gl_call_arguments; for (size_t i = 0; i < function.parameters.size(); ++i) { auto const& parameter = function.parameters[i]; auto parameter_name = parameter.name.to_snakecase(); if (parameter.type->is_numeric() || parameter.type->is_boolean()) { gl_call_arguments.append(parameter_name); continue; } if (parameter.type->is_string()) { function_impl_generator.set("parameter_name", parameter_name); function_impl_generator.append(R"~~~( auto @parameter_name@_null_terminated = null_terminated_string(@parameter_name@); )~~~"); gl_call_arguments.append(ByteString::formatted("{}_null_terminated.data()", parameter_name)); continue; } if (is_webgl_object_type(parameter.type->name())) { if (function.return_type->name() == "undefined"sv) { function_impl_generator.set("early_return_value", ""); } else if (function.return_type->is_integer()) { function_impl_generator.set("early_return_value", "-1"); } else if (function.return_type->is_boolean()) { function_impl_generator.set("early_return_value", "false"); } else { VERIFY_NOT_REACHED(); } function_impl_generator.set("handle_parameter_name", parameter_name); function_impl_generator.append(R"~~~( auto @handle_parameter_name@_handle = 0; if (@handle_parameter_name@) { auto handle_or_error = @handle_parameter_name@->handle(this); if (handle_or_error.is_error()) { set_error(GL_INVALID_OPERATION); return @early_return_value@; } @handle_parameter_name@_handle = handle_or_error.release_value(); } )~~~"); gl_call_arguments.append(ByteString::formatted("{}_handle", parameter_name)); continue; } if (parameter.type->name() == "WebGLUniformLocation"sv) { gl_call_arguments.append(ByteString::formatted("{} ? {}->handle() : 0", parameter_name, parameter_name)); continue; } if (parameter.type->name() == "WebGLSync"sv) { // FIXME: Remove the GLsync cast once sync_handle actually returns the proper GLsync type. gl_call_arguments.append(ByteString::formatted("(GLsync)({} ? {}->sync_handle() : nullptr)", parameter_name, parameter_name)); continue; } if (parameter.type->name() == "BufferSource"sv) { function_impl_generator.set("buffer_source_name", parameter_name); function_impl_generator.append(R"~~~( void const* ptr = nullptr; size_t byte_size = 0; if (@buffer_source_name@->is_typed_array_base()) { auto& typed_array_base = static_cast(*@buffer_source_name@->raw_object()); ptr = typed_array_base.viewed_array_buffer()->buffer().data() + typed_array_base.byte_offset(); byte_size = @buffer_source_name@->byte_length(); } else if (@buffer_source_name@->is_data_view()) { auto& data_view = static_cast(*@buffer_source_name@->raw_object()); ptr = data_view.viewed_array_buffer()->buffer().data(); byte_size = data_view.viewed_array_buffer()->byte_length(); } else if (@buffer_source_name@->is_array_buffer()) { auto& array_buffer = static_cast(*@buffer_source_name@->raw_object()); ptr = array_buffer.buffer().data(); byte_size = array_buffer.byte_length(); } else { VERIFY_NOT_REACHED(); } )~~~"); gl_call_arguments.append(ByteString::formatted("byte_size")); gl_call_arguments.append(ByteString::formatted("ptr")); continue; } VERIFY_NOT_REACHED(); } StringBuilder gl_call_arguments_string_builder; gl_call_arguments_string_builder.join(", "sv, gl_call_arguments); auto gl_call_string = ByteString::formatted("{}({})", idl_to_gl_function_name(function.name), gl_call_arguments_string_builder.string_view()); function_impl_generator.set("call_string", gl_call_string); if (gl_function_modifies_framebuffer(function.name)) { function_impl_generator.append(" needs_to_present();\n"sv); } if (function.return_type->name() == "undefined"sv) { function_impl_generator.append(" @call_string@;"sv); } else if (function.return_type->is_integer() || function.return_type->is_boolean()) { function_impl_generator.append(" return @call_string@;"sv); } else if (is_webgl_object_type(function.return_type->name())) { function_impl_generator.set("return_type_name", function.return_type->name()); function_impl_generator.append(" return @return_type_name@::create(m_realm, *this, @call_string@);"sv); } else { VERIFY_NOT_REACHED(); } function_impl_generator.append("\n"sv); } header_file_generator.append(R"~~~( protected: virtual void visit_edges(JS::Cell::Visitor&) override; private: GC::Ref m_realm; GC::Ptr m_array_buffer_binding; GC::Ptr m_element_array_buffer_binding; GC::Ptr m_current_program; GC::Ptr m_framebuffer_binding; GC::Ptr m_renderbuffer_binding; GC::Ptr m_texture_binding_2d; GC::Ptr m_texture_binding_cube_map; )~~~"); if (webgl_version == 2) { header_file_generator.append(R"~~~( GC::Ptr m_uniform_buffer_binding; GC::Ptr m_copy_read_buffer_binding; GC::Ptr m_copy_write_buffer_binding; GC::Ptr m_texture_binding_2d_array; GC::Ptr m_texture_binding_3d; )~~~"); } header_file_generator.append(R"~~~( NonnullOwnPtr m_context; }; } )~~~"); implementation_file_generator.append(R"~~~( void @class_name@::visit_edges(JS::Cell::Visitor& visitor) { visitor.visit(m_realm); visitor.visit(m_array_buffer_binding); visitor.visit(m_element_array_buffer_binding); visitor.visit(m_current_program); visitor.visit(m_framebuffer_binding); visitor.visit(m_renderbuffer_binding); visitor.visit(m_texture_binding_2d); visitor.visit(m_texture_binding_cube_map); )~~~"); if (webgl_version == 2) { implementation_file_generator.append(R"~~~( visitor.visit(m_uniform_buffer_binding); visitor.visit(m_copy_read_buffer_binding); visitor.visit(m_copy_write_buffer_binding); visitor.visit(m_texture_binding_2d_array); visitor.visit(m_texture_binding_3d); )~~~"); } implementation_file_generator.append(R"~~~( } } )~~~"); MUST(generated_header_file->write_until_depleted(header_file_generator.as_string_view().bytes())); MUST(generated_implementation_file->write_until_depleted(implementation_file_generator.as_string_view().bytes())); return 0; }