From d915b574abde1c779cec10eb0f6bf2abc66c18d7 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 10 Jan 2025 16:43:24 +0000 Subject: [PATCH] LibWeb/WebGL: Implement pixel format conversion for TexImageSource This is done by using the combination of format and type to map to the appropriate Skia bitmap type. With this, we then read the SkImage of the TexImageSource into a new SkPixmap with the destination format information and holding an appropriately sized buffer. Once created, readPixels is called to convert and write the image into the buffer. --- .../LibWeb/GenerateWebGLRenderingContext.cpp | 381 ++++++++++++++---- 1 file changed, 300 insertions(+), 81 deletions(-) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp index 13edb6e2151..adc90974a01 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateWebGLRenderingContext.cpp @@ -512,6 +512,11 @@ ErrorOr serenity_main(Main::Arguments arguments) #include #include +#include +#include +#include +#include + namespace Web::WebGL { static Vector null_terminated_string(StringView string) @@ -522,6 +527,282 @@ static Vector null_terminated_string(StringView string) result.append('\\0'); return result; } +)~~~"); + + if (webgl_version == 2) { + implementation_file_generator.append(R"~~~( +static constexpr Optional opengl_format_number_of_components(WebIDL::UnsignedLong format) +{ + switch (format) { + case GL_RED: + case GL_RED_INTEGER: + case GL_LUMINANCE: + case GL_ALPHA: + case GL_DEPTH_COMPONENT: + return 1; + case GL_RG: + case GL_RG_INTEGER: + case GL_DEPTH_STENCIL: + case GL_LUMINANCE_ALPHA: + return 2; + case GL_RGB: + case GL_RGB_INTEGER: + return 3; + case GL_RGBA: + case GL_RGBA_INTEGER: + return 4; + default: + return OptionalNone {}; + } +} + +static constexpr Optional opengl_type_size_in_bytes(WebIDL::UnsignedLong type) +{ + switch (type) { + case GL_UNSIGNED_BYTE: + case GL_BYTE: + return 1; + case GL_UNSIGNED_SHORT: + case GL_SHORT: + case GL_HALF_FLOAT: + case GL_UNSIGNED_SHORT_5_6_5: + case GL_UNSIGNED_SHORT_4_4_4_4: + case GL_UNSIGNED_SHORT_5_5_5_1: + return 2; + case GL_UNSIGNED_INT: + case GL_INT: + case GL_UNSIGNED_INT_2_10_10_10_REV: + case GL_UNSIGNED_INT_10F_11F_11F_REV: + case GL_UNSIGNED_INT_5_9_9_9_REV: + case GL_UNSIGNED_INT_24_8: + return 4; + case GL_FLOAT_32_UNSIGNED_INT_24_8_REV: + return 8; + default: + return OptionalNone {}; + } +} + +static constexpr SkColorType opengl_format_and_type_to_skia_color_type(WebIDL::UnsignedLong format, WebIDL::UnsignedLong type) +{ + switch (format) + { + case GL_RGB: + switch (type) { + case GL_UNSIGNED_BYTE: + return SkColorType::kRGB_888x_SkColorType; + case GL_UNSIGNED_SHORT_5_6_5: + return SkColorType::kRGB_565_SkColorType; + default: + break; + } + break; + case GL_RGBA: + switch (type) { + case GL_UNSIGNED_BYTE: + return SkColorType::kRGBA_8888_SkColorType; + case GL_UNSIGNED_SHORT_4_4_4_4: + // FIXME: This is not exactly the same as RGBA. + return SkColorType::kARGB_4444_SkColorType; + case GL_UNSIGNED_SHORT_5_5_5_1: + dbgln("WebGL2 FIXME: Support conversion to RGBA5551."); + break; + default: + break; + } + break; + case GL_ALPHA: + switch (type) { + case GL_UNSIGNED_BYTE: + return SkColorType::kAlpha_8_SkColorType; + default: + break; + } + break; + case GL_LUMINANCE: + switch (type) { + case GL_UNSIGNED_BYTE: + return SkColorType::kGray_8_SkColorType; + default: + break; + } + break; + default: + break; + } + + dbgln("WebGL2: Unsupported format and type combination. format: 0x{:04x}, type: 0x{:04x}", format, type); + return SkColorType::kUnknown_SkColorType; +} +)~~~"); + } else { + implementation_file_generator.append(R"~~~( +static constexpr Optional opengl_format_number_of_components(WebIDL::UnsignedLong format) +{ + switch (format) { + case GL_LUMINANCE: + case GL_ALPHA: + return 1; + case GL_LUMINANCE_ALPHA: + return 2; + case GL_RGB: + return 3; + case GL_RGBA: + return 4; + default: + return OptionalNone {}; + } +} + +static constexpr Optional opengl_type_size_in_bytes(WebIDL::UnsignedLong type) +{ + switch (type) { + case GL_UNSIGNED_BYTE: + return 1; + case GL_UNSIGNED_SHORT_5_6_5: + case GL_UNSIGNED_SHORT_4_4_4_4: + case GL_UNSIGNED_SHORT_5_5_5_1: + return 2; + default: + return OptionalNone {}; + } +} + +static constexpr SkColorType opengl_format_and_type_to_skia_color_type(WebIDL::UnsignedLong format, WebIDL::UnsignedLong type) +{ + switch (format) + { + case GL_RGB: + switch (type) { + case GL_UNSIGNED_BYTE: + return SkColorType::kRGB_888x_SkColorType; + case GL_UNSIGNED_SHORT_5_6_5: + return SkColorType::kRGB_565_SkColorType; + default: + break; + } + break; + case GL_RGBA: + switch (type) { + case GL_UNSIGNED_BYTE: + return SkColorType::kRGBA_8888_SkColorType; + case GL_UNSIGNED_SHORT_4_4_4_4: + // FIXME: This is not exactly the same as RGBA. + return SkColorType::kARGB_4444_SkColorType; + case GL_UNSIGNED_SHORT_5_5_5_1: + dbgln("WebGL FIXME: Support conversion to RGBA5551."); + break; + default: + break; + } + break; + case GL_ALPHA: + switch (type) { + case GL_UNSIGNED_BYTE: + return SkColorType::kAlpha_8_SkColorType; + default: + break; + } + break; + case GL_LUMINANCE: + switch (type) { + case GL_UNSIGNED_BYTE: + return SkColorType::kGray_8_SkColorType; + default: + break; + } + break; + default: + break; + } + + dbgln("WebGL: Unsupported format and type combination. format: 0x{:04x}, type: 0x{:04x}", format, type); + return SkColorType::kUnknown_SkColorType; +} +)~~~"); + } + + implementation_file_generator.append(R"~~~( +struct ConvertedTexture { + ByteBuffer buffer; + int width { 0 }; + int height { 0 }; +}; + +static Optional read_and_pixel_convert_texture_image_source(Variant, GC::Root, GC::Root, GC::Root, GC::Root> const& source, WebIDL::UnsignedLong format, WebIDL::UnsignedLong type, Optional destination_width = OptionalNone {}, Optional destination_height = OptionalNone {}) +{ + // 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. + 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 OptionalNone {}; + + int width = destination_width.value_or(bitmap->width()); + int height = destination_height.value_or(bitmap->height()); + + Checked buffer_pitch = width; + + auto number_of_components = opengl_format_number_of_components(format); + if (!number_of_components.has_value()) + return OptionalNone {}; + + buffer_pitch *= number_of_components.value(); + + auto type_size = opengl_type_size_in_bytes(type); + if (!type_size.has_value()) + return OptionalNone {}; + + buffer_pitch *= type_size.value(); + + if (buffer_pitch.has_overflow()) + return OptionalNone {}; + + if (Checked::multiplication_would_overflow(buffer_pitch.value(), height)) + return OptionalNone {}; + + auto buffer = MUST(ByteBuffer::create_zeroed(buffer_pitch.value() * height)); + + auto skia_format = opengl_format_and_type_to_skia_color_type(format, type); + + // FIXME: Respect UNPACK_PREMULTIPLY_ALPHA_WEBGL + // FIXME: Respect unpackColorSpace + auto color_space = SkColorSpace::MakeSRGB(); + auto image_info = SkImageInfo::Make(width, height, skia_format, SkAlphaType::kPremul_SkAlphaType, color_space); + SkPixmap const pixmap(image_info, buffer.data(), buffer_pitch.value()); + bitmap->sk_image()->readPixels(pixmap, 0, 0); + return ConvertedTexture { + .buffer = move(buffer), + .width = width, + .height = height, + }; +} @class_name@::@class_name@(JS::Realm& realm, NonnullOwnPtr context) : m_realm(realm) @@ -825,52 +1106,21 @@ public: } 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); + auto maybe_converted_texture = read_and_pixel_convert_texture_image_source(source, format, type, width, height); + if (!maybe_converted_texture.has_value()) + return; + auto converted_texture = maybe_converted_texture.release_value(); + glTexImage2D(target, level, internalformat, converted_texture.width, converted_texture.height, border, format, type, converted_texture.buffer.data()); )~~~"); } 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); + 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(); + glTexImage2D(target, level, internalformat, converted_texture.width, converted_texture.height, 0, format, type, converted_texture.buffer.data()); )~~~"); } continue; @@ -903,53 +1153,22 @@ public: } 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); + auto maybe_converted_texture = read_and_pixel_convert_texture_image_source(source, format, type, width, height); )~~~"); } 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); + auto maybe_converted_texture = read_and_pixel_convert_texture_image_source(source, format, type); )~~~"); } + + function_impl_generator.append(R"~~~( + if (!maybe_converted_texture.has_value()) + return; + auto converted_texture = maybe_converted_texture.release_value(); + glTexSubImage2D(target, level, xoffset, yoffset, converted_texture.width, converted_texture.height, format, type, converted_texture.buffer.data()); +)~~~"); continue; }