webgl: Ignore pixel storage parameters for ImageBitmap source (#37635)

Follow the WebGL specification and ignore the values of UNPACK_FLIP_Y,
UNPACK_PREMULTIPLY_ALPHA, and UNPACK_COLORSPACE_CONVERSION
if the TexImageSource is an ImageBitmap.
https://registry.khronos.org/webgl/specs/latest/1.0/#6.10

Testing: There are no dedicated test results changes without the PR
#37634
which unblocks new testing scenarios with premultiplied ImageBitmap from
Blob/Canvas/Image sources.

Signed-off-by: Andrei Volykhin <andrei.volykhin@gmail.com>
This commit is contained in:
Andrei Volykhin 2025-06-24 14:15:50 +03:00 committed by GitHub
parent 3f496f8225
commit 9487f66eaa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 130 additions and 72 deletions

View file

@ -3257,6 +3257,8 @@ impl WebGL2RenderingContextMethods<crate::DomTypeHolder> for WebGL2RenderingCont
let size = Size2D::new(width, height); let size = Size2D::new(width, height);
let (alpha_treatment, y_axis_treatment) = self.base.get_current_unpack_state(false);
self.base.tex_image_2d( self.base.tex_image_2d(
&texture, &texture,
target, target,
@ -3267,7 +3269,12 @@ impl WebGL2RenderingContextMethods<crate::DomTypeHolder> for WebGL2RenderingCont
border, border,
unpacking_alignment, unpacking_alignment,
size, size,
TexSource::Pixels(TexPixels::from_array(buff, size)), TexSource::Pixels(TexPixels::from_array(
buff,
size,
alpha_treatment,
y_axis_treatment,
)),
); );
Ok(()) Ok(())

View file

@ -507,6 +507,28 @@ impl WebGLRenderingContext {
self.texture_packing_alignment.get() self.texture_packing_alignment.get()
} }
pub(crate) fn get_current_unpack_state(
&self,
premultiplied: bool,
) -> (Option<AlphaTreatment>, YAxisTreatment) {
let settings = self.texture_unpacking_settings.get();
let dest_premultiplied = settings.contains(TextureUnpacking::PREMULTIPLY_ALPHA);
let alpha_treatment = match (premultiplied, dest_premultiplied) {
(true, false) => Some(AlphaTreatment::Unmultiply),
(false, true) => Some(AlphaTreatment::Premultiply),
_ => None,
};
let y_axis_treatment = if settings.contains(TextureUnpacking::FLIP_Y_AXIS) {
YAxisTreatment::Flipped
} else {
YAxisTreatment::AsIs
};
(alpha_treatment, y_axis_treatment)
}
// LINEAR filtering may be forbidden when using WebGL extensions. // LINEAR filtering may be forbidden when using WebGL extensions.
// https://www.khronos.org/registry/webgl/extensions/OES_texture_float_linear/ // https://www.khronos.org/registry/webgl/extensions/OES_texture_float_linear/
fn validate_filterable_texture( fn validate_filterable_texture(
@ -552,7 +574,8 @@ impl WebGLRenderingContext {
IpcSharedMemory::from_bytes(&pixels), IpcSharedMemory::from_bytes(&pixels),
size, size,
PixelFormat::RGBA8, PixelFormat::RGBA8,
true, None,
YAxisTreatment::AsIs,
)), )),
); );
@ -578,6 +601,7 @@ impl WebGLRenderingContext {
if !bitmap.origin_is_clean() { if !bitmap.origin_is_clean() {
return Err(Error::Security); return Err(Error::Security);
} }
let Some(snapshot) = bitmap.bitmap_data().clone() else { let Some(snapshot) = bitmap.bitmap_data().clone() else {
return Ok(None); return Ok(None);
}; };
@ -588,15 +612,32 @@ impl WebGLRenderingContext {
SnapshotPixelFormat::RGBA => PixelFormat::RGBA8, SnapshotPixelFormat::RGBA => PixelFormat::RGBA8,
SnapshotPixelFormat::BGRA => PixelFormat::BGRA8, SnapshotPixelFormat::BGRA => PixelFormat::BGRA8,
}; };
let premultiply = snapshot.alpha_mode().is_premultiplied();
TexPixels::new(snapshot.to_ipc_shared_memory(), size, format, premultiply) // If the TexImageSource is an ImageBitmap, the values of
// UNPACK_FLIP_Y, UNPACK_PREMULTIPLY_ALPHA, and
// UNPACK_COLORSPACE_CONVERSION are to be ignored.
// Set alpha and y_axis treatment parameters such that no
// conversions will be made.
// <https://registry.khronos.org/webgl/specs/latest/1.0/#6.10>
TexPixels::new(
snapshot.to_ipc_shared_memory(),
size,
format,
None,
YAxisTreatment::AsIs,
)
}, },
TexImageSource::ImageData(image_data) => TexPixels::new( TexImageSource::ImageData(image_data) => {
let (alpha_treatment, y_axis_treatment) = self.get_current_unpack_state(false);
TexPixels::new(
image_data.to_shared_memory(), image_data.to_shared_memory(),
image_data.get_size(), image_data.get_size(),
PixelFormat::RGBA8, PixelFormat::RGBA8,
false, alpha_treatment,
), y_axis_treatment,
)
},
TexImageSource::HTMLImageElement(image) => { TexImageSource::HTMLImageElement(image) => {
let document = match self.canvas { let document = match self.canvas {
HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(ref canvas) => { HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(ref canvas) => {
@ -650,9 +691,11 @@ impl WebGLRenderingContext {
}; };
let size = Size2D::new(img.metadata.width, img.metadata.height); let size = Size2D::new(img.metadata.width, img.metadata.height);
let data = IpcSharedMemory::from_bytes(img.first_frame().bytes); let data = IpcSharedMemory::from_bytes(img.first_frame().bytes);
TexPixels::new(data, size, img.format, false)
let (alpha_treatment, y_axis_treatment) = self.get_current_unpack_state(false);
TexPixels::new(data, size, img.format, alpha_treatment, y_axis_treatment)
}, },
// TODO(emilio): Getting canvas data is implemented in CanvasRenderingContext2D, // TODO(emilio): Getting canvas data is implemented in CanvasRenderingContext2D,
// but we need to refactor it moving it to `HTMLCanvasElement` and support // but we need to refactor it moving it to `HTMLCanvasElement` and support
@ -661,18 +704,28 @@ impl WebGLRenderingContext {
if !canvas.origin_is_clean() { if !canvas.origin_is_clean() {
return Err(Error::Security); return Err(Error::Security);
} }
if let Some(snapshot) = canvas.get_image_data() {
let Some(snapshot) = canvas.get_image_data() else {
return Ok(None);
};
let snapshot = snapshot.as_ipc(); let snapshot = snapshot.as_ipc();
let size = snapshot.size().cast(); let size = snapshot.size().cast();
let format = match snapshot.format() { let format = match snapshot.format() {
SnapshotPixelFormat::RGBA => PixelFormat::RGBA8, SnapshotPixelFormat::RGBA => PixelFormat::RGBA8,
SnapshotPixelFormat::BGRA => PixelFormat::BGRA8, SnapshotPixelFormat::BGRA => PixelFormat::BGRA8,
}; };
let premultiply = snapshot.alpha_mode().is_premultiplied();
TexPixels::new(snapshot.to_ipc_shared_memory(), size, format, premultiply) let (alpha_treatment, y_axis_treatment) =
} else { self.get_current_unpack_state(snapshot.alpha_mode().is_premultiplied());
return Ok(None);
} TexPixels::new(
snapshot.to_ipc_shared_memory(),
size,
format,
alpha_treatment,
y_axis_treatment,
)
}, },
TexImageSource::HTMLVideoElement(video) => { TexImageSource::HTMLVideoElement(video) => {
if !video.origin_is_clean() { if !video.origin_is_clean() {
@ -689,8 +742,17 @@ impl WebGLRenderingContext {
SnapshotPixelFormat::RGBA => PixelFormat::RGBA8, SnapshotPixelFormat::RGBA => PixelFormat::RGBA8,
SnapshotPixelFormat::BGRA => PixelFormat::BGRA8, SnapshotPixelFormat::BGRA => PixelFormat::BGRA8,
}; };
let premultiply = snapshot.alpha_mode().is_premultiplied();
TexPixels::new(snapshot.to_ipc_shared_memory(), size, format, premultiply) let (alpha_treatment, y_axis_treatment) =
self.get_current_unpack_state(snapshot.alpha_mode().is_premultiplied());
TexPixels::new(
snapshot.to_ipc_shared_memory(),
size,
format,
alpha_treatment,
y_axis_treatment,
)
}, },
})) }))
} }
@ -769,15 +831,6 @@ impl WebGLRenderingContext {
) )
); );
let settings = self.texture_unpacking_settings.get();
let dest_premultiplied = settings.contains(TextureUnpacking::PREMULTIPLY_ALPHA);
let y_axis_treatment = if settings.contains(TextureUnpacking::FLIP_Y_AXIS) {
YAxisTreatment::Flipped
} else {
YAxisTreatment::AsIs
};
let internal_format = self let internal_format = self
.extension_manager .extension_manager
.get_effective_tex_internal_format(internal_format, data_type.as_gl_constant()); .get_effective_tex_internal_format(internal_format, data_type.as_gl_constant());
@ -788,12 +841,6 @@ impl WebGLRenderingContext {
match source { match source {
TexSource::Pixels(pixels) => { TexSource::Pixels(pixels) => {
let alpha_treatment = match (pixels.premultiplied, dest_premultiplied) {
(true, false) => Some(AlphaTreatment::Unmultiply),
(false, true) => Some(AlphaTreatment::Premultiply),
_ => None,
};
// TODO(emilio): convert colorspace if requested. // TODO(emilio): convert colorspace if requested.
self.send_command(WebGLCommand::TexImage2D { self.send_command(WebGLCommand::TexImage2D {
target: target.as_gl_constant(), target: target.as_gl_constant(),
@ -804,8 +851,8 @@ impl WebGLRenderingContext {
data_type, data_type,
effective_data_type, effective_data_type,
unpacking_alignment, unpacking_alignment,
alpha_treatment, alpha_treatment: pixels.alpha_treatment,
y_axis_treatment, y_axis_treatment: pixels.y_axis_treatment,
pixel_format: pixels.pixel_format, pixel_format: pixels.pixel_format,
data: pixels.data.into(), data: pixels.data.into(),
}); });
@ -873,21 +920,6 @@ impl WebGLRenderingContext {
return self.webgl_error(InvalidOperation); return self.webgl_error(InvalidOperation);
} }
let settings = self.texture_unpacking_settings.get();
let dest_premultiplied = settings.contains(TextureUnpacking::PREMULTIPLY_ALPHA);
let alpha_treatment = match (pixels.premultiplied, dest_premultiplied) {
(true, false) => Some(AlphaTreatment::Unmultiply),
(false, true) => Some(AlphaTreatment::Premultiply),
_ => None,
};
let y_axis_treatment = if settings.contains(TextureUnpacking::FLIP_Y_AXIS) {
YAxisTreatment::Flipped
} else {
YAxisTreatment::AsIs
};
let effective_data_type = self let effective_data_type = self
.extension_manager .extension_manager
.effective_type(data_type.as_gl_constant()); .effective_type(data_type.as_gl_constant());
@ -903,8 +935,8 @@ impl WebGLRenderingContext {
data_type, data_type,
effective_data_type, effective_data_type,
unpacking_alignment, unpacking_alignment,
alpha_treatment, alpha_treatment: pixels.alpha_treatment,
y_axis_treatment, y_axis_treatment: pixels.y_axis_treatment,
pixel_format: pixels.pixel_format, pixel_format: pixels.pixel_format,
data: pixels.data.into(), data: pixels.data.into(),
}); });
@ -1581,9 +1613,7 @@ impl WebGLRenderingContext {
} }
let size = Size2D::new(width, height); let size = Size2D::new(width, height);
let buff = IpcSharedMemory::from_bytes(data); let data = IpcSharedMemory::from_bytes(data);
let pixels = TexPixels::from_array(buff, size);
let data = pixels.data;
handle_potential_webgl_error!( handle_potential_webgl_error!(
self, self,
@ -1646,9 +1676,7 @@ impl WebGLRenderingContext {
Err(_) => return, Err(_) => return,
}; };
let buff = IpcSharedMemory::from_bytes(data); let data = IpcSharedMemory::from_bytes(data);
let pixels = TexPixels::from_array(buff, Size2D::new(width, height));
let data = pixels.data;
self.send_command(WebGLCommand::CompressedTexSubImage2D { self.send_command(WebGLCommand::CompressedTexSubImage2D {
target: target.as_gl_constant(), target: target.as_gl_constant(),
@ -4533,6 +4561,8 @@ impl WebGLRenderingContextMethods<crate::DomTypeHolder> for WebGLRenderingContex
let size = Size2D::new(width, height); let size = Size2D::new(width, height);
let (alpha_treatment, y_axis_treatment) = self.get_current_unpack_state(false);
self.tex_image_2d( self.tex_image_2d(
&texture, &texture,
target, target,
@ -4543,7 +4573,12 @@ impl WebGLRenderingContextMethods<crate::DomTypeHolder> for WebGLRenderingContex
border, border,
unpacking_alignment, unpacking_alignment,
size, size,
TexSource::Pixels(TexPixels::from_array(buff, size)), TexSource::Pixels(TexPixels::from_array(
buff,
size,
alpha_treatment,
y_axis_treatment,
)),
); );
Ok(()) Ok(())
@ -4703,6 +4738,8 @@ impl WebGLRenderingContextMethods<crate::DomTypeHolder> for WebGLRenderingContex
}; };
} }
let (alpha_treatment, y_axis_treatment) = self.get_current_unpack_state(false);
self.tex_sub_image_2d( self.tex_sub_image_2d(
texture, texture,
target, target,
@ -4712,7 +4749,12 @@ impl WebGLRenderingContextMethods<crate::DomTypeHolder> for WebGLRenderingContex
format, format,
data_type, data_type,
unpacking_alignment, unpacking_alignment,
TexPixels::from_array(buff, Size2D::new(width, height)), TexPixels::from_array(
buff,
Size2D::new(width, height),
alpha_treatment,
y_axis_treatment,
),
); );
Ok(()) Ok(())
} }
@ -5056,7 +5098,8 @@ pub(crate) struct TexPixels {
data: IpcSharedMemory, data: IpcSharedMemory,
size: Size2D<u32>, size: Size2D<u32>,
pixel_format: Option<PixelFormat>, pixel_format: Option<PixelFormat>,
premultiplied: bool, alpha_treatment: Option<AlphaTreatment>,
y_axis_treatment: YAxisTreatment,
} }
impl TexPixels { impl TexPixels {
@ -5064,22 +5107,30 @@ impl TexPixels {
data: IpcSharedMemory, data: IpcSharedMemory,
size: Size2D<u32>, size: Size2D<u32>,
pixel_format: PixelFormat, pixel_format: PixelFormat,
premultiplied: bool, alpha_treatment: Option<AlphaTreatment>,
y_axis_treatment: YAxisTreatment,
) -> Self { ) -> Self {
Self { Self {
data, data,
size, size,
pixel_format: Some(pixel_format), pixel_format: Some(pixel_format),
premultiplied, alpha_treatment,
y_axis_treatment,
} }
} }
pub(crate) fn from_array(data: IpcSharedMemory, size: Size2D<u32>) -> Self { pub(crate) fn from_array(
data: IpcSharedMemory,
size: Size2D<u32>,
alpha_treatment: Option<AlphaTreatment>,
y_axis_treatment: YAxisTreatment,
) -> Self {
Self { Self {
data, data,
size, size,
pixel_format: None, pixel_format: None,
premultiplied: false, alpha_treatment,
y_axis_treatment,
} }
} }