use std::collections::{BTreeMap, HashMap};
use std::hash::{DefaultHasher, Hash, Hasher};
use std::sync::{Arc, Mutex};
use nalgebra::Matrix4;
use vulkano::buffer::{BufferContents, Subbuffer};
use vulkano::command_buffer::{
AutoCommandBufferBuilder, ClearColorImageInfo, CopyImageInfo,
PrimaryAutoCommandBuffer, RenderPassBeginInfo, SubpassBeginInfo,
SubpassContents,
};
use vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator;
use vulkano::descriptor_set::layout::{
DescriptorSetLayout, DescriptorSetLayoutBinding,
DescriptorSetLayoutCreateInfo, DescriptorType,
};
use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet};
use vulkano::device::Queue;
use vulkano::format::Format;
use vulkano::image::sampler::{
Filter, Sampler, SamplerAddressMode, SamplerCreateInfo,
};
use vulkano::image::view::ImageView;
use vulkano::image::{Image, ImageCreateInfo, 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::{PipelineLayoutCreateInfo, PushConstantRange};
use vulkano::pipeline::{
DynamicState, GraphicsPipeline, PipelineBindPoint, PipelineLayout,
PipelineShaderStageCreateInfo,
};
use vulkano::render_pass::{
Framebuffer, FramebufferCreateInfo, RenderPass, Subpass,
};
use vulkano::shader::ShaderStages;
use super::scene_renderer::{
compile_glsl, composite_glsl, name_object, particle_fragment_glsl,
post_fragment_glsl, sky_fragment_glsl, surface_fragment_glsl,
tonemap_vertex_shader, vertex_glsl, SceneRenderError,
};
use super::shader_pack::{
ActiveShaderPack, ShaderPackError, ShaderWorld, MAX_COMPOSITE_PASSES,
SURFACE_PROGRAMS,
};
fn error(error: impl std::fmt::Display) -> SceneRenderError {
SceneRenderError(error.to_string())
}
#[repr(C)]
#[derive(BufferContents, Clone, Copy, Debug, Default)]
pub(super) struct FrameBlock {
pub view: [[f32; 4]; 4],
pub projection: [[f32; 4]; 4],
pub view_projection: [[f32; 4]; 4],
pub inverse_view: [[f32; 4]; 4],
pub inverse_projection: [[f32; 4]; 4],
pub previous_view: [[f32; 4]; 4],
pub previous_projection: [[f32; 4]; 4],
pub previous_view_projection: [[f32; 4]; 4],
pub camera_position: [f32; 4],
pub previous_camera_position: [f32; 4],
pub sun_direction: [f32; 4],
pub moon_direction: [f32; 4],
pub sun_color: [f32; 4],
pub time: [f32; 4],
pub screen: [f32; 4],
pub weather: [f32; 4],
pub eye: [f32; 4],
pub params: [[f32; 4]; 32],
}
const _: () = assert!(std::mem::size_of::<FrameBlock>() == 1168);
pub(super) const POST_FRAME_DEFINES: &str =
"#define RUSTING_FRAME_SET 1\n#define RUSTING_FRAME_BINDING 3\n";
pub(super) const SCENE_FRAME_BINDING: u32 = 20;
pub(super) const POST_FRAME_BINDING: u32 = 3;
pub(super) fn add_frame_binding(
info: &mut vulkano::pipeline::layout::PipelineDescriptorSetLayoutCreateInfo,
set: usize,
binding: u32,
) {
while info.set_layouts.len() <= set {
info.set_layouts.push(Default::default());
}
info.set_layouts[set].bindings.insert(
binding,
DescriptorSetLayoutBinding {
stages: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
..DescriptorSetLayoutBinding::descriptor_type(
DescriptorType::UniformBuffer,
)
},
);
}
#[derive(Clone, Debug, Default)]
pub(super) struct ProgramHooks {
pub fragment: String,
pub vertex: String,
pub shadow: String,
}
#[derive(Debug, Default)]
pub(super) struct PackHooks {
pub revision: u64,
pub programs: HashMap<&'static str, ProgramHooks>,
pub fog: String,
pub sky: String,
pub clouds: String,
pub cloud_layer: String,
pub cloud_divisor: u32,
pub sky_lut: String,
pub post: String,
pub particle: String,
pub composite: Vec<(String, f32, String)>,
pub composite_gate: Vec<(String, bool)>,
pub history: bool,
}
impl PackHooks {
#[must_use]
pub fn new(active: &ActiveShaderPack) -> Self {
let programs = SURFACE_PROGRAMS
.iter()
.map(|&program| {
(
program,
ProgramHooks {
fragment: active.hook(program, "fragment"),
vertex: active.hook(program, "vertex"),
shadow: active.hook(program, "shadow"),
},
)
})
.filter(|(_, hooks)| {
!hooks.fragment.is_empty()
|| !hooks.vertex.is_empty()
|| !hooks.shadow.is_empty()
})
.collect();
let composite = active
.pack
.composite
.iter()
.map(|pass| {
let source = super::shader_pack::ShaderPack::hook_source(
&pass.fragment,
&active.prelude,
);
(source, pass.scale, pass.fragment.name.clone())
})
.collect();
let composite_gate = active
.pack
.composite
.iter()
.map(|pass| {
(
pass.name.clone(),
active.pack.composite_on(pass, &active.values),
)
})
.collect();
let history = active
.pack
.composite
.iter()
.map(|pass| &pass.fragment.text)
.chain(active.pack.include.iter().map(|file| &file.text))
.any(|text| text.contains("history_at"));
let cloud_divisor = active
.pack
.programs
.get("clouds")
.map_or(1, |program| u32::from(program.layer_divisor.max(1)));
let mut clouds = active.hook("clouds", "fragment");
let mut cloud_layer = String::new();
if cloud_divisor > 1 {
std::mem::swap(&mut clouds, &mut cloud_layer);
}
Self {
revision: active.revision,
programs,
fog: active.hook("fog", "fragment"),
sky: active.hook("sky", "fragment"),
clouds,
cloud_layer,
cloud_divisor,
sky_lut: active.hook("sky_lut", "fragment"),
post: active.hook_with("final", "fragment", POST_FRAME_DEFINES),
particle: active.hook_with(
"particles",
"fragment",
"#define RUSTING_NO_FRAME\n",
),
composite,
composite_gate,
history,
}
}
#[must_use]
pub fn composite_live(
&self,
off: &std::collections::BTreeSet<String>,
) -> Vec<bool> {
self.composite_gate
.iter()
.map(|(name, on)| *on && !off.contains(name))
.collect()
}
#[must_use]
pub fn program(&self, program: &str) -> Option<&ProgramHooks> {
self.programs.get(program)
}
}
static SPIRV: Mutex<Option<HashMap<u64, Arc<Vec<u32>>>>> = Mutex::new(None);
pub(super) fn spirv(stage: &str, glsl: &str) -> Result<Arc<Vec<u32>>, String> {
use std::io::Write;
use std::process::{Command, Stdio};
let mut hasher = DefaultHasher::new();
(stage, glsl).hash(&mut hasher);
let key = hasher.finish();
if let Some(words) = SPIRV
.lock()
.ok()
.and_then(|cache| cache.as_ref()?.get(&key).cloned())
{
return Ok(words);
}
let compiler =
std::env::var_os("RUSTING_GLSLC").unwrap_or_else(|| "glslc".into());
let mut child = Command::new(&compiler)
.args([&format!("-fshader-stage={stage}"), "-o", "-", "-"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|error| format!("cannot run {compiler:?}: {error}"))?;
child
.stdin
.take()
.expect("stdin is piped")
.write_all(glsl.as_bytes())
.map_err(|error| error.to_string())?;
let output = child
.wait_with_output()
.map_err(|error| error.to_string())?;
if !output.status.success() {
return Err(String::from_utf8_lossy(&output.stderr).into_owned());
}
let words = Arc::new(
vulkano::shader::spirv::bytes_to_words(&output.stdout)
.map_err(|error| error.to_string())?
.into_owned(),
);
if let Ok(mut cache) = SPIRV.lock() {
let cache = cache.get_or_insert_with(HashMap::new);
if cache.len() >= 256 {
cache.clear();
}
cache.insert(key, words.clone());
}
Ok(words)
}
fn compile_error(file: &str, message: &str) -> ShaderPackError {
let found = message.lines().find_map(|line| {
let (head, text) = line.split_once(": error: ")?;
let (name, number) = head.rsplit_once(':')?;
Some((
name.trim().to_owned(),
number.trim().parse().ok()?,
text.trim(),
))
});
let (file, line, message) = match found {
Some((name, line, text)) => {
let name = if name == "<stdin>" {
"engine".to_owned()
} else {
name
};
(name, line, text.to_owned())
}
None => (
file.to_owned(),
0,
message
.lines()
.next()
.unwrap_or("does not compile")
.trim()
.to_owned(),
),
};
ShaderPackError {
code: "SHADER_PACK_COMPILE",
file,
line,
message,
}
}
pub(crate) fn validate_pack(
active: &ActiveShaderPack,
) -> Result<(), ShaderPackError> {
let hooks = PackHooks::new(active);
let pack = &active.pack;
let file_of = |program: &str, slot: &str| {
pack.programs
.get(program)
.and_then(|p| match slot {
"vertex" => p.vertex.as_ref(),
"shadow" => p.shadow.as_ref().or(p.vertex.as_ref()),
_ => p.fragment.as_ref(),
})
.map_or_else(|| "pack.json".to_owned(), |file| file.name.clone())
};
let mut units: Vec<(String, &str, Result<String, String>)> = Vec::new();
for (&program, program_hooks) in &hooks.programs {
if !program_hooks.fragment.is_empty() {
units.push((
file_of(program, "fragment"),
"fragment",
surface_fragment_glsl(&program_hooks.fragment, &hooks.fog),
));
}
if !program_hooks.vertex.is_empty() {
units.push((
file_of(program, "vertex"),
"vertex",
vertex_glsl(&program_hooks.vertex, false),
));
}
if !program_hooks.shadow.is_empty() {
units.push((
file_of(program, "shadow"),
"vertex",
vertex_glsl(&program_hooks.shadow, true),
));
}
}
if !hooks.fog.is_empty() {
units.push((
file_of("fog", "fragment"),
"fragment",
surface_fragment_glsl("", &hooks.fog),
));
}
let layer = !hooks.cloud_layer.is_empty();
if layer
|| !(hooks.sky.is_empty()
&& hooks.clouds.is_empty()
&& hooks.fog.is_empty())
{
let file = if hooks.sky.is_empty() && hooks.fog.is_empty() {
file_of("clouds", "fragment")
} else if hooks.sky.is_empty() {
file_of("fog", "fragment")
} else {
file_of("sky", "fragment")
};
units.push((
file,
"fragment",
sky_fragment_glsl(&hooks.sky, &hooks.clouds, &hooks.fog)
.map(|glsl| super::cloud_layer::bound_sky_glsl(&glsl, layer)),
));
}
if layer {
units.push((
file_of("clouds", "fragment"),
"fragment",
super::cloud_layer::cloud_layer_glsl(&hooks.cloud_layer),
));
}
if !hooks.sky_lut.is_empty() {
units.push((
file_of("sky_lut", "fragment"),
"fragment",
super::sky_lut::sky_lut_glsl(&hooks.sky_lut),
));
}
if !hooks.post.is_empty() {
units.push((
file_of("final", "fragment"),
"fragment",
post_fragment_glsl(&hooks.post),
));
}
if !hooks.particle.is_empty() {
units.push((
file_of("particles", "fragment"),
"fragment",
particle_fragment_glsl(&hooks.particle),
));
}
for (source, _, file) in &hooks.composite {
units.push((file.clone(), "fragment", composite_glsl(source)));
}
let results: Vec<Result<(), ShaderPackError>> =
std::thread::scope(|scope| {
let jobs: Vec<_> = units
.iter()
.map(|(file, stage, glsl)| {
scope.spawn(move || match glsl {
Err(message) => Err(ShaderPackError {
code: "SHADER_PACK_INVALID",
file: file.clone(),
line: 1,
message: message.clone(),
}),
Ok(glsl) => spirv(stage, glsl)
.map(drop)
.map_err(|message| compile_error(file, &message)),
})
})
.collect();
jobs.into_iter()
.map(|job| job.join().unwrap_or(Ok(())))
.collect()
});
results.into_iter().collect()
}
#[derive(Default)]
pub(super) struct PackRenderer {
hooks: Option<Arc<PackHooks>>,
previous: Option<(Matrix4<f32>, Matrix4<f32>, [f32; 3])>,
frames: u32,
pub shadow:
HashMap<(u64, &'static str), Option<[Arc<GraphicsPipeline>; 2]>>,
pub chain: Option<CompositeChain>,
pub sky_lut: super::sky_lut::SkyLutCache,
pub cloud_layer: super::cloud_layer::CloudLayerCache,
}
pub(super) struct FrameInputs<'a> {
pub camera: Matrix4<f32>,
pub clip: Matrix4<f32>,
pub near_far: [f32; 2],
pub sun: Option<([f32; 3], f32, [f32; 3])>,
pub world: Option<&'a ShaderWorld>,
pub seconds: f32,
pub extent: [u32; 2],
}
impl PackRenderer {
pub fn hooks(
&mut self,
active: Option<&ActiveShaderPack>,
) -> Option<Arc<PackHooks>> {
let Some(active) = active else {
self.hooks = None;
self.shadow.clear();
return None;
};
if self
.hooks
.as_ref()
.is_none_or(|hooks| hooks.revision != active.revision)
{
self.hooks = Some(Arc::new(PackHooks::new(active)));
self.shadow
.retain(|(revision, _), _| *revision == active.revision);
}
self.hooks.clone()
}
pub fn frame_block(&mut self, input: &FrameInputs<'_>) -> FrameBlock {
let view = input.camera.try_inverse().unwrap_or_else(Matrix4::identity);
let projection = input.clip * input.camera;
let eye = [
input.camera[(0, 3)],
input.camera[(1, 3)],
input.camera[(2, 3)],
];
let (previous_view, previous_projection, previous_eye) =
self.previous.unwrap_or((view, projection, eye));
self.previous = Some((view, projection, eye));
self.frames = self.frames.wrapping_add(1);
let inverse =
|m: Matrix4<f32>| m.try_inverse().unwrap_or_else(Matrix4::identity);
let [w, h] =
[input.extent[0].max(1) as f32, input.extent[1].max(1) as f32];
let world = input.world.copied();
let (sun_direction, sun_lux, sun_color) = match &world {
Some(world) => {
(world.sun_direction, world.sun_lux, world.sun_color)
}
None => input.sun.unwrap_or(([0.0, 1.0, 0.0], 0.0, [0.0; 3])),
};
let world = world.unwrap_or_default();
let [near, far] = input.near_far;
FrameBlock {
view: view.into(),
projection: projection.into(),
view_projection: input.clip.into(),
inverse_view: input.camera.into(),
inverse_projection: inverse(projection).into(),
previous_view: previous_view.into(),
previous_projection: previous_projection.into(),
previous_view_projection: (previous_projection * previous_view)
.into(),
camera_position: [eye[0], eye[1], eye[2], near],
previous_camera_position: [
previous_eye[0],
previous_eye[1],
previous_eye[2],
far,
],
sun_direction: [
sun_direction[0],
sun_direction[1],
sun_direction[2],
sun_lux,
],
moon_direction: [
world.moon_direction[0],
world.moon_direction[1],
world.moon_direction[2],
world.moon_lux,
],
sun_color: [sun_color[0], sun_color[1], sun_color[2], 0.0],
time: [
input.seconds,
world.world_time,
world.day_fraction,
world.moon_phase,
],
screen: [w, h, 1.0 / w, 1.0 / h],
weather: [
world.rain,
world.thunder,
world.temperature,
world.humidity,
],
eye: [
f32::from(u8::from(world.underwater)),
world.eye_sky_light,
world.eye_block_light,
self.frames as f32,
],
params: world.params,
}
}
}
pub(super) struct CompositeInputs<'a> {
pub hdr: &'a Arc<ImageView>,
pub depth: &'a Arc<ImageView>,
pub shadow_map: &'a Arc<ImageView>,
pub shadow_sampler: &'a Arc<Sampler>,
pub shadow: Subbuffer<[u8]>,
pub frame: Subbuffer<FrameBlock>,
}
struct Target {
view: Arc<ImageView>,
framebuffer: Arc<Framebuffer>,
size: [u32; 2],
}
pub(super) struct CompositeChain {
queue: Arc<Queue>,
layout: Arc<PipelineLayout>,
render_pass: Arc<RenderPass>,
linear: Arc<Sampler>,
nearest: Arc<Sampler>,
dummy: Arc<ImageView>,
fresh: Vec<Arc<Image>>,
pipelines: (u64, Vec<Option<Arc<GraphicsPipeline>>>),
targets: (([u32; 2], Vec<u32>), Vec<Target>),
history: Option<Arc<ImageView>>,
}
fn hdr_image(
memory: &Arc<StandardMemoryAllocator>,
format: Format,
size: [u32; 2],
usage: ImageUsage,
name: &str,
) -> Result<Arc<ImageView>, SceneRenderError> {
let image = Image::new(
memory.clone(),
ImageCreateInfo {
format,
extent: [size[0].max(1), size[1].max(1), 1],
usage,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE,
..Default::default()
},
)
.map_err(error)?;
name_object(&*image, name);
ImageView::new_default(image).map_err(error)
}
impl CompositeChain {
pub fn new(
queue: &Arc<Queue>,
memory: &Arc<StandardMemoryAllocator>,
format: Format,
) -> Result<Self, SceneRenderError> {
let device = queue.device().clone();
let image = || DescriptorSetLayoutBinding {
stages: ShaderStages::FRAGMENT,
..DescriptorSetLayoutBinding::descriptor_type(
DescriptorType::CombinedImageSampler,
)
};
let mut bindings = BTreeMap::new();
for binding in [0, 1, 2, 4] {
bindings.insert(binding, image());
}
bindings.insert(
3,
DescriptorSetLayoutBinding {
stages: ShaderStages::FRAGMENT,
..DescriptorSetLayoutBinding::descriptor_type(
DescriptorType::StorageBuffer,
)
},
);
bindings.insert(
5,
DescriptorSetLayoutBinding {
descriptor_count: MAX_COMPOSITE_PASSES as u32,
..image()
},
);
bindings.insert(
SCENE_FRAME_BINDING,
DescriptorSetLayoutBinding {
stages: ShaderStages::FRAGMENT,
..DescriptorSetLayoutBinding::descriptor_type(
DescriptorType::UniformBuffer,
)
},
);
let set = DescriptorSetLayout::new(
device.clone(),
DescriptorSetLayoutCreateInfo {
bindings,
..Default::default()
},
)
.map_err(error)?;
let layout = PipelineLayout::new(
device.clone(),
PipelineLayoutCreateInfo {
set_layouts: vec![set],
push_constant_ranges: vec![PushConstantRange {
stages: ShaderStages::FRAGMENT,
offset: 0,
size: 16,
}],
..Default::default()
},
)
.map_err(error)?;
let render_pass = vulkano::single_pass_renderpass!(
device.clone(),
attachments: {
color: {
format: format,
samples: 1,
load_op: DontCare,
store_op: Store,
}
},
pass: {
color: [color],
depth_stencil: {}
}
)
.map_err(error)?;
name_object(&*render_pass, "Composite");
let sampler = |filter| {
Sampler::new(
device.clone(),
SamplerCreateInfo {
mag_filter: filter,
min_filter: filter,
address_mode: [SamplerAddressMode::ClampToEdge; 3],
..Default::default()
},
)
.map_err(error)
};
let dummy = hdr_image(
memory,
format,
[1, 1],
ImageUsage::SAMPLED | ImageUsage::TRANSFER_DST,
"Composite black",
)?;
Ok(Self {
queue: queue.clone(),
layout,
render_pass,
linear: sampler(Filter::Linear)?,
nearest: sampler(Filter::Nearest)?,
fresh: vec![dummy.image().clone()],
dummy,
pipelines: (u64::MAX, Vec::new()),
targets: (([0, 0], Vec::new()), Vec::new()),
history: None,
})
}
fn pipeline(&self, source: &str) -> Result<Arc<GraphicsPipeline>, String> {
let device = self.queue.device().clone();
let fragment =
compile_glsl(&device, "fragment", &composite_glsl(source)?)?
.entry_point("main")
.ok_or("composite entry point is missing")?;
let vertex = tonemap_vertex_shader::load(device.clone())
.map_err(|e| e.to_string())?
.entry_point("main")
.ok_or("composite vertex entry point is missing")?;
let subpass = Subpass::from(self.render_pass.clone(), 0)
.ok_or("composite subpass is missing")?;
GraphicsPipeline::new(
device,
None,
GraphicsPipelineCreateInfo {
stages: [
PipelineShaderStageCreateInfo::new(vertex),
PipelineShaderStageCreateInfo::new(fragment),
]
.into_iter()
.collect(),
vertex_input_state: Some(Default::default()),
input_assembly_state: Some(InputAssemblyState::default()),
viewport_state: Some(ViewportState::default()),
rasterization_state: Some(RasterizationState::default()),
multisample_state: Some(MultisampleState::default()),
color_blend_state: Some(
ColorBlendState::with_attachment_states(
1,
ColorBlendAttachmentState::default(),
),
),
dynamic_state: [DynamicState::Viewport].into_iter().collect(),
subpass: Some(PipelineSubpassType::BeginRenderPass(subpass)),
..GraphicsPipelineCreateInfo::layout(self.layout.clone())
},
)
.map_err(|e| e.to_string())
}
pub fn record(
&mut self,
commands: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
descriptors: &Arc<StandardDescriptorSetAllocator>,
memory: &Arc<StandardMemoryAllocator>,
hooks: &PackHooks,
live: &[bool],
input: CompositeInputs<'_>,
) -> Result<u64, SceneRenderError> {
if self.pipelines.0 != hooks.revision {
let pipelines = hooks
.composite
.iter()
.map(|(source, _, file)| {
self.pipeline(source)
.inspect_err(|e| {
eprintln!("composite pass {file} skipped:\n{e}")
})
.ok()
})
.collect();
self.pipelines = (hooks.revision, pipelines);
}
let extent = {
let [w, h, _] = input.hdr.image().extent();
[w, h]
};
let format = input.hdr.format();
let divisors: Vec<u32> = hooks
.composite
.iter()
.map(|(_, scale, _)| (1.0 / scale.max(0.25)).round() as u32)
.collect();
if self.targets.0 != (extent, divisors.clone()) {
let mut targets = Vec::new();
for divisor in &divisors {
let size = [
extent[0].div_ceil(*divisor),
extent[1].div_ceil(*divisor),
];
let view = hdr_image(
memory,
format,
size,
ImageUsage::COLOR_ATTACHMENT
| ImageUsage::SAMPLED
| ImageUsage::TRANSFER_SRC,
"Composite target",
)?;
let framebuffer = Framebuffer::new(
self.render_pass.clone(),
FramebufferCreateInfo {
attachments: vec![view.clone()],
..Default::default()
},
)
.map_err(error)?;
targets.push(Target {
view,
framebuffer,
size,
});
}
self.targets = ((extent, divisors), targets);
}
let history_stale = self.history.as_ref().is_none_or(|history| {
let [w, h, _] = history.image().extent();
[w, h] != extent
});
if !hooks.history {
self.history = None;
} else if history_stale {
let history = hdr_image(
memory,
format,
extent,
ImageUsage::SAMPLED | ImageUsage::TRANSFER_DST,
"Composite history",
)?;
self.fresh.push(history.image().clone());
self.history = Some(history);
}
for image in self.fresh.drain(..) {
commands
.clear_color_image(ClearColorImageInfo::image(image))
.map_err(error)?;
}
let mut chain = input.hdr.clone();
let mut draws = 0;
for (index, target) in self.targets.1.iter().enumerate() {
if !live.get(index).copied().unwrap_or(true) {
continue;
}
let Some(Some(pipeline)) = self.pipelines.1.get(index) else {
continue;
};
let earlier = (0..MAX_COMPOSITE_PASSES).map(|slot| {
let view = if slot < index {
self.targets.1[slot].view.clone()
} else {
self.dummy.clone()
};
(view, self.linear.clone())
});
let set = DescriptorSet::new(
descriptors.clone(),
self.layout.set_layouts()[0].clone(),
[
WriteDescriptorSet::image_view_sampler(
0,
chain.clone(),
self.linear.clone(),
),
WriteDescriptorSet::image_view_sampler(
1,
input.depth.clone(),
self.nearest.clone(),
),
WriteDescriptorSet::image_view_sampler(
2,
input.shadow_map.clone(),
input.shadow_sampler.clone(),
),
WriteDescriptorSet::buffer(3, input.shadow.clone()),
WriteDescriptorSet::image_view_sampler(
4,
self.history
.clone()
.unwrap_or_else(|| self.dummy.clone()),
self.linear.clone(),
),
WriteDescriptorSet::image_view_sampler_array(5, 0, earlier),
WriteDescriptorSet::buffer(
SCENE_FRAME_BINDING,
input.frame.clone(),
),
],
[],
)
.map_err(error)?;
let [w, h] = [target.size[0] as f32, target.size[1] as f32];
commands
.begin_render_pass(
RenderPassBeginInfo {
clear_values: vec![None],
..RenderPassBeginInfo::framebuffer(
target.framebuffer.clone(),
)
},
SubpassBeginInfo {
contents: SubpassContents::Inline,
..Default::default()
},
)
.map_err(error)?
.set_viewport(
0,
[Viewport {
offset: [0.0, 0.0],
extent: [w, h],
depth_range: 0.0..=1.0,
}]
.into_iter()
.collect(),
)
.map_err(error)?
.bind_pipeline_graphics(pipeline.clone())
.map_err(error)?
.bind_descriptor_sets(
PipelineBindPoint::Graphics,
self.layout.clone(),
0,
set,
)
.map_err(error)?
.push_constants(
self.layout.clone(),
0,
[w, h, 1.0 / w, 1.0 / h],
)
.map_err(error)?;
unsafe { commands.draw(3, 1, 0, 0) }.map_err(error)?;
commands
.end_render_pass(Default::default())
.map_err(error)?;
draws += 1;
if self.targets.0 .1[index] == 1 {
chain = target.view.clone();
}
}
if !Arc::ptr_eq(&chain, input.hdr) {
commands
.copy_image(CopyImageInfo::images(
chain.image().clone(),
input.hdr.image().clone(),
))
.map_err(error)?;
}
if let Some(history) = self.history.as_ref().filter(|_| draws > 0) {
commands
.copy_image(CopyImageInfo::images(
input.hdr.image().clone(),
history.image().clone(),
))
.map_err(error)?;
}
Ok(draws)
}
}