byte-engine-ghi 0.1.0

Graphics hardware interface layer used by Byte-Engine.
use std::num::NonZeroU32;

use utils::Extent;

use crate::{DeviceAccesses, Formats, PrivateHandle, PrivateHandles, UseCases, Uses};

pub struct Builder<'a> {
	pub(crate) name: Option<&'a str>,
	pub(crate) extent: Extent,
	pub(crate) format: Formats,
	pub(crate) resource_uses: Uses,
	pub(crate) device_accesses: DeviceAccesses,
	pub(crate) use_case: UseCases,
	pub(crate) mip_levels: u32,
	pub(crate) array_layers: Option<NonZeroU32>,
}

impl<'a> Builder<'a> {
	/// Creates an image builder for the given format and resource uses.
	///
	/// The default image is static, has one mip level, and uses GPU-only memory.
	/// Its name and array-layer count are `None`, and its extent is zero.
	pub fn new(format: Formats, resource_uses: Uses) -> Self {
		Self {
			name: None,
			extent: Extent::cube(0, 0, 0),
			format,
			resource_uses,
			device_accesses: DeviceAccesses::DeviceOnly,
			use_case: UseCases::STATIC,
			mip_levels: 1,
			array_layers: None,
		}
	}

	pub fn name(mut self, name: &'a str) -> Self {
		self.name = Some(name);
		self
	}

	pub fn extent(mut self, extent: Extent) -> Self {
		self.extent = extent;
		self
	}

	pub fn device_accesses(mut self, device_accesses: DeviceAccesses) -> Self {
		self.device_accesses = device_accesses;
		self
	}

	pub fn use_case(mut self, use_case: UseCases) -> Self {
		self.use_case = use_case;
		self
	}

	pub fn mip_levels(mut self, mip_levels: u32) -> Self {
		self.mip_levels = mip_levels;
		self
	}

	pub fn array_layers(mut self, array_layers: Option<NonZeroU32>) -> Self {
		self.array_layers = array_layers;
		self
	}

	pub fn get_name(&self) -> Option<&'a str> {
		self.name
	}

	pub fn get_format(&self) -> Formats {
		self.format
	}
}

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub(crate) struct ImageHandle(pub(crate) u64);

impl From<ImageHandle> for PrivateHandles {
	fn from(val: ImageHandle) -> Self {
		PrivateHandles::Image(val)
	}
}

impl PrivateHandle for ImageHandle {
	fn new(i: u64) -> Self {
		Self(i)
	}

	fn index(&self) -> u64 {
		self.0
	}
}

#[cfg(test)]
mod tests {
	use std::num::NonZeroU32;

	use utils::Extent;

	use super::{Builder, ImageHandle};
	use crate::{DeviceAccesses, Formats, PrivateHandle, PrivateHandles, UseCases, Uses};

	#[test]
	fn builder_defaults_are_valid_for_a_single_static_image() {
		let builder = Builder::new(Formats::RGBA8UNORM, Uses::Image);
		assert_eq!(builder.get_name(), None);
		assert_eq!(builder.get_format(), Formats::RGBA8UNORM);
		assert_eq!(builder.extent, Extent::cube(0, 0, 0));
		assert_eq!(builder.resource_uses, Uses::Image);
		assert_eq!(builder.device_accesses, DeviceAccesses::DeviceOnly);
		assert_eq!(builder.use_case, UseCases::STATIC);
		assert_eq!(builder.mip_levels, 1);
		assert_eq!(builder.array_layers, None);
	}

	#[test]
	fn builder_preserves_all_explicit_image_constraints() {
		let builder = Builder::new(Formats::BC7, Uses::Image | Uses::TransferDestination)
			.name("albedo")
			.extent(Extent::rectangle(64, 32))
			.device_accesses(DeviceAccesses::HostToDevice)
			.use_case(UseCases::DYNAMIC)
			.mip_levels(7)
			.array_layers(NonZeroU32::new(6));

		assert_eq!(builder.get_name(), Some("albedo"));
		assert_eq!(builder.extent, Extent::rectangle(64, 32));
		assert_eq!(builder.resource_uses, Uses::Image | Uses::TransferDestination);
		assert_eq!(builder.device_accesses, DeviceAccesses::HostToDevice);
		assert_eq!(builder.use_case, UseCases::DYNAMIC);
		assert_eq!(builder.mip_levels, 7);
		assert_eq!(builder.array_layers, NonZeroU32::new(6));
	}

	#[test]
	fn private_image_handle_round_trips_index_and_variant() {
		let handle = ImageHandle::new(9);
		assert_eq!(handle.index(), 9);
		assert!(matches!(PrivateHandles::from(handle), PrivateHandles::Image(value) if value == handle));
	}
}