engine/skybox/src/lib.rs
2025-03-04 15:00:59 +01:00

328 lines
11 KiB
Rust

mod vertex;
use std::{path::PathBuf, sync::Arc, time::Duration};
use anyhow::Result;
use ecs::*;
use engine::prelude::{shader_type::*, *};
use plexus::primitive::{
cube::{Bounds, Cube},
decompose::Triangulate,
generate::PolygonsWithPosition,
};
use vertex::VertexPoint;
pub struct SkyBoxImages {
left: PathBuf,
right: PathBuf,
front: PathBuf,
back: PathBuf,
top: PathBuf,
bottom: PathBuf,
}
impl<T: ExactSizeIterator<Item = PathBuf>> From<T> for SkyBoxImages {
fn from(mut paths: T) -> Self {
debug_assert_eq!(paths.len(), 6);
Self {
left: paths.next().unwrap(),
right: paths.next().unwrap(),
front: paths.next().unwrap(),
back: paths.next().unwrap(),
top: paths.next().unwrap(),
bottom: paths.next().unwrap(),
}
}
}
pub struct SkyBox {
enabled: bool,
_cube_map: Arc<Image>,
cube_buffer: Arc<Buffer<VertexPoint>>,
vertex_shader: Arc<ShaderModule<Vertex>>,
fragment_shader: Arc<ShaderModule<Fragment>>,
render_target: TargetMode<RenderTarget>,
pipeline: TargetMode<Arc<Pipeline>>,
descriptor_set: Arc<DescriptorSet>,
}
impl SkyBox {
pub fn new(world: &mut WorldBuilder, images: impl Into<SkyBoxImages>) -> Result<()> {
let sample_count = world
.resources
.get::<EngineSettings>()
.graphics_info()?
.sample_count;
let context = world.resources.get_mut_unchecked::<Context>();
context.render_core_mut().add_render_routine::<Self>(1);
let images = images.into();
let cube_map = Image::cube_map([
images.left.try_into()?,
images.right.try_into()?,
images.front.try_into()?,
images.back.try_into()?,
images.top.try_into()?,
images.bottom.try_into()?,
])?
.format(VK_FORMAT_R8G8B8A8_UNORM)
.max_mip_map_levels()
.attach_pretty_sampler(context.device())?
.build(context.device(), context.queue())?;
let descriptor_set_layout = DescriptorSetLayout::builder()
.add_layout_binding(
0,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
VK_SHADER_STAGE_VERTEX_BIT,
0,
)
.add_layout_binding(
1,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
VK_SHADER_STAGE_FRAGMENT_BIT,
0,
)
.build(context.device().clone())?;
let render_target = Self::create_render_target(context, sample_count)?;
let pipeline_layout = PipelineLayout::builder()
.add_descriptor_set_layout(&descriptor_set_layout)
.build(context.device().clone())?;
let vertex_shader = ShaderModule::from_slice(
context.device().clone(),
include_bytes!("../shader/skybox.vert.spv"),
)?;
let fragment_shader = ShaderModule::from_slice(
context.device().clone(),
include_bytes!("../shader/skybox.frag.spv"),
)?;
let pipeline = Self::create_pipeline(
context,
sample_count,
&render_target,
&pipeline_layout,
&vertex_shader,
&fragment_shader,
)?;
let descriptor_pool = DescriptorPool::builder()
.set_layout(descriptor_set_layout)
.build(context.device().clone())?;
let descriptor_set = descriptor_pool.prepare_set().allocate()?;
let scene = world.resources.get::<Scene>();
let view = scene.view();
descriptor_set.update(&[
DescriptorWrite::uniform_buffers(0, &[view.buffer()]),
DescriptorWrite::combined_samplers(1, &[&cube_map]),
])?;
let cube_mesh = Cube::new()
.polygons_with_position_from(Bounds::unit_radius())
.triangulate()
.map(|d| {
vec![
[d.a.0.into_inner(), d.a.1.into_inner(), d.a.2.into_inner()],
[d.b.0.into_inner(), d.b.1.into_inner(), d.b.2.into_inner()],
[d.c.0.into_inner(), d.c.1.into_inner(), d.c.2.into_inner()],
]
})
.flatten()
.map(|t| VertexPoint::new(t[0] as f32, t[1] as f32, t[2] as f32))
.collect::<Vec<_>>();
let command_buffer = CommandBuffer::new_primary()
.build(context.device().clone(), context.queue().clone())?;
let cube_buffer = SingleSubmit::builder(&command_buffer, context.queue(), |recorder| {
Buffer::builder()
.set_memory_usage(MemoryUsage::CpuToGpu)
.set_usage(VK_BUFFER_USAGE_TRANSFER_SRC_BIT)
.set_data(&cube_mesh)
.build(context.device().clone())?
.into_device_local(
recorder,
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
)
})
.wait_for_timeout(Duration::from_secs(2))
.submit()?;
let me = Self {
enabled: true,
_cube_map: cube_map,
cube_buffer,
vertex_shader,
fragment_shader,
render_target,
pipeline,
descriptor_set,
};
world.resources.insert(me);
Ok(())
}
pub fn enable(&mut self) {
self.enabled = true;
}
pub fn disable(&mut self) {
self.enabled = false;
}
fn create_render_target(
context: &Context,
sample_count: VkSampleCountFlags,
) -> Result<TargetMode<RenderTarget>> {
context.images().execute(|images| {
let first = images.first().unwrap();
let width = first.width();
let height = first.height();
let format = first.vk_format();
if sample_count == VK_SAMPLE_COUNT_1_BIT {
RenderTarget::builder()
.add_sub_pass(
SubPass::builder(width, height)
// render directly into swapchain target if there is no multi sampling
.set_prepared_targets(images, 0, None)
.use_queue(context.queue().clone())
.build(context.device())?,
)
.build(context.device())
} else {
RenderTarget::builder()
.add_sub_pass(
SubPass::builder(width, height)
// render into multi sampled images
.add_target_info(CustomTarget {
usage: VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT.into(),
format,
clear_on_load: true,
store_on_save: true,
attach_sampler: false,
use_as_input: false,
clear_value: ClearValue::Color([0.0, 0.0, 0.0, 0.0]),
})
.set_sample_count(sample_count)
// resolve multi sampling into swapchain image
.add_resolve_targets((images, false))
.use_queue(context.queue().clone())
.build(context.device())?,
)
.build(context.device())
}
})
}
fn create_pipeline(
context: &Context,
sample_count: VkSampleCountFlags,
render_target: &TargetMode<RenderTarget>,
pipeline_layout: &Arc<PipelineLayout>,
vertex_shader: &Arc<ShaderModule<Vertex>>,
fragment_shader: &Arc<ShaderModule<Fragment>>,
) -> Result<TargetMode<Arc<Pipeline>>> {
render_target.execute(|render_target| {
Pipeline::new_graphics()
.set_vertex_shader::<VertexPoint>(vertex_shader.clone())
.set_fragment_shader(fragment_shader.clone())
.input_assembly(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, false)
.default_multisample(sample_count)
.default_color_blend(vec![VkPipelineColorBlendAttachmentState::default()])
.default_rasterization(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE)
.whole_area(render_target.width(), render_target.height())
.build(
context.device().clone(),
pipeline_layout,
render_target.render_pass(),
0,
)
})
}
}
impl TScene for SkyBox {
fn process(
&mut self,
buffer_recorder: &mut CommandBufferRecorder<'_>,
_images: &TargetMode<Vec<Arc<Image>>>,
indices: &TargetMode<usize>,
_world: &mut World,
) -> Result<()> {
if !self.enabled {
return Ok(());
}
self.render_target
.chain(indices)
.chain(&self.pipeline)
.unfold()
.execute(|(render_target, index, pipeline)| {
render_target.begin(buffer_recorder, VK_SUBPASS_CONTENTS_INLINE, ***index);
buffer_recorder.bind_pipeline(pipeline)?;
buffer_recorder.bind_descriptor_sets_minimal(&[&self.descriptor_set]);
buffer_recorder.bind_vertex_buffer(&self.cube_buffer);
buffer_recorder.draw_complete_single_instance(self.cube_buffer.size() as u32);
render_target.end(buffer_recorder);
Ok(())
})?;
Ok(())
}
fn resize(
&mut self,
_window_width: f32,
_window_height: f32,
_images: &TargetMode<Vec<Arc<Image>>>,
world: &mut World,
) -> Result<()> {
let sample_count = world
.resources
.get::<EngineSettings>()
.graphics_info()?
.sample_count;
let context = world.resources.get::<Context>();
let pipeline_layout = match &self.pipeline {
TargetMode::Mono(p) => p.pipeline_layout().clone(),
TargetMode::Stereo(p, _) => p.pipeline_layout().clone(),
};
self.render_target = Self::create_render_target(context, sample_count)?;
self.pipeline = Self::create_pipeline(
context,
sample_count,
&self.render_target,
&pipeline_layout,
&self.vertex_shader,
&self.fragment_shader,
)?;
Ok(())
}
}