Skip to content
Snippets Groups Projects
sprite.rs 5.87 KiB
Newer Older
Noxim's avatar
Noxim committed
use crate::prelude::*;
use wgpu::*;

pub struct Sprite {
    layout: BindGroupLayout,
    uniforms: Buffer,
    bindgroup: BindGroup,
    pub transforms: Mat4,
}

impl Sprite {
    pub async fn new(name: &str, queue: &Queue, device: &Device, smooth: bool) -> Result<Self> {
        let img = load_png(name).await?;

        let label = format!("Sprite {name}");
        let label = Some(label.as_str());

        let texture = device.create_texture_with_data(
            queue,
            &TextureDescriptor {
                label,
                size: Extent3d {
                    width: img.width,
                    height: img.height,
                    depth_or_array_layers: 1,
                },
                mip_level_count: 1,
                sample_count: 1,
                dimension: TextureDimension::D2,
                format: TextureFormat::Rgba8Unorm,
                usage: TextureUsages::TEXTURE_BINDING,
                view_formats: &[],
            },
            &img.rgba,
        );

        let uniforms = device.create_buffer(&BufferDescriptor {
            label,
            size: 16 * 4,
            usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        let layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
            label,
            entries: &[
                BindGroupLayoutEntry {
                    binding: 0,
                    visibility: ShaderStages::VERTEX,
                    ty: BindingType::Buffer {
                        ty: BufferBindingType::Uniform,
                        has_dynamic_offset: false,
                        min_binding_size: None,
                    },
                    count: None,
                },
                BindGroupLayoutEntry {
                    binding: 1,
                    visibility: ShaderStages::FRAGMENT,
                    ty: BindingType::Texture {
                        sample_type: TextureSampleType::Float { filterable: true },
                        view_dimension: TextureViewDimension::D2,
                        multisampled: false,
                    },
                    count: None,
                },
                BindGroupLayoutEntry {
                    binding: 2,
                    visibility: ShaderStages::FRAGMENT,
                    ty: BindingType::Sampler(SamplerBindingType::Filtering),
                    count: None,
                },
            ],
        });

        let filter = smooth
            .then_some(FilterMode::Linear)
            .unwrap_or(FilterMode::Nearest);
        let sampler = device.create_sampler(&SamplerDescriptor {
            label,
            mag_filter: filter,
            min_filter: filter,
            ..Default::default()
        });

        let bindgroup = device.create_bind_group(&BindGroupDescriptor {
            label,
            layout: &layout,
            entries: &[
                BindGroupEntry {
                    binding: 0,
                    resource: uniforms.as_entire_binding(),
                },
                BindGroupEntry {
                    binding: 1,
                    resource: BindingResource::TextureView(
                        &texture.create_view(&Default::default()),
                    ),
                },
                BindGroupEntry {
                    binding: 2,
                    resource: BindingResource::Sampler(&sampler),
                },
            ],
        });

        let transforms = Mat4::IDENTITY;

        Ok(Self {
            layout,
            uniforms,
            bindgroup,
            transforms,
        })
    }

    pub fn record<'a, 'b: 'a>(
        &'b self,
        queue: &Queue,
        device: &Device,
        pass: &mut RenderPass<'a>,
        format: TextureFormat,
        aspect: f32,
    ) {
        let mat = Mat4::from_scale(Vec3::new(1.0 / aspect, 1.0, 1.0)) * self.transforms;

        queue.write_buffer(
            &self.uniforms,
            0,
            bytemuck::bytes_of(&mat),
        );

        static PIPELINE: std::sync::OnceLock<RenderPipeline> = std::sync::OnceLock::new();
        let pipeline = PIPELINE.get_or_init(|| {
            let module = device.create_shader_module(include_wgsl!("../assets/sprite.wgsl"));

            let layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
                label: Some("Sprite"),
                bind_group_layouts: &[&self.layout],
                push_constant_ranges: &[],
            });

            device.create_render_pipeline(&RenderPipelineDescriptor {
                label: Some("Sprite"),
                layout: Some(&layout),
                vertex: VertexState {
                    module: &module,
                    entry_point: "vs_main",
                    buffers: &[],
                },
                primitive: Default::default(),
                depth_stencil: Some(DepthStencilState {
                    format: TextureFormat::Depth24Plus,
                    depth_write_enabled: false,
                    depth_compare: CompareFunction::Always,
                    stencil: Default::default(),
                    bias: Default::default(),
                }),
                multisample: MultisampleState {
                    count: 4,
                    mask: 0xFF,
                    alpha_to_coverage_enabled: false,
                },
                fragment: Some(FragmentState {
                    module: &module,
                    entry_point: "fs_main",
                    targets: &[Some(ColorTargetState {
                        format,
                        blend: Some(BlendState::ALPHA_BLENDING),
                        write_mask: ColorWrites::all(),
                    })],
                }),
                multiview: None,
            })
        });

        pass.set_pipeline(pipeline);
        pass.set_bind_group(0, &self.bindgroup, &[]);
        pass.draw(0..6, 0..1);
    }
}