LibWeb/WebGL: Respect UNPACK_FLIP_Y_WEBGL pixel storage parameter

When this is true, we have to vertically flip TexImageSource provided
images before uploading them.

Fixes several graphical glitches on Google Maps.
Fixes globe being upside down on Shopify's homepage.
Likely fixes more websites.
This commit is contained in:
Luke Wilde 2025-10-23 20:24:14 +01:00 committed by Andreas Kling
parent 008699c129
commit 3e7061da40
4 changed files with 45 additions and 3 deletions

View File

@ -2849,6 +2849,8 @@ JS::Value WebGL2RenderingContextImpl::get_parameter(WebIDL::UnsignedLong pname)
glGetBooleanvRobustANGLE(GL_TRANSFORM_FEEDBACK_PAUSED, 1, nullptr, &result); glGetBooleanvRobustANGLE(GL_TRANSFORM_FEEDBACK_PAUSED, 1, nullptr, &result);
return JS::Value(result == GL_TRUE); return JS::Value(result == GL_TRUE);
} }
case UNPACK_FLIP_Y_WEBGL:
return JS::Value(m_unpack_flip_y);
default: default:
dbgln("Unknown WebGL parameter name: {:x}", pname); dbgln("Unknown WebGL parameter name: {:x}", pname);
set_error(GL_INVALID_ENUM); set_error(GL_INVALID_ENUM);
@ -3145,6 +3147,13 @@ void WebGL2RenderingContextImpl::link_program(GC::Root<WebGLProgram> program)
void WebGL2RenderingContextImpl::pixel_storei(WebIDL::UnsignedLong pname, WebIDL::Long param) void WebGL2RenderingContextImpl::pixel_storei(WebIDL::UnsignedLong pname, WebIDL::Long param)
{ {
m_context->make_current(); m_context->make_current();
switch (pname) {
case UNPACK_FLIP_Y_WEBGL:
m_unpack_flip_y = param != GL_FALSE;
return;
}
glPixelStorei(pname, param); glPixelStorei(pname, param);
} }

View File

@ -12,6 +12,7 @@ extern "C" {
#include <GLES2/gl2ext_angle.h> #include <GLES2/gl2ext_angle.h>
} }
#include <LibGfx/SkiaUtils.h>
#include <LibWeb/HTML/HTMLCanvasElement.h> #include <LibWeb/HTML/HTMLCanvasElement.h>
#include <LibWeb/HTML/HTMLImageElement.h> #include <LibWeb/HTML/HTMLImageElement.h>
#include <LibWeb/HTML/HTMLVideoElement.h> #include <LibWeb/HTML/HTMLVideoElement.h>
@ -20,10 +21,12 @@ extern "C" {
#include <LibWeb/WebGL/OpenGLContext.h> #include <LibWeb/WebGL/OpenGLContext.h>
#include <LibWeb/WebGL/WebGLRenderingContextBase.h> #include <LibWeb/WebGL/WebGLRenderingContextBase.h>
#include <core/SkCanvas.h>
#include <core/SkColorSpace.h> #include <core/SkColorSpace.h>
#include <core/SkColorType.h> #include <core/SkColorType.h>
#include <core/SkImage.h> #include <core/SkImage.h>
#include <core/SkPixmap.h> #include <core/SkPixmap.h>
#include <core/SkSurface.h>
namespace Web::WebGL { namespace Web::WebGL {
@ -178,8 +181,20 @@ Optional<WebGLRenderingContextBase::ConvertedTexture> WebGLRenderingContextBase:
// FIXME: Respect unpackColorSpace // FIXME: Respect unpackColorSpace
auto color_space = SkColorSpace::MakeSRGB(); auto color_space = SkColorSpace::MakeSRGB();
auto image_info = SkImageInfo::Make(width, height, skia_format, SkAlphaType::kPremul_SkAlphaType, color_space); auto image_info = SkImageInfo::Make(width, height, skia_format, SkAlphaType::kPremul_SkAlphaType, color_space);
SkPixmap const pixmap(image_info, buffer.data(), buffer_pitch.value()); auto surface = SkSurfaces::WrapPixels(image_info, buffer.data(), buffer_pitch.value());
bitmap->sk_image()->readPixels(pixmap, 0, 0); auto surface_canvas = surface->getCanvas();
auto dst_rect = Gfx::to_skia_rect(Gfx::Rect { 0, 0, width, height });
// The first pixel transferred from the source to the WebGL implementation corresponds to the upper left corner of
// the source. This behavior is modified by the UNPACK_FLIP_Y_WEBGL pixel storage parameter, except for ImageBitmap
// arguments, as described in the abovementioned section.
if (m_unpack_flip_y && !source.has<GC::Root<HTML::ImageBitmap>>()) {
surface_canvas->translate(0, dst_rect.height());
surface_canvas->scale(1, -1);
}
surface_canvas->drawImageRect(bitmap->sk_image(), dst_rect, Gfx::to_skia_sampling_options(Gfx::ScalingMode::NearestNeighbor));
return ConvertedTexture { return ConvertedTexture {
.buffer = move(buffer), .buffer = move(buffer),
.width = width, .width = width,

View File

@ -14,6 +14,8 @@
namespace Web::WebGL { namespace Web::WebGL {
static constexpr int UNPACK_FLIP_Y_WEBGL = 0x9240;
// NOTE: This is the Variant created by the IDL wrapper generator, and needs to be updated accordingly. // NOTE: This is the Variant created by the IDL wrapper generator, and needs to be updated accordingly.
using TexImageSource = Variant<GC::Root<HTML::ImageBitmap>, GC::Root<HTML::ImageData>, GC::Root<HTML::HTMLImageElement>, GC::Root<HTML::HTMLCanvasElement>, GC::Root<HTML::OffscreenCanvas>, GC::Root<HTML::HTMLVideoElement>>; using TexImageSource = Variant<GC::Root<HTML::ImageBitmap>, GC::Root<HTML::ImageData>, GC::Root<HTML::HTMLImageElement>, GC::Root<HTML::HTMLCanvasElement>, GC::Root<HTML::OffscreenCanvas>, GC::Root<HTML::HTMLVideoElement>>;
@ -107,7 +109,14 @@ public:
int width { 0 }; int width { 0 };
int height { 0 }; int height { 0 };
}; };
static Optional<ConvertedTexture> read_and_pixel_convert_texture_image_source(TexImageSource const& source, WebIDL::UnsignedLong format, WebIDL::UnsignedLong type, Optional<int> destination_width = OptionalNone {}, Optional<int> destination_height = OptionalNone {}); Optional<ConvertedTexture> read_and_pixel_convert_texture_image_source(TexImageSource const& source, WebIDL::UnsignedLong format, WebIDL::UnsignedLong type, Optional<int> destination_width = OptionalNone {}, Optional<int> destination_height = OptionalNone {});
protected:
// UNPACK_FLIP_Y_WEBGL of type boolean
// If set, then during any subsequent calls to texImage2D or texSubImage2D, the source data is flipped along
// the vertical axis, so that conceptually the last row is the first one transferred. The initial value is false.
// Any non-zero value is interpreted as true.
bool m_unpack_flip_y { false };
}; };
} }

View File

@ -1501,6 +1501,8 @@ JS::Value WebGLRenderingContextImpl::get_parameter(WebIDL::UnsignedLong pname)
set_error(GL_INVALID_ENUM); set_error(GL_INVALID_ENUM);
return JS::js_null(); return JS::js_null();
} }
case UNPACK_FLIP_Y_WEBGL:
return JS::Value(m_unpack_flip_y);
default: default:
dbgln("Unknown WebGL parameter name: {:x}", pname); dbgln("Unknown WebGL parameter name: {:x}", pname);
set_error(GL_INVALID_ENUM); set_error(GL_INVALID_ENUM);
@ -1793,6 +1795,13 @@ void WebGLRenderingContextImpl::link_program(GC::Root<WebGLProgram> program)
void WebGLRenderingContextImpl::pixel_storei(WebIDL::UnsignedLong pname, WebIDL::Long param) void WebGLRenderingContextImpl::pixel_storei(WebIDL::UnsignedLong pname, WebIDL::Long param)
{ {
m_context->make_current(); m_context->make_current();
switch (pname) {
case UNPACK_FLIP_Y_WEBGL:
m_unpack_flip_y = param != GL_FALSE;
return;
}
glPixelStorei(pname, param); glPixelStorei(pname, param);
} }