From 1a39c4376295c06ffd530527add98abb9a59eafe Mon Sep 17 00:00:00 2001 From: "Pedro M. de Echanove Pasquin" Date: Mon, 19 Oct 2026 22:16:17 +0200 Subject: [PATCH] Composite modes on the GLES canvas: Canvas::set_composite_mode and CompositeMode, completing the blend state the previous commit already applies A canvas only composited source-over. A host that draws Porter-Duff modes, as Android's Canvas does with an xfermode, had to read the pixels back, combine them on the CPU and upload them again, which on the GLES backend meant a glReadPixels and a fresh texture for every such draw. The new `CompositeMode` in types.rs, exported from the crate root, names the Porter-Duff operators (Clear, Src, Dst, SrcOver, DstOver, SrcIn, DstIn, SrcOut, DstOut, SrcAtop, DstAtop, Xor) and the separable modes with an exact premultiplied form (Plus, Multiply, Screen); SrcOver is the default. Darken and Lighten are left out, since GL can only approximate them where alpha is below one. `Canvas::set_composite_mode` sets how the following draws, blits included, combine with the canvas. On a GLES canvas it stores the mode, which `activate_target` turns into the blend function before every draw: each canvas carries its own, because the GL blend state is global and canvases share one context. A sub-canvas starts in SrcOver and does not inherit its parent's mode. A software canvas ignores the call and keeps compositing source-over. The field, its initialisation and `apply_composite` landed with the previous commit; this one adds the type and the public entry point they use, without which that commit does not build. The inset shadow, which switches the blend for one draw, now restores the canvas's mode instead of always going back to source-over. --- src/gles_render/primitives.rs | 3 +-- src/render/mod.rs | 9 +++++++++ src/types.rs | 24 ++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/gles_render/primitives.rs b/src/gles_render/primitives.rs index 343c022..1feee13 100644 --- a/src/gles_render/primitives.rs +++ b/src/gles_render/primitives.rs @@ -491,10 +491,9 @@ impl GlesCanvas self.gl.draw_arrays( glow::TRIANGLES, 0, 6 ); self.gl.bind_vertex_array( None ); - // Restore the pipeline default. if !matches!( shadow.blend, BlendMode::Normal ) { - self.gl.blend_func( glow::ONE, glow::ONE_MINUS_SRC_ALPHA ); + self.apply_composite(); } } } diff --git a/src/render/mod.rs b/src/render/mod.rs index 188c5c1..e4ed522 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -497,6 +497,15 @@ impl Canvas } } + /// How the following draws, blits included, combine with the canvas. GPU canvases only; a software canvas always composites src-over. + pub fn set_composite_mode( &mut self, mode: crate::types::CompositeMode ) + { + if let Canvas::Gles( c ) = self + { + c.set_composite_mode( mode ); + } + } + /// Shared font handle. Exposed so widgets that need raw face /// access (e.g. `Text` for ascent/descent) do not have to go /// through wrappers for every metric they read. diff --git a/src/types.rs b/src/types.rs index 62370c0..813e42f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -286,6 +286,30 @@ pub enum PathCmd Close, } +/// How a draw combines with what the canvas already holds: the Porter-Duff +/// operators plus the separable modes with an exact premultiplied form. Set +/// with [`Canvas::set_composite_mode`](crate::Canvas::set_composite_mode). +#[ derive( Clone, Copy, Debug, Default, PartialEq, Eq ) ] +pub enum CompositeMode +{ + #[ default ] + SrcOver, + Clear, + Src, + Dst, + DstOver, + SrcIn, + DstIn, + SrcOut, + DstOut, + SrcAtop, + DstAtop, + Xor, + Plus, + Multiply, + Screen, +} + /// A stable widget identifier used for focus management. /// /// Assign an id to a widget with `.id( WidgetId("my_widget") )`, then request