galileo 0.2.1

Cross-platform general purpose map rendering engine
Documentation
use std::mem::size_of;
use std::sync::Arc;

use screen_set_image::ScreenSetImagePipeline;
use screen_set_vertex::ScreenSetPipeline;
use wgpu::util::{DeviceExt, TextureDataOrder};
use wgpu::{
    BindGroup, BindGroupLayout, Buffer, CompareFunction, DepthStencilState, Device, PipelineLayout,
    Queue, RenderPass, RenderPipelineDescriptor, ShaderModule, StencilFaceState, StencilOperation,
    StencilState, TextureFormat, VertexBufferLayout,
};

use super::WgpuScreenSetData;
use crate::decoded_image::{DecodedImage, DecodedImageType};
use crate::render::wgpu::pipelines::clip::ClipPipeline;
use crate::render::wgpu::pipelines::dot::DotPipeline;
use crate::render::wgpu::pipelines::image::ImagePipeline;
use crate::render::wgpu::pipelines::map_ref::MapRefPipeline;
use crate::render::wgpu::{ViewUniform, WgpuPackedBundle, DEPTH_FORMAT};
use crate::render::RenderOptions;

mod clip;
mod dot;
pub mod image;
mod map_ref;
mod screen_set_image;
mod screen_set_vertex;

pub struct Pipelines {
    map_view_binding: BindGroup,
    map_view_buffer: Buffer,
    texture_bind_group_layout: BindGroupLayout,

    image: ImagePipeline,
    map_ref: MapRefPipeline,
    clip: ClipPipeline,
    dot: DotPipeline,
    screen_set: ScreenSetPipeline,
    screen_set_image: ScreenSetImagePipeline,
}

impl Pipelines {
    pub fn create(device: &Device, format: TextureFormat) -> Self {
        let map_view_buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("Map view buffer"),
            size: size_of::<ViewUniform>() as wgpu::BufferAddress,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let map_view_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                entries: &[wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::VERTEX,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Uniform,
                        has_dynamic_offset: false,
                        min_binding_size: None,
                    },
                    count: None,
                }],
                label: None,
            });

        let map_view_binding = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &map_view_bind_group_layout,
            entries: &[wgpu::BindGroupEntry {
                binding: 0,
                resource: map_view_buffer.as_entire_binding(),
            }],
            label: Some("view_bind_group"),
        });

        let texture_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                entries: &[
                    wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Texture {
                            multisampled: false,
                            view_dimension: wgpu::TextureViewDimension::D2,
                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
                        },
                        count: None,
                    },
                    wgpu::BindGroupLayoutEntry {
                        binding: 1,
                        visibility: wgpu::ShaderStages::FRAGMENT,
                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                        count: None,
                    },
                ],
                label: Some("texture_bind_group_label"),
            });

        Self {
            map_view_binding,
            map_view_buffer,
            texture_bind_group_layout: texture_bind_group_layout.clone(),
            image: ImagePipeline::create(
                device,
                format,
                &map_view_bind_group_layout,
                &texture_bind_group_layout,
            ),
            map_ref: MapRefPipeline::create(device, format, &map_view_bind_group_layout),
            clip: ClipPipeline::create(device, format, &map_view_bind_group_layout),
            dot: DotPipeline::create(device, format, &map_view_bind_group_layout),
            screen_set: ScreenSetPipeline::create(device, format, &map_view_bind_group_layout),
            screen_set_image: ScreenSetImagePipeline::create(
                device,
                format,
                &map_view_bind_group_layout,
                &texture_bind_group_layout,
            ),
        }
    }

    pub fn render<'a>(
        &'a self,
        render_pass: &mut RenderPass<'a>,
        bundle: &'a WgpuPackedBundle,
        render_options: RenderOptions,
        bundle_index: u32,
    ) {
        self.set_bindings(render_pass);

        if let Some(clip) = &bundle.clip_area_buffers {
            self.clip.clip(clip, render_pass, render_options);
        }

        for image in &bundle.image_buffers {
            self.image
                .render(image, render_pass, render_options, bundle_index);
        }

        if bundle.map_ref_buffers.index_count > 0 {
            self.map_ref.render(
                &bundle.map_ref_buffers,
                render_pass,
                render_options,
                bundle_index,
            );
        }

        if let Some(clip) = &bundle.clip_area_buffers {
            self.clip.unclip(clip, render_pass, render_options);
        }

        if let Some(dot_buffers) = &bundle.dot_buffers {
            self.dot
                .render(dot_buffers, render_pass, render_options, bundle_index);
        }
    }

    pub fn map_view_buffer(&self) -> &Buffer {
        &self.map_view_buffer
    }

    pub fn image_pipeline(&self) -> &ImagePipeline {
        &self.image
    }

    pub fn screen_set_image_pipeline(&self) -> &ScreenSetImagePipeline {
        &self.screen_set_image
    }

    fn set_bindings<'a>(&'a self, render_pass: &mut RenderPass<'a>) {
        render_pass.set_bind_group(0, &self.map_view_binding, &[]);
    }

    pub fn render_screen_set<'a>(
        &'a self,
        data: &'a WgpuScreenSetData,
        render_pass: &mut RenderPass<'a>,
        bundle_index: u32,
    ) {
        self.set_bindings(render_pass);
        match data {
            WgpuScreenSetData::Vertex(wgpu_vertex_buffers) => {
                self.screen_set
                    .render(wgpu_vertex_buffers, render_pass, bundle_index)
            }
            WgpuScreenSetData::Image(wgpu_image) => {
                self.screen_set_image
                    .render(wgpu_image, render_pass, bundle_index)
            }
        }
    }

    pub fn create_image_texture(
        &self,
        device: &Device,
        queue: &Queue,
        image: &DecodedImage,
    ) -> Arc<BindGroup> {
        let texture_size = wgpu::Extent3d {
            width: image.width(),
            height: image.height(),
            depth_or_array_layers: 1,
        };

        let texture = match &image.0 {
            DecodedImageType::Bitmap { bytes, .. } => device.create_texture_with_data(
                queue,
                &wgpu::TextureDescriptor {
                    size: texture_size,
                    mip_level_count: 1,
                    sample_count: 1,
                    dimension: wgpu::TextureDimension::D2,
                    format: TextureFormat::Rgba8UnormSrgb,
                    usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
                    label: None,
                    view_formats: &[],
                },
                TextureDataOrder::default(),
                bytes,
            ),
            #[cfg(target_arch = "wasm32")]
            DecodedImageType::JsImageBitmap { js_image, .. } => {
                use wgpu::{CopyExternalImageSourceInfo, ExternalImageSource, Origin2d};

                let texture = device.create_texture(&wgpu::TextureDescriptor {
                    size: texture_size,
                    mip_level_count: 1,
                    sample_count: 1,
                    dimension: wgpu::TextureDimension::D2,
                    format: TextureFormat::Rgba8UnormSrgb,
                    usage: wgpu::TextureUsages::TEXTURE_BINDING
                        | wgpu::TextureUsages::COPY_DST
                        | wgpu::TextureUsages::RENDER_ATTACHMENT,
                    label: None,
                    view_formats: &[],
                });
                let texture_size = wgpu::Extent3d {
                    width: js_image.width(),
                    height: js_image.height(),
                    depth_or_array_layers: 1,
                };
                let image = CopyExternalImageSourceInfo {
                    source: ExternalImageSource::ImageBitmap(js_image.clone()),
                    origin: Origin2d::ZERO,
                    flip_y: false,
                };
                queue.copy_external_image_to_texture(
                    &image,
                    texture
                        .as_image_copy()
                        .to_tagged(wgpu::PredefinedColorSpace::Srgb, false),
                    texture_size,
                );

                texture
            }
        };

        let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());

        let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            address_mode_w: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Linear,
            min_filter: wgpu::FilterMode::Linear,
            mipmap_filter: wgpu::FilterMode::Nearest,
            ..Default::default()
        });

        let texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            layout: &self.texture_bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&texture_view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&diffuse_sampler),
                },
            ],
            label: Some("diffuse_bind_group"),
        });

        Arc::new(texture_bind_group)
    }
}

fn default_targets(format: TextureFormat) -> [Option<wgpu::ColorTargetState>; 1] {
    [Some(wgpu::ColorTargetState {
        format,
        blend: Some(wgpu::BlendState::ALPHA_BLENDING),
        write_mask: wgpu::ColorWrites::ALL,
    })]
}

fn default_pipeline_descriptor<'a>(
    pipeline_layout: &'a PipelineLayout,
    shader: &'a ShaderModule,
    targets: &'a [Option<wgpu::ColorTargetState>],
    buffers: &'a [VertexBufferLayout<'a>],
    antialias: bool,
) -> RenderPipelineDescriptor<'a> {
    let stencil_state = StencilFaceState {
        compare: CompareFunction::Equal,
        fail_op: StencilOperation::Keep,
        depth_fail_op: StencilOperation::Keep,
        pass_op: StencilOperation::Keep,
    };

    RenderPipelineDescriptor {
        label: None,
        layout: Some(pipeline_layout),
        vertex: wgpu::VertexState {
            module: shader,
            entry_point: Some("vs_main"),
            buffers,
            compilation_options: Default::default(),
        },
        fragment: Some(wgpu::FragmentState {
            module: shader,
            entry_point: Some("fs_main"),
            targets,
            compilation_options: Default::default(),
        }),
        primitive: wgpu::PrimitiveState {
            topology: wgpu::PrimitiveTopology::TriangleList,
            strip_index_format: None,
            front_face: wgpu::FrontFace::Ccw,
            cull_mode: None,
            polygon_mode: wgpu::PolygonMode::Fill,
            unclipped_depth: false,
            conservative: false,
        },
        depth_stencil: Some(DepthStencilState {
            format: DEPTH_FORMAT,
            depth_write_enabled: false,
            depth_compare: CompareFunction::Always,
            stencil: StencilState {
                front: stencil_state,
                back: stencil_state,
                read_mask: 0xff,
                write_mask: 0xff,
            },
            bias: Default::default(),
        }),
        multisample: wgpu::MultisampleState {
            count: if antialias { 4 } else { 1 },
            mask: !0,
            alpha_to_coverage_enabled: false,
        },
        multiview: None,
        cache: Default::default(),
    }
}