use rustc_hash::FxHashMap;
use valo_dl::BlendMode;
pub const SAMPLE_COUNT: u32 = 4;
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth24PlusStencil8;
pub(crate) const MESH_VERTEX_FLOATS: usize = 2;
pub(crate) const GLYPH_VERTEX_FLOATS: usize = 4;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Frag {
Solid,
Image,
ImageMatrix,
ImageBlend,
ImageGamma,
Linear,
Radial,
Sweep,
BlendSolid,
BlendTexture,
RRectBlur,
Blur,
Downsample,
MaskCombine,
DropShadow,
MaskComposite,
LinearRamp,
RadialRamp,
SweepRamp,
ColorMatrix,
ColorBlend,
ColorGamma,
Pattern,
GlyphMask,
GlyphSdf,
GlyphColor,
GlyphColorMatrix,
GlyphColorBlend,
GlyphColorGamma,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum BindLayout {
Plain,
Textured,
Blend,
Blur,
}
impl Frag {
fn bind_layout(self) -> BindLayout {
match self {
Frag::Solid | Frag::Linear | Frag::Radial | Frag::Sweep | Frag::RRectBlur => {
BindLayout::Plain
}
Frag::BlendTexture | Frag::MaskCombine | Frag::DropShadow => BindLayout::Blend,
Frag::Blur => BindLayout::Blur,
Frag::Image
| Frag::ImageMatrix
| Frag::ImageBlend
| Frag::ImageGamma
| Frag::BlendSolid
| Frag::Downsample
| Frag::MaskComposite
| Frag::LinearRamp
| Frag::RadialRamp
| Frag::SweepRamp
| Frag::ColorMatrix
| Frag::ColorBlend
| Frag::ColorGamma
| Frag::Pattern
| Frag::GlyphMask
| Frag::GlyphSdf
| Frag::GlyphColor
| Frag::GlyphColorMatrix
| Frag::GlyphColorBlend
| Frag::GlyphColorGamma => BindLayout::Textured,
}
}
fn entry_point(self) -> &'static str {
match self {
Frag::Solid => "fs_solid",
Frag::Image => "fs_image",
Frag::ImageMatrix => "fs_image_matrix",
Frag::ImageBlend => "fs_image_blend",
Frag::ImageGamma => "fs_image_gamma",
Frag::Linear => "fs_linear",
Frag::Radial => "fs_radial",
Frag::Sweep => "fs_sweep",
Frag::BlendSolid => "fs_blend_solid",
Frag::BlendTexture => "fs_blend_texture",
Frag::RRectBlur => "fs_rrect_blur",
Frag::Blur => "fs_blur",
Frag::Downsample => "fs_downsample",
Frag::MaskCombine => "fs_mask_combine",
Frag::DropShadow => "fs_drop_shadow",
Frag::MaskComposite => "fs_mask_composite",
Frag::LinearRamp => "fs_linear_ramp",
Frag::RadialRamp => "fs_radial_ramp",
Frag::SweepRamp => "fs_sweep_ramp",
Frag::ColorMatrix => "fs_color_matrix",
Frag::ColorBlend => "fs_color_blend",
Frag::ColorGamma => "fs_color_gamma",
Frag::Pattern => "fs_pattern",
Frag::GlyphMask => "fs_text",
Frag::GlyphSdf => "fs_text_sdf",
Frag::GlyphColor => "fs_text_color",
Frag::GlyphColorMatrix => "fs_text_color_matrix",
Frag::GlyphColorBlend => "fs_text_color_blend",
Frag::GlyphColorGamma => "fs_text_color_gamma",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PipelineKind {
Draw(Frag),
Cover(Frag),
OpaqueDraw(Frag),
OpaqueCover(Frag),
StencilFan { even_odd: bool },
StencilStrip,
ClipCover { difference: bool },
Filter(Frag),
Strip(Frag),
Glyphs(Frag),
}
impl PipelineKind {
fn writes_color(self) -> bool {
matches!(
self,
PipelineKind::Draw(_)
| PipelineKind::Cover(_)
| PipelineKind::OpaqueDraw(_)
| PipelineKind::OpaqueCover(_)
| PipelineKind::Filter(_)
| PipelineKind::Strip(_)
| PipelineKind::Glyphs(_)
)
}
fn frag(self) -> Option<Frag> {
match self {
PipelineKind::Draw(f)
| PipelineKind::Cover(f)
| PipelineKind::OpaqueDraw(f)
| PipelineKind::OpaqueCover(f)
| PipelineKind::Filter(f)
| PipelineKind::Strip(f)
| PipelineKind::Glyphs(f) => Some(f),
PipelineKind::StencilFan { .. }
| PipelineKind::StencilStrip
| PipelineKind::ClipCover { .. } => None,
}
}
fn replaces_dst(self) -> bool {
matches!(
self,
PipelineKind::OpaqueDraw(_) | PipelineKind::OpaqueCover(_) | PipelineKind::Filter(_)
) || matches!(
self.frag(),
Some(Frag::BlendSolid) | Some(Frag::BlendTexture)
)
}
fn fragment_entry(self) -> &'static str {
self.frag().map_or("fs_solid", Frag::entry_point)
}
pub fn sample_count(self) -> u32 {
match self {
PipelineKind::Filter(_) => 1,
_ => SAMPLE_COUNT,
}
}
fn vertex_entry(self) -> &'static str {
match self {
PipelineKind::StencilFan { .. }
| PipelineKind::StencilStrip
| PipelineKind::Strip(_) => "vs_mesh",
PipelineKind::Glyphs(_) => "vs_text",
_ => "vs_quad",
}
}
fn blends(self) -> bool {
self.writes_color() && !self.replaces_dst()
}
fn normalized_blend(self, blend: PipelineBlend) -> PipelineBlend {
if self.blends() {
blend
} else {
PipelineBlend::SrcOver
}
}
fn bind_layout(self) -> BindLayout {
self.frag().map_or(BindLayout::Plain, Frag::bind_layout)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Blend {
Fixed(PipelineBlend),
ReadsDestination(AdvancedBlend),
}
impl Blend {
pub fn of(mode: BlendMode) -> Self {
use AdvancedBlend as A;
use PipelineBlend as P;
match mode {
BlendMode::Clear => Blend::Fixed(P::Clear),
BlendMode::Src => Blend::Fixed(P::Src),
BlendMode::Dst => Blend::Fixed(P::Dst),
BlendMode::SrcOver => Blend::Fixed(P::SrcOver),
BlendMode::DstOver => Blend::Fixed(P::DstOver),
BlendMode::SrcIn => Blend::Fixed(P::SrcIn),
BlendMode::DstIn => Blend::Fixed(P::DstIn),
BlendMode::SrcOut => Blend::Fixed(P::SrcOut),
BlendMode::DstOut => Blend::Fixed(P::DstOut),
BlendMode::SrcAtop => Blend::Fixed(P::SrcAtop),
BlendMode::DstAtop => Blend::Fixed(P::DstAtop),
BlendMode::Xor => Blend::Fixed(P::Xor),
BlendMode::Plus => Blend::Fixed(P::Plus),
BlendMode::Modulate => Blend::Fixed(P::Modulate),
BlendMode::Screen => Blend::Fixed(P::Screen),
BlendMode::Multiply => Blend::ReadsDestination(A::Multiply),
BlendMode::Overlay => Blend::ReadsDestination(A::Overlay),
BlendMode::Darken => Blend::ReadsDestination(A::Darken),
BlendMode::Lighten => Blend::ReadsDestination(A::Lighten),
BlendMode::ColorDodge => Blend::ReadsDestination(A::ColorDodge),
BlendMode::ColorBurn => Blend::ReadsDestination(A::ColorBurn),
BlendMode::HardLight => Blend::ReadsDestination(A::HardLight),
BlendMode::SoftLight => Blend::ReadsDestination(A::SoftLight),
BlendMode::Difference => Blend::ReadsDestination(A::Difference),
BlendMode::Exclusion => Blend::ReadsDestination(A::Exclusion),
BlendMode::Hue => Blend::ReadsDestination(A::Hue),
BlendMode::Saturation => Blend::ReadsDestination(A::Saturation),
BlendMode::Color => Blend::ReadsDestination(A::Color),
BlendMode::Luminosity => Blend::ReadsDestination(A::Luminosity),
}
}
pub fn filter_id(self) -> u32 {
match self {
Blend::Fixed(blend) => blend.id(),
Blend::ReadsDestination(blend) => crate::shader_abi::ADVANCED_FILTER_BASE + blend.id(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PipelineBlend {
Clear,
Src,
Dst,
SrcOver,
DstOver,
SrcIn,
DstIn,
SrcOut,
DstOut,
SrcAtop,
DstAtop,
Xor,
Plus,
Modulate,
Screen,
}
impl PipelineBlend {
pub(crate) const ALL: [Self; 15] = [
Self::Clear,
Self::Src,
Self::Dst,
Self::SrcOver,
Self::DstOver,
Self::SrcIn,
Self::DstIn,
Self::SrcOut,
Self::DstOut,
Self::SrcAtop,
Self::DstAtop,
Self::Xor,
Self::Plus,
Self::Modulate,
Self::Screen,
];
pub fn id(self) -> u32 {
self as u32
}
pub(crate) fn wgsl_name(self) -> &'static str {
match self {
Self::Clear => "CLEAR",
Self::Src => "SRC",
Self::Dst => "DST",
Self::SrcOver => "SRC_OVER",
Self::DstOver => "DST_OVER",
Self::SrcIn => "SRC_IN",
Self::DstIn => "DST_IN",
Self::SrcOut => "SRC_OUT",
Self::DstOut => "DST_OUT",
Self::SrcAtop => "SRC_ATOP",
Self::DstAtop => "DST_ATOP",
Self::Xor => "XOR",
Self::Plus => "PLUS",
Self::Modulate => "MODULATE",
Self::Screen => "SCREEN",
}
}
fn state(self) -> wgpu::BlendState {
use wgpu::BlendFactor as F;
let (src, dst) = match self {
Self::Clear => (F::Zero, F::Zero),
Self::Src => (F::One, F::Zero),
Self::Dst => (F::Zero, F::One),
Self::SrcOver => (F::One, F::OneMinusSrcAlpha),
Self::DstOver => (F::OneMinusDstAlpha, F::One),
Self::SrcIn => (F::DstAlpha, F::Zero),
Self::DstIn => (F::Zero, F::SrcAlpha),
Self::SrcOut => (F::OneMinusDstAlpha, F::Zero),
Self::DstOut => (F::Zero, F::OneMinusSrcAlpha),
Self::SrcAtop => (F::DstAlpha, F::OneMinusSrcAlpha),
Self::DstAtop => (F::OneMinusDstAlpha, F::SrcAlpha),
Self::Xor => (F::OneMinusDstAlpha, F::OneMinusSrcAlpha),
Self::Plus => (F::One, F::One),
Self::Modulate => (F::Zero, F::Src),
Self::Screen => (F::One, F::OneMinusSrc),
};
let component = wgpu::BlendComponent {
src_factor: src,
dst_factor: dst,
operation: wgpu::BlendOperation::Add,
};
wgpu::BlendState {
color: component,
alpha: component,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AdvancedBlend {
Multiply,
Overlay,
Darken,
Lighten,
ColorDodge,
ColorBurn,
HardLight,
SoftLight,
Difference,
Exclusion,
Hue,
Saturation,
Color,
Luminosity,
}
impl AdvancedBlend {
pub(crate) const ALL: [Self; 14] = [
Self::Multiply,
Self::Overlay,
Self::Darken,
Self::Lighten,
Self::ColorDodge,
Self::ColorBurn,
Self::HardLight,
Self::SoftLight,
Self::Difference,
Self::Exclusion,
Self::Hue,
Self::Saturation,
Self::Color,
Self::Luminosity,
];
pub fn id(self) -> u32 {
self as u32
}
pub(crate) fn wgsl_name(self) -> &'static str {
match self {
Self::Multiply => "MULTIPLY",
Self::Overlay => "OVERLAY",
Self::Darken => "DARKEN",
Self::Lighten => "LIGHTEN",
Self::ColorDodge => "COLOR_DODGE",
Self::ColorBurn => "COLOR_BURN",
Self::HardLight => "HARD_LIGHT",
Self::SoftLight => "SOFT_LIGHT",
Self::Difference => "DIFFERENCE",
Self::Exclusion => "EXCLUSION",
Self::Hue => "HUE",
Self::Saturation => "SATURATION",
Self::Color => "COLOR",
Self::Luminosity => "LUMINOSITY",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct PipelineKey {
pub format: wgpu::TextureFormat,
pub blend: PipelineBlend,
pub kind: PipelineKind,
}
impl PipelineKey {
pub fn new(format: wgpu::TextureFormat, blend: PipelineBlend, kind: PipelineKind) -> Self {
Self {
format,
blend: kind.normalized_blend(blend),
kind,
}
}
}
pub struct CompiledPipelines<'a> {
map: &'a FxHashMap<PipelineKey, wgpu::RenderPipeline>,
}
impl CompiledPipelines<'_> {
pub fn get(&self, key: &PipelineKey) -> &wgpu::RenderPipeline {
&self.map[key]
}
}
pub struct PipelineCache {
shader: wgpu::ShaderModule,
plain_layout: wgpu::PipelineLayout,
textured_layout: wgpu::PipelineLayout,
blend_layout: wgpu::PipelineLayout,
blur_layout: wgpu::PipelineLayout,
texture_bind_layout: wgpu::BindGroupLayout,
blend_bind_layout: wgpu::BindGroupLayout,
map: FxHashMap<PipelineKey, wgpu::RenderPipeline>,
}
impl PipelineCache {
pub fn new(
device: &wgpu::Device,
uniforms_layout: &wgpu::BindGroupLayout,
kernel_layout: &wgpu::BindGroupLayout,
) -> Self {
let source = crate::shader_abi::wgsl_prelude() + include_str!("shaders/solid.wgsl");
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("valo.solid"),
source: wgpu::ShaderSource::Wgsl(source.into()),
});
let texture_bind_layout = texture_bind_group_layout(device);
let plain_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("valo.plain"),
bind_group_layouts: &[Some(uniforms_layout)],
immediate_size: 0,
});
let textured_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("valo.textured"),
bind_group_layouts: &[Some(uniforms_layout), Some(&texture_bind_layout)],
immediate_size: 0,
});
let blend_bind_layout = blend_bind_group_layout(device);
let blend_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("valo.blend"),
bind_group_layouts: &[Some(uniforms_layout), Some(&blend_bind_layout)],
immediate_size: 0,
});
let blur_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("valo.blur"),
bind_group_layouts: &[
Some(uniforms_layout),
Some(&texture_bind_layout),
Some(kernel_layout),
],
immediate_size: 0,
});
Self {
shader,
plain_layout,
textured_layout,
blend_layout,
blur_layout,
texture_bind_layout,
blend_bind_layout,
map: FxHashMap::default(),
}
}
pub fn blend_bind_layout(&self) -> &wgpu::BindGroupLayout {
&self.blend_bind_layout
}
pub fn texture_bind_layout(&self) -> &wgpu::BindGroupLayout {
&self.texture_bind_layout
}
pub fn compile(
&mut self,
device: &wgpu::Device,
keys: impl IntoIterator<Item = PipelineKey>,
) -> CompiledPipelines<'_> {
for key in keys {
if !self.map.contains_key(&key) {
let pipeline = self.create(device, key);
self.map.insert(key, pipeline);
}
}
CompiledPipelines { map: &self.map }
}
fn create(&self, device: &wgpu::Device, key: PipelineKey) -> wgpu::RenderPipeline {
let layout = match key.kind.bind_layout() {
BindLayout::Plain => &self.plain_layout,
BindLayout::Textured => &self.textured_layout,
BindLayout::Blend => &self.blend_layout,
BindLayout::Blur => &self.blur_layout,
};
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("valo.solid"),
layout: Some(layout),
vertex: wgpu::VertexState {
module: &self.shader,
entry_point: Some(key.kind.vertex_entry()),
compilation_options: Default::default(),
buffers: vertex_buffers(key.kind),
},
fragment: Some(wgpu::FragmentState {
module: &self.shader,
entry_point: Some(key.kind.fragment_entry()),
compilation_options: Default::default(),
targets: &[Some(color_target(key))],
}),
primitive: wgpu::PrimitiveState {
topology: match key.kind {
PipelineKind::Strip(_) | PipelineKind::StencilStrip => {
wgpu::PrimitiveTopology::TriangleStrip
}
_ => wgpu::PrimitiveTopology::TriangleList,
},
..Default::default()
},
depth_stencil: depth_stencil(key.kind),
multisample: wgpu::MultisampleState {
count: key.kind.sample_count(),
..Default::default()
},
multiview_mask: None,
cache: None,
})
}
}
fn texture_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("valo.texture"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
})
}
fn blend_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
let texture_entry = |binding| wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
};
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("valo.blend"),
entries: &[
texture_entry(0), wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
texture_entry(2), ],
})
}
const MESH_LAYOUT: [Option<wgpu::VertexBufferLayout<'static>>; 1] =
[Some(wgpu::VertexBufferLayout {
array_stride: (MESH_VERTEX_FLOATS * std::mem::size_of::<f32>()) as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![0 => Float32x2],
})];
const GLYPH_LAYOUT: [Option<wgpu::VertexBufferLayout<'static>>; 1] =
[Some(wgpu::VertexBufferLayout {
array_stride: (GLYPH_VERTEX_FLOATS * std::mem::size_of::<f32>()) as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2],
})];
fn vertex_buffers(kind: PipelineKind) -> &'static [Option<wgpu::VertexBufferLayout<'static>>] {
match kind {
PipelineKind::StencilFan { .. } | PipelineKind::StencilStrip | PipelineKind::Strip(_) => {
&MESH_LAYOUT
}
PipelineKind::Glyphs(_) => &GLYPH_LAYOUT,
_ => &[],
}
}
fn color_target(key: PipelineKey) -> wgpu::ColorTargetState {
let writes_color = key.kind.writes_color();
wgpu::ColorTargetState {
format: key.format,
blend: key.kind.blends().then(|| key.blend.state()),
write_mask: if writes_color {
wgpu::ColorWrites::ALL
} else {
wgpu::ColorWrites::empty()
},
}
}
fn depth_stencil(kind: PipelineKind) -> Option<wgpu::DepthStencilState> {
let (depth_write_enabled, depth_compare, stencil) = match kind {
PipelineKind::Draw(_) | PipelineKind::Strip(_) => (
false,
wgpu::CompareFunction::GreaterEqual,
face_pair(ALWAYS_KEEP),
),
PipelineKind::OpaqueDraw(_) => (
true,
wgpu::CompareFunction::GreaterEqual,
face_pair(ALWAYS_KEEP),
),
PipelineKind::OpaqueCover(_) => (
true,
wgpu::CompareFunction::GreaterEqual,
face_pair(COVER_WOUND),
),
PipelineKind::Cover(_) => (
false,
wgpu::CompareFunction::GreaterEqual,
face_pair(COVER_WOUND),
),
PipelineKind::StencilFan { even_odd } => {
let winding = |op| wgpu::StencilFaceState {
compare: wgpu::CompareFunction::Always,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: op,
};
let stencil = if even_odd {
face_pair(winding(wgpu::StencilOperation::Invert))
} else {
wgpu::StencilState {
front: winding(wgpu::StencilOperation::IncrementWrap),
back: winding(wgpu::StencilOperation::DecrementWrap),
read_mask: 0xFF,
write_mask: 0xFF,
}
};
(false, wgpu::CompareFunction::Always, stencil)
}
PipelineKind::StencilStrip => (
false,
wgpu::CompareFunction::Always,
face_pair(wgpu::StencilFaceState {
compare: wgpu::CompareFunction::Always,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::IncrementClamp,
}),
),
PipelineKind::Filter(_) => return None,
PipelineKind::Glyphs(_) => (
false,
wgpu::CompareFunction::GreaterEqual,
face_pair(ALWAYS_KEEP),
),
PipelineKind::ClipCover { difference } => (
true,
wgpu::CompareFunction::Greater,
face_pair(wgpu::StencilFaceState {
compare: if difference {
wgpu::CompareFunction::NotEqual } else {
wgpu::CompareFunction::Equal },
fail_op: wgpu::StencilOperation::Zero,
depth_fail_op: wgpu::StencilOperation::Zero,
pass_op: wgpu::StencilOperation::Zero,
}),
),
};
Some(wgpu::DepthStencilState {
format: DEPTH_FORMAT,
depth_write_enabled: Some(depth_write_enabled),
depth_compare: Some(depth_compare),
stencil,
bias: Default::default(),
})
}
const COVER_WOUND: wgpu::StencilFaceState = wgpu::StencilFaceState {
compare: wgpu::CompareFunction::NotEqual,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Zero,
pass_op: wgpu::StencilOperation::Zero,
};
const ALWAYS_KEEP: wgpu::StencilFaceState = wgpu::StencilFaceState {
compare: wgpu::CompareFunction::Always,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Keep,
};
fn face_pair(face: wgpu::StencilFaceState) -> wgpu::StencilState {
wgpu::StencilState {
front: face,
back: face,
read_mask: 0xFF,
write_mask: 0xFF,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_blend_mode_is_run_by_the_blend_unit_or_the_fragment() {
assert_eq!(
Blend::of(BlendMode::Screen),
Blend::Fixed(PipelineBlend::Screen)
);
assert_eq!(
Blend::of(BlendMode::Luminosity),
Blend::ReadsDestination(AdvancedBlend::Luminosity)
);
for (id, blend) in PipelineBlend::ALL.into_iter().enumerate() {
assert_eq!(blend.id(), id as u32);
}
for (id, blend) in AdvancedBlend::ALL.into_iter().enumerate() {
assert_eq!(blend.id(), id as u32);
}
assert_eq!(Blend::of(BlendMode::Multiply).filter_id(), 15);
}
}