use fovea::pixel::{
Bgra8, Mono8, Mono16, MonoA8, MonoA16, MonoAF32, MonoF32, PlainPixel, Rgba8, Rgba16, RgbaF32,
SrgbMono8, Srgba8,
};
#[cfg(test)]
use fovea::pixel::PlainChannel;
mod sealed {
pub trait Sealed {}
}
pub trait DisplayPixel: PlainPixel + sealed::Sealed {
fn to_framebuffer_u32(&self) -> u32;
}
impl sealed::Sealed for Srgba8 {}
impl DisplayPixel for Srgba8 {
#[inline]
fn to_framebuffer_u32(&self) -> u32 {
let r = self.r.0 as u32;
let g = self.g.0 as u32;
let b = self.b.0 as u32;
(r << 16) | (g << 8) | b
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TextureFormat {
R8Unorm,
R8Srgb,
Rg8Unorm,
Rgba8Unorm,
Rgba8Srgb,
Bgra8Unorm,
Bgra8Srgb,
R16Unorm,
Rg16Unorm,
Rgba16Unorm,
R32Float,
Rg32Float,
Rgba32Float,
}
impl TextureFormat {
#[inline]
#[must_use]
pub const fn bytes_per_pixel(&self) -> usize {
match self {
TextureFormat::R8Unorm | TextureFormat::R8Srgb => 1,
TextureFormat::Rg8Unorm => 2,
TextureFormat::Rgba8Unorm
| TextureFormat::Rgba8Srgb
| TextureFormat::Bgra8Unorm
| TextureFormat::Bgra8Srgb => 4,
TextureFormat::R16Unorm => 2,
TextureFormat::Rg16Unorm => 4,
TextureFormat::Rgba16Unorm => 8,
TextureFormat::R32Float => 4,
TextureFormat::Rg32Float => 8,
TextureFormat::Rgba32Float => 16,
}
}
#[inline]
#[must_use]
pub const fn channel_count(&self) -> usize {
match self {
TextureFormat::R8Unorm
| TextureFormat::R8Srgb
| TextureFormat::R16Unorm
| TextureFormat::R32Float => 1,
TextureFormat::Rg8Unorm | TextureFormat::Rg16Unorm | TextureFormat::Rg32Float => 2,
TextureFormat::Rgba8Unorm
| TextureFormat::Rgba8Srgb
| TextureFormat::Bgra8Unorm
| TextureFormat::Bgra8Srgb
| TextureFormat::Rgba16Unorm
| TextureFormat::Rgba32Float => 4,
}
}
}
pub trait GpuPixel: PlainPixel {
const TEXTURE_FORMAT: TextureFormat;
}
impl GpuPixel for Mono8 {
const TEXTURE_FORMAT: TextureFormat = TextureFormat::R8Unorm;
}
impl GpuPixel for SrgbMono8 {
const TEXTURE_FORMAT: TextureFormat = TextureFormat::R8Srgb;
}
impl GpuPixel for MonoA8 {
const TEXTURE_FORMAT: TextureFormat = TextureFormat::Rg8Unorm;
}
impl GpuPixel for Rgba8 {
const TEXTURE_FORMAT: TextureFormat = TextureFormat::Rgba8Unorm;
}
impl GpuPixel for Srgba8 {
const TEXTURE_FORMAT: TextureFormat = TextureFormat::Rgba8Srgb;
}
impl GpuPixel for Bgra8 {
const TEXTURE_FORMAT: TextureFormat = TextureFormat::Bgra8Unorm;
}
impl GpuPixel for Mono16 {
const TEXTURE_FORMAT: TextureFormat = TextureFormat::R16Unorm;
}
impl GpuPixel for MonoA16 {
const TEXTURE_FORMAT: TextureFormat = TextureFormat::Rg16Unorm;
}
impl GpuPixel for Rgba16 {
const TEXTURE_FORMAT: TextureFormat = TextureFormat::Rgba16Unorm;
}
impl GpuPixel for MonoF32 {
const TEXTURE_FORMAT: TextureFormat = TextureFormat::R32Float;
}
impl GpuPixel for MonoAF32 {
const TEXTURE_FORMAT: TextureFormat = TextureFormat::Rg32Float;
}
impl GpuPixel for RgbaF32 {
const TEXTURE_FORMAT: TextureFormat = TextureFormat::Rgba32Float;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn srgba8_to_framebuffer_red() {
assert_eq!(Srgba8::new(255, 0, 0, 255).to_framebuffer_u32(), 0x00FF0000);
}
#[test]
fn srgba8_to_framebuffer_green() {
assert_eq!(Srgba8::new(0, 255, 0, 128).to_framebuffer_u32(), 0x0000FF00);
}
#[test]
fn srgba8_to_framebuffer_blue() {
assert_eq!(Srgba8::new(0, 0, 255, 0).to_framebuffer_u32(), 0x000000FF);
}
#[test]
fn srgba8_to_framebuffer_black() {
assert_eq!(Srgba8::new(0, 0, 0, 0).to_framebuffer_u32(), 0x00000000);
}
#[test]
fn srgba8_to_framebuffer_white() {
assert_eq!(
Srgba8::new(255, 255, 255, 255).to_framebuffer_u32(),
0x00FFFFFF
);
}
#[test]
fn srgba8_to_framebuffer_0x123456() {
assert_eq!(
Srgba8::new(18, 52, 86, 200).to_framebuffer_u32(),
0x00123456
);
}
#[test]
fn srgba8_alpha_is_discarded() {
let a = Srgba8::new(100, 150, 200, 0).to_framebuffer_u32();
let b = Srgba8::new(100, 150, 200, 255).to_framebuffer_u32();
assert_eq!(a, b);
}
#[test]
fn bytes_per_pixel_1byte_formats() {
assert_eq!(TextureFormat::R8Unorm.bytes_per_pixel(), 1);
assert_eq!(TextureFormat::R8Srgb.bytes_per_pixel(), 1);
}
#[test]
fn bytes_per_pixel_2byte_formats() {
assert_eq!(TextureFormat::Rg8Unorm.bytes_per_pixel(), 2);
assert_eq!(TextureFormat::R16Unorm.bytes_per_pixel(), 2);
}
#[test]
fn bytes_per_pixel_4byte_formats() {
assert_eq!(TextureFormat::Rgba8Unorm.bytes_per_pixel(), 4);
assert_eq!(TextureFormat::Rgba8Srgb.bytes_per_pixel(), 4);
assert_eq!(TextureFormat::Bgra8Unorm.bytes_per_pixel(), 4);
assert_eq!(TextureFormat::Bgra8Srgb.bytes_per_pixel(), 4);
assert_eq!(TextureFormat::Rg16Unorm.bytes_per_pixel(), 4);
assert_eq!(TextureFormat::R32Float.bytes_per_pixel(), 4);
}
#[test]
fn bytes_per_pixel_8byte_formats() {
assert_eq!(TextureFormat::Rgba16Unorm.bytes_per_pixel(), 8);
assert_eq!(TextureFormat::Rg32Float.bytes_per_pixel(), 8);
}
#[test]
fn bytes_per_pixel_16byte_formats() {
assert_eq!(TextureFormat::Rgba32Float.bytes_per_pixel(), 16);
}
#[test]
fn channel_count_1ch() {
assert_eq!(TextureFormat::R8Unorm.channel_count(), 1);
assert_eq!(TextureFormat::R8Srgb.channel_count(), 1);
assert_eq!(TextureFormat::R16Unorm.channel_count(), 1);
assert_eq!(TextureFormat::R32Float.channel_count(), 1);
}
#[test]
fn channel_count_2ch() {
assert_eq!(TextureFormat::Rg8Unorm.channel_count(), 2);
assert_eq!(TextureFormat::Rg16Unorm.channel_count(), 2);
assert_eq!(TextureFormat::Rg32Float.channel_count(), 2);
}
#[test]
fn channel_count_4ch() {
assert_eq!(TextureFormat::Rgba8Unorm.channel_count(), 4);
assert_eq!(TextureFormat::Rgba8Srgb.channel_count(), 4);
assert_eq!(TextureFormat::Bgra8Unorm.channel_count(), 4);
assert_eq!(TextureFormat::Bgra8Srgb.channel_count(), 4);
assert_eq!(TextureFormat::Rgba16Unorm.channel_count(), 4);
assert_eq!(TextureFormat::Rgba32Float.channel_count(), 4);
}
#[test]
fn bytes_per_pixel_equals_channels_times_component_size() {
let all = [
TextureFormat::R8Unorm,
TextureFormat::R8Srgb,
TextureFormat::Rg8Unorm,
TextureFormat::Rgba8Unorm,
TextureFormat::Rgba8Srgb,
TextureFormat::Bgra8Unorm,
TextureFormat::Bgra8Srgb,
TextureFormat::R16Unorm,
TextureFormat::Rg16Unorm,
TextureFormat::Rgba16Unorm,
TextureFormat::R32Float,
TextureFormat::Rg32Float,
TextureFormat::Rgba32Float,
];
for fmt in &all {
let bpp = fmt.bytes_per_pixel();
let ch = fmt.channel_count();
assert!(
bpp % ch == 0,
"{fmt:?}: bytes_per_pixel ({bpp}) not divisible by channel_count ({ch})"
);
}
}
#[test]
fn gpu_pixel_mono8() {
assert_eq!(Mono8::TEXTURE_FORMAT, TextureFormat::R8Unorm);
}
#[test]
fn gpu_pixel_srgb_mono8() {
assert_eq!(SrgbMono8::TEXTURE_FORMAT, TextureFormat::R8Srgb);
}
#[test]
fn gpu_pixel_mono_a8() {
assert_eq!(MonoA8::TEXTURE_FORMAT, TextureFormat::Rg8Unorm);
}
#[test]
fn gpu_pixel_rgba8() {
assert_eq!(Rgba8::TEXTURE_FORMAT, TextureFormat::Rgba8Unorm);
}
#[test]
fn gpu_pixel_srgba8() {
assert_eq!(Srgba8::TEXTURE_FORMAT, TextureFormat::Rgba8Srgb);
}
#[test]
fn gpu_pixel_bgra8() {
assert_eq!(Bgra8::TEXTURE_FORMAT, TextureFormat::Bgra8Unorm);
}
#[test]
fn gpu_pixel_mono16() {
assert_eq!(Mono16::TEXTURE_FORMAT, TextureFormat::R16Unorm);
}
#[test]
fn gpu_pixel_mono_a16() {
assert_eq!(MonoA16::TEXTURE_FORMAT, TextureFormat::Rg16Unorm);
}
#[test]
fn gpu_pixel_rgba16() {
assert_eq!(Rgba16::TEXTURE_FORMAT, TextureFormat::Rgba16Unorm);
}
#[test]
fn gpu_pixel_mono_f32() {
assert_eq!(MonoF32::TEXTURE_FORMAT, TextureFormat::R32Float);
}
#[test]
fn gpu_pixel_mono_af32() {
assert_eq!(MonoAF32::TEXTURE_FORMAT, TextureFormat::Rg32Float);
}
#[test]
fn gpu_pixel_rgba_f32() {
assert_eq!(RgbaF32::TEXTURE_FORMAT, TextureFormat::Rgba32Float);
}
#[test]
fn gpu_pixel_format_bytes_matches_pixel_size() {
assert_eq!(Mono8::TEXTURE_FORMAT.bytes_per_pixel(), Mono8::SIZE);
assert_eq!(SrgbMono8::TEXTURE_FORMAT.bytes_per_pixel(), SrgbMono8::SIZE);
assert_eq!(MonoA8::TEXTURE_FORMAT.bytes_per_pixel(), MonoA8::SIZE);
assert_eq!(Rgba8::TEXTURE_FORMAT.bytes_per_pixel(), Rgba8::SIZE);
assert_eq!(Srgba8::TEXTURE_FORMAT.bytes_per_pixel(), Srgba8::SIZE);
assert_eq!(Bgra8::TEXTURE_FORMAT.bytes_per_pixel(), Bgra8::SIZE);
assert_eq!(Mono16::TEXTURE_FORMAT.bytes_per_pixel(), Mono16::SIZE);
assert_eq!(MonoA16::TEXTURE_FORMAT.bytes_per_pixel(), MonoA16::SIZE);
assert_eq!(Rgba16::TEXTURE_FORMAT.bytes_per_pixel(), Rgba16::SIZE);
assert_eq!(MonoF32::TEXTURE_FORMAT.bytes_per_pixel(), MonoF32::SIZE);
assert_eq!(MonoAF32::TEXTURE_FORMAT.bytes_per_pixel(), MonoAF32::SIZE);
assert_eq!(RgbaF32::TEXTURE_FORMAT.bytes_per_pixel(), RgbaF32::SIZE);
}
}