use super::{ProgramSettings, GLSL_VERSION, default_settings, fbo};
use rin_gl::{self as gl, uniforms, Renderer3d};
use rin_util::{Result, Error, LogErr};
use rin_math::{clamp, vec3};
use std::rc::Rc;
use std::borrow::Borrow;
use std::cell::{Cell, UnsafeCell};
use rin_graphics::Vertex2DTex;
use rin_events::{RangedPropertyMut, RangedProperty, Property, ParamsDefault};
#[cfg(gui)]
use rin_gui::{ParameterGroup, EnumParam};
use color::consts::BLACK;
use crate::bloom::BloomBlend;
#[cfg(glsl_debug)]
use rin_util::AutoLoader;
pub struct Tonemap {
fbo_tonemap: Rc<gl::Fbo>,
tonemap: UnsafeCell<gl::Program>,
tonemap_type: Cell<TonemapTy>,
#[cfg(glsl_debug)]
tonemap_loader: UnsafeCell<AutoLoader<gl::Program>>,
bloom_passes: Cell<Option<u8>>,
bloom_blend_type: Cell<BloomBlend>,
vignette: Cell<bool>,
has_bloom: Cell<bool>,
fullscreen_quad: Rc<gl::SimpleVao<Vertex2DTex>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[cfg_attr(gui, derive(EnumParam))]
#[allow(non_camel_case_types)]
pub enum TonemapTy{
Linear,
Gamma,
Rec709,
Reinhard1,
Reinhard2,
Filmic,
Uncharted2,
#[cfg_attr(gui, gui(name = "ACES"))]
ACES,
#[cfg_attr(gui, gui(name = "ACES_REC2020_1K"))]
ACES_REC2020_1K,
Uchimura,
Unreal,
}
impl TonemapTy{
fn to_str(self) -> &'static str{
match self{
TonemapTy::Linear => "LINEAR",
TonemapTy::Gamma => "GAMMA",
TonemapTy::Rec709 => "REC709",
TonemapTy::Reinhard1 => "REINHARD",
TonemapTy::Reinhard2 => "REINHARD2",
TonemapTy::Filmic => "FILMIC",
TonemapTy::Uncharted2 => "UNCHARTED2",
TonemapTy::ACES => "ACES",
TonemapTy::ACES_REC2020_1K => "ACES_REC2020_1K",
TonemapTy::Uchimura => "UCHIMURA",
TonemapTy::Unreal => "UNREAL",
}
}
}
impl Default for TonemapTy{
fn default() -> TonemapTy{
TonemapTy::Uncharted2
}
}
#[derive(Clone, Debug, ParamsDefault)]
#[cfg_attr(gui, derive(ParameterGroup))]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct TonemapParameters{
#[param(default = 1.0, range = 0. .. 3.)]
pub contrast: RangedPropertyMut<'static,f32>,
#[param(default = 0.0, range = -3. .. 3.)]
pub brightness: RangedPropertyMut<'static,f32>,
#[param(default = 1.0, range = 0. .. 3.)]
pub saturation: RangedPropertyMut<'static,f32>,
#[cfg_attr(gui, gui(not_parameter))]
#[param(default = 0.2)]
pub bloom_strength: Property<'static,f32>,
#[cfg_attr(gui, gui(not_parameter))]
pub bloom_blend: Property<'static, BloomBlend>,
#[cfg(feature="gaussian_bloom")]
#[param(default = 6.5)]
#[cfg_attr(gui, gui(not_parameter))]
pub bloom_radius: Property<'static, f32>,
pub vignette: Property<'static, bool>,
#[param(default = 0.15, range = 0. .. 1.)]
pub vignette_size: RangedPropertyMut<'static, f32>,
#[param(default = 0.5, range = 0. .. 1.)]
pub vignette_top_strength: RangedPropertyMut<'static, f32>,
#[param(default = 0.5, range = 0. .. 1.)]
pub vignette_bottom_strength: RangedPropertyMut<'static, f32>,
pub ty: Property<'static, TonemapTy>,
#[param(default = 1.0, range = 0. .. 5.)]
pub exposure_bias: RangedPropertyMut<'static,f32>,
#[param(default = 0.22, range = 0.01 .. 1.)]
pub a: RangedPropertyMut<'static,f32>,
#[param(default = 0.3, range = 0. .. 1.)]
pub b: RangedPropertyMut<'static,f32>,
#[param(default = 0.1, range = 0. .. 1.)]
pub c: RangedPropertyMut<'static,f32>,
#[param(default = 0.2, range = 0. .. 1.)]
pub d: RangedPropertyMut<'static,f32>,
#[param(default = 0.01, range_log = 0. .. 0.1)]
pub e: RangedPropertyMut<'static,f32>,
#[param(default = 0.3, range = 0.05 .. 1.)]
pub f: RangedPropertyMut<'static,f32>,
#[param(default = 11.2, range = 1. .. 20.)]
pub w: RangedPropertyMut<'static,f32>,
#[cfg_attr(feature = "serialize", serde(skip_serializing))]
#[param(with = tonemap_response_function(&a, &b, &c, &d, &e, &f, &w, &ty))]
pub response_function: RangedProperty<'static, Vec<f32>, f32>,
}
fn tonemap_settings(tonemap_type: TonemapTy, bloom_blend_type: BloomBlend, has_bloom: bool, vignette: bool, bloom_passes: Option<u8>) -> ProgramSettings{
let mut defines = vec![
("HAS_BLOOM".to_string(), (has_bloom as u8).to_string()),
("TONEMAP_TYPE".to_string(), tonemap_type.to_str().to_owned()),
("BLOOM_BLEND_TYPE".to_string(), bloom_blend_type.to_str().to_owned()),
("VIGNETTE".to_string(), (vignette as u8).to_string()),
];
if let Some(bloom_passes) = bloom_passes {
defines.push(("HAS_BLOOM_LEVELS".to_string(), 1.to_string()));
for i in 0 ..= 7 {
if bloom_passes > i {
defines.push((format!("USE_BLOOM_LEVEL{}", i), 1.to_string()));
}else{
defines.push((format!("USE_BLOOM_LEVEL{}", i), 0.to_string()));
}
}
}else{
defines.push(("HAS_BLOOM_LEVELS".to_string(), 0.to_string()));
}
ProgramSettings{
version: GLSL_VERSION,
precision: gl::program::ShaderPrecision::High,
extensions: vec![],
defines,
shaders: vec![
(gl::VERTEX_SHADER, shader!("shaders/full_quad.vs.glsl")),
(gl::FRAGMENT_SHADER, shader!("shaders/tonemap.fs.glsl")),
],
.. default_settings()
}
}
impl Tonemap {
pub fn new(gl: &gl::Renderer, w: u32, h: u32, format: gl::fbo::ColorFormat, fullscreen_quad: Rc<gl::SimpleVao<Vertex2DTex>>) -> Result<Tonemap> {
let fbo_tonemap = fbo(gl, w, h, format)
.map_err(|err| Error::with_cause("Couldn't create tonemap fbo", err))?;
#[cfg(feature="gaussian_bloom")]
let bloom_passes = Some(1);
#[cfg(not(feature="gaussian_bloom"))]
let bloom_passes = None;
let tonemap_settings = tonemap_settings(
TonemapTy::Uncharted2,
BloomBlend::SoftLight,
true,
false,
bloom_passes,
);
#[cfg(glsl_debug)]
let mut tonemap_loader = gl.new_auto_program(tonemap_settings);
#[cfg(glsl_debug)]
let tonemap = tonemap_loader.load().log_err("")?;
#[cfg(not(glsl_debug))]
let tonemap = gl.new_program().from_settings(tonemap_settings)
.log_err("")
.map_err(|err| Error::with_cause("Error loading tonemap shader", err))?;
Ok(Tonemap {
fbo_tonemap: Rc::new(fbo_tonemap),
tonemap: UnsafeCell::new(tonemap),
#[cfg(glsl_debug)]
tonemap_loader: UnsafeCell::new(tonemap_loader),
tonemap_type: Cell::new(TonemapTy::Uncharted2),
bloom_passes: Cell::new(bloom_passes),
bloom_blend_type: Cell::new(BloomBlend::Screen),
has_bloom: Cell::new(false),
vignette: Cell::new(false),
fullscreen_quad,
})
}
#[cfg(glsl_debug)]
pub fn update(&mut self){
match unsafe{ (*self.tonemap_loader.get()).update() }{
Ok(Some(tonemap)) => {
self.tonemap = UnsafeCell::new(tonemap);
println!("tonemap shader reloaded correctly");
}
Err(err) => {
println!("{}", err);
}
_ => {}
}
}
pub fn fbo(&self) -> &Rc<gl::Fbo> {
&self.fbo_tonemap
}
pub fn process(&self, gl: &gl::Renderer,
tex: &gl::Texture,
bloomed_tex: Option<&gl::Texture>,
parameters: &TonemapParameters) -> Result<&gl::Texture>
{
#[cfg(gl_debug_groups)]
let _debug_group = gl.new_debug_group(0, "Tonemap");
let tonemap_type = *parameters.ty.borrow();
let bloom_blend_type = *parameters.bloom_blend.borrow();
let vignette = *parameters.vignette.borrow();
#[cfg(not(feature="gaussian_bloom"))]
let bloom_passes = None;
#[cfg(feature="gaussian_bloom")]
let bloom_passes = {
let bloom_radius: f32 = *parameters.bloom_radius.borrow();
let min_dim = tex.width().min(tex.height()) as f32;
let log_min_dim = min_dim.log(2.) + bloom_radius - 8.;
Some(clamp(log_min_dim as usize, 1, 8) as u8)
};
let has_bloom = bloomed_tex.is_some();
if tonemap_type != self.tonemap_type.get()
|| bloom_blend_type != self.bloom_blend_type.get()
|| has_bloom != self.has_bloom.get()
|| vignette != self.vignette.get()
|| bloom_passes != self.bloom_passes.get()
{
#[cfg(glsl_debug)]
{
let mut tonemap_loader = gl.new_auto_program(tonemap_settings(
tonemap_type,
bloom_blend_type,
has_bloom,
vignette,
bloom_passes
));
unsafe{
*self.tonemap.get() = tonemap_loader.load()?;
*self.tonemap_loader.get() = tonemap_loader;
}
}
#[cfg(not(glsl_debug))]
{
let tonemap = gl.new_program()
.from_settings(tonemap_settings(
tonemap_type,
bloom_blend_type,
has_bloom,
vignette,
bloom_passes
))
.map_err(|err| Error::with_cause("Error re-loading tonemap shader", err))?;
unsafe{
*self.tonemap.get() = tonemap;
}
}
self.tonemap_type.set(tonemap_type);
self.bloom_blend_type.set(bloom_blend_type);
self.vignette.set(vignette);
self.has_bloom.set(has_bloom);
self.bloom_passes.set(bloom_passes);
}
let gl = gl.with_fbo(&*self.fbo_tonemap);
gl.clear_color(&BLACK);
let tonemap_uniforms = if vignette {
if let Some(bloomed_tex) = bloomed_tex {
uniforms!{
tex0: (tex, 1),
blurred1: (bloomed_tex, 2),
contrast: parameters.contrast,
brightness: parameters.brightness,
saturation: parameters.saturation,
bloom_strength: parameters.bloom_strength,
vignette_size: parameters.vignette_size,
vignette_top_strength: parameters.vignette_top_strength,
vignette_bottom_strength: parameters.vignette_bottom_strength,
a: parameters.a,
b: parameters.b,
c: parameters.c,
d: parameters.d,
e: parameters.e,
f: parameters.f,
w: parameters.w,
exposureBias: parameters.exposure_bias,
}
}else{
uniforms!{
tex0: (tex, 1),
contrast: parameters.contrast,
brightness: parameters.brightness,
saturation: parameters.saturation,
vignette_size: parameters.vignette_size,
vignette_top_strength: parameters.vignette_top_strength,
vignette_bottom_strength: parameters.vignette_bottom_strength,
a: parameters.a,
b: parameters.b,
c: parameters.c,
d: parameters.d,
e: parameters.e,
f: parameters.f,
w: parameters.w,
exposureBias: parameters.exposure_bias,
}
}
}else{
if let Some(bloomed_tex) = bloomed_tex {
uniforms!{
tex0: (tex, 1),
blurred1: (bloomed_tex, 2),
contrast: parameters.contrast,
brightness: parameters.brightness,
saturation: parameters.saturation,
bloom_strength: parameters.bloom_strength,
a: parameters.a,
b: parameters.b,
c: parameters.c,
d: parameters.d,
e: parameters.e,
f: parameters.f,
w: parameters.w,
exposureBias: parameters.exposure_bias,
}
}else{
uniforms!{
tex0: (tex, 1),
contrast: parameters.contrast,
brightness: parameters.brightness,
saturation: parameters.saturation,
a: parameters.a,
b: parameters.b,
c: parameters.c,
d: parameters.d,
e: parameters.e,
f: parameters.f,
w: parameters.w,
exposureBias: parameters.exposure_bias,
}
}
};
gl.draw_vao(&*self.fullscreen_quad, unsafe{&*self.tonemap.get()}, &tonemap_uniforms);
Ok(self.fbo_tonemap.color_tex(0).unwrap())
}
}
fn tonemap_response_function(
a: &RangedPropertyMut<'static,f32>,
b: &RangedPropertyMut<'static,f32>,
c: &RangedPropertyMut<'static,f32>,
d: &RangedPropertyMut<'static,f32>,
e: &RangedPropertyMut<'static,f32>,
f: &RangedPropertyMut<'static,f32>,
w: &RangedPropertyMut<'static,f32>,
ty: &Property<'static, TonemapTy>
) -> RangedProperty<'static, Vec<f32>, f32>{
fn uncharted2(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32, x: f32) -> f32{
(x*(a*x+c*b)+d*e)/(x*(a*x+b)+d*f) - e/f
}
fn aces(x: f32) -> f32 {
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;
return (x*(A*x + B) )/(x*(C*x + D) + E);
}
fn aces_rec2020_1k(x: f32) -> f32 {
const A: f32 = 15.8;
const B: f32 = 2.12;
const C: f32 = 1.2;
const D: f32 = 5.92;
const E: f32 = 1.9;
(x * (A * x + B)) / (x * (C * x + D) + E)
}
fn reinhard1(x: f32, w: f32) -> f32 {
clamp((w * x + x) / (x * w + w), 0., 1.)
}
fn reinhard2(x: f32, w: f32) -> f32 {
let lum = vec3(0.2126729, 0.7151522, 0.0721750).dot(&vec3!(x));
let lum_scale = (lum * (1.0 + lum / w))/(1.0 + lum);
x*lum_scale/lum
}
fn filmic(x: f32) -> f32 {
let x = 0f32.max(x - 0.004);
(x*(6.2*x + 0.5) ) / (x*(6.2*x + 1.7) + 0.06)
}
fn smoothstep(edge0: f32, edge1: f32, x: f32) -> f32{
let x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
x * x * (3. - 2. * x)
}
fn step(edge: f32, x: f32) -> f32 {
if x < edge {
0.
}else{
1.
}
}
fn uchimura_params(x: f32, p: f32, a: f32, m: f32, l: f32, c: f32, b: f32) -> f32{
let l0 = ((p - m) * l) / a;
let s0 = m + l0;
let s1 = m + a * l0;
let c2 = (a * p) / (p - s1);
let cp = -c2 / p;
let w0 = 1.0 - smoothstep(0.0, m, x);
let w2 = step(m + l0, x);
let w1 = 1.0 - w0 - w2;
let t = m * (x / m).powf(c) + b;
let s = p - (p - s1) * (cp * (x - s0)).exp();
let l = m + a * (x - m);
return t * w0 + l * w1 + s * w2;
}
fn uchimura(x: f32) -> f32{
const P: f32 = 1.0;
const A: f32 = 1.0;
const M: f32 = 0.22;
const L: f32 = 0.4;
const C: f32 = 1.33;
const B: f32 = 0.0;
uchimura_params(x, P, A, M, L, C, B)
}
fn unreal(x: f32) -> f32 {
x / (x + 0.155) * 1.019
}
let curve_data = a.clone().into_property()
.zip(b.clone().into_property())
.zip(c.clone().into_property())
.zip(d.clone().into_property())
.zip(e.clone().into_property())
.zip(f.clone().into_property())
.zip(w.clone().into_property())
.zip(ty.clone())
.map(move |(((((((a,b),c),d),e),f),w), ty)|{
(0usize..200).map(|sample| sample as f32 / 10.)
.map(|x| {
match ty {
TonemapTy::Linear => x,
TonemapTy::Gamma => x,
TonemapTy::Rec709 => x,
TonemapTy::Reinhard1 => reinhard1(x, w),
TonemapTy::Reinhard2 => reinhard2(x, w),
TonemapTy::Filmic => filmic(x),
TonemapTy::Uncharted2 =>
uncharted2(a,b,c,d,e,f,x) / uncharted2(a,b,c,d,e,f,w),
TonemapTy::ACES =>
aces(x) / aces(w),
TonemapTy::ACES_REC2020_1K =>
aces_rec2020_1k(x) / aces_rec2020_1k(w),
TonemapTy::Uchimura =>
uchimura(x) / uchimura(w),
TonemapTy::Unreal => unreal(x).powf(2.2),
}
})
.collect()
});
RangedProperty::new(curve_data, 0. .. 1.)
}