use crabslab::{Slab, SlabItem};
use glam::{mat3, Mat3, Vec2, Vec3, Vec4, Vec4Swizzles};
use spirv_std::{image::Image2d, spirv, Sampler};
#[cfg(not(target_arch = "spirv"))]
mod cpu;
#[cfg(not(target_arch = "spirv"))]
pub use cpu::*;
const GAMMA: f32 = 2.2;
const INV_GAMMA: f32 = 1.0 / GAMMA;
const ACESINPUT_MAT: Mat3 = mat3(
Vec3::new(0.59719, 0.07600, 0.02840),
Vec3::new(0.35458, 0.90834, 0.13383),
Vec3::new(0.04823, 0.01566, 0.83777),
);
const ACESOUTPUT_MAT: Mat3 = mat3(
Vec3::new(1.60475, -0.10208, -0.00327),
Vec3::new(-0.53108, 1.10813, -0.07276),
Vec3::new(-0.07367, -0.00605, 1.07602),
);
pub fn linear_to_srgb(color: Vec3) -> Vec3 {
color.powf(INV_GAMMA)
}
pub fn srgb_to_linear(srgb_in: Vec3) -> Vec3 {
srgb_in.powf(GAMMA)
}
pub fn srgba_to_linear(srgb_in: Vec4) -> Vec4 {
srgb_to_linear(srgb_in.xyz()).extend(srgb_in.w)
}
pub fn tone_map_aces_narkowicz(color: Vec3) -> Vec3 {
const A: f32 = 2.51;
const B: f32 = 0.03;
const C: f32 = 2.43;
const D: f32 = 0.59;
const E: f32 = 0.14;
let c = (color * (A * color + B)) / (color * (C * color + D) + E);
c.clamp(Vec3::ZERO, Vec3::ONE)
}
fn rrt_and_odtfit(color: Vec3) -> Vec3 {
let a: Vec3 = color * (color + 0.0245786) - 0.000090537;
let b: Vec3 = color * (0.983729 * color + 0.432951) + 0.238081;
a / b
}
pub fn tone_map_aces_hill(mut color: Vec3) -> Vec3 {
color = ACESINPUT_MAT * color;
color = rrt_and_odtfit(color);
color = ACESOUTPUT_MAT * color;
color = color.clamp(Vec3::ZERO, Vec3::ONE);
color
}
pub fn tone_map_reinhard(color: Vec3) -> Vec3 {
color / (color + Vec3::ONE)
}
#[repr(transparent)]
#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
#[derive(Clone, Copy, Default, PartialEq, Eq, SlabItem)]
pub struct Tonemap(u32);
impl Tonemap {
pub const NONE: Self = Tonemap(0);
pub const ACES_NARKOWICZ: Self = Tonemap(1);
pub const ACES_HILL: Self = Tonemap(2);
pub const ACES_HILL_EXPOSURE_BOOST: Self = Tonemap(3);
pub const REINHARD: Self = Tonemap(4);
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, SlabItem)]
pub struct TonemapConstants {
pub tonemap: Tonemap,
pub exposure: f32,
}
impl Default for TonemapConstants {
fn default() -> Self {
Self {
tonemap: Tonemap::NONE,
exposure: 1.0,
}
}
}
pub fn tonemap(mut color: Vec4, slab: &[u32]) -> Vec4 {
let constants = slab.read::<TonemapConstants>(0u32.into());
color *= constants.exposure;
match constants.tonemap {
Tonemap::ACES_NARKOWICZ => tone_map_aces_narkowicz(color.xyz()).extend(color.w),
Tonemap::ACES_HILL => tone_map_aces_hill(color.xyz()).extend(color.w),
Tonemap::ACES_HILL_EXPOSURE_BOOST => {
tone_map_aces_hill(color.xyz() / 0.6).extend(color.w)
}
Tonemap::REINHARD => {
tone_map_reinhard(color.xyz()).extend(color.w)
}
_ => color,
}
}
const QUAD_2D_POINTS: [(Vec2, Vec2); 6] = {
let tl = (Vec2::new(-1.0, 1.0), Vec2::new(0.0, 0.0));
let tr = (Vec2::new(1.0, 1.0), Vec2::new(1.0, 0.0));
let bl = (Vec2::new(-1.0, -1.0), Vec2::new(0.0, 1.0));
let br = (Vec2::new(1.0, -1.0), Vec2::new(1.0, 1.0));
[tl, bl, br, tl, br, tr]
};
#[cfg(feature = "tonemapping_vertex")]
#[spirv(vertex)]
pub fn tonemapping_vertex(
#[spirv(vertex_index)] vertex_id: u32,
out_uv: &mut glam::Vec2,
#[spirv(position)] gl_pos: &mut glam::Vec4,
) {
let (pos, uv) = QUAD_2D_POINTS[vertex_id as usize];
*out_uv = uv;
*gl_pos = pos.extend(0.0).extend(1.0);
}
#[cfg(feature = "tonemapping_fragment")]
#[spirv(fragment)]
pub fn tonemapping_fragment(
#[spirv(storage_buffer, descriptor_set = 0, binding = 0)] slab: &[u32],
#[spirv(descriptor_set = 0, binding = 1)] texture: &Image2d,
#[spirv(descriptor_set = 0, binding = 2)] sampler: &Sampler,
in_uv: glam::Vec2,
output: &mut glam::Vec4,
) {
let color: Vec4 = texture.sample(*sampler, in_uv);
let color = tonemap(color, slab);
*output = color;
}