use std::sync::{Arc, OnceLock};
use rayon::prelude::*;
use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer};
use vulkano::command_buffer::{
AutoCommandBufferBuilder, ClearColorImageInfo, CopyBufferToImageInfo,
PrimaryAutoCommandBuffer, RenderPassBeginInfo, SubpassBeginInfo,
SubpassContents,
};
use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator;
use vulkano::descriptor_set::layout::{
DescriptorSetLayoutBinding, DescriptorType,
};
use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet};
use vulkano::device::Queue;
use vulkano::format::{ClearColorValue, Format};
use vulkano::image::sampler::{
Filter, Sampler, SamplerAddressMode, SamplerCreateInfo,
};
use vulkano::image::view::ImageView;
use vulkano::image::{Image, ImageCreateInfo, ImageType, ImageUsage};
use vulkano::memory::allocator::{
AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator,
};
use vulkano::pipeline::graphics::color_blend::{
ColorBlendAttachmentState, ColorBlendState,
};
use vulkano::pipeline::graphics::input_assembly::InputAssemblyState;
use vulkano::pipeline::graphics::multisample::MultisampleState;
use vulkano::pipeline::graphics::rasterization::RasterizationState;
use vulkano::pipeline::graphics::subpass::PipelineSubpassType;
use vulkano::pipeline::graphics::viewport::{Viewport, ViewportState};
use vulkano::pipeline::graphics::GraphicsPipelineCreateInfo;
use vulkano::pipeline::layout::{
PipelineDescriptorSetLayoutCreateInfo, PushConstantRange,
};
use vulkano::pipeline::{
GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout,
PipelineShaderStageCreateInfo,
};
use vulkano::render_pass::{Framebuffer, FramebufferCreateInfo, Subpass};
use vulkano::shader::ShaderStages;
use super::composite::{FrameBlock, PackHooks, SCENE_FRAME_BINDING};
use super::scene_renderer::{
compile_glsl, hooked_glsl, name_object, tonemap_vertex_shader,
SceneRenderError,
};
fn error(error: impl std::fmt::Display) -> SceneRenderError {
SceneRenderError(error.to_string())
}
pub(super) const CLOUD_LAYER_BINDING: u32 = 14;
pub(super) const CLOUD_NOISE_BINDING: u32 = 15;
pub(super) fn add_pack_bindings(
info: &mut PipelineDescriptorSetLayoutCreateInfo,
textures: u32,
) {
insert(
info,
2,
CLOUD_LAYER_BINDING,
DescriptorType::CombinedImageSampler,
);
insert(
info,
2,
CLOUD_NOISE_BINDING,
DescriptorType::CombinedImageSampler,
);
insert(info, 2, textures, DescriptorType::CombinedImageSampler);
}
fn insert(
info: &mut PipelineDescriptorSetLayoutCreateInfo,
set: usize,
binding: u32,
kind: DescriptorType,
) {
while info.set_layouts.len() <= set {
info.set_layouts.push(Default::default());
}
info.set_layouts[set].bindings.insert(
binding,
DescriptorSetLayoutBinding {
stages: ShaderStages::FRAGMENT,
..DescriptorSetLayoutBinding::descriptor_type(kind)
},
);
}
pub(super) fn bound_sky_glsl(glsl: &str, layer: bool) -> String {
if layer {
glsl.replacen(
"#version 450\n",
"#version 450\n#define RUSTING_CLOUD_LAYER_BOUND 1\n",
1,
)
} else {
glsl.to_owned()
}
}
pub(super) fn cloud_layer_glsl(source: &str) -> Result<String, String> {
let frag = include_str!("../shaders/fragment/cloud_layer.frag").replacen(
"#include \"frame.glsl\"",
include_str!("../shaders/frame.glsl"),
1,
);
hooked_glsl(
&frag,
&[(
"clouds",
source,
(
"cloud_layer_shader.glsl",
include_str!("../shaders/cloud_layer_shader.glsl"),
),
)],
)
}
const NOISE_SIZE: u32 = 128;
fn hash3(x: i32, y: i32, z: i32, seed: u32) -> u32 {
let mut h = (x as u32).wrapping_mul(0x8da6_b343)
^ (y as u32).wrapping_mul(0xd816_3841)
^ (z as u32).wrapping_mul(0xcb1a_b31f)
^ seed.wrapping_mul(0x9e37_79b9);
h ^= h >> 15;
h = h.wrapping_mul(0x2c1b_3c6d);
h ^= h >> 12;
h = h.wrapping_mul(0x297a_2d39);
h ^ (h >> 15)
}
fn unit(h: u32) -> f32 {
(h >> 8) as f32 / (1u32 << 24) as f32
}
fn worley(p: [f32; 3], cells: i32, seed: u32) -> f32 {
let q = p.map(|v| v * cells as f32);
let c = q.map(|v| v.floor() as i32);
let mut best = f32::MAX;
for dz in -1..=1 {
for dy in -1..=1 {
for dx in -1..=1 {
let n = [c[0] + dx, c[1] + dy, c[2] + dz];
let w = n.map(|v| v.rem_euclid(cells));
let mut d2 = 0.0;
for (axis, &cell) in n.iter().enumerate() {
let h = hash3(w[0], w[1], w[2], seed + axis as u32 * 101);
let d = cell as f32 + unit(h) - q[axis];
d2 += d * d;
}
best = best.min(d2);
}
}
}
1.0 - best.sqrt().min(1.0)
}
fn perlin(p: [f32; 3], cells: i32, seed: u32) -> f32 {
let q = p.map(|v| v * cells as f32);
let c = q.map(|v| v.floor() as i32);
let f = [q[0] - c[0] as f32, q[1] - c[1] as f32, q[2] - c[2] as f32];
let fade = f.map(|t| t * t * t * (t * (t * 6.0 - 15.0) + 10.0));
let mut sum = 0.0;
for corner in 0..8 {
let o = [corner & 1, (corner >> 1) & 1, corner >> 2];
let h = hash3(
(c[0] + o[0]).rem_euclid(cells),
(c[1] + o[1]).rem_euclid(cells),
(c[2] + o[2]).rem_euclid(cells),
seed,
);
let g = match h % 12 {
0 => [1.0, 1.0, 0.0],
1 => [-1.0, 1.0, 0.0],
2 => [1.0, -1.0, 0.0],
3 => [-1.0, -1.0, 0.0],
4 => [1.0, 0.0, 1.0],
5 => [-1.0, 0.0, 1.0],
6 => [1.0, 0.0, -1.0],
7 => [-1.0, 0.0, -1.0],
8 => [0.0, 1.0, 1.0],
9 => [0.0, -1.0, 1.0],
10 => [0.0, 1.0, -1.0],
_ => [0.0, -1.0, -1.0],
};
let d = [f[0] - o[0] as f32, f[1] - o[1] as f32, f[2] - o[2] as f32];
let dot = g[0] * d[0] + g[1] * d[1] + g[2] * d[2];
let weight = (0..3)
.map(|axis| {
if o[axis] == 1 {
fade[axis]
} else {
1.0 - fade[axis]
}
})
.product::<f32>();
sum += dot * weight;
}
sum
}
fn worley_fbm(p: [f32; 3], cells: i32) -> f32 {
worley(p, cells, 1) * 0.625
+ worley(p, cells * 2, 2) * 0.25
+ worley(p, cells * 4, 3) * 0.125
}
fn cloud_noise_at(p: [f32; 3]) -> [f32; 4] {
let mut fbm = 0.0;
let mut amplitude = 1.0;
for octave in 0..4 {
fbm += amplitude * perlin(p, 4 << octave, 7 + octave);
amplitude *= 0.5;
}
let perlin = (0.5 + 0.75 * fbm).clamp(0.0, 1.0);
let g = worley_fbm(p, 4);
let r = g + perlin * (1.0 - g);
[r, g, worley_fbm(p, 8), worley_fbm(p, 16)]
}
fn cloud_noise_texels() -> &'static [u8] {
static TEXELS: OnceLock<Vec<u8>> = OnceLock::new();
TEXELS.get_or_init(|| {
let n = NOISE_SIZE as usize;
(0..n * n * n)
.into_par_iter()
.flat_map_iter(|i| {
let p = [i % n, i / n % n, i / (n * n)]
.map(|v| (v as f32 + 0.5) / n as f32);
cloud_noise_at(p)
.map(|v| (v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8)
})
.collect()
})
}
pub(super) struct CloudLayer {
pipeline: Arc<GraphicsPipeline>,
framebuffers: [Arc<Framebuffer>; 2],
views: [Arc<ImageView>; 2],
sampler: Arc<Sampler>,
target: usize,
fresh: bool,
held: bool,
noise: Arc<ImageView>,
noise_sampler: Arc<Sampler>,
noise_upload: Option<Subbuffer<[u8]>>,
}
pub(super) type CloudLayerCache = Option<(u64, [u32; 2], Option<CloudLayer>)>;
pub(super) fn cloud_layer<'a>(
cache: &'a mut CloudLayerCache,
queue: &Arc<Queue>,
memory: &Arc<StandardMemoryAllocator>,
hooks: &PackHooks,
extent: [u32; 2],
) -> Option<&'a mut CloudLayer> {
if hooks.cloud_layer.is_empty() {
*cache = None;
return None;
}
if cache.as_ref().is_none_or(|(revision, size, _)| {
*revision != hooks.revision || *size != extent
}) {
let size = extent.map(|side| side.max(1).div_ceil(hooks.cloud_divisor));
let layer = CloudLayer::new(queue, memory, &hooks.cloud_layer, size)
.inspect_err(|e| eprintln!("cloud layer skipped:\n{}", e.0))
.ok();
*cache = Some((hooks.revision, extent, layer));
}
cache.as_mut().and_then(|(.., layer)| layer.as_mut())
}
impl CloudLayer {
fn new(
queue: &Arc<Queue>,
memory: &Arc<StandardMemoryAllocator>,
source: &str,
[width, height]: [u32; 2],
) -> Result<Self, SceneRenderError> {
let device = queue.device().clone();
let fragment = compile_glsl(
&device,
"fragment",
&cloud_layer_glsl(source).map_err(error)?,
)
.map_err(error)?
.entry_point("main")
.ok_or_else(|| error("cloud layer entry point is missing"))?;
let vertex = tonemap_vertex_shader::load(device.clone())
.map_err(error)?
.entry_point("main")
.ok_or_else(|| {
error("cloud layer vertex entry point is missing")
})?;
let stages = [
PipelineShaderStageCreateInfo::new(vertex),
PipelineShaderStageCreateInfo::new(fragment),
];
let mut info =
PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages);
insert(&mut info, 0, 1, DescriptorType::StorageBuffer);
insert(&mut info, 0, 2, DescriptorType::CombinedImageSampler);
insert(&mut info, 0, 3, DescriptorType::CombinedImageSampler);
insert(&mut info, 0, 4, DescriptorType::CombinedImageSampler);
insert(
&mut info,
0,
SCENE_FRAME_BINDING,
DescriptorType::UniformBuffer,
);
info.push_constant_ranges = vec![PushConstantRange {
stages: ShaderStages::FRAGMENT,
offset: 0,
size: 32,
}];
let layout = PipelineLayout::new(
device.clone(),
info.into_pipeline_layout_create_info(device.clone())
.map_err(error)?,
)
.map_err(error)?;
let render_pass = vulkano::single_pass_renderpass!(
device.clone(),
attachments: {
color: {
format: Format::R16G16B16A16_SFLOAT,
samples: 1,
load_op: DontCare,
store_op: Store,
}
},
pass: {
color: [color],
depth_stencil: {}
}
)
.map_err(error)?;
let subpass = Subpass::from(render_pass.clone(), 0)
.ok_or_else(|| error("cloud layer subpass is missing"))?;
let pipeline = GraphicsPipeline::new(
device.clone(),
None,
GraphicsPipelineCreateInfo {
stages: stages.into_iter().collect(),
vertex_input_state: Some(Default::default()),
input_assembly_state: Some(InputAssemblyState::default()),
viewport_state: Some(ViewportState {
viewports: [Viewport {
offset: [0.0, 0.0],
extent: [width as f32, height as f32],
depth_range: 0.0..=1.0,
}]
.into_iter()
.collect(),
..Default::default()
}),
rasterization_state: Some(RasterizationState::default()),
multisample_state: Some(MultisampleState::default()),
color_blend_state: Some(
ColorBlendState::with_attachment_states(
1,
ColorBlendAttachmentState::default(),
),
),
subpass: Some(PipelineSubpassType::BeginRenderPass(subpass)),
..GraphicsPipelineCreateInfo::layout(layout)
},
)
.map_err(error)?;
let image = |name: &str| -> Result<
(Arc<ImageView>, Arc<Framebuffer>),
SceneRenderError,
> {
let image = Image::new(
memory.clone(),
ImageCreateInfo {
format: Format::R16G16B16A16_SFLOAT,
extent: [width, height, 1],
usage: ImageUsage::COLOR_ATTACHMENT
| ImageUsage::SAMPLED
| ImageUsage::TRANSFER_DST,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE,
..Default::default()
},
)
.map_err(error)?;
name_object(&*image, name);
let view = ImageView::new_default(image).map_err(error)?;
let framebuffer = Framebuffer::new(
render_pass.clone(),
FramebufferCreateInfo {
attachments: vec![view.clone()],
..Default::default()
},
)
.map_err(error)?;
Ok((view, framebuffer))
};
let (a, a_framebuffer) = image("Pack cloud layer A")?;
let (b, b_framebuffer) = image("Pack cloud layer B")?;
let sampler = Sampler::new(
device.clone(),
SamplerCreateInfo {
mag_filter: Filter::Linear,
min_filter: Filter::Linear,
address_mode: [SamplerAddressMode::ClampToEdge; 3],
..Default::default()
},
)
.map_err(error)?;
let noise_image = Image::new(
memory.clone(),
ImageCreateInfo {
image_type: ImageType::Dim3d,
format: Format::R8G8B8A8_UNORM,
extent: [NOISE_SIZE; 3],
usage: ImageUsage::SAMPLED | ImageUsage::TRANSFER_DST,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE,
..Default::default()
},
)
.map_err(error)?;
name_object(&*noise_image, "Pack cloud noise");
let noise_upload = Buffer::from_iter(
memory.clone(),
BufferCreateInfo {
usage: BufferUsage::TRANSFER_SRC,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_HOST
| MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
..Default::default()
},
cloud_noise_texels().iter().copied(),
)
.map_err(error)?;
let noise_sampler = Sampler::new(
device,
SamplerCreateInfo {
mag_filter: Filter::Linear,
min_filter: Filter::Linear,
address_mode: [SamplerAddressMode::Repeat; 3],
..Default::default()
},
)
.map_err(error)?;
Ok(Self {
noise: ImageView::new_default(noise_image).map_err(error)?,
noise_sampler,
noise_upload: Some(noise_upload),
pipeline,
framebuffers: [a_framebuffer, b_framebuffer],
views: [a, b],
sampler,
target: 0,
fresh: true,
held: false,
})
}
pub(super) fn noise(&self) -> (Arc<ImageView>, Arc<Sampler>) {
(self.noise.clone(), self.noise_sampler.clone())
}
pub(super) fn upload_noise(
&mut self,
commands: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
) -> Result<(), SceneRenderError> {
if let Some(texels) = self.noise_upload.take() {
commands
.copy_buffer_to_image(CopyBufferToImageInfo::buffer_image(
texels,
self.noise.image().clone(),
))
.map_err(error)?;
}
Ok(())
}
pub(super) fn output(&self) -> (Arc<ImageView>, Arc<Sampler>) {
let view = if self.held {
1 - self.target
} else {
self.target
};
(self.views[view].clone(), self.sampler.clone())
}
pub(super) fn hold(&mut self) -> bool {
if self.fresh && !self.held {
return false;
}
self.held = true;
self.fresh = true;
true
}
pub(super) fn record(
&mut self,
commands: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
descriptors: &Arc<StandardDescriptorSetAllocator>,
frame: Subbuffer<FrameBlock>,
shadow: Subbuffer<[u8]>,
environment: (Arc<ImageView>, Arc<Sampler>),
ambient: [[f32; 4]; 2],
) -> Result<(), SceneRenderError> {
self.held = false;
self.upload_noise(commands)?;
if std::mem::take(&mut self.fresh) {
for view in &self.views {
commands
.clear_color_image(ClearColorImageInfo {
clear_value: ClearColorValue::Float([
0.0, 0.0, 0.0, 1.0,
]),
..ClearColorImageInfo::image(view.image().clone())
})
.map_err(error)?;
}
}
let layout = self.pipeline.layout().clone();
let set = DescriptorSet::new(
descriptors.clone(),
layout.set_layouts()[0].clone(),
[
WriteDescriptorSet::buffer(1, shadow),
WriteDescriptorSet::image_view_sampler(
2,
environment.0,
environment.1,
),
WriteDescriptorSet::image_view_sampler(
3,
self.views[1 - self.target].clone(),
self.sampler.clone(),
),
WriteDescriptorSet::image_view_sampler(
4,
self.noise.clone(),
self.noise_sampler.clone(),
),
WriteDescriptorSet::buffer(SCENE_FRAME_BINDING, frame),
],
[],
)
.map_err(error)?;
commands
.begin_render_pass(
RenderPassBeginInfo {
clear_values: vec![None],
..RenderPassBeginInfo::framebuffer(
self.framebuffers[self.target].clone(),
)
},
SubpassBeginInfo {
contents: SubpassContents::Inline,
..Default::default()
},
)
.map_err(error)?
.bind_pipeline_graphics(self.pipeline.clone())
.map_err(error)?
.bind_descriptor_sets(
PipelineBindPoint::Graphics,
layout.clone(),
0,
set,
)
.map_err(error)?
.push_constants(layout, 0, ambient)
.map_err(error)?;
unsafe { commands.draw(3, 1, 0, 0) }.map_err(error)?;
commands
.end_render_pass(Default::default())
.map_err(error)?;
self.target = 1 - self.target;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cloud_noise_tiles_and_stays_in_range() {
for i in 0..200 {
let p = [
i as f32 * 0.0137,
(i * 7 % 13) as f32 * 0.071,
(i * 3 % 11) as f32 * 0.089,
];
let a = cloud_noise_at(p);
for axis in 0..3 {
let mut q = p;
q[axis] += 1.0;
let b = cloud_noise_at(q);
for k in 0..4 {
assert!(
(a[k] - b[k]).abs() < 1e-3,
"{p:?} axis {axis} channel {k}"
);
}
}
assert!(a.iter().all(|v| (0.0..=1.0).contains(v)), "{a:?}");
}
assert_eq!(
cloud_noise_texels().len(),
(NOISE_SIZE as usize).pow(3) * 4
);
}
}