blend_formula/
lib.rs

1//! Provides two macros, [`blend_formula!`](blend_formula) and
2//! [`blend_equation!`](blend_equation), used to evaluate an arbitrary formula
3//! to its equivalent GPU blend mode. If none exist, it's a compile-time error.
4//! 
5//! Valid formulae are equivalent to some `Term*Factor Op Term*Factor`, where:
6//! 
7//! - Each `Term` is either "`src`" or "`dst`" (mutually exclusive).
8//! - Each `Factor` is a [`BlendFactor`].
9//! - `Op` is a [`BlendOperation`].
10//! 
11//! If a formula is for a color or alpha [equation](BlendEquation), non-scalar
12//! terms must be "converted" using the appropriate accessor (`.rgb` or `.a`).
13//! 
14//! For convenience, any valid operator can be used to combine the source and
15//! destination without a factor (`+`, `-`, `<`, `>`, `*`).
16//! 
17//! # Examples
18//! 
19//! ```
20//! use blend_formula::*;
21//! 
22//!  // Formulae:
23//!	assert_eq!(blend_formula!(src*src.a + dst*(1 - src.a)), BlendFormula {
24//!     src_factor: BlendFactor::SrcAlpha,
25//!     dst_factor: BlendFactor::OneMinusSrcAlpha,
26//!     operation: BlendOperation::Add
27//! });
28//!	assert_eq!(blend_formula!(-src), BlendFormula {
29//!     src_factor: BlendFactor::One,
30//!     dst_factor: BlendFactor::Zero,
31//!     operation: BlendOperation::RevSub
32//! });
33//!	assert_eq!(blend_formula!(dst < src*c), BlendFormula {
34//!     src_factor: BlendFactor::Constant,
35//!     dst_factor: BlendFactor::One,
36//!     operation: BlendOperation::Min
37//! });
38//! 
39//!  // Equations:
40//! assert_eq!(blend_equation!(src + dst*(1-src.a)), 
41//!     BlendEquation::PREMULTIPLIED_ALPHA_BLENDING
42//! );
43//! assert_eq!(blend_equation!((dst+(src-dst)*src.a).rgb, (dst+src-dst*src).a),
44//!     BlendEquation::ALPHA_BLENDING
45//! );
46//! 
47//!  // Shortcuts:
48//! assert_eq!(blend_formula!(+), blend_formula!(src+dst));
49//! assert_eq!(blend_formula!(*), blend_formula!(src*dst));
50//! assert_eq!(blend_equation!(-, dst.a), BlendEquation {
51//!     color: blend_formula!(src-dst),
52//!     alpha: blend_formula!(dst),
53//! });
54//! ```
55//! 
56//! # Conversion
57//! 
58//! If the feature `wgpu` is enabled, conversion traits are implemented for the
59//! corresponding types of the [`wgpu`](https://wgpu.rs/) crate.
60//! 
61//! - `blend_formula::BlendFactor` -> `wgpu::BlendFactor`
62//! - `blend_formula::BlendOperation` -> `wgpu::BlendOperation`
63//! - `blend_formula::BlendComponent` -> `wgpu::BlendComponent`
64//! - `blend_formula::BlendState` -> `wgpu::BlendState`
65
66/// Produces a [`BlendEquation`] equivalent to some formulae, if one exists.
67/// 
68/// Two comma-separated formulas can be given to provide separate
69/// formulas for the color and alpha channels.
70/// 
71/// See the main [crate] documentation for more details.
72pub use blend_formula_proc_macro::blend_equation;
73
74/// Produces a [`BlendFormula`] equivalent to some formula, if one exists.
75/// 
76/// See the main [crate] documentation for more details.
77pub use blend_formula_proc_macro::blend_formula;
78
79/// Corresponds to [`wgpu::BlendFactor`](https://docs.rs/wgpu/latest/wgpu/enum.BlendFactor.html).
80#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
81pub enum BlendFactor {
82	/** `0`               */ Zero,
83	/** `1`               */ One,
84	/** `src`             */ Src,
85	/** `src.a`           */ SrcAlpha,
86	/** `dst`             */ Dst,
87	/** `dst.a`           */ DstAlpha,
88	/** `c`               */ Constant,
89	/** `1-src`           */ OneMinusSrc,
90	/** `1-src.a`         */ OneMinusSrcAlpha,
91	/** `1-dst`           */ OneMinusDst,
92	/** `1-dst.a`         */ OneMinusDstAlpha,
93	/** `1-c`             */ OneMinusConstant,
94    /** `src.a < 1-dst.a` */ SaturatedSrcAlpha,
95}
96
97/// Corresponds to [`wgpu::BlendOperation`](https://docs.rs/wgpu/latest/wgpu/enum.BlendOperation.html).
98#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)]
99pub enum BlendOperation {
100	#[default]
101	/** `src + dst` */ Add,
102	/** `src - dst` */ Sub,
103	/** `dst - src` */ RevSub,
104	/** `src < dst` */ Min,
105	/** `src > dst` */ Max,
106}
107
108/// Corresponds to [`wgpu::BlendComponent`](https://docs.rs/wgpu/latest/wgpu/struct.BlendComponent.html).
109#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
110pub struct BlendFormula {
111    /// Multiplier for the source, which is produced by the fragment shader.
112	pub src_factor: BlendFactor,
113    /// Multiplier for the destination, which is stored in the target.
114	pub dst_factor: BlendFactor,
115    /// The binary operation applied to the source and destination,
116    /// multiplied by their respective factors.
117	pub operation: BlendOperation,
118}
119
120impl BlendFormula {
121    /// Default blending formula that replaces the destination with the source.
122    /// 
123	/// Equivalent to `blend_formula!(src)`.
124    pub const REPLACE: Self = Self {
125        src_factor: BlendFactor::One,
126        dst_factor: BlendFactor::Zero,
127        operation: BlendOperation::Add,
128    };
129	
130	/// Alpha blending formula that combines the destination with the source.
131	/// 
132	/// Equivalent to `blend_formula!(src + dst*(1-src.a))`.
133	pub const OVER: Self = Self {
134		src_factor: BlendFactor::One,
135		dst_factor: BlendFactor::OneMinusSrcAlpha,
136		operation: BlendOperation::Add,
137	};
138}
139
140impl Default for BlendFormula {
141	fn default() -> Self {
142		Self::REPLACE
143	}
144}
145
146/// Corresponds to [`wgpu::BlendState`](https://docs.rs/wgpu/latest/wgpu/struct.BlendState.html).
147#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)]
148pub struct BlendEquation {
149	pub color: BlendFormula,
150	pub alpha: BlendFormula,
151}
152
153impl BlendEquation {
154    /// Default blending equation that replaces the destination with the source.
155    /// 
156    /// Equivalent to `blend_equation!(src)`.
157	pub const REPLACE: Self = Self {
158		color: BlendFormula::REPLACE,
159		alpha: BlendFormula::REPLACE,
160	};
161	
162	/// Standard alpha blending without premultiplied color channels.
163	/// 
164	/// Equivalent to
165	/// `blend_equation!((src*src.a+dst*(1-src.a)).rgb, (src+dst*(1-src.a)).a)`.
166	pub const ALPHA_BLENDING: Self = Self {
167		color: BlendFormula {
168			src_factor: BlendFactor::SrcAlpha,
169			dst_factor: BlendFactor::OneMinusSrcAlpha,
170			operation: BlendOperation::Add,
171		},
172		alpha: BlendFormula::OVER,
173	};
174	
175	/// Standard alpha blending with premultiplied color channels.
176	/// 
177	/// Equivalent to `blend_equation!(src + dst*(1-src.a))`.
178	pub const PREMULTIPLIED_ALPHA_BLENDING: Self = Self {
179		color: BlendFormula::OVER,
180		alpha: BlendFormula::OVER,
181	};
182}
183
184#[cfg(feature = "wgpu")]
185mod wgpu {
186	use crate::BlendFactor;
187	use crate::BlendOperation;
188	use crate::BlendFormula;
189	use crate::BlendEquation;
190	
191	impl From<BlendFactor> for wgpu::BlendFactor {
192		fn from(value: BlendFactor) -> Self {
193			match value {
194				BlendFactor::Zero              => Self::Zero,
195				BlendFactor::One               => Self::One,
196				BlendFactor::Src               => Self::Src,
197				BlendFactor::SrcAlpha          => Self::SrcAlpha,
198				BlendFactor::Dst               => Self::Dst,
199				BlendFactor::DstAlpha          => Self::DstAlpha,
200				BlendFactor::Constant          => Self::Constant,
201				BlendFactor::OneMinusSrc       => Self::OneMinusSrc,
202				BlendFactor::OneMinusSrcAlpha  => Self::OneMinusSrcAlpha,
203				BlendFactor::OneMinusDst       => Self::OneMinusDst,
204				BlendFactor::OneMinusDstAlpha  => Self::OneMinusDstAlpha,
205				BlendFactor::OneMinusConstant  => Self::OneMinusConstant,
206				BlendFactor::SaturatedSrcAlpha => Self::SrcAlphaSaturated,
207			}
208		}
209	}
210	
211	impl From<BlendOperation> for wgpu::BlendOperation {
212		fn from(value: BlendOperation) -> Self {
213			match value {
214				BlendOperation::Add    => Self::Add,
215				BlendOperation::Sub    => Self::Subtract,
216				BlendOperation::RevSub => Self::ReverseSubtract,
217				BlendOperation::Min    => Self::Min,
218				BlendOperation::Max    => Self::Max,
219			}
220		}
221	}
222	
223	impl From<BlendFormula> for wgpu::BlendComponent {
224		fn from(value: BlendFormula) -> Self {
225			wgpu::BlendComponent {
226				src_factor: value.src_factor.into(),
227				dst_factor: value.dst_factor.into(),
228				operation: value.operation.into(),
229			}
230		}
231	}
232	
233	impl From<BlendEquation> for wgpu::BlendState {
234		fn from(value: BlendEquation) -> Self {
235			wgpu::BlendState {
236				color: value.color.into(),
237				alpha: value.alpha.into(),
238			}
239		}
240	}
241}
242
243#[test]
244fn saturated_src_alpha() {
245	//! Saturated source alpha uses a factor of "1" for the alpha channel, so
246	//! it's only usable as a color channel factor.
247	
248	use crate as blend_formula;
249	
250	use BlendFactor::*;
251	use BlendOperation::*;
252	
253	assert_eq!(blend_equation!(dst*(src.a < 1-dst.a)), BlendEquation {
254		color: BlendFormula { src_factor: Dst, dst_factor: OneMinusDst, operation: Min },
255		alpha: BlendFormula { src_factor: Dst, dst_factor: OneMinusDst, operation: Min }
256	});
257	assert_eq!(blend_equation!(dst.rgb*(src.a < 1-dst.a), dst.a*(src.a < 1-dst.a)), BlendEquation {
258		color: BlendFormula { src_factor: Zero, dst_factor: SaturatedSrcAlpha, operation: Add },
259		alpha: BlendFormula { src_factor: DstAlpha, dst_factor: OneMinusDstAlpha, operation: Min }
260	});
261}