M12.7: GPU effect family modules (colour, distort, generate) scaffolding; clippy fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas 2026-10-03 02:25:12 -04:00
parent 878b47b5ed
commit 9a6389db86
12 changed files with 93 additions and 16 deletions

View File

@ -188,7 +188,7 @@ pub(crate) fn draw_run(e: &mut Enc, run: &Run3d, canvas: &GpuImage) -> Option<Gp
}
n_geos += p.geos.len() as u32;
}
if (mattes.len() * 4) as u64 > 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();

View File

@ -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<u8>) -> 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 })

View File

@ -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<GBuf> {
"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<GBuf> {
// ---------------------------------------------------------------- blurs
/// raster::blur::box_radii.
fn box_radii(sigma: f64, n: usize) -> Vec<usize> {
pub(crate) fn box_radii(sigma: f64, n: usize) -> Vec<usize> {
if sigma <= 0.0 {
return vec![0; n];
}
@ -137,7 +140,7 @@ fn box_radii(sigma: f64, n: usize) -> Vec<usize> {
}
/// 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)

View File

@ -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<GBuf> {
None
}

View File

@ -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<GBuf> {
None
}

View File

@ -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<GBuf> {
None
}

View File

@ -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;

View File

@ -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;

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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] {