use anyhow::{Context, Result};
use cgmath::{Angle, Matrix4, Rad};
use std::{sync::Arc, time::Instant};
use tracy_client::{plot, span};
use vulkano::{
buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage},
descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet},
device::Device,
memory::allocator::{AllocationCreateInfo, MemoryUsage},
pipeline::{
graphics::{
color_blend::ColorBlendState,
depth_stencil::{CompareOp, DepthState, DepthStencilState},
input_assembly::InputAssemblyState,
rasterization::RasterizationState,
vertex_input::Vertex,
viewport::{Viewport, ViewportState},
},
GraphicsPipeline, Pipeline, StateMode,
},
render_pass::{RenderPass, Subpass},
shader::ShaderModule,
};
use crate::{
block_renderer::VkChunkVertexDataGpu,
vulkan::{
shaders::{frag_lighting, vert_3d::ModelMatrix},
CommandBufferBuilder, Texture2DHolder, VulkanContext, VulkanWindow,
},
};
use crate::vulkan::shaders::{
vert_3d::{self, UniformData},
PipelineProvider, PipelineWrapper,
};
use super::{frag_lighting_sparse, SceneState};
#[derive(BufferContents, Vertex, Copy, Clone, Debug, PartialEq)]
#[repr(C)]
pub(crate) struct CubeGeometryVertex {
#[format(R32G32B32_SFLOAT)]
pub(crate) position: [f32; 3],
#[format(R32G32B32_SFLOAT)]
pub(crate) normal: [f32; 3],
#[format(R32G32_SFLOAT)]
pub(crate) uv_texcoord: [f32; 2],
#[format(R32_SFLOAT)]
pub(crate) brightness: f32,
#[format(R32_SFLOAT)]
pub(crate) global_brightness_contribution: f32,
#[format(R32_SFLOAT)]
pub(crate) wave_horizontal: f32,
}
pub(crate) struct CubeGeometryDrawCall {
pub(crate) models: VkChunkVertexDataGpu,
pub(crate) model_matrix: Matrix4<f32>,
}
pub(crate) struct CubePipelineWrapper {
solid_pipeline: Arc<GraphicsPipeline>,
sparse_pipeline: Arc<GraphicsPipeline>,
translucent_pipeline: Arc<GraphicsPipeline>,
solid_descriptor: Arc<PersistentDescriptorSet>,
sparse_descriptor: Arc<PersistentDescriptorSet>,
translucent_descriptor: Arc<PersistentDescriptorSet>,
start_time: Instant,
}
const PLANT_WAVE_FREQUENCY_HZ: f64 = 0.25;
impl CubePipelineWrapper {
fn get_plant_wave_vector(&self) -> [f32; 2] {
let time = self.start_time.elapsed().as_secs_f64();
let sin_cos = Rad(PLANT_WAVE_FREQUENCY_HZ * time * 2.0 * std::f64::consts::PI).sin_cos();
[sin_cos.0 as f32, sin_cos.1 as f32]
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum BlockRenderPass {
Opaque,
Transparent,
Translucent,
}
impl PipelineWrapper<&mut [CubeGeometryDrawCall], SceneState> for CubePipelineWrapper {
type PassIdentifier = BlockRenderPass;
fn draw<L>(
&mut self,
builder: &mut CommandBufferBuilder<L>,
draw_calls: &mut [CubeGeometryDrawCall],
pass: BlockRenderPass,
) -> Result<()> {
let _span = match pass {
BlockRenderPass::Opaque => span!("draw opaque"),
BlockRenderPass::Transparent => span!("draw transparent"),
BlockRenderPass::Translucent => span!("draw translucent"),
};
let pipeline = match pass {
BlockRenderPass::Opaque => self.solid_pipeline.clone(),
BlockRenderPass::Transparent => self.sparse_pipeline.clone(),
BlockRenderPass::Translucent => self.translucent_pipeline.clone(),
};
let layout = pipeline.layout().clone();
builder.bind_pipeline_graphics(pipeline);
let mut effective_calls = 0;
for call in draw_calls.iter_mut() {
let pass_data = match pass {
BlockRenderPass::Opaque => call.models.solid_opaque.take(),
BlockRenderPass::Transparent => call.models.transparent.take(),
BlockRenderPass::Translucent => call.models.translucent.take(),
};
if let Some(pass_data) = pass_data {
effective_calls += 1;
let push_data: ModelMatrix = call.model_matrix.into();
builder
.push_constants(layout.clone(), 0, push_data)
.bind_vertex_buffers(0, pass_data.vtx.clone())
.bind_index_buffer(pass_data.idx.clone())
.draw_indexed(pass_data.idx.len().try_into()?, 1, 0, 0, 0)?;
}
}
plot!("total_calls", draw_calls.len() as f64);
let draw_rate = effective_calls as f64 / (draw_calls.len() as f64);
match pass {
BlockRenderPass::Opaque => {
plot!("opaque_rate", draw_rate);
}
BlockRenderPass::Transparent => {
plot!("transparent_rate", draw_rate);
}
BlockRenderPass::Translucent => {
plot!("translucent_rate", draw_rate);
}
};
Ok(())
}
fn bind<L>(
&mut self,
ctx: &VulkanContext,
per_frame_config: SceneState,
command_buf_builder: &mut CommandBufferBuilder<L>,
pass: BlockRenderPass,
) -> Result<()> {
let _span = match pass {
BlockRenderPass::Opaque => span!("bind opaque"),
BlockRenderPass::Transparent => span!("bind transparent"),
BlockRenderPass::Translucent => span!("bind translucent"),
};
let layout = match pass {
BlockRenderPass::Opaque => self.solid_pipeline.layout().clone(),
BlockRenderPass::Transparent => self.sparse_pipeline.layout().clone(),
BlockRenderPass::Translucent => self.translucent_pipeline.layout().clone(),
};
let per_frame_set_layout = layout
.set_layouts()
.get(1)
.with_context(|| "Layout missing set 1")?;
let uniform_buffer = Buffer::from_data(
&ctx.memory_allocator,
BufferCreateInfo {
usage: BufferUsage::UNIFORM_BUFFER,
..Default::default()
},
AllocationCreateInfo {
usage: MemoryUsage::Upload,
..Default::default()
},
UniformData {
vp_matrix: per_frame_config.vp_matrix.into(),
plant_wave_vector: self.get_plant_wave_vector().into(),
global_brightness_color: per_frame_config.global_light_color.into(),
global_light_direction: per_frame_config.global_light_direction.into(),
},
)?;
let per_frame_set = PersistentDescriptorSet::new(
&ctx.descriptor_set_allocator,
per_frame_set_layout.clone(),
[WriteDescriptorSet::buffer(0, uniform_buffer)],
)?;
let descriptor = match pass {
BlockRenderPass::Opaque => self.solid_descriptor.clone(),
BlockRenderPass::Transparent => self.sparse_descriptor.clone(),
BlockRenderPass::Translucent => self.translucent_descriptor.clone(),
};
command_buf_builder.bind_descriptor_sets(
vulkano::pipeline::PipelineBindPoint::Graphics,
layout,
0,
vec![descriptor, per_frame_set],
);
Ok(())
}
}
pub(crate) struct CubePipelineProvider {
device: Arc<Device>,
vs_cube: Arc<ShaderModule>,
fs_solid: Arc<ShaderModule>,
fs_sparse: Arc<ShaderModule>,
}
impl CubePipelineProvider {
pub(crate) fn new(device: Arc<Device>) -> Result<CubePipelineProvider> {
let vs_cube = vert_3d::load_cube_geometry(device.clone())?;
let fs_solid = frag_lighting::load(device.clone())?;
let fs_sparse = frag_lighting_sparse::load(device.clone())?;
Ok(CubePipelineProvider {
device,
vs_cube,
fs_solid,
fs_sparse,
})
}
pub(crate) fn build_pipeline(
&self,
_ctx: &VulkanContext,
viewport: Viewport,
render_pass: Arc<RenderPass>,
tex: &Texture2DHolder,
) -> Result<CubePipelineWrapper> {
let default_pipeline = GraphicsPipeline::start()
.vertex_input_state(CubeGeometryVertex::per_vertex())
.vertex_shader(
self.vs_cube
.entry_point("main")
.context("Could not find vertex shader entry point")?,
(),
)
.input_assembly_state(InputAssemblyState::new())
.viewport_state(ViewportState::viewport_fixed_scissor_irrelevant([viewport]))
.rasterization_state(
RasterizationState::default()
.front_face(
vulkano::pipeline::graphics::rasterization::FrontFace::CounterClockwise,
)
.cull_mode(vulkano::pipeline::graphics::rasterization::CullMode::Back),
)
.color_blend_state(ColorBlendState::default().blend_alpha())
.depth_stencil_state(DepthStencilState::simple_depth_test())
.render_pass(
Subpass::from(render_pass.clone(), 0).context("Could not find subpass 0")?,
);
let solid_pipeline = default_pipeline
.clone()
.fragment_shader(
self.fs_solid
.entry_point("main")
.with_context(|| "Couldn't find dense fragment shader entry point")?,
(),
)
.build(self.device.clone())?;
let sparse_pipeline = default_pipeline
.clone()
.fragment_shader(
self.fs_sparse
.entry_point("main")
.with_context(|| "Couldn't find sparse fragment shader entry point")?,
(),
)
.build(self.device.clone())?;
let translucent_pipeline = default_pipeline
.clone()
.fragment_shader(
self.fs_solid
.entry_point("main")
.with_context(|| "Couldn't find dense fragment shader entry point")?,
(),
)
.depth_stencil_state(DepthStencilState {
depth: Some(DepthState {
enable_dynamic: false,
compare_op: StateMode::Fixed(CompareOp::Less),
write_enable: StateMode::Fixed(false),
}),
depth_bounds: Default::default(),
stencil: Default::default(),
})
.build(self.device.clone())?;
let solid_descriptor = tex.descriptor_set(&solid_pipeline, 0, 0)?;
let sparse_descriptor = tex.descriptor_set(&sparse_pipeline, 0, 0)?;
let translucent_descriptor = tex.descriptor_set(&translucent_pipeline, 0, 0)?;
Ok(CubePipelineWrapper {
solid_pipeline,
sparse_pipeline,
translucent_pipeline,
solid_descriptor,
sparse_descriptor,
translucent_descriptor,
start_time: Instant::now(),
})
}
}
impl PipelineProvider for CubePipelineProvider {
fn make_pipeline(
&self,
wnd: &VulkanWindow,
config: &Texture2DHolder,
) -> Result<CubePipelineWrapper> {
self.build_pipeline(
&wnd.vk_ctx,
wnd.viewport.clone(),
wnd.render_pass.clone(),
config,
)
}
type DrawCall<'a> = &'a mut [CubeGeometryDrawCall];
type PerFrameConfig = SceneState;
type PipelineWrapperImpl = CubePipelineWrapper;
type PerPipelineConfig<'a> = &'a Texture2DHolder;
}