diff --git a/crates/gpu/src/classic3d.rs b/crates/gpu/src/classic3d.rs index 9e73edb3..0c72dd60 100644 --- a/crates/gpu/src/classic3d.rs +++ b/crates/gpu/src/classic3d.rs @@ -188,7 +188,7 @@ pub(crate) fn draw_run(e: &mut Enc, run: &Run3d, canvas: &GpuImage) -> Option e.g.device.limits().max_storage_buffer_binding_size as u64 { + if (mattes.len() * 4) as u64 > e.g.device.limits().max_storage_buffer_binding_size { return None; } let mut lights = Words::default(); diff --git a/crates/gpu/src/context.rs b/crates/gpu/src/context.rs index 66bee99d..e7cda93e 100644 --- a/crates/gpu/src/context.rs +++ b/crates/gpu/src/context.rs @@ -141,7 +141,15 @@ impl GpuContext { } } let name = format!("{} ({:?})", info.name, info.backend); - let src = [include_str!("shaders/common.wgsl"), include_str!("shaders/kernels.wgsl"), include_str!("shaders/classic3d.wgsl")].concat(); + let src = [ + include_str!("shaders/common.wgsl"), + include_str!("shaders/kernels.wgsl"), + include_str!("shaders/classic3d.wgsl"), + include_str!("shaders/fx_color.wgsl"), + include_str!("shaders/fx_distort.wgsl"), + include_str!("shaders/fx_generate.wgsl"), + ] + .concat(); let module = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("effectcraft kernels"), source: wgpu::ShaderSource::Wgsl(src.into()) }); let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { @@ -190,6 +198,9 @@ impl GpuContext { }); let pipelines = ENTRIES .iter() + .chain(crate::fx_color::KERNELS) + .chain(crate::fx_distort::KERNELS) + .chain(crate::fx_generate::KERNELS) .map(|e| (e, &layout)) .chain(EXT_ENTRIES.iter().map(|e| (e, &layout_ext))) .map(|(e, layout)| { @@ -585,7 +596,7 @@ impl<'g> Enc<'g> { /// Copy an image into a storage buffer (RGBA f32 rows) for kernels that read a third image /// through `data`: (buffer, row length in pixels). - pub fn to_buffer(&mut self, img: &GpuImage) -> (wgpu::Buffer, u32) { + pub fn image_rows(&mut self, img: &GpuImage) -> (wgpu::Buffer, u32) { let row = (img.width * 16).div_ceil(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT) * wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; let buf = self.g.device.create_buffer(&wgpu::BufferDescriptor { label: Some("image rows"), @@ -606,7 +617,7 @@ impl<'g> Enc<'g> { /// A read-only storage buffer holding `bytes` (padded to 16 bytes). pub fn bytes(&self, mut bytes: Vec) -> wgpu::Buffer { - while bytes.len() < 16 || bytes.len() % 4 != 0 { + while bytes.len() < 16 || !bytes.len().is_multiple_of(4) { bytes.push(0); } self.g.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("data"), contents: &bytes, usage: wgpu::BufferUsages::STORAGE }) diff --git a/crates/gpu/src/effects.rs b/crates/gpu/src/effects.rs index 36b692f1..2ef75c48 100644 --- a/crates/gpu/src/effects.rs +++ b/crates/gpu/src/effects.rs @@ -16,19 +16,19 @@ pub fn supports(id: &str) -> bool { } /// A layer buffer on the GPU (see [`Buf`]). -struct GBuf { - img: GpuImage, - offset: [f64; 2], - scale: f64, +pub(crate) struct GBuf { + pub img: GpuImage, + pub offset: [f64; 2], + pub scale: f64, } impl GBuf { - fn to_px(&self, p: [f64; 2]) -> (f64, f64) { + pub fn to_px(&self, p: [f64; 2]) -> (f64, f64) { (p[0] * self.scale + self.offset[0], p[1] * self.scale + self.offset[1]) } /// Buf::pad: grow by `pad` transparent pixels on every side. - fn pad(&mut self, e: &mut Enc, pad: u32) -> Option<()> { + pub fn pad(&mut self, e: &mut Enc, pad: u32) -> Option<()> { if pad == 0 { return Some(()); } @@ -114,6 +114,9 @@ fn apply(e: &mut Enc, id: &str, ctx: &EffectCtx, b: GBuf) -> Option { "ec.distort.transform" => transform(e, ctx, b), "ec.color.curves" => curves(e, ctx, b), _ if id.starts_with("ec.control.") => Some(b), + _ if crate::fx_color::IDS.contains(&id) => crate::fx_color::apply(e, id, ctx, b), + _ if crate::fx_distort::IDS.contains(&id) => crate::fx_distort::apply(e, id, ctx, b), + _ if crate::fx_generate::IDS.contains(&id) => crate::fx_generate::apply(e, id, ctx, b), _ => pointwise(e, id, ctx, b), } } @@ -121,7 +124,7 @@ fn apply(e: &mut Enc, id: &str, ctx: &EffectCtx, b: GBuf) -> Option { // ---------------------------------------------------------------- blurs /// raster::blur::box_radii. -fn box_radii(sigma: f64, n: usize) -> Vec { +pub(crate) fn box_radii(sigma: f64, n: usize) -> Vec { if sigma <= 0.0 { return vec![0; n]; } @@ -137,7 +140,7 @@ fn box_radii(sigma: f64, n: usize) -> Vec { } /// Horizontal passes with radii `rx`, then vertical passes with radii `ry` (raster::blur). -fn box_passes(e: &mut Enc, img: &GpuImage, rx: &[usize], ry: &[usize], repeat: bool) -> GpuImage { +pub(crate) fn box_passes(e: &mut Enc, img: &GpuImage, rx: &[usize], ry: &[usize], repeat: bool) -> GpuImage { let mut cur = img.clone(); let passes = rx.iter().map(|&r| (false, r)).chain(ry.iter().map(|&r| (true, r))).filter(|p| p.1 > 0); for (vertical, r) in passes { @@ -153,7 +156,7 @@ fn box_passes(e: &mut Enc, img: &GpuImage, rx: &[usize], ry: &[usize], repeat: b } /// raster::gaussian_blur (3 box passes per axis). -fn gaussian_blur(e: &mut Enc, img: &GpuImage, sx: f64, sy: f64, repeat: bool) -> GpuImage { +pub(crate) fn gaussian_blur(e: &mut Enc, img: &GpuImage, sx: f64, sy: f64, repeat: bool) -> GpuImage { let rx = if sx > 0.05 { box_radii(sx, 3) } else { vec![] }; let ry = if sy > 0.05 { box_radii(sy, 3) } else { vec![] }; box_passes(e, img, &rx, &ry, repeat) diff --git a/crates/gpu/src/fx_color.rs b/crates/gpu/src/fx_color.rs new file mode 100644 index 00000000..8205f042 --- /dev/null +++ b/crates/gpu/src/fx_color.rs @@ -0,0 +1,18 @@ +//! GPU effects, colour family (kernels in `shaders/fx_color.wgsl`): the CPU effects' exact +//! steps with the pixel loops as compute kernels. + +use effectcraft_effects::EffectCtx; + +use crate::context::Enc; +use crate::effects::GBuf; + +/// Compute entry points in `fx_color.wgsl`. +pub(crate) const KERNELS: &[&str] = &[]; + +/// Effect ids implemented here. +pub(crate) const IDS: &[&str] = &[]; + +/// Run effect `id` (one of [`IDS`]); `None` = this parameter combination runs on the CPU. +pub(crate) fn apply(_e: &mut Enc, _id: &str, _ctx: &EffectCtx, _b: GBuf) -> Option { + None +} diff --git a/crates/gpu/src/fx_distort.rs b/crates/gpu/src/fx_distort.rs new file mode 100644 index 00000000..fe92ad91 --- /dev/null +++ b/crates/gpu/src/fx_distort.rs @@ -0,0 +1,18 @@ +//! GPU effects, distortion and stylize family (kernels in `shaders/fx_distort.wgsl`): the CPU effects' exact +//! steps with the pixel loops as compute kernels. + +use effectcraft_effects::EffectCtx; + +use crate::context::Enc; +use crate::effects::GBuf; + +/// Compute entry points in `fx_distort.wgsl`. +pub(crate) const KERNELS: &[&str] = &[]; + +/// Effect ids implemented here. +pub(crate) const IDS: &[&str] = &[]; + +/// Run effect `id` (one of [`IDS`]); `None` = this parameter combination runs on the CPU. +pub(crate) fn apply(_e: &mut Enc, _id: &str, _ctx: &EffectCtx, _b: GBuf) -> Option { + None +} diff --git a/crates/gpu/src/fx_generate.rs b/crates/gpu/src/fx_generate.rs new file mode 100644 index 00000000..61c18def --- /dev/null +++ b/crates/gpu/src/fx_generate.rs @@ -0,0 +1,18 @@ +//! GPU effects, blur, transition and generate family (kernels in `shaders/fx_generate.wgsl`): the CPU effects' exact +//! steps with the pixel loops as compute kernels. + +use effectcraft_effects::EffectCtx; + +use crate::context::Enc; +use crate::effects::GBuf; + +/// Compute entry points in `fx_generate.wgsl`. +pub(crate) const KERNELS: &[&str] = &[]; + +/// Effect ids implemented here. +pub(crate) const IDS: &[&str] = &[]; + +/// Run effect `id` (one of [`IDS`]); `None` = this parameter combination runs on the CPU. +pub(crate) fn apply(_e: &mut Enc, _id: &str, _ctx: &EffectCtx, _b: GBuf) -> Option { + None +} diff --git a/crates/gpu/src/lib.rs b/crates/gpu/src/lib.rs index a7c9cdc4..9ebce209 100644 --- a/crates/gpu/src/lib.rs +++ b/crates/gpu/src/lib.rs @@ -25,6 +25,9 @@ mod adv3d; mod classic3d; mod context; mod effects; +mod fx_color; +mod fx_distort; +mod fx_generate; mod ops; mod walk; diff --git a/crates/gpu/src/ops.rs b/crates/gpu/src/ops.rs index cd743fbf..e7a9c406 100644 --- a/crates/gpu/src/ops.rs +++ b/crates/gpu/src/ops.rs @@ -136,7 +136,7 @@ pub fn channel_mix(e: &mut Enc, canvas: &GpuImage, tmp: &GpuImage, channels: [bo /// Adjustment layer finish: move `canvas` toward `adjusted` by `matte`'s alpha × `opacity`. pub fn adjust_mix(e: &mut Enc, canvas: &GpuImage, adjusted: &GpuImage, matte: &GpuImage, opacity: f32) -> GpuImage { - let (rows, stride) = e.to_buffer(matte); + let (rows, stride) = e.image_rows(matte); let mut p = Params::default(); p.u[0][0] = stride; p.f[0][0] = opacity; diff --git a/crates/gpu/src/shaders/fx_color.wgsl b/crates/gpu/src/shaders/fx_color.wgsl new file mode 100644 index 00000000..3e42688d --- /dev/null +++ b/crates/gpu/src/shaders/fx_color.wgsl @@ -0,0 +1,2 @@ +// GPU effects (color family): see src/fx_color.rs. Each entry point mirrors the CPU effect in +// effectcraft-effects operation for operation; parameter layouts are documented per entry. diff --git a/crates/gpu/src/shaders/fx_distort.wgsl b/crates/gpu/src/shaders/fx_distort.wgsl new file mode 100644 index 00000000..b3ce3463 --- /dev/null +++ b/crates/gpu/src/shaders/fx_distort.wgsl @@ -0,0 +1,2 @@ +// GPU effects (distort family): see src/fx_distort.rs. Each entry point mirrors the CPU effect in +// effectcraft-effects operation for operation; parameter layouts are documented per entry. diff --git a/crates/gpu/src/shaders/fx_generate.wgsl b/crates/gpu/src/shaders/fx_generate.wgsl new file mode 100644 index 00000000..bc67b93a --- /dev/null +++ b/crates/gpu/src/shaders/fx_generate.wgsl @@ -0,0 +1,2 @@ +// GPU effects (generate family): see src/fx_generate.rs. Each entry point mirrors the CPU effect in +// effectcraft-effects operation for operation; parameter layouts are documented per entry. diff --git a/crates/gpu/src/tests.rs b/crates/gpu/src/tests.rs index 137fdec3..f795b58d 100644 --- a/crates/gpu/src/tests.rs +++ b/crates/gpu/src/tests.rs @@ -379,7 +379,7 @@ fn layer_styles_collapsed_precomps_and_cpu_fallbacks() { /// The effect alone on a buffer: GPU kernels vs the CPU effect (32-bit float), including the /// buffer geometry (padding, offset). -fn effect_direct(id: &str, vals: &[(&str, Value)], adjustment: bool) { +pub(crate) fn effect_direct(id: &str, vals: &[(&str, Value)], adjustment: bool) { let Some(g) = gpu() else { return }; let spec = effectcraft_effects::find(id).unwrap(); let size = [70.0, 44.0]; @@ -401,7 +401,7 @@ fn effect_direct(id: &str, vals: &[(&str, Value)], adjustment: bool) { } /// One footage layer with `fx` applied, composited over a background, at 8 and 32 bpc. -fn effect_case(id: &str, vals: &[(&str, Value)]) { +pub(crate) fn effect_case(id: &str, vals: &[(&str, Value)]) { effect_direct(id, vals, false); effect_direct(id, vals, true); for depth in [BitDepth::Bpc8, BitDepth::Bpc32] {