Compare commits
2 Commits
e500914fa0
...
63a8080e60
| Author | SHA1 | Date |
|---|---|---|
|
|
63a8080e60 | |
|
|
4e79abdc35 |
|
|
@ -10,22 +10,22 @@ const BUNDLED_MODELS: &[BundledModel] = &[
|
||||||
BundledModel {
|
BundledModel {
|
||||||
name: "BossSD1",
|
name: "BossSD1",
|
||||||
filename: "BossSD1-WaveNet.nam",
|
filename: "BossSD1-WaveNet.nam",
|
||||||
data: include_bytes!("../../../../../vendor/NeuralAudio/Utils/Models/BossSD1-WaveNet.nam"),
|
data: include_bytes!("../../../../../src/assets/nam_models/BossSD1-WaveNet.nam"),
|
||||||
},
|
},
|
||||||
BundledModel {
|
BundledModel {
|
||||||
name: "DeluxeReverb",
|
name: "DeluxeReverb",
|
||||||
filename: "DeluxeReverb.nam",
|
filename: "DeluxeReverb.nam",
|
||||||
data: include_bytes!("../../../../../vendor/NeuralAudio/Utils/Models/DeluxeReverb.nam"),
|
data: include_bytes!("../../../../../src/assets/nam_models/DeluxeReverb.nam"),
|
||||||
},
|
},
|
||||||
BundledModel {
|
BundledModel {
|
||||||
name: "DingwallBass",
|
name: "DingwallBass",
|
||||||
filename: "DingwallBass.nam",
|
filename: "DingwallBass.nam",
|
||||||
data: include_bytes!("../../../../../vendor/NeuralAudio/Utils/Models/DingwallBass.nam"),
|
data: include_bytes!("../../../../../src/assets/nam_models/DingwallBass.nam"),
|
||||||
},
|
},
|
||||||
BundledModel {
|
BundledModel {
|
||||||
name: "Rhythm",
|
name: "Rhythm",
|
||||||
filename: "Rhythm.nam",
|
filename: "Rhythm.nam",
|
||||||
data: include_bytes!("../../../../../vendor/NeuralAudio/Utils/Models/Rhythm.nam"),
|
data: include_bytes!("../../../../../src/assets/nam_models/Rhythm.nam"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,8 @@ impl BrushEngine {
|
||||||
if stroke.points.len() < 2 {
|
if stroke.points.len() < 2 {
|
||||||
if let Some(pt) = stroke.points.first() {
|
if let Some(pt) = stroke.points.first() {
|
||||||
let r = stroke.brush_settings.radius_at_pressure(pt.pressure);
|
let r = stroke.brush_settings.radius_at_pressure(pt.pressure);
|
||||||
let o = stroke.brush_settings.opacity_at_pressure(pt.pressure);
|
let raw_o = stroke.brush_settings.opacity_at_pressure(pt.pressure);
|
||||||
|
let o = 1.0 - (1.0 - raw_o).powf(stroke.brush_settings.dabs_per_radius * 0.5);
|
||||||
// Single-tap smudge has no direction — skip (same as CPU engine)
|
// Single-tap smudge has no direction — skip (same as CPU engine)
|
||||||
if !matches!(stroke.blend_mode, RasterBlendMode::Smudge) {
|
if !matches!(stroke.blend_mode, RasterBlendMode::Smudge) {
|
||||||
push_dab(&mut dabs, &mut bbox, pt.x, pt.y, r, o, 0.0, 0.0, 0.0);
|
push_dab(&mut dabs, &mut bbox, pt.x, pt.y, r, o, 0.0, 0.0, 0.0);
|
||||||
|
|
@ -177,7 +178,12 @@ impl BrushEngine {
|
||||||
let y2 = p0.y + t * dy;
|
let y2 = p0.y + t * dy;
|
||||||
let pressure2 = p0.pressure + t * (p1.pressure - p0.pressure);
|
let pressure2 = p0.pressure + t * (p1.pressure - p0.pressure);
|
||||||
let radius2 = stroke.brush_settings.radius_at_pressure(pressure2);
|
let radius2 = stroke.brush_settings.radius_at_pressure(pressure2);
|
||||||
let opacity2 = stroke.brush_settings.opacity_at_pressure(pressure2);
|
let raw_opacity = stroke.brush_settings.opacity_at_pressure(pressure2);
|
||||||
|
// Normalize per-dab opacity so dense dabs don't saturate faster than sparse ones.
|
||||||
|
// Formula: per_dab = 1 − (1 − raw)^(dabs_per_radius / 2)
|
||||||
|
// Derivation: N = 2/dabs_per_radius dabs cover one full diameter at the centre;
|
||||||
|
// accumulated = 1 − (1 − per_dab)^N = raw → per_dab = 1 − (1−raw)^(dabs_per_radius/2)
|
||||||
|
let opacity2 = 1.0 - (1.0 - raw_opacity).powf(stroke.brush_settings.dabs_per_radius * 0.5);
|
||||||
|
|
||||||
if matches!(stroke.blend_mode, RasterBlendMode::Smudge) {
|
if matches!(stroke.blend_mode, RasterBlendMode::Smudge) {
|
||||||
let ndx = dx / seg_len;
|
let ndx = dx / seg_len;
|
||||||
|
|
|
||||||
|
|
@ -276,12 +276,10 @@ impl GpuBrushEngine {
|
||||||
|
|
||||||
/// Dispatch the brush compute shader for `dabs` onto the canvas of `keyframe_id`.
|
/// Dispatch the brush compute shader for `dabs` onto the canvas of `keyframe_id`.
|
||||||
///
|
///
|
||||||
/// Each dab is dispatched as a separate copy+compute+swap so that every dab
|
/// Each dab is dispatched serially: copy the dab's bounding box from src→dst,
|
||||||
/// reads the result of the previous one. This is required for the smudge tool:
|
/// dispatch the compute shader, then swap. The bbox-only copy is safe because
|
||||||
/// if all dabs were batched into one dispatch they would all read the pre-batch
|
/// neither normal/erase nor smudge reads outside the current dab's radius.
|
||||||
/// canvas state, breaking the carry-forward that makes smudge drag pixels along.
|
|
||||||
///
|
///
|
||||||
/// `dab_bbox` is the union bounding box (unused here; kept for API compat).
|
|
||||||
/// If `dabs` is empty, does nothing.
|
/// If `dabs` is empty, does nothing.
|
||||||
pub fn render_dabs(
|
pub fn render_dabs(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
@ -294,35 +292,21 @@ impl GpuBrushEngine {
|
||||||
canvas_h: u32,
|
canvas_h: u32,
|
||||||
) {
|
) {
|
||||||
if dabs.is_empty() { return; }
|
if dabs.is_empty() { return; }
|
||||||
|
|
||||||
if !self.canvases.contains_key(&keyframe_id) { return; }
|
if !self.canvases.contains_key(&keyframe_id) { return; }
|
||||||
|
|
||||||
let full_extent = wgpu::Extent3d {
|
|
||||||
width: self.canvases[&keyframe_id].width,
|
|
||||||
height: self.canvases[&keyframe_id].height,
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
for dab in dabs {
|
for dab in dabs {
|
||||||
// Per-dab bounding box
|
|
||||||
let r_fringe = dab.radius + 1.0;
|
let r_fringe = dab.radius + 1.0;
|
||||||
let dx0 = (dab.x - r_fringe).floor() as i32;
|
let x0 = ((dab.x - r_fringe).floor() as i32).max(0) as u32;
|
||||||
let dy0 = (dab.y - r_fringe).floor() as i32;
|
let y0 = ((dab.y - r_fringe).floor() as i32).max(0) as u32;
|
||||||
let dx1 = (dab.x + r_fringe).ceil() as i32;
|
let x1 = ((dab.x + r_fringe).ceil() as i32).min(canvas_w as i32) as u32;
|
||||||
let dy1 = (dab.y + r_fringe).ceil() as i32;
|
let y1 = ((dab.y + r_fringe).ceil() as i32).min(canvas_h as i32) as u32;
|
||||||
|
if x1 <= x0 || y1 <= y0 { continue; }
|
||||||
|
|
||||||
let x0 = dx0.max(0) as u32;
|
let bbox_w = x1 - x0;
|
||||||
let y0 = dy0.max(0) as u32;
|
let bbox_h = y1 - y0;
|
||||||
let x1 = (dx1.min(canvas_w as i32 - 1)).max(0) as u32;
|
|
||||||
let y1 = (dy1.min(canvas_h as i32 - 1)).max(0) as u32;
|
|
||||||
if x1 < x0 || y1 < y0 { continue; }
|
|
||||||
|
|
||||||
let bbox_w = x1 - x0 + 1;
|
|
||||||
let bbox_h = y1 - y0 + 1;
|
|
||||||
|
|
||||||
let canvas = self.canvases.get_mut(&keyframe_id).unwrap();
|
let canvas = self.canvases.get_mut(&keyframe_id).unwrap();
|
||||||
|
|
||||||
// Pre-fill dst from src so pixels outside this dab's bbox are preserved.
|
|
||||||
let mut copy_enc = device.create_command_encoder(
|
let mut copy_enc = device.create_command_encoder(
|
||||||
&wgpu::CommandEncoderDescriptor { label: Some("canvas_copy_encoder") },
|
&wgpu::CommandEncoderDescriptor { label: Some("canvas_copy_encoder") },
|
||||||
);
|
);
|
||||||
|
|
@ -330,20 +314,19 @@ impl GpuBrushEngine {
|
||||||
wgpu::TexelCopyTextureInfo {
|
wgpu::TexelCopyTextureInfo {
|
||||||
texture: canvas.src(),
|
texture: canvas.src(),
|
||||||
mip_level: 0,
|
mip_level: 0,
|
||||||
origin: wgpu::Origin3d::ZERO,
|
origin: wgpu::Origin3d { x: x0, y: y0, z: 0 },
|
||||||
aspect: wgpu::TextureAspect::All,
|
aspect: wgpu::TextureAspect::All,
|
||||||
},
|
},
|
||||||
wgpu::TexelCopyTextureInfo {
|
wgpu::TexelCopyTextureInfo {
|
||||||
texture: canvas.dst(),
|
texture: canvas.dst(),
|
||||||
mip_level: 0,
|
mip_level: 0,
|
||||||
origin: wgpu::Origin3d::ZERO,
|
origin: wgpu::Origin3d { x: x0, y: y0, z: 0 },
|
||||||
aspect: wgpu::TextureAspect::All,
|
aspect: wgpu::TextureAspect::All,
|
||||||
},
|
},
|
||||||
full_extent,
|
wgpu::Extent3d { width: bbox_w, height: bbox_h, depth_or_array_layers: 1 },
|
||||||
);
|
);
|
||||||
queue.submit(Some(copy_enc.finish()));
|
queue.submit(Some(copy_enc.finish()));
|
||||||
|
|
||||||
// Upload single-dab buffer and params
|
|
||||||
let dab_bytes = bytemuck::bytes_of(dab);
|
let dab_bytes = bytemuck::bytes_of(dab);
|
||||||
let dab_buf = device.create_buffer(&wgpu::BufferDescriptor {
|
let dab_buf = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
label: Some("dab_storage_buf"),
|
label: Some("dab_storage_buf"),
|
||||||
|
|
@ -375,22 +358,10 @@ impl GpuBrushEngine {
|
||||||
label: Some("brush_dab_bg"),
|
label: Some("brush_dab_bg"),
|
||||||
layout: &self.compute_bg_layout,
|
layout: &self.compute_bg_layout,
|
||||||
entries: &[
|
entries: &[
|
||||||
wgpu::BindGroupEntry {
|
wgpu::BindGroupEntry { binding: 0, resource: dab_buf.as_entire_binding() },
|
||||||
binding: 0,
|
wgpu::BindGroupEntry { binding: 1, resource: params_buf.as_entire_binding() },
|
||||||
resource: dab_buf.as_entire_binding(),
|
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(canvas.src_view()) },
|
||||||
},
|
wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(canvas.dst_view()) },
|
||||||
wgpu::BindGroupEntry {
|
|
||||||
binding: 1,
|
|
||||||
resource: params_buf.as_entire_binding(),
|
|
||||||
},
|
|
||||||
wgpu::BindGroupEntry {
|
|
||||||
binding: 2,
|
|
||||||
resource: wgpu::BindingResource::TextureView(canvas.src_view()),
|
|
||||||
},
|
|
||||||
wgpu::BindGroupEntry {
|
|
||||||
binding: 3,
|
|
||||||
resource: wgpu::BindingResource::TextureView(canvas.dst_view()),
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -399,18 +370,13 @@ impl GpuBrushEngine {
|
||||||
);
|
);
|
||||||
{
|
{
|
||||||
let mut pass = compute_enc.begin_compute_pass(
|
let mut pass = compute_enc.begin_compute_pass(
|
||||||
&wgpu::ComputePassDescriptor {
|
&wgpu::ComputePassDescriptor { label: Some("brush_dab_pass"), timestamp_writes: None },
|
||||||
label: Some("brush_dab_pass"),
|
|
||||||
timestamp_writes: None,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
pass.set_pipeline(&self.compute_pipeline);
|
pass.set_pipeline(&self.compute_pipeline);
|
||||||
pass.set_bind_group(0, &bg, &[]);
|
pass.set_bind_group(0, &bg, &[]);
|
||||||
pass.dispatch_workgroups(bbox_w.div_ceil(8), bbox_h.div_ceil(8), 1);
|
pass.dispatch_workgroups(bbox_w.div_ceil(8), bbox_h.div_ceil(8), 1);
|
||||||
}
|
}
|
||||||
queue.submit(Some(compute_enc.finish()));
|
queue.submit(Some(compute_enc.finish()));
|
||||||
|
|
||||||
// Swap: the just-written dst becomes src for the next dab.
|
|
||||||
canvas.swap();
|
canvas.swap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue