use crate::gpu::effect::{
create_blit_bind_group, create_blit_pipeline, EffectCache, EffectPipeline,
};
use crate::gpu::void::{DirtyFlag, ParamDef, ParamValue, Void, VoidRegistration};
use std::cell::Cell;
use std::sync::Arc;
const AUX_DOWNSCALE: u32 = 2;
const AUX_MIN_DIM: u32 = 64;
const AUX_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
const NOISE3D_DIM: u32 = 64;
pub const TYPE_ID: &str = "noise";
const PARAMS: &[ParamDef] = &[
ParamDef::Int {
name: "seed",
min: 0,
max: i32::MAX,
default: 42,
},
ParamDef::Int {
name: "octaves",
min: 1,
max: 8,
default: 5,
},
ParamDef::Float {
name: "size",
min: 20.0,
max: 2000.0,
default: 200.0,
},
ParamDef::Float {
name: "warp",
min: 0.0,
max: 3.0,
default: 1.5,
},
ParamDef::Float {
name: "darkness",
min: 0.0,
max: 3.0,
default: 1.0,
},
ParamDef::Float {
name: "time",
min: 0.0,
max: 100.0,
default: 0.0,
},
];
pub fn register() -> VoidRegistration {
VoidRegistration {
type_id: TYPE_ID,
display_name: "Noise",
params: PARAMS,
icon: "tabler:galaxy",
supports_preview: true,
supports_live_transform: true,
capture_kind: None,
default_transform: |_, _| crate::transform::Transform::identity(),
create_pipeline,
from_params: |params, shared| Box::new(Noise::from_params(params, shared)),
}
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct NoiseUniforms {
seed: u32,
octaves: i32,
frequency: f32,
warp: f32,
darkness: f32,
time: f32,
canvas_scale: f32,
_pad0: f32,
inv_row0: [f32; 4],
inv_row1: [f32; 4],
}
#[derive(Debug)]
pub struct Noise {
pub seed: i32,
pub octaves: i32,
pub size: f32,
pub warp: f32,
pub darkness: f32,
pub time: f32,
transform: crate::transform::Transform,
canvas_scale: Cell<f32>,
shared: Arc<EffectPipeline>,
dirty: DirtyFlag,
}
impl Clone for Noise {
fn clone(&self) -> Self {
Noise {
seed: self.seed,
octaves: self.octaves,
size: self.size,
warp: self.warp,
darkness: self.darkness,
time: self.time,
transform: self.transform,
canvas_scale: Cell::new(self.canvas_scale.get()),
shared: self.shared.clone(),
dirty: DirtyFlag::new_dirty(),
}
}
}
impl Noise {
fn from_params(params: &[ParamValue], shared: Arc<EffectPipeline>) -> Self {
let seed = match params.first() {
Some(ParamValue::Int(v)) => *v,
_ => 42,
};
let octaves = match params.get(1) {
Some(ParamValue::Int(v)) => *v,
_ => 5,
};
let size = match params.get(2) {
Some(ParamValue::Float(v)) => *v,
_ => 200.0,
};
let warp = match params.get(3) {
Some(ParamValue::Float(v)) => *v,
_ => 1.5,
};
let darkness = match params.get(4) {
Some(ParamValue::Float(v)) => *v,
_ => 1.0,
};
let time = match params.get(5) {
Some(ParamValue::Float(v)) => *v,
_ => 0.0,
};
Noise {
seed,
octaves,
size,
warp,
darkness,
time,
transform: crate::transform::Transform::identity(),
canvas_scale: Cell::new(1.0),
shared,
dirty: DirtyFlag::new_dirty(),
}
}
fn uniforms(&self) -> NoiseUniforms {
let fwd = self.transform.to_affine();
let inv = crate::transform::affine_inverse(&fwd).unwrap_or(crate::transform::IDENTITY);
NoiseUniforms {
seed: self.seed as u32,
octaves: self.octaves,
frequency: 1.0 / self.size.max(1.0),
warp: self.warp,
darkness: self.darkness,
time: self.time,
canvas_scale: self.canvas_scale.get(),
_pad0: 0.0,
inv_row0: [inv[0], inv[1], inv[2], 0.0],
inv_row1: [inv[3], inv[4], inv[5], 0.0],
}
}
}
impl Void for Noise {
fn type_id(&self) -> &'static str {
TYPE_ID
}
fn clone_boxed(&self) -> Box<dyn Void> {
Box::new(self.clone())
}
fn param_values(&self) -> Vec<ParamValue> {
vec![
ParamValue::Int(self.seed),
ParamValue::Int(self.octaves),
ParamValue::Float(self.size),
ParamValue::Float(self.warp),
ParamValue::Float(self.darkness),
ParamValue::Float(self.time),
]
}
fn take_dirty(&mut self) -> bool {
self.dirty.take()
}
fn mark_dirty(&mut self) {
self.dirty.mark();
}
fn update_params(&mut self, queue: &wgpu::Queue, cache: &EffectCache, params: &[ParamValue]) {
self.seed = match params.first() {
Some(ParamValue::Int(v)) => *v,
_ => self.seed,
};
self.octaves = match params.get(1) {
Some(ParamValue::Int(v)) => *v,
_ => self.octaves,
};
self.size = match params.get(2) {
Some(ParamValue::Float(v)) => *v,
_ => self.size,
};
self.warp = match params.get(3) {
Some(ParamValue::Float(v)) => *v,
_ => self.warp,
};
self.darkness = match params.get(4) {
Some(ParamValue::Float(v)) => *v,
_ => self.darkness,
};
self.time = match params.get(5) {
Some(ParamValue::Float(v)) => *v,
_ => self.time,
};
if let Some(buf) = cache.uniform_bufs.first() {
queue.write_buffer(buf, 0, bytemuck::bytes_of(&self.uniforms()));
}
self.dirty.mark();
}
fn set_transform(
&mut self,
queue: &wgpu::Queue,
cache: &EffectCache,
transform: &crate::transform::Transform,
) {
self.transform = *transform;
if let Some(buf) = cache.uniform_bufs.first() {
queue.write_buffer(buf, 0, bytemuck::bytes_of(&self.uniforms()));
}
self.dirty.mark();
}
fn create_cache(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
_dst_view: &wgpu::TextureView,
sampler: &wgpu::Sampler,
render_width: u32,
render_height: u32,
) -> EffectCache {
let aux_w = (render_width / AUX_DOWNSCALE).max(AUX_MIN_DIM);
let aux_h = (render_height / AUX_DOWNSCALE).max(AUX_MIN_DIM);
self.canvas_scale.set(render_width as f32 / aux_w as f32);
let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("void-noise-uniforms"),
size: std::mem::size_of::<NoiseUniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&uniform_buf, 0, bytemuck::bytes_of(&self.uniforms()));
let noise_tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some("void-noise-volume"),
size: wgpu::Extent3d {
width: NOISE3D_DIM,
height: NOISE3D_DIM,
depth_or_array_layers: NOISE3D_DIM,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D3,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let noise_bytes = seed_noise_volume(NOISE3D_DIM, self.seed as u32);
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &noise_tex,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&noise_bytes,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(NOISE3D_DIM * 4),
rows_per_image: Some(NOISE3D_DIM),
},
wgpu::Extent3d {
width: NOISE3D_DIM,
height: NOISE3D_DIM,
depth_or_array_layers: NOISE3D_DIM,
},
);
let noise_view = noise_tex.create_view(&Default::default());
let noise_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("void-noise-volume-sampler"),
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
address_mode_w: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..Default::default()
});
let fbm_bg = |label: &str| {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(label),
layout: &self.shared.bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&noise_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&noise_sampler),
},
],
})
};
let fbm_bgs = [fbm_bg("void-noise-fbm-bg-0"), fbm_bg("void-noise-fbm-bg-1")];
let aux_tex = device.create_texture(&wgpu::TextureDescriptor {
label: Some("void-noise-aux"),
size: wgpu::Extent3d {
width: aux_w,
height: aux_h,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: AUX_FORMAT,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let aux_view = aux_tex.create_view(&Default::default());
let blit = create_blit_pipeline(device, AUX_FORMAT, "void-noise-blit");
let blit_bg = |label: &str| {
create_blit_bind_group(device, &blit.bind_group_layout, &aux_view, sampler, label)
};
let blit_bgs = [
blit_bg("void-noise-blit-bg-0"),
blit_bg("void-noise-blit-bg-1"),
];
EffectCache {
uniform_bufs: vec![uniform_buf],
bind_groups: vec![fbm_bgs, blit_bgs],
aux_textures: vec![aux_tex],
aux_views: vec![aux_view],
aux_pipelines: vec![blit.pipeline],
}
}
fn encode(
&self,
encoder: &mut wgpu::CommandEncoder,
cache: &EffectCache,
dst_view: &wgpu::TextureView,
) {
{
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("void-noise-fbm"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &cache.aux_views[0],
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
})],
..Default::default()
});
rpass.set_pipeline(&self.shared.pipeline);
rpass.set_bind_group(0, &cache.bind_groups[0][0], &[]);
rpass.draw(0..3, 0..1);
}
{
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("void-noise-blit"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: dst_view,
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
})],
..Default::default()
});
rpass.set_pipeline(&cache.aux_pipelines[0]);
rpass.set_bind_group(0, &cache.bind_groups[1][0], &[]);
rpass.draw(0..3, 0..1);
}
}
}
fn create_pipeline(device: &wgpu::Device, _format: wgpu::TextureFormat) -> EffectPipeline {
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("void-noise-bgl"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D3,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("void-noise-pipeline-layout"),
bind_group_layouts: &[Some(&bind_group_layout)],
immediate_size: 0,
});
let fbm_src = include_str!("../../../shaders/lib/fbm.wgsl");
let void_src = include_str!("../../../shaders/voids/noise.wgsl");
let full_src = format!("{fbm_src}\n{void_src}");
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("void-noise-shader"),
source: wgpu::ShaderSource::Wgsl(full_src.into()),
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("void-noise-pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: AUX_FORMAT,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
EffectPipeline {
pipeline,
bind_group_layout,
}
}
fn seed_noise_volume(dim: u32, seed: u32) -> Vec<u8> {
let count = (dim * dim * dim) as usize;
let mut bytes = vec![0u8; count * 4];
let mut s = seed.wrapping_mul(747796405).wrapping_add(2891336453);
for b in &mut bytes {
s = pcg_hash(s);
*b = (s >> 24) as u8;
}
bytes
}
fn pcg_hash(n: u32) -> u32 {
let mut h = n.wrapping_mul(747796405).wrapping_add(2891336453);
h = ((h >> ((h >> 28) + 4)) ^ h).wrapping_mul(277803737);
(h >> 22) ^ h
}