summaryrefslogtreecommitdiff
path: root/render/src/texture.rs
blob: 90d23d1c387adf950af8d19ca677967c8c9bee7a (plain)
use std::error::Error;

use image::{EncodableLayout, RgbaImage};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum TextureError {
	#[error("Unexpected Error (this is a bug in alligator_render): {}", .0)]
	Unexpected(#[source] Box<dyn Error>),
}

/// Simpler constructor for a wgpu extent3d
const fn extent_3d(width: u32, height: u32) -> wgpu::Extent3d {
	wgpu::Extent3d {
		width,
		height,
		depth_or_array_layers: 1,
	}
}

/// A texture atlas, usable by the renderer
// TODO make these resizable
#[derive(Debug)]
pub struct TextureAtlas {
	diffuse_texture: wgpu::Texture,
	diffuse_bind_group: wgpu::BindGroup,
	image: RgbaImage,
}

impl TextureAtlas {
	/// Creates a new texture atlas, with the given size
	// TODO this is still too large
	pub fn new(device: &wgpu::Device, width: u32, height: u32) -> (Self, wgpu::BindGroupLayout) {
		let atlas_size = extent_3d(width, height);
		let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor {
			label: Some("Diffuse Texture"),
			size: atlas_size,
			mip_level_count: 1,
			sample_count: 1,
			dimension: wgpu::TextureDimension::D2,
			format: wgpu::TextureFormat::Rgba8Unorm,
			usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
			view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb],
		});

		// TODO I don't think this refreshes anything
		let diffuse_texture_view =
			diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default());

		let diffuse_sampler = device.create_sampler(&wgpu::SamplerDescriptor::default());

		let texture_bind_group_layout =
			device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
				label: Some("Texture Bind Group Layout"),
				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 diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
			label: Some("Diffuse Bind Group"),
			layout: &texture_bind_group_layout,
			entries: &[
				wgpu::BindGroupEntry {
					binding: 0,
					resource: wgpu::BindingResource::TextureView(&diffuse_texture_view),
				},
				wgpu::BindGroupEntry {
					binding: 1,
					resource: wgpu::BindingResource::Sampler(&diffuse_sampler),
				},
			],
		});

		(
			Self {
				diffuse_texture,
				diffuse_bind_group,
				image: RgbaImage::from_raw(
					width,
					height,
					vec![0; 4 * width as usize * height as usize],
				)
				.unwrap(),
			},
			texture_bind_group_layout,
		)
	}

	/// get the bind group for the texture
	pub(crate) const fn bind_group(&self) -> &wgpu::BindGroup {
		&self.diffuse_bind_group
	}

	pub(crate) fn fill_textures(&self, queue: &wgpu::Queue) {
		queue.write_texture(
			wgpu::ImageCopyTexture {
				texture: &self.diffuse_texture,
				mip_level: 0,
				origin: wgpu::Origin3d::ZERO,
				aspect: wgpu::TextureAspect::All,
			},
			self.image.as_bytes(),
			wgpu::ImageDataLayout {
				offset: 0,
				bytes_per_row: Some(self.image.width() * 4),
				rows_per_image: Some(self.image.height()),
			},
			extent_3d(self.image.width(), self.image.height()),
		);
	}
}