twgpu 0.4.1

Render Teeworlds and DDNet maps
Documentation
use std::collections::HashMap;

pub struct Blit {
    shader: wgpu::ShaderModule,
    sampler: wgpu::Sampler,
    bind_group_layout: wgpu::BindGroupLayout,
    pipeline_layout: wgpu::PipelineLayout,
    pipelines: HashMap<wgpu::TextureFormat, wgpu::RenderPipeline>,
}

const LABEL: Option<&str> = Some("Blit");

impl Blit {
    pub fn new(device: &wgpu::Device) -> Self {
        let shader = device.create_shader_module(wgpu::include_wgsl!("blit.wgsl"));
        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            min_filter: wgpu::FilterMode::Linear,
            mag_filter: wgpu::FilterMode::Linear,
            ..wgpu::SamplerDescriptor::default()
        });
        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
            label: LABEL,
            entries: &[
                wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Texture {
                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
                        view_dimension: wgpu::TextureViewDimension::D2,
                        multisampled: false,
                    },
                    count: None,
                },
                wgpu::BindGroupLayoutEntry {
                    binding: 1,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                    count: None,
                },
            ],
        });
        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: LABEL,
            bind_group_layouts: &[&bind_group_layout],
            push_constant_ranges: &[],
        });
        let mut blit = Self {
            shader,
            sampler,
            bind_group_layout,
            pipeline_layout,
            pipelines: HashMap::new(),
        };
        blit.add_target_format(wgpu::TextureFormat::Rgba8Unorm, device);
        blit
    }

    pub fn add_target_format(&mut self, target_format: wgpu::TextureFormat, device: &wgpu::Device) {
        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: LABEL,
            layout: Some(&self.pipeline_layout),
            vertex: wgpu::VertexState {
                module: &self.shader,
                entry_point: Some("vs_main"),
                compilation_options: wgpu::PipelineCompilationOptions::default(),
                buffers: &[],
            },
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleStrip,
                ..wgpu::PrimitiveState::default()
            },
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
            fragment: Some(wgpu::FragmentState {
                module: &self.shader,
                entry_point: Some("fs_main"),
                compilation_options: wgpu::PipelineCompilationOptions::default(),
                targets: &[Some(wgpu::ColorTargetState {
                    format: target_format,
                    blend: None,
                    write_mask: wgpu::ColorWrites::ALL,
                })],
            }),
            multiview: None,
            cache: None, // TODO: Use this cache
        });
        self.pipelines.insert(target_format, pipeline);
    }

    /// Disregards the x and y coordinates of the `ImageCopyTexture`s
    pub fn blit(
        &self,
        from: wgpu::TexelCopyTextureInfo,
        to: wgpu::TexelCopyTextureInfo,
        encoder: &mut wgpu::CommandEncoder,
        device: &wgpu::Device,
    ) {
        let target_format = to.texture.format();
        let pipeline = &self
            .pipelines
            .get(&target_format)
            .expect("For non-Rgba8Unorm color formats, first use `add_target_format`");

        let from_view = from.texture.create_view(&wgpu::TextureViewDescriptor {
            label: LABEL,
            format: None,
            dimension: Some(wgpu::TextureViewDimension::D2),
            usage: None,
            aspect: wgpu::TextureAspect::All,
            base_mip_level: from.mip_level,
            mip_level_count: Some(1),
            base_array_layer: from.origin.z,
            array_layer_count: Some(1),
        });
        let to_view = to.texture.create_view(&wgpu::TextureViewDescriptor {
            label: LABEL,
            format: None,
            dimension: Some(wgpu::TextureViewDimension::D2),
            usage: None,
            aspect: wgpu::TextureAspect::All,
            base_mip_level: to.mip_level,
            mip_level_count: Some(1),
            base_array_layer: to.origin.z,
            array_layer_count: Some(1),
        });
        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: LABEL,
            layout: &self.bind_group_layout,
            entries: &[
                wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&from_view),
                },
                wgpu::BindGroupEntry {
                    binding: 1,
                    resource: wgpu::BindingResource::Sampler(&self.sampler),
                },
            ],
        });
        let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
            label: LABEL,
            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                view: &to_view,
                resolve_target: None,
                ops: wgpu::Operations::default(),
                depth_slice: None,
            })],
            ..wgpu::RenderPassDescriptor::default()
        });
        render_pass.set_pipeline(pipeline);
        render_pass.set_bind_group(0, &bind_group, &[]);
        render_pass.draw(0..4, 0..1);
    }
}