perovskite_client 0.3.2

Multiplayer voxel game written in Rust - Game client
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 weather(&self) -> &bitvec::BitArr!(for PADDED_CHUNK_VOLUME) {
    //     &bitvec::array::BitArray::ZERO
    // }

    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.),
        }
    };
}