use super::{
block_renderer::{BlockRenderer, VkChunkVertexDataGpu},
shaders::cube_geometry::{
CubeDrawStep, CubeGeometryDrawCall, CubePipelineProvider, CubePipelineWrapper,
},
LoadOp, RenderPassHolder, RenderPassId, VulkanContext,
};
use crate::vulkan::atlas::TextureAtlas;
use crate::vulkan::block_renderer::VkChunkRaytraceData;
use crate::vulkan::shaders::cube_geometry::RenderPasses;
use crate::vulkan::shaders::vert_3d::UniformData;
use crate::vulkan::shaders::VkDrawBufferGpu;
use crate::{client_state::chunk::ChunkDataView, vulkan::shaders::SceneState};
use anyhow::{ensure, Context, Result};
use cgmath::{vec3, Deg, Matrix4, SquareMatrix};
use enum_map::enum_map;
use image::{DynamicImage, RgbaImage};
use perovskite_core::{
block_id::BlockId, constants::PADDED_CHUNK_VOLUME, coordinates::ChunkOffset,
};
use perovskite_core::{coordinates::ChunkOffsetForOcclusionExt, protocol::map::ClientExtendedData};
use smallvec::smallvec;
use std::iter;
use std::sync::Arc;
use tinyvec::array_vec;
use vulkano::command_buffer::{
AutoCommandBufferBuilder, PrimaryAutoCommandBuffer, SubpassBeginInfo, SubpassEndInfo,
};
use vulkano::image::{Image, ImageCreateInfo, ImageType};
use vulkano::memory::allocator::MemoryTypeFilter;
use vulkano::pipeline::graphics::viewport::{Scissor, ViewportState};
use vulkano::{
buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer},
command_buffer::{
CopyImageToBufferInfo, PrimaryCommandBufferAbstract, RenderPassBeginInfo, SubpassContents,
},
format::Format,
image::{view::ImageView, ImageUsage},
memory::allocator::AllocationCreateInfo,
pipeline::graphics::viewport::Viewport,
render_pass::{Framebuffer, FramebufferCreateInfo},
sync::GpuFuture,
DeviceSize,
};
const BATCH_SIZE: usize = 16;
pub(crate) struct MiniBlockRenderer {
ctx: Arc<VulkanContext>,
surface_size: [u32; 2],
framebuffers: Vec<(Arc<ImageView>, Arc<Framebuffer>, Subbuffer<[u8]>)>,
cube_pipeline: CubePipelineWrapper,
uniform: Subbuffer<UniformData>,
fake_chunks: Vec<Box<[BlockId; PADDED_CHUNK_VOLUME]>>,
}
impl MiniBlockRenderer {
pub(crate) fn new(
ctx: Arc<VulkanContext>,
surface_size: [u32; 2],
atlas_texture: &TextureAtlas,
) -> Result<Self> {
let renderpasses = RenderPassHolder::new(ctx.vk_device.clone(), ctx.non_swapchain_config());
let render_pass = renderpasses.get(RenderPassId {
color_attachments: array_vec!({ (Format::R8G8B8A8_SRGB, LoadOp::Clear) }),
depth_stencil_attachment: Some((ctx.depth_stencil_format, LoadOp::Clear)),
input_attachments: array_vec!(),
})?;
let extent = [surface_size[0], surface_size[1], 1];
let mut target_images = Vec::new();
let mut framebuffers = Vec::new();
for _ in 0..BATCH_SIZE {
let target_image = ImageView::new_default(
Image::new(
ctx.memory_allocator.clone(),
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: Format::R8G8B8A8_SRGB,
extent,
usage: ImageUsage::COLOR_ATTACHMENT | ImageUsage::TRANSFER_SRC,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE,
..Default::default()
},
)
.context("Create RGBA image for mini renderer")?,
)
.context("Create ImageView for mini renderer")?;
target_images.push(target_image.clone());
let depth_stencil_attachment = ImageView::new_default(
Image::new(
ctx.memory_allocator.clone(),
ImageCreateInfo {
image_type: ImageType::Dim2d,
format: ctx.depth_stencil_format,
extent,
usage: ImageUsage::DEPTH_STENCIL_ATTACHMENT,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_DEVICE,
..Default::default()
},
)
.context("Create depth buffer for mini renderer")?,
)
.context("Create depth buffer imageview for mini renderer")?;
let framebuffer_create_info = FramebufferCreateInfo {
attachments: vec![target_image.clone(), depth_stencil_attachment],
..Default::default()
};
let framebuffer = Framebuffer::new(render_pass.clone(), framebuffer_create_info)?;
let download_buffer = Buffer::new_slice(
ctx.clone_allocator(),
BufferCreateInfo {
usage: BufferUsage::TRANSFER_DST,
..Default::default()
},
AllocationCreateInfo {
memory_type_filter: MemoryTypeFilter::PREFER_HOST
| MemoryTypeFilter::HOST_RANDOM_ACCESS,
..Default::default()
},
(surface_size[0] as DeviceSize)
.checked_mul(surface_size[1] as DeviceSize)
.context("Surface too big")?
.checked_mul(4)
.context("Surface too big")?,
)
.context("Create download buffer for mini renderer")?;
framebuffers.push((target_image, framebuffer, download_buffer));
}
let viewport = Viewport {
offset: [0.0, 0.0],
extent: [surface_size[0] as f32, surface_size[1] as f32],
depth_range: 0.0..=1.0,
};
let viewport_state = ViewportState {
viewports: smallvec![Viewport {
offset: [0.0, 0.0],
depth_range: 0.0..=1.0,
extent: [viewport.extent[0], viewport.extent[1]],
}],
scissors: smallvec![Scissor {
offset: [0, 0],
extent: [surface_size[0], surface_size[1]],
}],
..Default::default()
};
let cube_provider = CubePipelineProvider::new(ctx.vk_device.clone())?;
let cube_pipeline = cube_provider.build_pipeline(
&ctx,
viewport_state,
RenderPasses::MainOnly(render_pass.clone()),
atlas_texture,
&ctx.non_swapchain_config(),
)?;
let uniform = cube_pipeline.make_uniform_buffer(&ctx, *SCENE_STATE)?;
Ok(Self {
ctx,
surface_size,
framebuffers,
cube_pipeline,
uniform,
fake_chunks: iter::repeat(bytemuck::zeroed_box())
.take(BATCH_SIZE)
.collect(),
})
}
pub(crate) fn render_all(
&mut self,
block_renderer: &BlockRenderer,
block_ids: &[BlockId],
) -> Result<Vec<DynamicImage>> {
let mut results = vec![];
for chunk in block_ids.chunks(BATCH_SIZE) {
results.extend(self.render_batch(block_renderer, chunk)?)
}
Ok(results)
}
fn render_batch(
&mut self,
block_renderer: &BlockRenderer,
block_ids: &[BlockId],
) -> Result<smallvec::SmallVec<[DynamicImage; BATCH_SIZE]>> {
let mut commands = self.ctx.start_command_buffer()?;
ensure!(block_ids.len() <= BATCH_SIZE);
for (index, &id) in block_ids.iter().enumerate() {
self.render(&mut commands, block_renderer, id, index)?;
}
let commands = commands.build()?;
commands.execute(self.ctx.clone_graphics_queue())?.flush()?;
let mut result = smallvec::SmallVec::new();
for i in 0..block_ids.len() {
let guard = self.framebuffers[i].2.read()?;
let image =
RgbaImage::from_raw(self.surface_size[0], self.surface_size[1], guard.to_vec())
.context("Failed to create image")?;
result.push(image.into());
}
Ok(result)
}
fn render(
&mut self,
commands: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>,
block_renderer: &BlockRenderer,
block_id: BlockId,
index: usize,
) -> Result<()> {
let mut vtx = vec![];
let mut idx = vec![];
let fake_chunk = &mut self.fake_chunks[index];
fake_chunk[ChunkOffset { x: 0, y: 0, z: 0 }.as_padded_index()] = block_id;
let fake_chunk_data = FakeChunkDataView {
block_ids: fake_chunk.as_ref(),
};
let offset = ChunkOffset { x: 0, y: 0, z: 0 };
block_renderer.render_single_block(
block_renderer.block_types().get_blockdef(block_id).unwrap(),
block_id,
offset,
&fake_chunk_data,
&mut vtx,
&mut idx,
&|_, _| false,
);
commands.begin_render_pass(
RenderPassBeginInfo {
clear_values: vec![
Some([0.0, 0.0, 0.0, 0.0].into()),
Some(self.ctx.depth_clear_value()),
],
..RenderPassBeginInfo::framebuffer(self.framebuffers[index].1.clone())
},
SubpassBeginInfo {
contents: SubpassContents::Inline,
..Default::default()
},
)?;
let pass = VkDrawBufferGpu::from_buffers(&vtx, &idx, self.ctx.clone_allocator())?;
if let Some(buffer) = pass {
let mut draw_buffers = enum_map! { _ => None };
draw_buffers[CubeDrawStep::Transparent] = Some(buffer);
let draw_call = CubeGeometryDrawCall {
models: VkChunkVertexDataGpu { draw_buffers },
model_matrix: Matrix4::identity(),
};
self.cube_pipeline.draw_single_step(
&self.ctx,
commands,
self.uniform.clone(),
&mut [draw_call],
CubeDrawStep::Transparent,
&SCENE_STATE,
)?;
}
commands.end_render_pass(SubpassEndInfo {
..Default::default()
})?;
commands.copy_image_to_buffer(CopyImageToBufferInfo {
dst_buffer: self.framebuffers[index].2.clone(),
..CopyImageToBufferInfo::image_buffer(
self.framebuffers[index].0.image().clone(),
self.framebuffers[index].2.clone(),
)
})?;
Ok(())
}
}
static FAKE_LIGHTMAP: [u8; PADDED_CHUNK_VOLUME] = [255; PADDED_CHUNK_VOLUME];
struct FakeChunkDataView<'a> {
block_ids: &'a [BlockId; PADDED_CHUNK_VOLUME],
}
impl<'a> ChunkDataView for FakeChunkDataView<'a> {
fn is_empty_optimization_hint(&self) -> bool {
false
}
fn block_ids(&self) -> &[BlockId; PADDED_CHUNK_VOLUME] {
self.block_ids
}
fn lightmap(&self) -> &[u8; PADDED_CHUNK_VOLUME] {
&FAKE_LIGHTMAP
}
fn client_ext_data(&self, _offset: ChunkOffset) -> Option<&ClientExtendedData> {
None
}
fn raytrace_data(&self) -> Option<&VkChunkRaytraceData> {
log::warn!("Tried to access raytrace data from FakeChunkDataView");
None
}
}
lazy_static::lazy_static! {
static ref SCENE_STATE: SceneState = {
let projection = cgmath::ortho(-1., 1., -1., 1., -2., 2.);
let rotation = Matrix4::from_angle_x(Deg(-30.0)) * Matrix4::from_angle_y(Deg(135.0));
let coord_system_conversion = Matrix4::from_nonuniform_scale(-1.0, 1.0, 1.0);
let translation = Matrix4::from_translation(vec3(0.0, 0.0, -1.0));
let vp_matrix = projection * translation * rotation * coord_system_conversion;
SceneState {
vp_matrix,
global_light_color: [0., 0., 0.],
sun_direction: vec3(0., 0., 0.),
player_pos_block: 0,
alt_diffuse_enabled: false,
player_position: vec3(0., 0., 0.),
}
};
}