bevy_ecs_tilemap 0.18.1

A tilemap rendering plugin for bevy which is more ECS friendly by having an entity per tile.
Documentation
use bevy::{
    asset::uuid_handle,
    core_pipeline::core_2d::CORE_2D_DEPTH_FORMAT,
    image::BevyDefault,
    mesh::VertexBufferLayout,
    prelude::{Component, FromWorld, Handle, Resource, Shader, World},
    render::{
        globals::GlobalsUniform,
        render_resource::{
            BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendComponent,
            BlendFactor, BlendOperation, BlendState, BufferBindingType, ColorTargetState,
            ColorWrites, CompareFunction, DepthBiasState, DepthStencilState, Face, FragmentState,
            FrontFace, MultisampleState, PolygonMode, PrimitiveState, PrimitiveTopology,
            RenderPipelineDescriptor, SamplerBindingType, ShaderStages, ShaderType,
            SpecializedRenderPipeline, StencilFaceState, StencilState, TextureFormat,
            TextureSampleType, TextureViewDimension, VertexFormat, VertexState, VertexStepMode,
        },
        view::{ViewTarget, ViewUniform},
    },
};

use crate::map::{HexCoordSystem, IsoCoordSystem, TilemapType};

use super::{chunk::TilemapUniformData, prepare::MeshUniform};

pub const TILEMAP_SHADER_VERTEX: Handle<Shader> =
    uuid_handle!("915ef471-58b4-4431-acae-f38b41969a9e");
pub const TILEMAP_SHADER_FRAGMENT: Handle<Shader> =
    uuid_handle!("bf34308e-69df-4da8-b0ff-816042c716c8");

#[derive(Clone, Resource)]
pub struct TilemapPipeline {
    pub view_layout: BindGroupLayoutDescriptor,
    pub material_layout: BindGroupLayoutDescriptor,
    pub mesh_layout: BindGroupLayoutDescriptor,
}

impl FromWorld for TilemapPipeline {
    fn from_world(_world: &mut World) -> Self {
        let view_layout = BindGroupLayoutDescriptor::new(
            "tilemap_view_layout",
            &[
                // View
                BindGroupLayoutEntry {
                    binding: 0,
                    visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
                    ty: BindingType::Buffer {
                        ty: BufferBindingType::Uniform,
                        has_dynamic_offset: true,
                        min_binding_size: Some(ViewUniform::min_size()),
                    },
                    count: None,
                },
                BindGroupLayoutEntry {
                    binding: 1,
                    visibility: ShaderStages::VERTEX_FRAGMENT,
                    ty: BindingType::Buffer {
                        ty: BufferBindingType::Uniform,
                        has_dynamic_offset: false,
                        min_binding_size: Some(GlobalsUniform::min_size()),
                    },
                    count: None,
                },
            ],
        );

        let mesh_layout = BindGroupLayoutDescriptor::new(
            "tilemap_mesh_layout",
            &[
                BindGroupLayoutEntry {
                    binding: 0,
                    visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
                    ty: BindingType::Buffer {
                        ty: BufferBindingType::Uniform,
                        has_dynamic_offset: true,
                        // TODO: change this to MeshUniform::std140_size_static once crevice fixes this!
                        // Context: https://github.com/LPGhatguy/crevice/issues/29
                        min_binding_size: Some(MeshUniform::min_size()),
                    },
                    count: None,
                },
                BindGroupLayoutEntry {
                    binding: 1,
                    visibility: ShaderStages::VERTEX_FRAGMENT,
                    ty: BindingType::Buffer {
                        ty: BufferBindingType::Uniform,
                        has_dynamic_offset: true,
                        min_binding_size: Some(TilemapUniformData::min_size()),
                    },
                    count: None,
                },
            ],
        );

        #[cfg(not(feature = "atlas"))]
        let material_layout = BindGroupLayoutDescriptor::new(
            "tilemap_material_layout",
            &[
                BindGroupLayoutEntry {
                    binding: 0,
                    visibility: ShaderStages::FRAGMENT,
                    ty: BindingType::Texture {
                        multisampled: false,
                        sample_type: TextureSampleType::Float { filterable: true },
                        view_dimension: TextureViewDimension::D2Array,
                    },
                    count: None,
                },
                BindGroupLayoutEntry {
                    binding: 1,
                    visibility: ShaderStages::FRAGMENT,
                    ty: BindingType::Sampler(SamplerBindingType::Filtering),
                    count: None,
                },
            ],
        );

        #[cfg(feature = "atlas")]
        let material_layout = BindGroupLayoutDescriptor::new(
            "tilemap_material_layout",
            &[
                BindGroupLayoutEntry {
                    binding: 0,
                    visibility: ShaderStages::FRAGMENT,
                    ty: BindingType::Texture {
                        multisampled: false,
                        sample_type: TextureSampleType::Float { filterable: true },
                        view_dimension: TextureViewDimension::D2,
                    },
                    count: None,
                },
                BindGroupLayoutEntry {
                    binding: 1,
                    visibility: ShaderStages::FRAGMENT,
                    ty: BindingType::Sampler(SamplerBindingType::Filtering),
                    count: None,
                },
            ],
        );

        TilemapPipeline {
            view_layout,
            material_layout,
            mesh_layout,
        }
    }
}
#[derive(Debug, Component, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TilemapPipelineKey {
    pub msaa: u32,
    pub map_type: TilemapType,
    pub hdr: bool,
}

impl SpecializedRenderPipeline for TilemapPipeline {
    type Key = TilemapPipelineKey;

    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
        let mut shader_defs = Vec::new();

        #[cfg(feature = "atlas")]
        shader_defs.push("ATLAS".into());

        let mesh_string = match key.map_type {
            TilemapType::Square => "SQUARE",
            TilemapType::Isometric(coord_system) => match coord_system {
                IsoCoordSystem::Diamond => "ISO_DIAMOND",
                IsoCoordSystem::Staggered => "ISO_STAGGERED",
            },
            TilemapType::Hexagon(coord_system) => match coord_system {
                HexCoordSystem::Column => "COLUMN_HEX",
                HexCoordSystem::ColumnEven => "COLUMN_EVEN_HEX",
                HexCoordSystem::ColumnOdd => "COLUMN_ODD_HEX",
                HexCoordSystem::Row => "ROW_HEX",
                HexCoordSystem::RowEven => "ROW_EVEN_HEX",
                HexCoordSystem::RowOdd => "ROW_ODD_HEX",
            },
        };
        shader_defs.push(mesh_string.into());

        let formats = vec![
            // Position
            VertexFormat::Float32x4,
            // Uv
            VertexFormat::Float32x4,
            // Color
            VertexFormat::Float32x4,
        ];

        let vertex_layout =
            VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats);

        RenderPipelineDescriptor {
            vertex: VertexState {
                shader: TILEMAP_SHADER_VERTEX,
                entry_point: Some("vertex".into()),
                shader_defs: shader_defs.clone(),
                buffers: vec![vertex_layout],
            },
            fragment: Some(FragmentState {
                shader: TILEMAP_SHADER_FRAGMENT,
                shader_defs,
                entry_point: Some("fragment".into()),
                targets: vec![Some(ColorTargetState {
                    format: if key.hdr {
                        ViewTarget::TEXTURE_FORMAT_HDR
                    } else {
                        TextureFormat::bevy_default()
                    },
                    blend: Some(BlendState {
                        color: BlendComponent {
                            src_factor: BlendFactor::SrcAlpha,
                            dst_factor: BlendFactor::OneMinusSrcAlpha,
                            operation: BlendOperation::Add,
                        },
                        alpha: BlendComponent {
                            src_factor: BlendFactor::One,
                            dst_factor: BlendFactor::One,
                            operation: BlendOperation::Add,
                        },
                    }),
                    write_mask: ColorWrites::ALL,
                })],
            }),
            layout: vec![
                self.view_layout.clone(),
                self.mesh_layout.clone(),
                self.material_layout.clone(),
            ],
            primitive: PrimitiveState {
                conservative: false,
                cull_mode: Some(Face::Back),
                front_face: FrontFace::Ccw,
                polygon_mode: PolygonMode::Fill,
                strip_index_format: None,
                topology: PrimitiveTopology::TriangleList,
                unclipped_depth: false,
            },
            depth_stencil: Some(DepthStencilState {
                format: CORE_2D_DEPTH_FORMAT,
                depth_write_enabled: false,
                depth_compare: CompareFunction::GreaterEqual,
                stencil: StencilState {
                    front: StencilFaceState::IGNORE,
                    back: StencilFaceState::IGNORE,
                    read_mask: 0,
                    write_mask: 0,
                },
                bias: DepthBiasState {
                    constant: 0,
                    slope_scale: 0.0,
                    clamp: 0.0,
                },
            }),
            multisample: MultisampleState {
                count: key.msaa,
                mask: !0,
                alpha_to_coverage_enabled: false,
            },
            label: Some("tilemap_pipeline".into()),
            push_constant_ranges: vec![],
            zero_initialize_workgroup_memory: false,
        }
    }
}