use crate::coord::CanvasRect;
use crate::gpu::atlas::{CanvasFrame, LayerTexture};
#[derive(Copy, Clone)]
pub struct GpuPaintTarget<'a> {
texture: &'a wgpu::Texture,
view: &'a wgpu::TextureView,
format: wgpu::TextureFormat,
width: u32,
height: u32,
offset_x: i32,
offset_y: i32,
canvas_width: u32,
canvas_height: u32,
canvas_origin_x: i32,
canvas_origin_y: i32,
}
impl<'a> GpuPaintTarget<'a> {
pub fn from_node(tex: &'a LayerTexture, canvas: CanvasRect) -> Self {
let extent = tex.canvas_extent();
GpuPaintTarget {
texture: tex.texture(),
view: tex.view(),
format: tex.format(),
width: extent.width,
height: extent.height,
offset_x: extent.origin.x,
offset_y: extent.origin.y,
canvas_width: canvas.width,
canvas_height: canvas.height,
canvas_origin_x: canvas.origin.x,
canvas_origin_y: canvas.origin.y,
}
}
pub fn from_canvas_texture(
texture: &'a wgpu::Texture,
view: &'a wgpu::TextureView,
format: wgpu::TextureFormat,
canvas: CanvasRect,
) -> Self {
Self::from_extent(texture, view, format, canvas, canvas)
}
pub fn from_extent(
texture: &'a wgpu::Texture,
view: &'a wgpu::TextureView,
format: wgpu::TextureFormat,
canvas_extent: CanvasRect,
canvas: CanvasRect,
) -> Self {
GpuPaintTarget {
texture,
view,
format,
width: canvas_extent.width,
height: canvas_extent.height,
offset_x: canvas_extent.x0(),
offset_y: canvas_extent.y0(),
canvas_width: canvas.width,
canvas_height: canvas.height,
canvas_origin_x: canvas.origin.x,
canvas_origin_y: canvas.origin.y,
}
}
pub fn texture(&self) -> &'a wgpu::Texture {
self.texture
}
pub fn view(&self) -> &'a wgpu::TextureView {
self.view
}
pub fn format(&self) -> wgpu::TextureFormat {
self.format
}
pub fn layer_extent(&self) -> crate::coord::LayerRect {
crate::coord::LayerRect::from_xywh(0, 0, self.width, self.height)
}
pub fn canvas_extent(&self) -> CanvasRect {
CanvasRect::from_xywh(self.offset_x, self.offset_y, self.width, self.height)
}
pub fn canvas_size(&self) -> (u32, u32) {
(self.canvas_width, self.canvas_height)
}
pub fn canvas_origin(&self) -> (i32, i32) {
(self.canvas_origin_x, self.canvas_origin_y)
}
pub fn canvas_frame(&self) -> CanvasFrame<'a> {
CanvasFrame {
texture: self.texture,
canvas_extent: self.canvas_extent(),
}
}
pub fn composite_circle(
&self,
encoder: &mut wgpu::CommandEncoder,
pipelines: &PaintPipelines,
queue: &wgpu::Queue,
cx: f32,
cy: f32,
radius: f32,
color: [u8; 4],
opacity: f32,
) {
let pipeline = pipelines.composite_pipeline(self.format);
self.draw_circle(
encoder, pipeline, pipelines, queue, cx, cy, radius, color, opacity, None,
);
}
pub fn composite_circle_with_selection(
&self,
encoder: &mut wgpu::CommandEncoder,
pipelines: &PaintPipelines,
queue: &wgpu::Queue,
cx: f32,
cy: f32,
radius: f32,
color: [u8; 4],
opacity: f32,
selection_bind_group: &wgpu::BindGroup,
) {
let pipeline = pipelines.composite_pipeline(self.format);
self.draw_circle(
encoder,
pipeline,
pipelines,
queue,
cx,
cy,
radius,
color,
opacity,
Some(selection_bind_group),
);
}
pub fn fill_rect(
&self,
encoder: &mut wgpu::CommandEncoder,
pipelines: &PaintPipelines,
queue: &wgpu::Queue,
rect: CanvasRect,
color: [u8; 4],
) {
self.fill_rect_inner(encoder, pipelines, queue, rect, color, None);
}
pub fn fill_rect_with_selection(
&self,
encoder: &mut wgpu::CommandEncoder,
pipelines: &PaintPipelines,
queue: &wgpu::Queue,
rect: CanvasRect,
color: [u8; 4],
selection_bind_group: &wgpu::BindGroup,
) {
self.fill_rect_inner(
encoder,
pipelines,
queue,
rect,
color,
Some(selection_bind_group),
);
}
pub fn erase_with_selection(
&self,
encoder: &mut wgpu::CommandEncoder,
pipelines: &PaintPipelines,
queue: &wgpu::Queue,
selection_bind_group: &wgpu::BindGroup,
) {
let pipeline = pipelines.erase_pipeline(self.format);
let uniforms = PaintUniforms {
origin: [self.offset_x as f32, self.offset_y as f32],
size: [self.width as f32, self.height as f32],
target_offset: [self.offset_x as f32, self.offset_y as f32],
target_size: [self.width as f32, self.height as f32],
canvas_size: [self.canvas_width as f32, self.canvas_height as f32],
canvas_origin: [self.canvas_origin_x as f32, self.canvas_origin_y as f32],
center: [0.0, 0.0],
radius: 0.0, softness: 0.0,
color: [1.0, 1.0, 1.0, 1.0], };
self.execute_pass(
encoder,
pipeline,
pipelines,
queue,
&uniforms,
Some(selection_bind_group),
);
}
pub fn multiply_by_mask(
&self,
encoder: &mut wgpu::CommandEncoder,
pipelines: &PaintPipelines,
queue: &wgpu::Queue,
mask_bind_group: &wgpu::BindGroup,
) {
let pipeline = pipelines.mask_multiply_pipeline(self.format);
let uniforms = PaintUniforms {
origin: [self.offset_x as f32, self.offset_y as f32],
size: [self.width as f32, self.height as f32],
target_offset: [self.offset_x as f32, self.offset_y as f32],
target_size: [self.width as f32, self.height as f32],
canvas_size: [self.canvas_width as f32, self.canvas_height as f32],
canvas_origin: [self.canvas_origin_x as f32, self.canvas_origin_y as f32],
center: [0.0, 0.0],
radius: 0.0,
softness: 0.0,
color: [0.0, 0.0, 0.0, 1.0],
};
self.execute_pass(
encoder,
pipeline,
pipelines,
queue,
&uniforms,
Some(mask_bind_group),
);
}
pub fn multiply_by_inverse_mask(
&self,
encoder: &mut wgpu::CommandEncoder,
pipelines: &PaintPipelines,
queue: &wgpu::Queue,
mask_bind_group: &wgpu::BindGroup,
) {
let pipeline = pipelines.inverse_mask_multiply_pipeline(self.format);
let uniforms = PaintUniforms {
origin: [self.offset_x as f32, self.offset_y as f32],
size: [self.width as f32, self.height as f32],
target_offset: [self.offset_x as f32, self.offset_y as f32],
target_size: [self.width as f32, self.height as f32],
canvas_size: [self.canvas_width as f32, self.canvas_height as f32],
canvas_origin: [self.canvas_origin_x as f32, self.canvas_origin_y as f32],
center: [0.0, 0.0],
radius: 0.0,
softness: 0.0,
color: [0.0, 0.0, 0.0, 1.0],
};
self.execute_pass(
encoder,
pipeline,
pipelines,
queue,
&uniforms,
Some(mask_bind_group),
);
}
pub fn multiply_alpha_by_mask(
&self,
encoder: &mut wgpu::CommandEncoder,
pipelines: &PaintPipelines,
queue: &wgpu::Queue,
mask_bind_group: &wgpu::BindGroup,
) {
let pipeline = pipelines.alpha_mask_multiply_pipeline(self.format);
let uniforms = PaintUniforms {
origin: [self.offset_x as f32, self.offset_y as f32],
size: [self.width as f32, self.height as f32],
target_offset: [self.offset_x as f32, self.offset_y as f32],
target_size: [self.width as f32, self.height as f32],
canvas_size: [self.canvas_width as f32, self.canvas_height as f32],
canvas_origin: [self.canvas_origin_x as f32, self.canvas_origin_y as f32],
center: [0.0, 0.0],
radius: 0.0,
softness: 0.0,
color: [0.0, 0.0, 0.0, 1.0],
};
self.execute_pass(
encoder,
pipeline,
pipelines,
queue,
&uniforms,
Some(mask_bind_group),
);
}
pub fn multiply_alpha_by_inverse_mask(
&self,
encoder: &mut wgpu::CommandEncoder,
pipelines: &PaintPipelines,
queue: &wgpu::Queue,
mask_bind_group: &wgpu::BindGroup,
) {
let pipeline = pipelines.alpha_inverse_mask_multiply_pipeline(self.format);
let uniforms = PaintUniforms {
origin: [self.offset_x as f32, self.offset_y as f32],
size: [self.width as f32, self.height as f32],
target_offset: [self.offset_x as f32, self.offset_y as f32],
target_size: [self.width as f32, self.height as f32],
canvas_size: [self.canvas_width as f32, self.canvas_height as f32],
canvas_origin: [self.canvas_origin_x as f32, self.canvas_origin_y as f32],
center: [0.0, 0.0],
radius: 0.0,
softness: 0.0,
color: [0.0, 0.0, 0.0, 1.0],
};
self.execute_pass(
encoder,
pipeline,
pipelines,
queue,
&uniforms,
Some(mask_bind_group),
);
}
pub fn clear_rect(
&self,
encoder: &mut wgpu::CommandEncoder,
pipelines: &PaintPipelines,
queue: &wgpu::Queue,
rect: CanvasRect,
) {
let pipeline = pipelines.clear_pipeline(self.format);
let color = match self.format {
wgpu::TextureFormat::R8Unorm => [1.0, 1.0, 1.0, 1.0], _ => [0.0, 0.0, 0.0, 0.0], };
let uniforms = PaintUniforms {
origin: [rect.x0() as f32, rect.y0() as f32],
size: [rect.width as f32, rect.height as f32],
target_offset: [self.offset_x as f32, self.offset_y as f32],
target_size: [self.width as f32, self.height as f32],
canvas_size: [self.canvas_width as f32, self.canvas_height as f32],
canvas_origin: [self.canvas_origin_x as f32, self.canvas_origin_y as f32],
center: [0.0, 0.0],
radius: 0.0,
softness: 0.0,
color,
};
self.execute_pass(encoder, pipeline, pipelines, queue, &uniforms, None);
}
pub fn linear_gradient(
&self,
encoder: &mut wgpu::CommandEncoder,
pipelines: &PaintPipelines,
queue: &wgpu::Queue,
x0: f32,
y0: f32,
x1: f32,
y1: f32,
color0: [u8; 4],
color1: [u8; 4],
selection: Option<&wgpu::BindGroup>,
) {
let pipeline = pipelines.gradient_pipeline(self.format);
let uniforms = GradientUniforms {
origin: [self.offset_x as f32, self.offset_y as f32],
size: [self.width as f32, self.height as f32],
target_offset: [self.offset_x as f32, self.offset_y as f32],
target_size: [self.width as f32, self.height as f32],
canvas_size: [self.canvas_width as f32, self.canvas_height as f32],
canvas_origin: [self.canvas_origin_x as f32, self.canvas_origin_y as f32],
start: [x0, y0],
end: [x1, y1],
color0: color_to_float(color0, 1.0),
color1: color_to_float(color1, 1.0),
};
queue.write_buffer(
&pipelines.gradient_uniform_buf,
0,
bytemuck::bytes_of(&uniforms),
);
let sel = selection.unwrap_or(&pipelines.default_selection_bind_group);
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("paint-gradient"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: self.view,
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
..Default::default()
});
pass.set_pipeline(pipeline);
pass.set_bind_group(0, &pipelines.gradient_uniform_bind_group, &[]);
pass.set_bind_group(1, sel, &[]);
pass.draw(0..3, 0..1);
}
fn fill_rect_inner(
&self,
encoder: &mut wgpu::CommandEncoder,
pipelines: &PaintPipelines,
queue: &wgpu::Queue,
rect: CanvasRect,
color: [u8; 4],
selection: Option<&wgpu::BindGroup>,
) {
let pipeline = pipelines.composite_pipeline(self.format);
let uniforms = PaintUniforms {
origin: [rect.x0() as f32, rect.y0() as f32],
size: [rect.width as f32, rect.height as f32],
target_offset: [self.offset_x as f32, self.offset_y as f32],
target_size: [self.width as f32, self.height as f32],
canvas_size: [self.canvas_width as f32, self.canvas_height as f32],
canvas_origin: [self.canvas_origin_x as f32, self.canvas_origin_y as f32],
center: [0.0, 0.0],
radius: 0.0, softness: 0.0,
color: color_to_float(color, 1.0),
};
self.execute_pass(encoder, pipeline, pipelines, queue, &uniforms, selection);
}
fn draw_circle(
&self,
encoder: &mut wgpu::CommandEncoder,
pipeline: &wgpu::RenderPipeline,
pipelines: &PaintPipelines,
queue: &wgpu::Queue,
cx: f32,
cy: f32,
radius: f32,
color: [u8; 4],
opacity: f32,
selection: Option<&wgpu::BindGroup>,
) {
let softness = 1.0_f32;
let pad = softness + 1.0;
let layer_x0 = self.offset_x as f32;
let layer_y0 = self.offset_y as f32;
let layer_x1 = layer_x0 + self.width as f32;
let layer_y1 = layer_y0 + self.height as f32;
let x0 = (cx - radius - pad).max(layer_x0);
let y0 = (cy - radius - pad).max(layer_y0);
let x1 = (cx + radius + pad).min(layer_x1);
let y1 = (cy + radius + pad).min(layer_y1);
let uniforms = PaintUniforms {
origin: [x0, y0],
size: [x1 - x0, y1 - y0],
target_offset: [self.offset_x as f32, self.offset_y as f32],
target_size: [self.width as f32, self.height as f32],
canvas_size: [self.canvas_width as f32, self.canvas_height as f32],
canvas_origin: [self.canvas_origin_x as f32, self.canvas_origin_y as f32],
center: [cx, cy],
radius,
softness,
color: color_to_float(color, opacity),
};
self.execute_pass(encoder, pipeline, pipelines, queue, &uniforms, selection);
}
fn execute_pass(
&self,
encoder: &mut wgpu::CommandEncoder,
pipeline: &wgpu::RenderPipeline,
pipelines: &PaintPipelines,
queue: &wgpu::Queue,
uniforms: &PaintUniforms,
selection: Option<&wgpu::BindGroup>,
) {
queue.write_buffer(&pipelines.uniform_buf, 0, bytemuck::bytes_of(uniforms));
let sel = selection.unwrap_or(&pipelines.default_selection_bind_group);
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("paint-target"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: self.view,
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
..Default::default()
});
pass.set_viewport(0.0, 0.0, self.width as f32, self.height as f32, 0.0, 1.0);
pass.set_pipeline(pipeline);
pass.set_bind_group(0, &pipelines.uniform_bind_group, &[]);
pass.set_bind_group(1, sel, &[]);
pass.draw(0..3, 0..1);
}
}
pub struct PaintPipelines {
composite_rgba: wgpu::RenderPipeline,
composite_r8: wgpu::RenderPipeline,
erase_rgba: wgpu::RenderPipeline,
erase_r8: wgpu::RenderPipeline,
clear_rgba: wgpu::RenderPipeline,
clear_r8: wgpu::RenderPipeline,
gradient_rgba: wgpu::RenderPipeline,
gradient_r8: wgpu::RenderPipeline,
mask_multiply_rgba: wgpu::RenderPipeline,
mask_multiply_r8: wgpu::RenderPipeline,
inverse_mask_multiply_rgba: wgpu::RenderPipeline,
inverse_mask_multiply_r8: wgpu::RenderPipeline,
alpha_mask_multiply_rgba: wgpu::RenderPipeline,
alpha_inverse_mask_multiply_rgba: wgpu::RenderPipeline,
pub(crate) uniform_buf: wgpu::Buffer,
pub(crate) uniform_bind_group: wgpu::BindGroup,
gradient_uniform_buf: wgpu::Buffer,
gradient_uniform_bind_group: wgpu::BindGroup,
pub(crate) default_selection_bind_group: wgpu::BindGroup,
pub(crate) selection_bind_group_layout: wgpu::BindGroupLayout,
}
impl PaintPipelines {
pub fn new(
device: &wgpu::Device,
queue: &wgpu::Queue,
selection_bgl: &wgpu::BindGroupLayout,
) -> Self {
let selection_bgl = selection_bgl.clone();
let paint_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("paint-circle"),
source: wgpu::ShaderSource::Wgsl(
crate::gpu::canvas_lib::with_canvas_lib(include_str!(
"../../shaders/paint_circle.wgsl"
))
.into(),
),
});
let gradient_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("gradient"),
source: wgpu::ShaderSource::Wgsl(
crate::gpu::canvas_lib::with_canvas_lib(include_str!(
"../../shaders/gradient.wgsl"
))
.into(),
),
});
let uniform_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("paint-uniform-bgl"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let paint_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("paint-pipeline-layout"),
bind_group_layouts: &[Some(&uniform_bgl), Some(&selection_bgl)],
immediate_size: 0,
});
let gradient_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("gradient-pipeline-layout"),
bind_group_layouts: &[Some(&uniform_bgl), Some(&selection_bgl)],
immediate_size: 0,
});
let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("paint-uniforms"),
size: std::mem::size_of::<PaintUniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("paint-uniform-bg"),
layout: &uniform_bgl,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buf.as_entire_binding(),
}],
});
let gradient_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("gradient-uniforms"),
size: std::mem::size_of::<GradientUniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let gradient_uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("gradient-uniform-bg"),
layout: &uniform_bgl,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: gradient_uniform_buf.as_entire_binding(),
}],
});
let sel_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("default-selection"),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &sel_texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&[255u8],
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(1),
rows_per_image: Some(1),
},
wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
);
let sel_view = sel_texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("paint-sampler"),
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});
let default_selection_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("paint-default-selection-bg"),
layout: &selection_bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&sel_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
});
let make_pipeline = |label: &str,
layout: &wgpu::PipelineLayout,
shader: &wgpu::ShaderModule,
format: wgpu::TextureFormat,
blend: wgpu::BlendState| {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(label),
layout: Some(layout),
vertex: wgpu::VertexState {
module: shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: Default::default(),
},
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
fragment: Some(wgpu::FragmentState {
module: shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(blend),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
multiview_mask: None,
cache: None,
})
};
let blend_composite = wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
};
let blend_erase_rgba = wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::One,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
};
let blend_erase_r8 = wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent::REPLACE,
};
let blend_clear = wgpu::BlendState::REPLACE;
let blend_gradient = blend_composite;
let blend_mask_multiply = wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::SrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::SrcAlpha,
operation: wgpu::BlendOperation::Add,
},
};
let blend_inverse_mask_multiply = wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
};
let blend_alpha_mask_multiply = wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::One,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::SrcAlpha,
operation: wgpu::BlendOperation::Add,
},
};
let blend_alpha_inverse_mask_multiply = wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::One,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
};
PaintPipelines {
composite_rgba: make_pipeline(
"paint-composite-rgba",
&paint_layout,
&paint_shader,
wgpu::TextureFormat::Rgba8Unorm,
blend_composite,
),
composite_r8: make_pipeline(
"paint-composite-r8",
&paint_layout,
&paint_shader,
wgpu::TextureFormat::R8Unorm,
blend_composite,
),
erase_rgba: make_pipeline(
"paint-erase-rgba",
&paint_layout,
&paint_shader,
wgpu::TextureFormat::Rgba8Unorm,
blend_erase_rgba,
),
erase_r8: make_pipeline(
"paint-erase-r8",
&paint_layout,
&paint_shader,
wgpu::TextureFormat::R8Unorm,
blend_erase_r8,
),
clear_rgba: make_pipeline(
"paint-clear-rgba",
&paint_layout,
&paint_shader,
wgpu::TextureFormat::Rgba8Unorm,
blend_clear,
),
clear_r8: make_pipeline(
"paint-clear-r8",
&paint_layout,
&paint_shader,
wgpu::TextureFormat::R8Unorm,
blend_clear,
),
gradient_rgba: make_pipeline(
"gradient-rgba",
&gradient_layout,
&gradient_shader,
wgpu::TextureFormat::Rgba8Unorm,
blend_gradient,
),
gradient_r8: make_pipeline(
"gradient-r8",
&gradient_layout,
&gradient_shader,
wgpu::TextureFormat::R8Unorm,
blend_gradient,
),
mask_multiply_rgba: make_pipeline(
"mask-multiply-rgba",
&paint_layout,
&paint_shader,
wgpu::TextureFormat::Rgba8Unorm,
blend_mask_multiply,
),
mask_multiply_r8: make_pipeline(
"mask-multiply-r8",
&paint_layout,
&paint_shader,
wgpu::TextureFormat::R8Unorm,
blend_mask_multiply,
),
inverse_mask_multiply_rgba: make_pipeline(
"inv-mask-mul-rgba",
&paint_layout,
&paint_shader,
wgpu::TextureFormat::Rgba8Unorm,
blend_inverse_mask_multiply,
),
inverse_mask_multiply_r8: make_pipeline(
"inv-mask-mul-r8",
&paint_layout,
&paint_shader,
wgpu::TextureFormat::R8Unorm,
blend_inverse_mask_multiply,
),
alpha_mask_multiply_rgba: make_pipeline(
"alpha-mask-mul-rgba",
&paint_layout,
&paint_shader,
wgpu::TextureFormat::Rgba8Unorm,
blend_alpha_mask_multiply,
),
alpha_inverse_mask_multiply_rgba: make_pipeline(
"alpha-inv-mask-mul-rgba",
&paint_layout,
&paint_shader,
wgpu::TextureFormat::Rgba8Unorm,
blend_alpha_inverse_mask_multiply,
),
uniform_buf,
uniform_bind_group,
gradient_uniform_buf,
gradient_uniform_bind_group,
default_selection_bind_group,
selection_bind_group_layout: selection_bgl,
}
}
pub fn upload_r8_bind_group(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
width: u32,
height: u32,
data: &[u8],
label: &str,
) -> wgpu::BindGroup {
let tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some(label),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &tex,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
data,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(width),
rows_per_image: Some(height),
},
wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
);
let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("r8-mask-sampler"),
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
self.create_selection_bind_group(device, &view, &sampler)
}
pub fn create_selection_bind_group(
&self,
device: &wgpu::Device,
selection_view: &wgpu::TextureView,
sampler: &wgpu::Sampler,
) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("paint-selection-bg"),
layout: &self.selection_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(selection_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(sampler),
},
],
})
}
fn composite_pipeline(&self, format: wgpu::TextureFormat) -> &wgpu::RenderPipeline {
match format {
wgpu::TextureFormat::R8Unorm => &self.composite_r8,
_ => &self.composite_rgba,
}
}
fn erase_pipeline(&self, format: wgpu::TextureFormat) -> &wgpu::RenderPipeline {
match format {
wgpu::TextureFormat::R8Unorm => &self.erase_r8,
_ => &self.erase_rgba,
}
}
fn clear_pipeline(&self, format: wgpu::TextureFormat) -> &wgpu::RenderPipeline {
match format {
wgpu::TextureFormat::R8Unorm => &self.clear_r8,
_ => &self.clear_rgba,
}
}
fn gradient_pipeline(&self, format: wgpu::TextureFormat) -> &wgpu::RenderPipeline {
match format {
wgpu::TextureFormat::R8Unorm => &self.gradient_r8,
_ => &self.gradient_rgba,
}
}
fn mask_multiply_pipeline(&self, format: wgpu::TextureFormat) -> &wgpu::RenderPipeline {
match format {
wgpu::TextureFormat::R8Unorm => &self.mask_multiply_r8,
_ => &self.mask_multiply_rgba,
}
}
fn inverse_mask_multiply_pipeline(&self, format: wgpu::TextureFormat) -> &wgpu::RenderPipeline {
match format {
wgpu::TextureFormat::R8Unorm => &self.inverse_mask_multiply_r8,
_ => &self.inverse_mask_multiply_rgba,
}
}
fn alpha_mask_multiply_pipeline(&self, format: wgpu::TextureFormat) -> &wgpu::RenderPipeline {
match format {
wgpu::TextureFormat::R8Unorm => &self.mask_multiply_r8,
_ => &self.alpha_mask_multiply_rgba,
}
}
fn alpha_inverse_mask_multiply_pipeline(
&self,
format: wgpu::TextureFormat,
) -> &wgpu::RenderPipeline {
match format {
wgpu::TextureFormat::R8Unorm => &self.inverse_mask_multiply_r8,
_ => &self.alpha_inverse_mask_multiply_rgba,
}
}
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct PaintUniforms {
origin: [f32; 2], size: [f32; 2], target_offset: [f32; 2], target_size: [f32; 2], canvas_size: [f32; 2], canvas_origin: [f32; 2], center: [f32; 2], radius: f32, softness: f32, color: [f32; 4], }
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct GradientUniforms {
origin: [f32; 2], size: [f32; 2], target_offset: [f32; 2], target_size: [f32; 2], canvas_size: [f32; 2], canvas_origin: [f32; 2], start: [f32; 2], end: [f32; 2], color0: [f32; 4], color1: [f32; 4], }
fn color_to_float(color: [u8; 4], opacity: f32) -> [f32; 4] {
[
color[0] as f32 / 255.0,
color[1] as f32 / 255.0,
color[2] as f32 / 255.0,
color[3] as f32 / 255.0 * opacity,
]
}