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}