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

@ -507,6 +507,28 @@ impl WebGLRenderingContext {
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.
// https://www.khronos.org/registry/webgl/extensions/OES_texture_float_linear/
fn validate_filterable_texture(
@ -552,7 +574,8 @@ impl WebGLRenderingContext {
IpcSharedMemory::from_bytes(&pixels),
size,
PixelFormat::RGBA8,
true,
None,
YAxisTreatment::AsIs,
)),
);
@ -578,6 +601,7 @@ impl WebGLRenderingContext {
if !bitmap.origin_is_clean() {
return Err(Error::Security);
}
let Some(snapshot) = bitmap.bitmap_data().clone() else {
return Ok(None);
};
@ -588,15 +612,32 @@ impl WebGLRenderingContext {
SnapshotPixelFormat::RGBA => PixelFormat::RGBA8,
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) => {
let (alpha_treatment, y_axis_treatment) = self.get_current_unpack_state(false);
TexPixels::new(
image_data.to_shared_memory(),
image_data.get_size(),
PixelFormat::RGBA8,
alpha_treatment,
y_axis_treatment,
)
},
TexImageSource::ImageData(image_data) => TexPixels::new(
image_data.to_shared_memory(),
image_data.get_size(),
PixelFormat::RGBA8,
false,
),
TexImageSource::HTMLImageElement(image) => {
let document = match self.canvas {
HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(ref canvas) => {
@ -650,9 +691,11 @@ impl WebGLRenderingContext {
};
let size = Size2D::new(img.metadata.width, img.metadata.height);
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,
// but we need to refactor it moving it to `HTMLCanvasElement` and support
@ -661,18 +704,28 @@ impl WebGLRenderingContext {
if !canvas.origin_is_clean() {
return Err(Error::Security);
}
if let Some(snapshot) = canvas.get_image_data() {
let snapshot = snapshot.as_ipc();
let size = snapshot.size().cast();
let format = match snapshot.format() {
SnapshotPixelFormat::RGBA => PixelFormat::RGBA8,
SnapshotPixelFormat::BGRA => PixelFormat::BGRA8,
};
let premultiply = snapshot.alpha_mode().is_premultiplied();
TexPixels::new(snapshot.to_ipc_shared_memory(), size, format, premultiply)
} else {
let Some(snapshot) = canvas.get_image_data() else {
return Ok(None);
}
};
let snapshot = snapshot.as_ipc();
let size = snapshot.size().cast();
let format = match snapshot.format() {
SnapshotPixelFormat::RGBA => PixelFormat::RGBA8,
SnapshotPixelFormat::BGRA => PixelFormat::BGRA8,
};
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,
)
},
TexImageSource::HTMLVideoElement(video) => {
if !video.origin_is_clean() {
@ -689,8 +742,17 @@ impl WebGLRenderingContext {
SnapshotPixelFormat::RGBA => PixelFormat::RGBA8,
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
.extension_manager
.get_effective_tex_internal_format(internal_format, data_type.as_gl_constant());
@ -788,12 +841,6 @@ impl WebGLRenderingContext {
match source {
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.
self.send_command(WebGLCommand::TexImage2D {
target: target.as_gl_constant(),
@ -804,8 +851,8 @@ impl WebGLRenderingContext {
data_type,
effective_data_type,
unpacking_alignment,
alpha_treatment,
y_axis_treatment,
alpha_treatment: pixels.alpha_treatment,
y_axis_treatment: pixels.y_axis_treatment,
pixel_format: pixels.pixel_format,
data: pixels.data.into(),
});
@ -873,21 +920,6 @@ impl WebGLRenderingContext {
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
.extension_manager
.effective_type(data_type.as_gl_constant());
@ -903,8 +935,8 @@ impl WebGLRenderingContext {
data_type,
effective_data_type,
unpacking_alignment,
alpha_treatment,
y_axis_treatment,
alpha_treatment: pixels.alpha_treatment,
y_axis_treatment: pixels.y_axis_treatment,
pixel_format: pixels.pixel_format,
data: pixels.data.into(),
});
@ -1581,9 +1613,7 @@ impl WebGLRenderingContext {
}
let size = Size2D::new(width, height);
let buff = IpcSharedMemory::from_bytes(data);
let pixels = TexPixels::from_array(buff, size);
let data = pixels.data;
let data = IpcSharedMemory::from_bytes(data);
handle_potential_webgl_error!(
self,
@ -1646,9 +1676,7 @@ impl WebGLRenderingContext {
Err(_) => return,
};
let buff = IpcSharedMemory::from_bytes(data);
let pixels = TexPixels::from_array(buff, Size2D::new(width, height));
let data = pixels.data;
let data = IpcSharedMemory::from_bytes(data);
self.send_command(WebGLCommand::CompressedTexSubImage2D {
target: target.as_gl_constant(),
@ -4533,6 +4561,8 @@ impl WebGLRenderingContextMethods<crate::DomTypeHolder> for WebGLRenderingContex
let size = Size2D::new(width, height);
let (alpha_treatment, y_axis_treatment) = self.get_current_unpack_state(false);
self.tex_image_2d(
&texture,
target,
@ -4543,7 +4573,12 @@ impl WebGLRenderingContextMethods<crate::DomTypeHolder> for WebGLRenderingContex
border,
unpacking_alignment,
size,
TexSource::Pixels(TexPixels::from_array(buff, size)),
TexSource::Pixels(TexPixels::from_array(
buff,
size,
alpha_treatment,
y_axis_treatment,
)),
);
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(
texture,
target,
@ -4712,7 +4749,12 @@ impl WebGLRenderingContextMethods<crate::DomTypeHolder> for WebGLRenderingContex
format,
data_type,
unpacking_alignment,
TexPixels::from_array(buff, Size2D::new(width, height)),
TexPixels::from_array(
buff,
Size2D::new(width, height),
alpha_treatment,
y_axis_treatment,
),
);
Ok(())
}
@ -5056,7 +5098,8 @@ pub(crate) struct TexPixels {
data: IpcSharedMemory,
size: Size2D<u32>,
pixel_format: Option<PixelFormat>,
premultiplied: bool,
alpha_treatment: Option<AlphaTreatment>,
y_axis_treatment: YAxisTreatment,
}
impl TexPixels {
@ -5064,22 +5107,30 @@ impl TexPixels {
data: IpcSharedMemory,
size: Size2D<u32>,
pixel_format: PixelFormat,
premultiplied: bool,
alpha_treatment: Option<AlphaTreatment>,
y_axis_treatment: YAxisTreatment,
) -> Self {
Self {
data,
size,
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 {
data,
size,
pixel_format: None,
premultiplied: false,
alpha_treatment,
y_axis_treatment,
}
}