├── .gitignore ├── lib └── gl │ ├── src │ └── lib.rs │ ├── Cargo.toml │ └── build.rs ├── shaders ├── linear.glsl ├── per-channel-exponential.glsl ├── stupid_thing.glsl ├── inc │ ├── ycbcr.hlsl │ ├── hlsl_to_glsl.glsl │ ├── math.hlsl │ ├── luv.hlsl │ ├── prelude.glsl │ ├── xyz.hlsl │ ├── ipt.hlsl │ ├── srgb.hlsl │ ├── lab.hlsl │ ├── oklab.hlsl │ ├── helmholtz_kohlrausch.hlsl │ ├── ictcp.hlsl │ ├── bezold_brucke.hlsl │ ├── standard_observer.hlsl │ └── display_transform.hlsl ├── lut │ └── bezold_brucke_lut.glsl ├── tmp_bezold_brucke.glsl ├── tmp_custom-hk.glsl └── brightness-hue-preserving.glsl ├── Cargo.toml ├── LICENSE-MIT ├── src ├── file.rs ├── fbo.rs ├── image_pool.rs ├── setup.rs ├── image_loading.rs ├── shader_lib.rs ├── texture.rs ├── lut_lib.rs ├── main.rs ├── shader.rs └── app_state.rs ├── README.md ├── LICENSE-APACHE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.exr 3 | *.hdr 4 | *.jpg 5 | -------------------------------------------------------------------------------- /lib/gl/src/lib.rs: -------------------------------------------------------------------------------- 1 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); -------------------------------------------------------------------------------- /shaders/linear.glsl: -------------------------------------------------------------------------------- 1 | #include "inc/prelude.glsl" 2 | 3 | float3 compress_stimulus(ShaderInput shader_input) { 4 | return shader_input.stimulus; 5 | } 6 | -------------------------------------------------------------------------------- /shaders/per-channel-exponential.glsl: -------------------------------------------------------------------------------- 1 | #include "inc/prelude.glsl" 2 | 3 | float3 compress_stimulus(ShaderInput shader_input) { 4 | return 1.0 - exp(-shader_input.stimulus); 5 | } 6 | -------------------------------------------------------------------------------- /lib/gl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gl" 3 | version = "0.1.0" 4 | authors = ["Tomasz Stachowiak "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | 9 | [build-dependencies] 10 | gl_generator = "0.14.0" 11 | -------------------------------------------------------------------------------- /shaders/stupid_thing.glsl: -------------------------------------------------------------------------------- 1 | #include "inc/prelude.glsl" 2 | #include "inc/display_transform.hlsl" 3 | 4 | float3 compress_stimulus(ShaderInput shader_input) { 5 | return display_transform_sRGB(shader_input.stimulus); 6 | } 7 | -------------------------------------------------------------------------------- /shaders/inc/ycbcr.hlsl: -------------------------------------------------------------------------------- 1 | #ifndef NOTORIOUS6_YCBCR_HLSL 2 | #define NOTORIOUS6_YCBCR_HLSL 3 | 4 | float3 sRGB_to_YCbCr(float3 col) { 5 | return mul(float3x3(0.2126, 0.7152, 0.0722, -0.1146,-0.3854, 0.5, 0.5,-0.4542,-0.0458), col); 6 | } 7 | 8 | float3 YCbCr_to_sRGB(float3 col) { 9 | return max(0.0.xxx, mul(float3x3(1.0, 0.0, 1.5748, 1.0, -0.1873, -.4681, 1.0, 1.8556, 0.0), col)); 10 | } 11 | 12 | #endif // NOTORIOUS6_YCBCR_HLSL 13 | -------------------------------------------------------------------------------- /shaders/inc/hlsl_to_glsl.glsl: -------------------------------------------------------------------------------- 1 | #define float2 vec2 2 | #define float3 vec3 3 | #define float4 vec4 4 | #define static 5 | #define mul(A, B) ((A) * (B)) 6 | #define atan2(y, x) atan(y, x) 7 | #define lerp(a, b, t) mix(a, b, t) 8 | #define saturate(a) clamp(a, 0.0, 1.0) 9 | #define frac(x) fract(x) 10 | 11 | #define float3x3(a, b, c, d, e, f, g, h, i) transpose(mat3(a, b, c, d, e, f, g, h, i)) 12 | #define float2x2(a, b, c, d) transpose(mat2(a, b, c, d)) 13 | -------------------------------------------------------------------------------- /lib/gl/build.rs: -------------------------------------------------------------------------------- 1 | extern crate gl_generator; 2 | 3 | use gl_generator::{Api, Fallbacks, Profile, Registry, StructGenerator}; 4 | use std::env; 5 | use std::fs::File; 6 | use std::path::Path; 7 | 8 | fn main() { 9 | let out_dir = env::var("OUT_DIR").unwrap(); 10 | let mut file = File::create(&Path::new(&out_dir).join("bindings.rs")).unwrap(); 11 | 12 | Registry::new( 13 | Api::Gl, 14 | (4, 5), 15 | Profile::Core, 16 | Fallbacks::All, 17 | ["GL_ARB_framebuffer_object"], 18 | ) 19 | .write_bindings(StructGenerator, &mut file) 20 | .unwrap(); 21 | } 22 | -------------------------------------------------------------------------------- /shaders/inc/math.hlsl: -------------------------------------------------------------------------------- 1 | #ifndef NOTORIOUS6_MATH_HLSL 2 | #define NOTORIOUS6_MATH_HLSL 3 | 4 | #define M_PI 3.1415926535897932384626433832795 5 | 6 | float cbrt(float x) { 7 | return sign(x) * pow(abs(x),1.0 / 3.0); 8 | } 9 | 10 | float soft_shoulder(float x, float max_val, float p) { 11 | return x / pow(pow(x / max_val, p) + 1.0, 1.0 / p); 12 | } 13 | 14 | float catmull_rom(float x, float v0,float v1, float v2,float v3) { 15 | float c2 = -.5 * v0 + 0.5*v2; 16 | float c3 = v0 + -2.5*v1 + 2.0*v2 + -.5*v3; 17 | float c4 = -.5 * v0 + 1.5*v1 + -1.5*v2 + 0.5*v3; 18 | return(((c4 * x + c3) * x + c2) * x + v1); 19 | } 20 | 21 | #endif // NOTORIOUS6_MATH_HLSL 22 | -------------------------------------------------------------------------------- /shaders/inc/luv.hlsl: -------------------------------------------------------------------------------- 1 | #ifndef NOTORIOUS6_LUV_HLSL 2 | #define NOTORIOUS6_LUV_HLSL 3 | 4 | float luminance_to_LUV_L(float Y){ 5 | return Y <= 0.0088564516790356308 ? Y * 903.2962962962963 : 116.0 * pow(max(0.0, Y), 1.0 / 3.0) - 16.0; 6 | } 7 | 8 | float LUV_L_to_luminance(float L) { 9 | return L <= 8.0 ? L / 903.2962962962963 : pow((L + 16.0) / 116.0, 3.0); 10 | } 11 | 12 | float2 CIE_xyY_xy_to_LUV_uv(float2 xy) { 13 | return xy * float2(4.0, 9.0) / (-2.0 * xy.x + 12.0 * xy.y + 3.0); 14 | } 15 | 16 | float2 CIE_XYZ_to_LUV_uv(float3 xyz) { 17 | return xyz.xy * float2(4.0, 9.0) / dot(xyz, float3(1.0, 15.0, 3.0)); 18 | } 19 | 20 | 21 | #endif // NOTORIOUS6_LUV_HLSL 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notorious6" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0" 10 | bytes = "1.1" 11 | exr = "1.5.3" 12 | gl = { path = "lib/gl" } 13 | glutin = "0.28" 14 | hotwatch = "0.4.6" 15 | jpeg-encoder = "0.4" 16 | lazy_static = "1.4" 17 | log = "0.4" 18 | parking_lot = "0.11" 19 | radiant = "0.3" 20 | relative-path = "1.0" 21 | shader-prepper = { version = "0.3.0-pre.1", features = ["gl_compiler"] } 22 | simple_logger = "1.13.0" 23 | smol = "1.2.5" 24 | structopt = "0.3" 25 | turbosloth = { git = "https://github.com/h3r2tic/turbosloth.git", rev = "92030af" } 26 | 27 | [profile.release] 28 | debug = 1 29 | -------------------------------------------------------------------------------- /shaders/inc/prelude.glsl: -------------------------------------------------------------------------------- 1 | #version 430 2 | #include "hlsl_to_glsl.glsl" 3 | #include "math.hlsl" 4 | 5 | uniform sampler2D input_texture; 6 | uniform float input_ev; 7 | in float2 input_uv; 8 | out float4 output_rgba; 9 | 10 | #define DECLARE_BEZOLD_BRUCKE_LUT uniform sampler1D bezold_brucke_lut 11 | #define SAMPLE_BEZOLD_BRUCKE_LUT(coord) textureLod(bezold_brucke_lut, (coord), 0).xy 12 | 13 | struct ShaderInput { 14 | float3 stimulus; 15 | float2 uv; 16 | }; 17 | 18 | ShaderInput prepare_shader_input() { 19 | ShaderInput shader_input; 20 | shader_input.stimulus = exp2(input_ev) * max(0.0.xxx, textureLod(input_texture, input_uv, 0).rgb); 21 | shader_input.uv = input_uv; 22 | return shader_input; 23 | } 24 | 25 | #define SHADER_MAIN_FN output_rgba = float4(compress_stimulus(prepare_shader_input()), 1.0); 26 | -------------------------------------------------------------------------------- /shaders/lut/bezold_brucke_lut.glsl: -------------------------------------------------------------------------------- 1 | #version 430 2 | #include "../inc/hlsl_to_glsl.glsl" 3 | #include "../inc/math.hlsl" 4 | 5 | #include "../inc/standard_observer.hlsl" 6 | #include "../inc/ipt.hlsl" 7 | #include "../inc/bezold_brucke.hlsl" 8 | 9 | layout(local_size_x = 64, local_size_y = 1) in; 10 | layout(rg32f) uniform image1D output_image; 11 | 12 | void main() { 13 | const int px = int(gl_GlobalInvocationID.x); 14 | 15 | const float2 xy = bb_lut_coord_to_xy_white_offset((px + 0.5) / 64) + white_D65_xy; 16 | float3 XYZ = CIE_xyY_to_XYZ(float3(xy, 1.0)); 17 | 18 | const float3 shifted_XYZ = bezold_brucke_shift_XYZ_brute_force(XYZ, 1.0); 19 | const float2 shifted_xy = normalize(CIE_XYZ_to_xyY(shifted_XYZ).xy - white_D65_xy) + white_D65_xy; 20 | 21 | imageStore(output_image, px, float4(shifted_xy - xy, 0.0, 0.0)); 22 | } 23 | -------------------------------------------------------------------------------- /shaders/inc/xyz.hlsl: -------------------------------------------------------------------------------- 1 | #ifndef NOTORIOUS6_XYZ_HLSL 2 | #define NOTORIOUS6_XYZ_HLSL 3 | 4 | float3 CIE_xyY_xy_to_xyz(float2 xy) { 5 | return float3( xy, 1.0f - xy.x - xy.y ); 6 | } 7 | 8 | float3 CIE_xyY_to_XYZ( float3 CIE_xyY ) 9 | { 10 | float x = CIE_xyY[0]; 11 | float y = CIE_xyY[1]; 12 | float Y = CIE_xyY[2]; 13 | 14 | float X = (Y / y) * x; 15 | float Z = (Y / y) * (1.0 - x - y); 16 | 17 | return float3( X, Y, Z ); 18 | } 19 | 20 | float3 CIE_XYZ_to_xyY( float3 CIE_XYZ ) 21 | { 22 | float X = CIE_XYZ[0]; 23 | float Y = CIE_XYZ[1]; 24 | float Z = CIE_XYZ[2]; 25 | 26 | float N = X + Y + Z; 27 | 28 | float x = X / N; 29 | float y = Y / N; 30 | float z = Z / N; 31 | 32 | return float3(x,y,Y); 33 | } 34 | 35 | static const float2 white_D65_xy = float2(0.31271, 0.32902); 36 | 37 | #endif // NOTORIOUS6_XYZ_HLSL 38 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Tomasz Stachowiak 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /shaders/inc/ipt.hlsl: -------------------------------------------------------------------------------- 1 | #ifndef NOTORIOUS6_IPT_HLSL 2 | #define NOTORIOUS6_IPT_HLSL 3 | 4 | float3 XYZ_to_IPT(float3 xyz) { 5 | float3 lms = mul( 6 | float3x3( 7 | 0.4002, 0.7075, -0.0807, 8 | -0.2280, 1.1500, 0.0612, 9 | 0.0, 0.0, 0.9184), 10 | xyz 11 | ); 12 | 13 | lms.x = lms.x >= 0.0 ? pow(lms.x, 0.43) : -pow(-lms.x, 0.43); 14 | lms.y = lms.y >= 0.0 ? pow(lms.y, 0.43) : -pow(-lms.y, 0.43); 15 | lms.z = lms.z >= 0.0 ? pow(lms.z, 0.43) : -pow(-lms.z, 0.43); 16 | 17 | return mul( 18 | float3x3( 19 | 0.4000, 0.4000, 0.2000, 20 | 4.4550, -4.8510, 0.3960, 21 | 0.8056, 0.3572, -1.1628), 22 | lms 23 | ); 24 | } 25 | 26 | 27 | float3 IPT_to_XYZ(float3 ipt) { 28 | float3 lms = mul( 29 | float3x3( 30 | 1.0, 0.0976, 0.2052, 31 | 1.0, -0.1139, 0.1332, 32 | 1.0, 0.0326, -0.6769), 33 | ipt 34 | ); 35 | 36 | lms.x = lms.x >= 0.0 ? pow(lms.x, 1.0 / 0.43) : -pow(-lms.x, 1.0 / 0.43); 37 | lms.y = lms.y >= 0.0 ? pow(lms.y, 1.0 / 0.43) : -pow(-lms.y, 1.0 / 0.43); 38 | lms.z = lms.z >= 0.0 ? pow(lms.z, 1.0 / 0.43) : -pow(-lms.z, 1.0 / 0.43); 39 | 40 | return mul( 41 | float3x3( 42 | 1.8501, -1.1383, 0.2385, 43 | 0.3668, 0.6439, -0.0107, 44 | 0.0, 0.0, 1.0889), 45 | lms 46 | ); 47 | } 48 | 49 | #endif // NOTORIOUS6_IPT_HLSL 50 | -------------------------------------------------------------------------------- /shaders/inc/srgb.hlsl: -------------------------------------------------------------------------------- 1 | #ifndef NOTORIOUS6_SRGB_HLSL 2 | #define NOTORIOUS6_SRGB_HLSL 3 | 4 | float sRGB_to_luminance(float3 col) { 5 | return dot(col, float3(0.2126, 0.7152, 0.0722)); 6 | } 7 | 8 | // from Alex Tardiff: http://alextardif.com/Lightness.html 9 | // Convert RGB with sRGB/Rec.709 primaries to CIE XYZ 10 | // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html 11 | float3 sRGB_to_XYZ(float3 color) { 12 | return mul(float3x3( 13 | 0.4124564, 0.3575761, 0.1804375, 14 | 0.2126729, 0.7151522, 0.0721750, 15 | 0.0193339, 0.1191920, 0.9503041 16 | ), color); 17 | } 18 | 19 | // from Alex Tardiff: http://alextardif.com/Lightness.html 20 | // Convert CIE XYZ to RGB with sRGB/Rec.709 primaries 21 | // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html 22 | float3 XYZ_to_sRGB(float3 color) { 23 | return mul(float3x3( 24 | 3.2404542, -1.5371385, -0.4985314, 25 | -0.9692660, 1.8760108, 0.0415560, 26 | 0.0556434, -0.2040259, 1.0572252 27 | ), color); 28 | } 29 | 30 | float sRGB_OETF(float a) { 31 | return .0031308f >= a ? 12.92f * a : 1.055f * pow(a, .4166666666666667f) - .055f; 32 | } 33 | 34 | float3 sRGB_OETF(float3 a) { 35 | return float3(sRGB_OETF(a.r), sRGB_OETF(a.g), sRGB_OETF(a.b)); 36 | } 37 | 38 | float sRGB_EOTF(float a) { 39 | return .04045f < a ? pow((a + .055f) / 1.055f, 2.4f) : a / 12.92f; 40 | } 41 | 42 | float3 sRGB_EOTF(float3 a) { 43 | return float3(sRGB_EOTF(a.r), sRGB_EOTF(a.g), sRGB_EOTF(a.b)); 44 | } 45 | 46 | #endif // NOTORIOUS6_SRGB_HLSL 47 | -------------------------------------------------------------------------------- /src/file.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context as _; 2 | use bytes::Bytes; 3 | use hotwatch::Hotwatch; 4 | use lazy_static::lazy_static; 5 | use parking_lot::Mutex; 6 | use std::{fs::File, path::PathBuf}; 7 | use turbosloth::*; 8 | 9 | lazy_static! { 10 | pub(crate) static ref FILE_WATCHER: Mutex = 11 | Mutex::new(Hotwatch::new_with_custom_delay(std::time::Duration::from_millis(100)).unwrap()); 12 | } 13 | 14 | #[derive(Clone, Hash)] 15 | pub struct LoadFile { 16 | path: PathBuf, 17 | } 18 | 19 | impl LoadFile { 20 | pub fn new(path: impl Into) -> anyhow::Result { 21 | let path = path.into().canonicalize()?; 22 | Ok(Self { path }) 23 | } 24 | } 25 | 26 | #[async_trait] 27 | impl LazyWorker for LoadFile { 28 | type Output = anyhow::Result; 29 | 30 | async fn run(self, ctx: RunContext) -> Self::Output { 31 | let invalidation_trigger = ctx.get_invalidation_trigger(); 32 | 33 | FILE_WATCHER 34 | .lock() 35 | .watch(self.path.clone(), move |event| { 36 | if matches!(event, hotwatch::Event::Write(_)) { 37 | invalidation_trigger(); 38 | } 39 | }) 40 | .with_context(|| format!("LoadFile: trying to watch {:?}", self.path))?; 41 | 42 | let mut buffer = Vec::new(); 43 | std::io::Read::read_to_end(&mut File::open(&self.path)?, &mut buffer) 44 | .with_context(|| format!("LoadFile: trying to read {:?}", self.path))?; 45 | 46 | Ok(Bytes::from(buffer)) 47 | } 48 | 49 | fn debug_description(&self) -> Option> { 50 | Some(format!("LoadFile({:?})", self.path).into()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | # 🌈🙈 notorious6 10 | 11 | Experiments in mapping HDR stimulus to display via shaders and stuff. 12 | 13 | ![screenshot](https://user-images.githubusercontent.com/16522064/155857613-7e9c4ff6-9cab-4007-8174-046807f26014.jpg) 14 | 15 |
16 | 17 | ## Building and running 18 | 19 | Should work on Windows and Linux (🐧 not tested yet). Mac will probably throw a fit due to the use of OpenGL. 20 | 21 | 1. [Get Rust](https://www.rust-lang.org/tools/install). 22 | 2. Clone the repo and run `cargo build --release` or `cargo run --release` 23 | 24 | The compiled binary sits in `target/release/notorious6` 25 | 26 | By default, the app loads images from a folder called `img`. To change it, pass the folder to the app as a parameter, for example: 27 | 28 | `cargo run --release -- some_other_folder_or_image` 29 | 30 | or 31 | 32 | `target/release/notorious6 some_other_folder_or_image` 33 | 34 | ## Controls 35 | 36 | * Left/right - switch images 37 | * Up/down - switch techniques (see the [`shaders`](shaders) folder) 38 | * Hold the left mouse button and drag up/down: change EV 39 | 40 | ## Acknowledgments 41 | 42 | Special thanks to Troy Sobotka for guidance and mind-bending discussions about color. 43 | 44 | ## License 45 | 46 | This contribution is dual licensed under EITHER OF 47 | 48 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 49 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 50 | 51 | at your option. 52 | -------------------------------------------------------------------------------- /shaders/inc/lab.hlsl: -------------------------------------------------------------------------------- 1 | // from Alex Tardiff: http://alextardif.com/Lightness.html 2 | 3 | #ifndef NOTORIOUS6_LAB_HLSL 4 | #define NOTORIOUS6_LAB_HLSL 5 | 6 | #include "xyz.hlsl" 7 | 8 | float degrees_to_radians(float degrees) 9 | { 10 | return degrees * M_PI / 180.0; 11 | } 12 | 13 | float radians_to_degrees(float radians) 14 | { 15 | return radians * (180.0 / M_PI); 16 | } 17 | 18 | // http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html 19 | float3 XYZ_to_LAB(float3 xyz) 20 | { 21 | const float3 D65_XYZ = float3(0.9504, 1.0000, 1.0888); 22 | 23 | xyz /= D65_XYZ; 24 | xyz = float3( 25 | xyz.x > 0.008856 ? pow(abs(xyz.x), 1.0 / 3.0) : xyz.x * 7.787 + 16.0 / 116.0, 26 | xyz.y > 0.008856 ? pow(abs(xyz.y), 1.0 / 3.0) : xyz.y * 7.787 + 16.0 / 116.0, 27 | xyz.z > 0.008856 ? pow(abs(xyz.z), 1.0 / 3.0) : xyz.z * 7.787 + 16.0 / 116.0 28 | ); 29 | 30 | float l = 116.0 * xyz.y - 16.0; 31 | float a = 500.0 * (xyz.x - xyz.y); 32 | float b = 200.0 * (xyz.y - xyz.z); 33 | 34 | return float3(l, a, b); 35 | } 36 | 37 | // http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html 38 | float3 LABToXYZ(float3 lab) 39 | { 40 | const float3 D65_XYZ = float3(0.9504, 1.0000, 1.0888); 41 | 42 | float fy = (lab[0] + 16.0) / 116.0; 43 | float fx = lab[1] / 500.0 + fy; 44 | float fz = fy - lab[2] / 200.0; 45 | float3 fxyz = float3(fx, fy, fz); 46 | 47 | float3 xyz = fxyz * fxyz * fxyz; 48 | return D65_XYZ * float3( 49 | fxyz.x > 0.206893 ? xyz.x : (116.0 * fxyz.x - 16.0) / 903.3, 50 | fxyz.y > 0.206893 ? xyz.y : (116.0 * fxyz.y - 16.0) / 903.3, 51 | fxyz.z > 0.206893 ? xyz.z : (116.0 * fxyz.z - 16.0) / 903.3 52 | ); 53 | } 54 | 55 | //http://www.brucelindbloom.com/index.html?Eqn_Lab_to_LCH.html 56 | float3 LAB_to_Lch(float3 lab) 57 | { 58 | float c = sqrt(lab.y * lab.y + lab.z * lab.z); 59 | float h = atan2(lab.z, lab.y); 60 | 61 | if (h >= 0.0) { 62 | h = radians_to_degrees(h); 63 | } else { 64 | h = radians_to_degrees(h) + 360.0; 65 | } 66 | 67 | return float3(lab.x, c, h); 68 | } 69 | 70 | #endif // NOTORIOUS6_LAB_HLSL 71 | -------------------------------------------------------------------------------- /src/fbo.rs: -------------------------------------------------------------------------------- 1 | pub struct Fbo { 2 | fbo: u32, 3 | rbo: u32, 4 | } 5 | 6 | impl Fbo { 7 | pub fn new(gl: &gl::Gl, size: [usize; 2]) -> Self { 8 | unsafe { 9 | let mut fbo: u32 = 0; 10 | gl.GenFramebuffers(1, &mut fbo); 11 | assert!(fbo != 0); 12 | 13 | gl.BindFramebuffer(gl::FRAMEBUFFER, fbo); 14 | 15 | let mut rbo = 0; 16 | gl.GenRenderbuffers(1, &mut rbo); 17 | gl.BindRenderbuffer(gl::RENDERBUFFER, rbo); 18 | gl.RenderbufferStorage( 19 | gl::RENDERBUFFER, 20 | gl::SRGB8_ALPHA8, 21 | size[0] as _, 22 | size[1] as _, 23 | ); 24 | gl.FramebufferRenderbuffer( 25 | gl::FRAMEBUFFER, 26 | gl::COLOR_ATTACHMENT0, 27 | gl::RENDERBUFFER, 28 | rbo, 29 | ); 30 | 31 | gl.BindFramebuffer(gl::FRAMEBUFFER, 0); 32 | 33 | Self { fbo, rbo } 34 | } 35 | } 36 | 37 | pub fn bind(&self, gl: &gl::Gl) { 38 | unsafe { 39 | gl.BindFramebuffer(gl::FRAMEBUFFER, self.fbo); 40 | } 41 | } 42 | 43 | pub fn bind_read(&self, gl: &gl::Gl) { 44 | unsafe { 45 | gl.BindFramebuffer(gl::READ_FRAMEBUFFER, self.fbo); 46 | } 47 | } 48 | 49 | pub fn unbind(&self, gl: &gl::Gl) { 50 | unsafe { 51 | gl.BindFramebuffer(gl::FRAMEBUFFER, 0); 52 | } 53 | } 54 | 55 | pub fn unbind_read(&self, gl: &gl::Gl) { 56 | unsafe { 57 | gl.BindFramebuffer(gl::READ_FRAMEBUFFER, 0); 58 | } 59 | } 60 | 61 | pub fn destroy(mut self, gl: &gl::Gl) { 62 | assert!(self.fbo != 0); 63 | 64 | self.unbind(gl); 65 | self.unbind_read(gl); 66 | unsafe { 67 | gl.DeleteRenderbuffers(1, &self.rbo); 68 | gl.DeleteFramebuffers(1, &self.fbo); 69 | } 70 | self.rbo = 0; 71 | self.fbo = 0; 72 | } 73 | } 74 | 75 | impl Drop for Fbo { 76 | fn drop(&mut self) { 77 | if self.fbo != 0 { 78 | log::error!("Fbo must be manually destroyed"); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /shaders/inc/oklab.hlsl: -------------------------------------------------------------------------------- 1 | #ifndef NOTORIOUS6_OKLAB_HLSL 2 | #define NOTORIOUS6_OKLAB_HLSL 3 | 4 | #include "math.hlsl" 5 | 6 | // Copyright(c) 2021 Björn Ottosson 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | // this softwareand associated documentation files(the "Software"), to deal in 10 | // the Software without restriction, including without limitation the rights to 11 | // use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies 12 | // of the Software, and to permit persons to whom the Software is furnished to do 13 | // so, subject to the following conditions : 14 | // The above copyright noticeand this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | 24 | float3 sRGB_to_Oklab(float3 c) { 25 | float l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b; 26 | float m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b; 27 | float s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b; 28 | 29 | float l_ = cbrt(l); 30 | float m_ = cbrt(m); 31 | float s_ = cbrt(s); 32 | 33 | return float3( 34 | 0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_, 35 | 1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_, 36 | 0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_ 37 | ); 38 | } 39 | 40 | float3 Oklab_to_sRGB(float3 c) { 41 | float l_ = c.x + 0.3963377774f * c.y + 0.2158037573f * c.z; 42 | float m_ = c.x - 0.1055613458f * c.y - 0.0638541728f * c.z; 43 | float s_ = c.x - 0.0894841775f * c.y - 1.2914855480f * c.z; 44 | 45 | float l = l_ * l_ * l_; 46 | float m = m_ * m_ * m_; 47 | float s = s_ * s_ * s_; 48 | 49 | return float3( 50 | +4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s, 51 | -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s, 52 | -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s 53 | ); 54 | } 55 | 56 | #endif // NOTORIOUS6_OKLAB_HLSL 57 | -------------------------------------------------------------------------------- /shaders/tmp_bezold_brucke.glsl: -------------------------------------------------------------------------------- 1 | #include "inc/prelude.glsl" 2 | #include "inc/srgb.hlsl" 3 | #include "inc/standard_observer.hlsl" 4 | #include "inc/ipt.hlsl" 5 | #include "inc/bezold_brucke.hlsl" 6 | 7 | #define IMAGE_PASSTHROUGH 0 8 | 9 | vec3 compress_stimulus(ShaderInput shader_input) { 10 | //return textureLod(bezold_brucke_lut, shader_input.uv.x, 0).xyz; 11 | 12 | //float wavelength = lerp(standard_observer_1931_w_min, standard_observer_1931_w_max, shader_input.uv.x); 13 | float wavelength = lerp(410, 650, shader_input.uv.x); 14 | 15 | // nulls for the 10:100 cd/m^2 6500K case 16 | const float null_wavelengths[4] = float[4](481, 506, 578, 620); 17 | 18 | for (int i = 0; i < 4; ++i) { 19 | if (abs(wavelength - null_wavelengths[i]) < 0.25) { 20 | return 1.0.xxx; 21 | } 22 | } 23 | 24 | const float ordinate = (shader_input.uv.y * 2 - 1) * 10; // -10 .. 10 25 | if (abs(ordinate) < 0.03) { 26 | return 0.0.xxx; 27 | } 28 | 29 | float3 xyY = wavelength_to_xyY(wavelength); 30 | float3 XYZ = CIE_xyY_to_XYZ(xyY); 31 | 32 | const float bb_shift = XYZ_to_BB_shift_nm(XYZ); 33 | 34 | if (abs(ordinate - bb_shift) < 0.05) { 35 | return 1.1.xxx; 36 | } 37 | 38 | { 39 | //vec3 shifted_XYZ = bezold_brucke_shift_XYZ(XYZ, 1.0); 40 | vec3 shifted_XYZ = bezold_brucke_shift_XYZ_with_lut(XYZ, 1.0); 41 | //vec3 shifted_XYZ = bezold_brucke_shift_XYZ_brute_force(XYZ, 1.0); 42 | 43 | vec3 shifted_sRGB = XYZ_to_sRGB(shifted_XYZ); 44 | 45 | float shifted_wavelength = CIE_xy_to_dominant_wavelength(CIE_XYZ_to_xyY(shifted_XYZ).xy); 46 | float wavelength_shift = shifted_wavelength - wavelength; 47 | 48 | if (abs(ordinate - wavelength_shift) < 0.08) { 49 | return float3(1, 0, 1); 50 | } 51 | 52 | #if !IMAGE_PASSTHROUGH 53 | // Bottom part: approximate ajustment 54 | if (shader_input.uv.y > 0.7) { 55 | return shifted_sRGB * 0.3; 56 | } 57 | #endif 58 | } 59 | 60 | // Middle part: brute force adjustment 61 | if (shader_input.uv.y > 0.3) { 62 | //float3 xyY = wavelength_to_xyY(wavelength + bb_shift); 63 | //XYZ = CIE_xyY_to_XYZ(xyY); 64 | XYZ = bezold_brucke_shift_XYZ_brute_force(XYZ, 1.0); 65 | } 66 | // else: top part: input 67 | 68 | vec3 sRGB = XYZ_to_sRGB(XYZ); 69 | 70 | #if IMAGE_PASSTHROUGH 71 | return shader_input.stimulus * 0.05; 72 | #endif 73 | 74 | return sRGB * 0.3; 75 | //return abs(wavelength - wavelength1).xxx; 76 | } 77 | -------------------------------------------------------------------------------- /shaders/tmp_custom-hk.glsl: -------------------------------------------------------------------------------- 1 | #include "inc/prelude.glsl" 2 | #include "inc/display_transform.hlsl" 3 | 4 | vec3 hsv2rgb(vec3 c) { 5 | vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 ); 6 | return c.z * lerp( vec3(1.0), rgb, c.y); 7 | } 8 | 9 | float hk_equivalent_luminance(float3 sRGB) { 10 | const HelmholtzKohlrauschEffect hk = hk_from_sRGB(sRGB); 11 | return srgb_to_equivalent_luminance(hk, sRGB); 12 | } 13 | 14 | vec3 compress_stimulus(ShaderInput shader_input) { 15 | shader_input.uv.x = frac(-shader_input.uv.x - 0.14); 16 | const float2 quant = float2(42, 24); 17 | 18 | const float2 uv = floor(shader_input.uv * quant) / quant; 19 | //const float2 uv = shader_input.uv; 20 | 21 | float h = uv.x; 22 | if (frac(uv.x * 20.0 + 0.3) > 0.85) { 23 | //h = frac(h + 0.5); 24 | } 25 | vec3 res = hsv2rgb(float3(h, 0.999, 1.0)); 26 | //return res * smoothstep(1.0, 0.0, uv.y); 27 | 28 | res.x = sRGB_EOTF(res.x); 29 | res.y = sRGB_EOTF(res.y); 30 | res.z = sRGB_EOTF(res.z); 31 | 32 | const float desaturation = 0.0; 33 | res = lerp(res, sRGB_to_luminance(res).xxx, desaturation); 34 | 35 | //return res; 36 | //res *= 1.0 - uv.y; 37 | 38 | const float3 hsv_output = res; 39 | 40 | //res = lerp(res, res.yyy, 0.5); 41 | 42 | #if 0 43 | if (frac(shader_input.uv.x * 20.0 + 0.3) < 0.7) { 44 | res = 1.0.xxx; 45 | } 46 | #endif 47 | 48 | for (int i = 0; i < 2; ++i) { 49 | res /= hk_equivalent_luminance(res); 50 | } 51 | 52 | res *= pow(smoothstep(1.0, 0.0, uv.y), 1.5) * 6; 53 | 54 | #if 1 55 | res = display_transform_sRGB(res); 56 | #endif 57 | 58 | if (true) { 59 | float h = shader_input.uv.x; 60 | vec3 hsv_output = hsv2rgb(float3(h, 0.999, 1.0)); 61 | hsv_output.x = sRGB_EOTF(hsv_output.x); 62 | hsv_output.y = sRGB_EOTF(hsv_output.y); 63 | hsv_output.z = sRGB_EOTF(hsv_output.z); 64 | 65 | hsv_output = lerp(hsv_output, sRGB_to_luminance(hsv_output).xxx, desaturation); 66 | 67 | hsv_output /= hk_equivalent_luminance(hsv_output); 68 | hsv_output /= hk_equivalent_luminance(hsv_output); 69 | 70 | //float equiv = sRGB_to_luminance(hsv_output); 71 | float equiv = log(1.0 / sRGB_to_luminance(hsv_output)) + 0.1; 72 | 73 | /*float above = (1 - shader_input.uv.y) * 2 > equiv ? 1.0 : 0.0; 74 | if (dFdy(above) != 0.0 || dFdx(above) != 0.0) { 75 | res *= 0.1; 76 | }*/ 77 | 78 | if (abs((1 - shader_input.uv.y) * 2 - equiv) < 0.01) { 79 | res *= 0.1; 80 | } 81 | } 82 | 83 | return res; 84 | } 85 | -------------------------------------------------------------------------------- /src/image_pool.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | image_loading::{self, ImageRgb32f}, 3 | texture::Texture, 4 | }; 5 | use anyhow::Context; 6 | use std::path::{Path, PathBuf}; 7 | 8 | enum PooledImageLoadStatus { 9 | NotLoaded, 10 | FailedToLoad, 11 | Loaded(ImageRgb32f), 12 | } 13 | 14 | struct PooledImage { 15 | path: PathBuf, 16 | image: PooledImageLoadStatus, 17 | texture: Option, 18 | } 19 | 20 | pub struct ImagePool { 21 | images: Vec, 22 | } 23 | 24 | fn is_supported_image_file_extension(ext: Option<&std::ffi::OsStr>) -> bool { 25 | return ext == Some(std::ffi::OsStr::new("exr")) || ext == Some(std::ffi::OsStr::new("hdr")); 26 | } 27 | 28 | impl ImagePool { 29 | pub fn new(path: impl AsRef) -> anyhow::Result { 30 | let path = path.as_ref(); 31 | 32 | if path.is_dir() { 33 | let dir = path 34 | .read_dir() 35 | .with_context(|| format!("Reading directory {:?}", path))?; 36 | Ok(Self { 37 | images: dir 38 | .filter_map(|entry| { 39 | let path = entry.ok()?.path(); 40 | (path.is_file() && is_supported_image_file_extension(path.extension())) 41 | .then(|| PooledImage { 42 | path: path.to_owned(), 43 | image: PooledImageLoadStatus::NotLoaded, 44 | texture: None, 45 | }) 46 | }) 47 | .collect(), 48 | }) 49 | } else { 50 | Ok(Self { 51 | images: vec![PooledImage { 52 | path: path.to_owned(), 53 | image: PooledImageLoadStatus::NotLoaded, 54 | texture: None, 55 | }], 56 | }) 57 | } 58 | } 59 | 60 | pub fn get_texture(&mut self, idx: usize, gl: &gl::Gl) -> Option<&Texture> { 61 | let img = self.images.get_mut(idx)?; 62 | 63 | if matches!(img.image, PooledImageLoadStatus::NotLoaded) { 64 | img.image = if let Ok(image) = image_loading::load_image(&img.path) 65 | .map_err(|err| log::error!("Failed to load {:?}: {:?}", img.path, err)) 66 | { 67 | PooledImageLoadStatus::Loaded(image) 68 | } else { 69 | PooledImageLoadStatus::FailedToLoad 70 | }; 71 | } 72 | 73 | match (&img.image, &mut img.texture) { 74 | (PooledImageLoadStatus::Loaded(loaded), target_image @ None) => { 75 | *target_image = Some(Texture::new_2d(gl, loaded)); 76 | target_image.as_ref() 77 | } 78 | (PooledImageLoadStatus::Loaded(_), Some(texture)) => Some(texture), 79 | _ => None, 80 | } 81 | } 82 | 83 | pub fn get_image_path(&self, idx: usize) -> Option<&PathBuf> { 84 | self.images.get(idx).map(|img| &img.path) 85 | } 86 | 87 | pub fn image_count(&self) -> usize { 88 | self.images.len() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/setup.rs: -------------------------------------------------------------------------------- 1 | pub fn setup_basic_gl_state(gl: &gl::Gl) { 2 | use std::ffi::CStr; 3 | 4 | unsafe { 5 | let mut vao: u32 = 0; 6 | gl.GenVertexArrays(1, &mut vao); 7 | gl.BindVertexArray(vao); 8 | 9 | log::info!( 10 | "GL_VENDOR: {:?}", 11 | CStr::from_ptr(gl.GetString(gl::VENDOR) as *const i8) 12 | ); 13 | log::info!( 14 | "GL_RENDERER: {:?}", 15 | CStr::from_ptr(gl.GetString(gl::RENDERER) as *const i8) 16 | ); 17 | 18 | gl.Enable(gl::FRAMEBUFFER_SRGB); 19 | 20 | gl.DebugMessageCallback(Some(gl_debug_message), std::ptr::null_mut()); 21 | 22 | // Disable everything by default 23 | gl.DebugMessageControl( 24 | gl::DONT_CARE, 25 | gl::DONT_CARE, 26 | gl::DONT_CARE, 27 | 0, 28 | std::ptr::null_mut(), 29 | 0, 30 | ); 31 | 32 | gl.DebugMessageControl( 33 | gl::DONT_CARE, 34 | gl::DONT_CARE, 35 | gl::DONT_CARE, 36 | 0, 37 | std::ptr::null_mut(), 38 | 1, 39 | ); 40 | 41 | gl.DebugMessageControl( 42 | gl::DEBUG_SOURCE_SHADER_COMPILER, 43 | gl::DONT_CARE, 44 | gl::DONT_CARE, 45 | 0, 46 | std::ptr::null_mut(), 47 | 0, 48 | ); 49 | 50 | gl.Enable(gl::DEBUG_OUTPUT_SYNCHRONOUS); 51 | } 52 | } 53 | 54 | extern "system" fn gl_debug_message( 55 | _source: u32, 56 | ty: u32, 57 | id: u32, 58 | severity: u32, 59 | _len: i32, 60 | message: *const i8, 61 | _param: *mut std::ffi::c_void, 62 | ) { 63 | unsafe { 64 | let s = std::ffi::CStr::from_ptr(message); 65 | 66 | #[allow(clippy::match_like_matches_macro)] 67 | let is_ignored_id = match id { 68 | 131216 => true, // Program/shader state info: GLSL shader * failed to compile. WAT. 69 | 131185 => true, // Buffer detailed info: (...) will use (...) memory as the source for buffer object operations. 70 | 131169 => true, // Framebuffer detailed info: The driver allocated storage for renderbuffer 71 | 131154 => true, // Pixel-path performance warning: Pixel transfer is synchronized with 3D rendering. 72 | _ => false, 73 | }; 74 | 75 | if !is_ignored_id { 76 | let is_important_type = matches!( 77 | ty, 78 | gl::DEBUG_TYPE_ERROR 79 | | gl::DEBUG_TYPE_UNDEFINED_BEHAVIOR 80 | | gl::DEBUG_TYPE_DEPRECATED_BEHAVIOR 81 | | gl::DEBUG_TYPE_PORTABILITY 82 | ); 83 | 84 | if !is_important_type { 85 | log::warn!("GL debug({}): {}\n", id, s.to_string_lossy()); 86 | } else { 87 | log::warn!( 88 | "OpenGL Debug message ({}, {:x}, {:x}): {}", 89 | id, 90 | ty, 91 | severity, 92 | s.to_string_lossy() 93 | ); 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/image_loading.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use exr::prelude::{self as exrs, ReadChannels as _, ReadLayers as _}; 3 | use std::path::Path; 4 | 5 | pub struct ImageRgb32f { 6 | pub size: [usize; 2], 7 | pub data: Vec, 8 | } 9 | 10 | impl ImageRgb32f { 11 | pub fn new(width: usize, height: usize) -> Self { 12 | Self { 13 | size: [width, height], 14 | data: vec![0.0; width * height * 3], 15 | } 16 | } 17 | 18 | #[inline(always)] 19 | fn put_pixel(&mut self, x: usize, y: usize, rgb: [f32; 3]) { 20 | let offset = (y * self.size[0] + x) * 3; 21 | self.data[offset..offset + 3].copy_from_slice(&rgb); 22 | 23 | /*unsafe { 24 | let dst = self.data.as_mut_ptr().add(offset); 25 | *dst = rgb[0]; 26 | *dst.add(1) = rgb[1]; 27 | *dst.add(2) = rgb[2]; 28 | }*/ 29 | } 30 | } 31 | 32 | pub fn load_image(file_path: impl AsRef) -> anyhow::Result { 33 | let path = file_path.as_ref(); 34 | let ext = path 35 | .extension() 36 | .map(|ext| ext.to_string_lossy().as_ref().to_owned()); 37 | 38 | match ext.as_deref() { 39 | Some("exr") => load_exr(path), 40 | Some("hdr") => load_hdr(path), 41 | _ => Err(anyhow::anyhow!("Unsupported file extension: {:?}", ext)), 42 | } 43 | } 44 | 45 | fn load_hdr(file_path: &Path) -> anyhow::Result { 46 | let image = radiant::load(std::io::Cursor::new( 47 | std::fs::read(file_path).context("failed to open specified file")?, 48 | )) 49 | .context("failed to load image data")?; 50 | 51 | let data: Vec = image 52 | .data 53 | .iter() 54 | .copied() 55 | .flat_map(|px| [px.r, px.g, px.b].into_iter()) 56 | .collect(); 57 | 58 | Ok(ImageRgb32f { 59 | size: [image.width, image.height], 60 | data, 61 | }) 62 | } 63 | 64 | fn load_exr(file_path: &Path) -> anyhow::Result { 65 | let reader = exrs::read() 66 | .no_deep_data() 67 | .largest_resolution_level() 68 | .rgb_channels( 69 | |resolution, _channels: &exrs::RgbChannels| -> ImageRgb32f { 70 | ImageRgb32f::new(resolution.width(), resolution.height()) 71 | }, 72 | // set each pixel in the png buffer from the exr file 73 | |output, position, (r, g, b): (f32, f32, f32)| { 74 | output.put_pixel(position.0, position.1, [r, g, b]); 75 | }, 76 | ) 77 | .first_valid_layer() 78 | .all_attributes(); 79 | 80 | //let t0 = std::time::Instant::now(); 81 | let contents = std::fs::read(file_path)?; 82 | // println!("Reading the file took {:?}", t0.elapsed()); 83 | 84 | //let t0 = std::time::Instant::now(); 85 | // an image that contains a single layer containing an png rgba buffer 86 | let maybe_image: Result< 87 | exrs::Image>>, 88 | exrs::Error, 89 | > = reader.from_unbuffered(std::io::Cursor::new(contents)); 90 | // println!("Rendering the image took {:?}", t0.elapsed()); 91 | 92 | let output = maybe_image?.layer_data.channel_data.pixels; 93 | Ok(output) 94 | } 95 | -------------------------------------------------------------------------------- /src/shader_lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use std::collections::HashMap; 3 | use std::iter::once; 4 | use std::path::Path; 5 | use std::sync::Arc; 6 | use turbosloth::*; 7 | 8 | use crate::shader::*; 9 | 10 | struct CompiledShader { 11 | preprocessed_ps: turbosloth::Lazy, 12 | gl_handle: Option, 13 | } 14 | 15 | impl CompiledShader { 16 | fn new(preprocessed_ps: turbosloth::Lazy) -> Self { 17 | Self { 18 | preprocessed_ps, 19 | gl_handle: None, 20 | } 21 | } 22 | } 23 | 24 | pub struct ShaderLib { 25 | shaders: HashMap, 26 | vs_handle: u32, 27 | lazy_cache: Arc, 28 | } 29 | 30 | pub enum AnyShadersChanged { 31 | Yes, 32 | No, 33 | } 34 | 35 | impl ShaderLib { 36 | pub fn new(lazy_cache: &Arc, gl: &gl::Gl) -> Self { 37 | let source = shader_prepper::SourceChunk::from_file_source( 38 | "no_file", 39 | r#" 40 | #version 430 41 | out vec2 input_uv; 42 | void main() 43 | { 44 | input_uv = vec2(gl_VertexID & 1, gl_VertexID >> 1) * 2.0; 45 | // Note: V flipped because our textures are flipped in memory 46 | gl_Position = vec4(input_uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0, 1); 47 | }"#, 48 | ); 49 | 50 | let vs = make_shader(gl, gl::VERTEX_SHADER, once(&source)) 51 | .expect("Vertex shader failed to compile"); 52 | 53 | Self { 54 | shaders: Default::default(), 55 | vs_handle: vs, 56 | lazy_cache: lazy_cache.clone(), 57 | } 58 | } 59 | 60 | pub fn add_shader(&mut self, path: impl AsRef) -> ShaderKey { 61 | let path = path.as_ref().to_owned(); 62 | let key = ShaderKey::new(&path); 63 | self.shaders.insert( 64 | key.clone(), 65 | CompiledShader::new(PreprocessShader { path }.into_lazy()), 66 | ); 67 | key 68 | } 69 | 70 | pub fn get_shader_gl_handle(&self, shader: &ShaderKey) -> Option { 71 | self.shaders.get(shader).and_then(|shader| shader.gl_handle) 72 | } 73 | 74 | pub fn compile_all(&mut self, gl: &gl::Gl) -> AnyShadersChanged { 75 | let mut any_shaders_changed = AnyShadersChanged::No; 76 | 77 | let ps_postamble = shader_prepper::SourceChunk::from_file_source( 78 | "postamble", 79 | r#" 80 | void main() { 81 | SHADER_MAIN_FN 82 | } 83 | "#, 84 | ); 85 | 86 | for shader in self.shaders.values_mut() { 87 | if !shader.preprocessed_ps.is_up_to_date() { 88 | let handle: anyhow::Result = 89 | smol::block_on(shader.preprocessed_ps.eval(&self.lazy_cache)) 90 | .context("Preprocessing") 91 | .and_then(|ps_src| { 92 | let sources = ps_src.source.iter().chain(once(&ps_postamble)); 93 | 94 | make_shader(gl, gl::FRAGMENT_SHADER, sources) 95 | }) 96 | .context("Compiling the pixel shader") 97 | .and_then(|ps| make_program(gl, &[self.vs_handle, ps])); 98 | 99 | match handle { 100 | Ok(handle) => { 101 | log::info!("Shader compiled."); 102 | shader.gl_handle = Some(handle); 103 | any_shaders_changed = AnyShadersChanged::Yes; 104 | } 105 | Err(err) => log::error!("Shader failed to compile: {:?}", err), 106 | } 107 | } 108 | } 109 | 110 | any_shaders_changed 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/texture.rs: -------------------------------------------------------------------------------- 1 | use gl::types::{GLenum, GLuint}; 2 | 3 | use crate::image_loading::ImageRgb32f; 4 | 5 | #[derive(Clone, Copy)] 6 | pub struct Texture { 7 | pub ty: GLenum, // gl::TEXTURE_2D 8 | pub size: [usize; 2], 9 | pub id: GLuint, 10 | pub internal_format: GLenum, 11 | } 12 | 13 | impl Texture { 14 | pub fn new_2d(gl: &gl::Gl, image: &ImageRgb32f) -> Self { 15 | let ty = gl::TEXTURE_2D; 16 | let internal_format = gl::RGB32F; 17 | 18 | let res = unsafe { 19 | let mut texture_id = 0; 20 | gl.GenTextures(1, &mut texture_id); 21 | gl.BindTexture(ty, texture_id); 22 | gl.TexStorage2D( 23 | ty, 24 | 1, 25 | internal_format, 26 | image.size[0] as _, 27 | image.size[1] as _, 28 | ); 29 | 30 | let mut pbo = 0; 31 | gl.GenBuffers(1, &mut pbo); 32 | 33 | // Bind the PBO 34 | gl.BindBuffer(gl::PIXEL_UNPACK_BUFFER, pbo); 35 | 36 | //let t0 = std::time::Instant::now(); 37 | let data_size: usize = image.data.len() * std::mem::size_of::(); 38 | 39 | gl.BufferStorage( 40 | gl::PIXEL_UNPACK_BUFFER, 41 | data_size as _, 42 | std::ptr::null(), 43 | gl::MAP_READ_BIT 44 | | gl::MAP_WRITE_BIT 45 | | gl::MAP_PERSISTENT_BIT 46 | | gl::MAP_COHERENT_BIT, 47 | ); 48 | 49 | let mapped = gl.MapNamedBufferRange( 50 | pbo, 51 | 0, 52 | data_size as _, 53 | gl::MAP_WRITE_BIT | gl::MAP_PERSISTENT_BIT | gl::MAP_COHERENT_BIT, 54 | ); 55 | assert_ne!(mapped, std::ptr::null_mut()); 56 | 57 | // Upload 58 | let mapped_slice = std::slice::from_raw_parts_mut(mapped as *mut u8, data_size); 59 | mapped_slice.copy_from_slice(std::slice::from_raw_parts( 60 | std::mem::transmute(image.data.as_ptr()), 61 | data_size, 62 | )); 63 | 64 | gl.TexSubImage2D( 65 | ty, 66 | 0, 67 | 0, 68 | 0, 69 | image.size[0] as _, 70 | image.size[1] as _, 71 | gl::RGB, 72 | gl::FLOAT, 73 | std::ptr::null(), 74 | ); 75 | // println!("Uploading the texture to the GPU took {:?}", t0.elapsed()); 76 | 77 | gl.DeleteBuffers(1, &pbo); 78 | 79 | gl.TexParameteri(ty, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32); 80 | gl.TexParameteri(ty, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32); 81 | gl.TexParameteri(ty, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32); 82 | gl.TexParameteri(ty, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32); 83 | 84 | Texture { 85 | ty, 86 | id: texture_id, 87 | size: image.size, 88 | internal_format, 89 | } 90 | }; 91 | 92 | res 93 | } 94 | 95 | pub fn new_1d(gl: &gl::Gl, width: u32, internal_format: GLenum) -> Self { 96 | let ty = gl::TEXTURE_1D; 97 | 98 | unsafe { 99 | let mut texture_id = 0; 100 | gl.GenTextures(1, &mut texture_id); 101 | gl.BindTexture(ty, texture_id); 102 | gl.TexStorage1D(ty, 1, internal_format, width as _); 103 | gl.TexParameteri(ty, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32); 104 | gl.TexParameteri(ty, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32); 105 | 106 | // TODO: make configurable 107 | gl.TexParameteri(ty, gl::TEXTURE_WRAP_S, gl::REPEAT as i32); 108 | 109 | Texture { 110 | ty, 111 | id: texture_id, 112 | size: [width as _, 1], 113 | internal_format, 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/lut_lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use gl::types::*; 3 | use std::path::PathBuf; 4 | use std::sync::Arc; 5 | use turbosloth::*; 6 | 7 | use crate::shader::*; 8 | use crate::texture::Texture; 9 | 10 | #[derive(Hash, PartialEq, Eq, Clone, PartialOrd, Ord)] 11 | pub struct LutDesc { 12 | pub width: u32, 13 | pub internal_format: GLenum, 14 | pub name: String, 15 | pub shader_path: PathBuf, 16 | } 17 | 18 | pub struct LutHandle(usize); 19 | 20 | struct LutState { 21 | desc: LutDesc, 22 | shader: CompiledShader, 23 | texture: Texture, 24 | } 25 | 26 | struct CompiledShader { 27 | preprocessed: turbosloth::Lazy, 28 | gl_handle: Option, 29 | } 30 | 31 | impl CompiledShader { 32 | fn new(preprocessed: turbosloth::Lazy) -> Self { 33 | Self { 34 | preprocessed, 35 | gl_handle: None, 36 | } 37 | } 38 | } 39 | 40 | pub struct LutLib { 41 | luts: Vec, 42 | lazy_cache: Arc, 43 | } 44 | 45 | pub enum AnyLutsChanged { 46 | Yes, 47 | No, 48 | } 49 | 50 | impl LutLib { 51 | pub fn new(lazy_cache: &Arc) -> Self { 52 | Self { 53 | luts: Default::default(), 54 | lazy_cache: lazy_cache.clone(), 55 | } 56 | } 57 | 58 | pub fn add_lut(&mut self, lut_desc: LutDesc, gl: &gl::Gl) -> LutHandle { 59 | let handle = LutHandle(self.luts.len()); 60 | let texture = Texture::new_1d(gl, lut_desc.width, lut_desc.internal_format); 61 | assert_eq!(texture.internal_format, lut_desc.internal_format); 62 | 63 | self.luts.push(LutState { 64 | shader: CompiledShader::new( 65 | PreprocessShader { 66 | path: lut_desc.shader_path.clone(), 67 | } 68 | .into_lazy(), 69 | ), 70 | texture, 71 | desc: lut_desc, 72 | }); 73 | handle 74 | } 75 | 76 | pub fn compile_all(&mut self, gl: &gl::Gl) -> AnyLutsChanged { 77 | let mut any_shaders_changed = AnyLutsChanged::No; 78 | 79 | for lut in &mut self.luts { 80 | if !lut.shader.preprocessed.is_up_to_date() { 81 | let handle: anyhow::Result = 82 | smol::block_on(lut.shader.preprocessed.eval(&self.lazy_cache)) 83 | .context("Preprocessing") 84 | .and_then(|src| make_shader(gl, gl::COMPUTE_SHADER, src.source.iter())) 85 | .context("Compiling the compute shader") 86 | .and_then(|cs| make_program(gl, &[cs])); 87 | 88 | match handle { 89 | Ok(handle) => { 90 | log::info!("Shader compiled."); 91 | lut.shader.gl_handle = Some(handle); 92 | any_shaders_changed = AnyLutsChanged::Yes; 93 | 94 | Self::compute_lut(lut, gl); 95 | } 96 | Err(err) => log::error!("Shader failed to compile: {:?}", err), 97 | } 98 | } 99 | } 100 | 101 | any_shaders_changed 102 | } 103 | 104 | pub fn iter(&self) -> impl Iterator { 105 | self.luts.iter().map(|lut| (&lut.desc, &lut.texture)) 106 | } 107 | 108 | fn compute_lut(lut: &mut LutState, gl: &gl::Gl) { 109 | let shader_program = lut.shader.gl_handle.unwrap(); 110 | 111 | unsafe { 112 | gl.UseProgram(shader_program); 113 | 114 | { 115 | let loc = 116 | gl.GetUniformLocation(shader_program, "output_image\0".as_ptr() as *const i8); 117 | if loc != -1 { 118 | let img_unit = 0; 119 | gl.Uniform1i(loc, img_unit); 120 | gl.BindImageTexture( 121 | img_unit as _, 122 | lut.texture.id, 123 | 0, 124 | gl::FALSE, 125 | 0, 126 | gl::READ_WRITE, 127 | lut.texture.internal_format, 128 | ); 129 | } 130 | } 131 | 132 | let mut work_group_size: [i32; 3] = [0, 0, 0]; 133 | gl.GetProgramiv( 134 | shader_program, 135 | gl::COMPUTE_WORK_GROUP_SIZE, 136 | &mut work_group_size[0], 137 | ); 138 | 139 | fn div_up(a: u32, b: u32) -> u32 { 140 | (a + b - 1) / b 141 | } 142 | 143 | gl.DispatchCompute(div_up(lut.desc.width, work_group_size[0] as u32), 1, 1); 144 | gl.UseProgram(0); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod app_state; 2 | mod fbo; 3 | mod file; 4 | mod image_loading; 5 | mod image_pool; 6 | mod lut_lib; 7 | mod setup; 8 | mod shader; 9 | mod shader_lib; 10 | mod texture; 11 | 12 | use std::path::PathBuf; 13 | 14 | use anyhow::Context; 15 | use app_state::*; 16 | use glutin::event::{Event, WindowEvent}; 17 | use glutin::event_loop::{ControlFlow, EventLoop}; 18 | use glutin::window::WindowBuilder; 19 | use glutin::ContextBuilder; 20 | 21 | use structopt::StructOpt; 22 | 23 | #[derive(StructOpt)] 24 | #[structopt(name = "notorious6", about = "HDR display mapping stuffs.")] 25 | struct Opt { 26 | #[structopt( 27 | parse(from_os_str), 28 | default_value = "img", 29 | help = "A single file or a folder containing .exr or .hdr images" 30 | )] 31 | input: PathBuf, 32 | 33 | #[structopt(subcommand)] 34 | cmd: Option, 35 | } 36 | 37 | #[derive(StructOpt)] 38 | #[structopt(settings = &[structopt::clap::AppSettings::AllowNegativeNumbers])] 39 | struct BatchCmd { 40 | /// Name of the shader, without the path or file extension, e.g. "linear" 41 | #[structopt(long)] 42 | shader: String, 43 | 44 | /// Min EV 45 | #[structopt(long)] 46 | ev_min: f64, 47 | 48 | /// Max EV 49 | #[structopt(long)] 50 | ev_max: f64, 51 | 52 | /// EV step 53 | #[structopt(long, default_value = "1.0")] 54 | ev_step: f64, 55 | } 56 | 57 | #[derive(StructOpt)] 58 | enum Command { 59 | /// Runs an interactive image viewer (default) 60 | View, 61 | /// Batch-processes images 62 | Batch(BatchCmd), 63 | } 64 | 65 | fn main() -> anyhow::Result<()> { 66 | let opt = Opt::from_args(); 67 | 68 | simple_logger::SimpleLogger::new() 69 | .with_level(log::LevelFilter::Info) 70 | .with_utc_timestamps() 71 | .init() 72 | .unwrap(); 73 | 74 | let el = EventLoop::new(); 75 | let wb = WindowBuilder::new().with_title("notorious6"); 76 | 77 | let windowed_context = ContextBuilder::new() 78 | .with_gl_debug_flag(true) 79 | .with_gl_profile(glutin::GlProfile::Core) 80 | .build_windowed(wb, &el) 81 | .unwrap(); 82 | 83 | let windowed_context = unsafe { windowed_context.make_current().unwrap() }; 84 | let gl = gl::Gl::load_with(|symbol| windowed_context.get_proc_address(symbol) as *const _); 85 | setup::setup_basic_gl_state(&gl); 86 | 87 | let mut state = AppState::new(opt.input, &gl)?; 88 | let mut exit_upon_batch_completion = false; 89 | 90 | if let Some(Command::Batch(BatchCmd { 91 | shader, 92 | ev_min, 93 | ev_max, 94 | ev_step, 95 | })) = opt.cmd 96 | { 97 | state 98 | .request_batch(ev_min, ev_max, ev_step, &shader) 99 | .context("state.request_batch")?; 100 | exit_upon_batch_completion = true; 101 | } 102 | 103 | el.run(move |event, _, control_flow| { 104 | *control_flow = ControlFlow::Poll; 105 | 106 | match event { 107 | Event::LoopDestroyed => {} 108 | Event::WindowEvent { event, .. } => match event { 109 | WindowEvent::Resized(physical_size) => windowed_context.resize(physical_size), 110 | WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, 111 | _ => { 112 | if matches!(state.handle_window_event(event), NeedsRedraw::Yes) { 113 | windowed_context.window().request_redraw(); 114 | } 115 | } 116 | }, 117 | Event::MainEventsCleared => { 118 | if matches!(state.update(&gl), NeedsRedraw::Yes) { 119 | windowed_context.window().request_redraw(); 120 | } 121 | 122 | windowed_context.window().set_title(&format!( 123 | "{} | EV {:2.2} | {}", 124 | state 125 | .current_image_name() 126 | .unwrap_or_else(|| "notorious6".to_owned()), 127 | state.ev, 128 | state.current_shader() 129 | )); 130 | 131 | match state.process_batched_requests(&gl) { 132 | Ok(_) => (), 133 | Err(err) => { 134 | log::error!("Batch processing error: {:?}", err); 135 | *control_flow = ControlFlow::Exit 136 | } 137 | } 138 | 139 | if state.pending_image_capture.is_empty() && exit_upon_batch_completion { 140 | *control_flow = ControlFlow::Exit; 141 | } 142 | } 143 | Event::RedrawRequested(_) => { 144 | let window_size = windowed_context.window().inner_size(); 145 | state.draw_frame( 146 | &gl, 147 | [window_size.width as usize, window_size.height as usize], 148 | ); 149 | windowed_context.swap_buffers().unwrap(); 150 | } 151 | _ => (), 152 | } 153 | }); 154 | } 155 | -------------------------------------------------------------------------------- /shaders/inc/helmholtz_kohlrausch.hlsl: -------------------------------------------------------------------------------- 1 | #ifndef NOTORIOUS6_HELMHOLTZ_KOHLRAUSCH_HLSL 2 | #define NOTORIOUS6_HELMHOLTZ_KOHLRAUSCH_HLSL 3 | 4 | #include "math.hlsl" 5 | #include "srgb.hlsl" 6 | 7 | // Helmholtz-Kohlrausch adjustment methods 8 | #define HK_ADJUSTMENT_METHOD_NONE 0 9 | #define HK_ADJUSTMENT_METHOD_NAYATANI 1 10 | #define HK_ADJUSTMENT_METHOD_CUSTOM_G0 2 11 | 12 | // Adapting luminance (L_a) used for the H-K adjustment. 20 cd/m2 was used in Sanders and Wyszecki (1964) 13 | #define HK_ADAPTING_LUMINANCE 20 14 | 15 | // Choose the method for performing the H-K adjustment 16 | #ifndef HK_ADJUSTMENT_METHOD 17 | #define HK_ADJUSTMENT_METHOD HK_ADJUSTMENT_METHOD_CUSTOM_G0 18 | #endif 19 | 20 | 21 | // Helmholtz-Kohlrausch 22 | // From https://github.com/ilia3101/HKE 23 | // Based on Nayatani, Y. (1997). Simple estimation methods for the Helmholtz-Kohlrausch effect 24 | 25 | // `uv`: CIE LUV u' and v' 26 | // `adapt_lum`: adating luminance (L_a) 27 | float hk_lightness_adjustment_multiplier_nayatani(float2 uv, float adapt_lum) { 28 | const float2 d65_uv = CIE_xyY_xy_to_LUV_uv(white_D65_xy); 29 | const float u_white = d65_uv[0]; 30 | const float v_white = d65_uv[1]; 31 | 32 | uv -= float2(u_white, v_white); 33 | 34 | float theta = atan2(uv[1], uv[0]); 35 | 36 | float q = 37 | - 0.01585 38 | - 0.03016 * cos(theta) - 0.04556 * cos(2 * theta) 39 | - 0.02667 * cos(3 * theta) - 0.00295 * cos(4 * theta) 40 | + 0.14592 * sin(theta) + 0.05084 * sin(2 * theta) 41 | - 0.01900 * sin(3 * theta) - 0.00764 * sin(4 * theta); 42 | 43 | float kbr = 0.2717 * (6.469 + 6.362 * pow(adapt_lum, 0.4495)) / (6.469 + pow(adapt_lum, 0.4495)); 44 | float suv = 13.0 * length(uv); 45 | 46 | return 1.0 + (-0.1340 * q + 0.0872 * kbr) * suv; 47 | } 48 | 49 | // Heavily modified from Nayatani 50 | // `uv`: CIE LUV u' and v' 51 | float XYZ_to_hk_luminance_multiplier_custom_g0(float3 XYZ) { 52 | float2 uv = CIE_XYZ_to_LUV_uv(XYZ); 53 | const float2 d65_uv = CIE_xyY_xy_to_LUV_uv(white_D65_xy); 54 | const float u_white = d65_uv[0]; 55 | const float v_white = d65_uv[1]; 56 | uv -= float2(u_white, v_white); 57 | 58 | const float theta = atan2(uv[1], uv[0]); 59 | 60 | // ---- 61 | // Custom q function eyeballed to achieve the greyness boundary condition in sRGB color sweeps. 62 | const uint SAMPLE_COUNT = 16; 63 | static const float samples[] = { 64 | -0.006, // greenish cyan 65 | -0.021, // greenish cyan 66 | -0.033, // cyan 67 | -0.009, // cyan 68 | 0.14, // blue 69 | 0.114, // purplish-blue 70 | 0.111, // magenta 71 | 0.1005, // magenta 72 | 0.069, // purplish-red 73 | 0.0135, // red 74 | -0.045, // orange 75 | -0.075, // reddish-yellow 76 | -0.075, // yellow 77 | -0.03, // yellowish-green 78 | 0.006, // green 79 | 0.006, // green 80 | }; 81 | 82 | const float t = (theta / M_PI) * 0.5 + 0.5; 83 | const uint i0 = uint(floor(t * SAMPLE_COUNT)) % SAMPLE_COUNT; 84 | const uint i1 = (i0 + 1) % SAMPLE_COUNT; 85 | const float q0 = samples[(i0 + SAMPLE_COUNT - 1) % SAMPLE_COUNT]; 86 | const float q1 = samples[i0]; 87 | const float q2 = samples[i1]; 88 | const float q3 = samples[(i1 + 1) % SAMPLE_COUNT]; 89 | const float interp = (t - float(i0) / SAMPLE_COUNT) * SAMPLE_COUNT; 90 | const float q = catmull_rom(interp, q0, q1, q2, q3); 91 | // ---- 92 | 93 | const float adapt_lum = 20.0; 94 | const float kbr = 0.2717 * (6.469 + 6.362 * pow(adapt_lum, 0.4495)) / (6.469 + pow(adapt_lum, 0.4495)); 95 | const float suv = 13.0 * length(uv); 96 | 97 | // Nayatani scales _lightness_ which is approximately a cubic root of luminance 98 | // To scale luminance, we need the third power of that multiplier. 99 | const float mult_cbrt = 1.0 + (q + 0.0872 * kbr) * suv; 100 | return mult_cbrt * mult_cbrt * mult_cbrt; 101 | } 102 | 103 | struct HelmholtzKohlrauschEffect { 104 | #if HK_ADJUSTMENT_METHOD == HK_ADJUSTMENT_METHOD_CUSTOM_G0 105 | float mult; 106 | #else 107 | float omg_glsl_y_u_no_empty_struct; 108 | #endif 109 | }; 110 | 111 | HelmholtzKohlrauschEffect hk_from_sRGB(float3 stimulus) { 112 | HelmholtzKohlrauschEffect res; 113 | #if HK_ADJUSTMENT_METHOD == HK_ADJUSTMENT_METHOD_CUSTOM_G0 114 | res.mult = XYZ_to_hk_luminance_multiplier_custom_g0(sRGB_to_XYZ(stimulus)); 115 | #endif 116 | return res; 117 | } 118 | 119 | float srgb_to_equivalent_luminance(HelmholtzKohlrauschEffect hk, float3 stimulus) { 120 | #if HK_ADJUSTMENT_METHOD == HK_ADJUSTMENT_METHOD_NAYATANI 121 | const float luminance = sRGB_to_luminance(stimulus); 122 | const float2 uv = CIE_XYZ_to_LUV_uv(sRGB_to_XYZ(stimulus)); 123 | const float luv_brightness = luminance_to_LUV_L(luminance); 124 | const float mult = hk_lightness_adjustment_multiplier_nayatani(uv, HK_ADAPTING_LUMINANCE); 125 | return LUV_L_to_luminance(luv_brightness * mult); 126 | #elif HK_ADJUSTMENT_METHOD == HK_ADJUSTMENT_METHOD_CUSTOM_G0 127 | return hk.mult * sRGB_to_XYZ(stimulus).y; 128 | #elif HK_ADJUSTMENT_METHOD == HK_ADJUSTMENT_METHOD_NONE 129 | return sRGB_to_luminance(stimulus); 130 | #endif 131 | } 132 | 133 | #endif // NOTORIOUS6_HELMHOLTZ_KOHLRAUSCH_HLSL 134 | -------------------------------------------------------------------------------- /shaders/inc/ictcp.hlsl: -------------------------------------------------------------------------------- 1 | #ifndef NOTORIOUS6_ICTCP_HLSL 2 | #define NOTORIOUS6_ICTCP_HLSL 3 | 4 | // From https://www.shadertoy.com/view/ldKcz3 5 | 6 | static const float PQ_C1 = 0.8359375f; // 3424.f / 4096.f; 7 | static const float PQ_C2 = 18.8515625f; // 2413.f / 4096.f * 32.f; 8 | static const float PQ_C3 = 18.6875f; // 2392.f / 4096.f * 32.f; 9 | static const float PQ_M1 = 0.159301758125f; // 2610.f / 4096.f / 4; 10 | static const float PQ_M2 = 78.84375f; // 2523.f / 4096.f * 128.f; 11 | static const float PQ_MAX = 10000.0; 12 | 13 | // PQ_OETF - Optical-Electro Transfer Function 14 | 15 | float linear_to_PQ( float linearValue ) 16 | { 17 | float L = linearValue / PQ_MAX; 18 | float Lm1 = pow( L, PQ_M1 ); 19 | float X = ( PQ_C1 + PQ_C2 * Lm1 ) / ( 1.0f + PQ_C3 * Lm1 ); 20 | float pqValue = pow( X, PQ_M2 ); 21 | return pqValue; 22 | } 23 | 24 | float3 linear_to_PQ( float3 linearValues ) 25 | { 26 | float3 L = linearValues / PQ_MAX; 27 | float3 Lm1 = pow( max(0.0.xxx, L.xyz), PQ_M1.xxx ); 28 | float3 X = ( PQ_C1 + PQ_C2 * Lm1 ) / ( 1.0f + PQ_C3 * Lm1 ); 29 | float3 pqValues = pow( max(0.0.xxx, X), PQ_M2.xxx ); 30 | return pqValues; 31 | } 32 | 33 | // PQ_EOTF - Electro-Optical Transfer Function 34 | 35 | float PQ_to_linear( float pqValue ) 36 | { 37 | float M = PQ_C2 - PQ_C3 * pow( max(0.0, pqValue), 1.0 / PQ_M2 ); 38 | float N = max( pow( max(0.0, pqValue), 1.0f / PQ_M2 ) - PQ_C1, 0.0f ); 39 | float L = pow( N / M, 1.0f / PQ_M1 ); 40 | float linearValue = L * PQ_MAX; 41 | return linearValue; 42 | } 43 | 44 | float3 PQ_to_linear( float3 pqValues ) 45 | { 46 | float3 M = PQ_C2 - PQ_C3 * pow( max(0.0.xxx, pqValues), 1. / PQ_M2.xxx ); 47 | float3 N = max( pow( max(0.0.xxx, pqValues), 1. / PQ_M2.xxx ) - PQ_C1, 0.0f ); 48 | float3 L = pow( max(0.0.xxx, N / M), 1. / PQ_M1.xxx ); 49 | float3 linearValues = L * PQ_MAX; 50 | return linearValues; 51 | } 52 | 53 | 54 | // BT.709 <-> BT.2020 Primaries 55 | 56 | float3 BT709_to_BT2020( float3 linearBT709 ) 57 | { 58 | float3 linearBT2020 = mul( 59 | float3x3( 60 | 0.6274, 0.3293, 0.0433, 61 | 0.0691, 0.9195, 0.0114, 62 | 0.0164, 0.0880, 0.8956 63 | ), 64 | linearBT709.rgb); 65 | return linearBT2020; 66 | } 67 | 68 | float3 BT2020_to_BT709( float3 linearBT2020 ) 69 | { 70 | float3 linearBT709 = mul( 71 | float3x3( 72 | 1.6605, -0.5877, -0.0728, 73 | -0.1246, 1.1330, -0.0084, 74 | -0.0182, -0.1006, 1.1187 75 | ), 76 | linearBT2020.rgb); 77 | return linearBT709; 78 | } 79 | 80 | // LMS <-> BT2020 81 | 82 | float3 BT2020_to_LMS( float3 linearBT2020 ) 83 | { 84 | float R = linearBT2020.r; 85 | float G = linearBT2020.g; 86 | float B = linearBT2020.b; 87 | 88 | float L = 0.4121093750000000f * R + 0.5239257812500000f * G + 0.0639648437500000f * B; 89 | float M = 0.1667480468750000f * R + 0.7204589843750000f * G + 0.1127929687500000f * B; 90 | float S = 0.0241699218750000f * R + 0.0754394531250000f * G + 0.9003906250000000f * B; 91 | 92 | float3 linearLMS = float3(L, M, S); 93 | return linearLMS; 94 | } 95 | 96 | float3 LMS_to_BT2020( float3 linearLMS ) 97 | { 98 | float L = linearLMS.x; 99 | float M = linearLMS.y; 100 | float S = linearLMS.z; 101 | 102 | float R = 3.4366066943330793f * L - 2.5064521186562705f * M + 0.0698454243231915f * S; 103 | float G = -0.7913295555989289f * L + 1.9836004517922909f * M - 0.1922708961933620f * S; 104 | float B = -0.0259498996905927f * L - 0.0989137147117265f * M + 1.1248636144023192f * S; 105 | 106 | float3 linearBT2020 = float3(R, G, B); 107 | return linearBT2020; 108 | } 109 | 110 | // Misc. Color Space Conversion 111 | 112 | // ICtCp <-> PQ LMS 113 | 114 | float3 PQ_LMS_to_ICtCp( float3 PQ_LMS ) 115 | { 116 | float L = PQ_LMS.x; 117 | float M = PQ_LMS.y; 118 | float S = PQ_LMS.z; 119 | 120 | float I = 0.5f * L + 0.5f * M; 121 | float Ct = 1.613769531250000f * L - 3.323486328125000f * M + 1.709716796875000f * S; 122 | float Cp = 4.378173828125000f * L - 4.245605468750000f * M - 0.132568359375000f * S; 123 | 124 | float3 ICtCp = float3(I, Ct, Cp); 125 | return ICtCp; 126 | } 127 | 128 | float3 ICtCp_to_PQ_LMS( float3 ICtCp ) 129 | { 130 | float I = ICtCp.x; 131 | float Ct = ICtCp.y; 132 | float Cp = ICtCp.z; 133 | 134 | float L = I + 0.00860903703793281f * Ct + 0.11102962500302593f * Cp; 135 | float M = I - 0.00860903703793281f * Ct - 0.11102962500302593f * Cp; 136 | float S = I + 0.56003133571067909f * Ct - 0.32062717498731880f * Cp; 137 | 138 | float3 PQ_LMS = float3(L, M, S); 139 | return PQ_LMS; 140 | } 141 | 142 | // Linear BT2020 <-> ICtCp 143 | // 144 | // https://www.dolby.com/us/en/technologies/dolby-vision/ictcp-white-paper.pdf 145 | // http://www.jonolick.com/home/hdr-videos-part-2-colors 146 | 147 | float3 BT2020_to_ICtCp( float3 linearBT2020 ) 148 | { 149 | float3 LMS = BT2020_to_LMS( linearBT2020 ); 150 | float3 PQ_LMS = linear_to_PQ( LMS ); 151 | float3 ICtCp = PQ_LMS_to_ICtCp( PQ_LMS ); 152 | 153 | return ICtCp; 154 | } 155 | 156 | float3 ICtCp_to_BT2020( float3 ICtCp ) 157 | { 158 | float3 PQ_LMS = ICtCp_to_PQ_LMS( ICtCp ); 159 | float3 LMS = PQ_to_linear( PQ_LMS ); 160 | float3 linearBT2020 = LMS_to_BT2020( LMS ); 161 | return linearBT2020; 162 | } 163 | 164 | // ---------------------------------------------------------------- 165 | 166 | float3 BT709_to_ICtCp( float3 linearBT709 ) 167 | { 168 | float3 linearBT2020 = BT709_to_BT2020(linearBT709); 169 | float3 LMS = BT2020_to_LMS( linearBT2020 ); 170 | float3 PQ_LMS = linear_to_PQ( LMS ); 171 | float3 ICtCp = PQ_LMS_to_ICtCp( PQ_LMS ); 172 | 173 | return ICtCp; 174 | } 175 | 176 | float3 ICtCp_to_BT709( float3 ICtCp ) 177 | { 178 | float3 PQ_LMS = ICtCp_to_PQ_LMS( ICtCp ); 179 | float3 LMS = PQ_to_linear( PQ_LMS ); 180 | float3 linearBT2020 = LMS_to_BT2020( LMS ); 181 | float3 linearBT709 = BT2020_to_BT709( linearBT2020 ); 182 | return linearBT709; 183 | } 184 | 185 | #endif // NOTORIOUS6_ICTCP_HLSL 186 | -------------------------------------------------------------------------------- /src/shader.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use bytes::Bytes; 3 | use gl::types::*; 4 | use relative_path::RelativePathBuf; 5 | use shader_prepper::gl_compiler::{compile_shader, ShaderCompilerOutput}; 6 | use std::sync::Arc; 7 | use std::{ffi::CString, path::PathBuf}; 8 | use turbosloth::*; 9 | 10 | #[derive(Clone, Hash)] 11 | pub struct PreprocessShader { 12 | pub path: PathBuf, 13 | } 14 | 15 | pub struct PreprocessedShader { 16 | pub name: String, 17 | pub source: Vec, 18 | } 19 | 20 | #[async_trait] 21 | impl LazyWorker for PreprocessShader { 22 | type Output = anyhow::Result; 23 | 24 | async fn run(self, ctx: RunContext) -> Self::Output { 25 | let name = self 26 | .path 27 | .file_stem() 28 | .map(|s| s.to_string_lossy().to_string()) 29 | .unwrap_or_else(|| "unknown".to_string()); 30 | 31 | let file_path = self.path.to_str().unwrap().to_owned(); 32 | let source = shader_prepper::process_file( 33 | &file_path, 34 | &mut ShaderIncludeProvider { ctx }, 35 | String::new(), 36 | ); 37 | let source = source.map_err(|err| anyhow::anyhow!("{}", err))?; 38 | 39 | Ok(PreprocessedShader { source, name }) 40 | } 41 | } 42 | 43 | struct ShaderIncludeProvider { 44 | ctx: RunContext, 45 | } 46 | 47 | impl shader_prepper::IncludeProvider for ShaderIncludeProvider { 48 | type IncludeContext = String; 49 | 50 | fn get_include( 51 | &mut self, 52 | path: &str, 53 | parent_file: &Self::IncludeContext, 54 | ) -> std::result::Result< 55 | (String, Self::IncludeContext), 56 | shader_prepper::BoxedIncludeProviderError, 57 | > { 58 | let resolved_path = if let Some('/') = path.chars().next() { 59 | path.to_owned() 60 | } else { 61 | let mut folder: RelativePathBuf = parent_file.into(); 62 | folder.pop(); 63 | folder.join(path).as_str().to_string() 64 | }; 65 | 66 | let blob: Arc = smol::block_on( 67 | crate::file::LoadFile::new(&resolved_path) 68 | .with_context(|| format!("Failed loading shader include {}", path))? 69 | .into_lazy() 70 | .eval(&self.ctx), 71 | )?; 72 | 73 | Ok((String::from_utf8(blob.to_vec())?, resolved_path)) 74 | } 75 | } 76 | 77 | pub(crate) fn make_shader<'chunk>( 78 | gl: &gl::Gl, 79 | shader_type: GLenum, 80 | sources: impl Iterator, 81 | ) -> anyhow::Result { 82 | unsafe { 83 | let compiled_shader = compile_shader(sources, |sources| { 84 | let handle = gl.CreateShader(shader_type); 85 | 86 | let (source_lengths, source_ptrs): (Vec, Vec<*const GLchar>) = sources 87 | .iter() 88 | .map(|s| (s.len() as GLint, s.as_ptr() as *const GLchar)) 89 | .unzip(); 90 | 91 | gl.ShaderSource( 92 | handle, 93 | source_ptrs.len() as i32, 94 | source_ptrs.as_ptr(), 95 | source_lengths.as_ptr(), 96 | ); 97 | gl.CompileShader(handle); 98 | 99 | let mut shader_ok: gl::types::GLint = 1; 100 | gl.GetShaderiv(handle, gl::COMPILE_STATUS, &mut shader_ok); 101 | 102 | if shader_ok != 1 { 103 | let mut log_len: gl::types::GLint = 0; 104 | gl.GetShaderiv(handle, gl::INFO_LOG_LENGTH, &mut log_len); 105 | 106 | let log_str = CString::from_vec_unchecked(vec![b'\0'; (log_len + 1) as usize]); 107 | gl.GetShaderInfoLog( 108 | handle, 109 | log_len, 110 | std::ptr::null_mut(), 111 | log_str.as_ptr() as *mut gl::types::GLchar, 112 | ); 113 | 114 | gl.DeleteShader(handle); 115 | 116 | ShaderCompilerOutput { 117 | artifact: None, 118 | log: Some(log_str.to_string_lossy().into_owned()), 119 | } 120 | } else { 121 | ShaderCompilerOutput { 122 | artifact: Some(handle), 123 | log: None, 124 | } 125 | } 126 | }); 127 | 128 | if let Some(shader) = compiled_shader.artifact { 129 | if let Some(log) = compiled_shader.log { 130 | log::info!("Shader compiler output: {}", log); 131 | } 132 | Ok(shader) 133 | } else { 134 | anyhow::bail!( 135 | "Shader failed to compile: {}", 136 | compiled_shader.log.as_deref().unwrap_or("Unknown error") 137 | ); 138 | } 139 | } 140 | } 141 | 142 | pub(crate) fn make_program(gl: &gl::Gl, shaders: &[u32]) -> anyhow::Result { 143 | unsafe { 144 | let handle = gl.CreateProgram(); 145 | for &shader in shaders.iter() { 146 | gl.AttachShader(handle, shader); 147 | } 148 | 149 | gl.LinkProgram(handle); 150 | 151 | let mut program_ok: gl::types::GLint = 1; 152 | gl.GetProgramiv(handle, gl::LINK_STATUS, &mut program_ok); 153 | 154 | if program_ok != 1 { 155 | let mut log_len: gl::types::GLint = 0; 156 | gl.GetProgramiv(handle, gl::INFO_LOG_LENGTH, &mut log_len); 157 | 158 | let log_str = CString::from_vec_unchecked(vec![b'\0'; (log_len + 1) as usize]); 159 | 160 | gl.GetProgramInfoLog( 161 | handle, 162 | log_len, 163 | std::ptr::null_mut(), 164 | log_str.as_ptr() as *mut gl::types::GLchar, 165 | ); 166 | 167 | let log_str = log_str.to_string_lossy().into_owned(); 168 | 169 | gl.DeleteProgram(handle); 170 | anyhow::bail!("Shader failed to link: {}", log_str); 171 | } else { 172 | Ok(handle) 173 | } 174 | } 175 | } 176 | 177 | #[derive(Hash, PartialEq, Eq, Clone, PartialOrd, Ord)] 178 | pub struct ShaderKey { 179 | path: PathBuf, 180 | } 181 | 182 | impl ShaderKey { 183 | pub fn new(path: impl Into) -> Self { 184 | Self { path: path.into() } 185 | } 186 | 187 | pub fn name(&self) -> String { 188 | self.path 189 | .file_stem() 190 | .map(|s| s.to_string_lossy().to_string()) 191 | .unwrap_or_else(|| "unknown".to_string()) 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /shaders/inc/bezold_brucke.hlsl: -------------------------------------------------------------------------------- 1 | #include "xyz.hlsl" 2 | #include "standard_observer.hlsl" 3 | 4 | // Assigns uniform angles to hue in CIE 1931, however that doesn't mean much. 5 | #define BB_LUT_LUT_MAPPING_ANGULAR 0 6 | 7 | // Probably the best bang/buck. 8 | #define BB_LUT_LUT_MAPPING_QUAD 1 9 | 10 | // Not really an improvement. 11 | #define BB_LUT_LUT_MAPPING_ROTATED_QUAD 2 12 | #define BB_LUT_LUT_MAPPING_ROTATED_QUAD_ANGLE 0.8595 13 | 14 | // Select the encoding method 15 | #define BB_LUT_LUT_MAPPING BB_LUT_LUT_MAPPING_QUAD 16 | 17 | 18 | float bb_xy_white_offset_to_lut_coord(float2 offset) { 19 | #if BB_LUT_LUT_MAPPING == BB_LUT_LUT_MAPPING_ANGULAR 20 | return frac((atan2(offset.y, offset.x) / M_PI) * 0.5); 21 | #elif BB_LUT_LUT_MAPPING == BB_LUT_LUT_MAPPING_QUAD 22 | offset /= max(abs(offset.x), abs(offset.y)); 23 | float sgn = (offset.x + offset.y) > 0.0 ? 1.0 : -1.0; 24 | // NOTE: needs a `frac` if the sampler's U wrap mode is not REPEAT. 25 | return sgn * (0.125 * (offset.x - offset.y) + 0.25); 26 | #elif BB_LUT_LUT_MAPPING == BB_LUT_LUT_MAPPING_ROTATED_QUAD 27 | const float angle = BB_LUT_LUT_MAPPING_ROTATED_QUAD_ANGLE; 28 | offset = mul(float2x2(cos(angle), sin(angle), -sin(angle), cos(angle)), offset); 29 | offset /= max(abs(offset.x), abs(offset.y)); 30 | float sgn = (offset.x + offset.y) > 0.0 ? 1.0 : -1.0; 31 | // NOTE: needs a `frac` if the sampler's U wrap mode is not REPEAT. 32 | return sgn * (0.125 * (offset.x - offset.y) + 0.25); 33 | #endif 34 | } 35 | 36 | float2 bb_lut_coord_to_xy_white_offset(float coord) { 37 | #if BB_LUT_LUT_MAPPING == BB_LUT_LUT_MAPPING_ANGULAR 38 | const float theta = coord * M_PI * 2.0; 39 | return float2(cos(theta), sin(theta)); 40 | #elif BB_LUT_LUT_MAPPING == BB_LUT_LUT_MAPPING_QUAD 41 | float side = (coord < 0.5 ? 1.0 : -1.0); 42 | float t = frac(coord * 2); 43 | return side * normalize( 44 | lerp(float2(-1, 1), float2(1, -1), t) 45 | + lerp(float2(0, 0), float2(1, 1), 1 - abs(t - 0.5) * 2) 46 | ); 47 | #elif BB_LUT_LUT_MAPPING == BB_LUT_LUT_MAPPING_ROTATED_QUAD 48 | float side = (coord < 0.5 ? 1.0 : -1.0); 49 | float t = frac(coord * 2); 50 | float2 offset = side * normalize(lerp(float2(-1, 1), float2(1, -1), t) + lerp(float2(0, 0), float2(1, 1), 1 - abs(t - 0.5) * 2)); 51 | const float angle = BB_LUT_LUT_MAPPING_ROTATED_QUAD_ANGLE; 52 | return mul(offset, float2x2(cos(angle), sin(angle), -sin(angle), cos(angle))); 53 | #endif 54 | } 55 | 56 | float XYZ_to_BB_shift_nm(float3 XYZ) { 57 | const float2 white = white_D65_xy; 58 | 59 | const float2 xy = CIE_XYZ_to_xyY(XYZ).xy; 60 | float2 white_offset = xy - white_D65_xy; 61 | float theta = atan2(white_offset.y, white_offset.x); 62 | 63 | // Piece-wise linear match to Pridmore's plot for 10:100 cd/m^2 64 | const uint SAMPLE_COUNT = 26; 65 | static const float2 samples[] = { 66 | float2(0.0, 0), 67 | float2(0.084, -5.0), 68 | float2(0.152, -5.0), 69 | float2(0.2055, -4.0), 70 | float2(0.25, 0.0), 71 | float2(0.265, 2.3), 72 | float2(0.291, 5), 73 | float2(0.31, 6), 74 | float2(0.3285, 6.5), 75 | float2(0.356, 5.4), 76 | float2(0.395, 4.4), 77 | float2(0.4445, 3.93), 78 | float2(0.551, -4.9), 79 | float2(0.585, -6), 80 | float2(0.6065, -6), 81 | float2(0.6133, -3), 82 | float2(0.621, 1.42), 83 | float2(0.6245, 1.9), 84 | float2(0.633, 2.55), 85 | // non-spectral gap 86 | float2(0.92495, 2.55), 87 | float2(0.92525, 3.35), 88 | float2(0.9267, 4.8), 89 | float2(0.93, 6.15), 90 | float2(0.934, 7), 91 | float2(0.942, 5.95), 92 | float2(0.956, 4.0), 93 | }; 94 | 95 | const float t = frac((-theta / M_PI) * 0.5 + 0.61); 96 | 97 | for (int i = 0; i < SAMPLE_COUNT; ++i) { 98 | float2 p0 = samples[i]; 99 | float2 p1 = samples[(i + 1) % SAMPLE_COUNT]; 100 | float interp = (t - p0.x) / frac(p1.x - p0.x + 1); 101 | if (t >= p0.x && interp <= 1.0) { 102 | return lerp(p0.y, p1.y, interp); 103 | } 104 | } 105 | 106 | return 0.0; 107 | } 108 | 109 | // Apply Bezold–Brucke shift to XYZ stimulus. Loosely based on 110 | // "Pridmore, R. W. (1999). Bezold–Brucke hue-shift as functions of luminance level, 111 | // luminance ratio, interstimulus interval and adapting white for aperture and object colors. 112 | // Vision Research, 39(23), 3873–3891. doi:10.1016/s0042-6989(99)00085-1" 113 | float3 bezold_brucke_shift_XYZ_brute_force(float3 XYZ, float amount) { 114 | const float3 xyY = CIE_XYZ_to_xyY(XYZ); 115 | const float white_offset_magnitude = length(xyY.xy - white_D65_xy); 116 | const float bb_shift = XYZ_to_BB_shift_nm(XYZ); 117 | 118 | const float dominant_wavelength = CIE_xy_to_dominant_wavelength(xyY.xy); 119 | if (dominant_wavelength == -1) { 120 | // Non-spectral stimulus. 121 | // We could calculate the shift for the two corner vertices of the gamut, 122 | // then interpolate the shift between them, however the wavelengths 123 | // get so compressed in the xyY space near limits of vision, that 124 | // the shift is effectively nullified. 125 | return XYZ; 126 | } 127 | 128 | float3 shifted_xyY = wavelength_to_xyY(dominant_wavelength + bb_shift * amount); 129 | float3 adjutsed_xyY = 130 | float3(white_D65_xy + (shifted_xyY.xy - white_D65_xy) * white_offset_magnitude / max(1e-10, length((shifted_xyY.xy - white_D65_xy))), xyY.z); 131 | return CIE_xyY_to_XYZ(adjutsed_xyY); 132 | } 133 | 134 | #ifdef DECLARE_BEZOLD_BRUCKE_LUT 135 | DECLARE_BEZOLD_BRUCKE_LUT; 136 | 137 | // Apply Bezold–Brucke shift to XYZ stimulus. Loosely based on 138 | // "Pridmore, R. W. (1999). Bezold–Brucke hue-shift as functions of luminance level, 139 | // luminance ratio, interstimulus interval and adapting white for aperture and object colors. 140 | // Vision Research, 39(23), 3873–3891. doi:10.1016/s0042-6989(99)00085-1" 141 | float3 bezold_brucke_shift_XYZ_with_lut(float3 XYZ, float amount) { 142 | const float2 white = white_D65_xy; 143 | 144 | const float3 xyY = CIE_XYZ_to_xyY(XYZ); 145 | const float2 offset = xyY.xy - white; 146 | 147 | const float lut_coord = bb_xy_white_offset_to_lut_coord(offset); 148 | 149 | const float2 shifted_xy = xyY.xy + SAMPLE_BEZOLD_BRUCKE_LUT(lut_coord) * length(offset) * amount; 150 | return CIE_xyY_to_XYZ(float3(shifted_xy, xyY.z)); 151 | } 152 | #endif // DECLARE_BEZOLD_BRUCKE_LUT 153 | -------------------------------------------------------------------------------- /shaders/inc/standard_observer.hlsl: -------------------------------------------------------------------------------- 1 | #ifndef NOTORIOUS6_STANDARD_OBSERVER_HLSL 2 | #define NOTORIOUS6_STANDARD_OBSERVER_HLSL 3 | 4 | #include "xyz.hlsl" 5 | 6 | // https://www.site.uottawa.ca/~edubois/mdsp/data/ciexyz31.txt 7 | #define standard_observer_1931_length 95 8 | static const float standard_observer_1931_w_min = 360.0; 9 | static const float standard_observer_1931_w_max = 830.0; 10 | static const float3 standard_observer_1931[standard_observer_1931_length] = { 11 | float3(0.000129900000, 0.000003917000, 0.000606100000), // 360 nm 12 | float3(0.000232100000, 0.000006965000, 0.001086000000), // 365 nm 13 | float3(0.000414900000, 0.000012390000, 0.001946000000), // 370 nm 14 | float3(0.000741600000, 0.000022020000, 0.003486000000), // 375 nm 15 | float3(0.001368000000, 0.000039000000, 0.006450001000), // 380 nm 16 | float3(0.002236000000, 0.000064000000, 0.010549990000), // 385 nm 17 | float3(0.004243000000, 0.000120000000, 0.020050010000), // 390 nm 18 | float3(0.007650000000, 0.000217000000, 0.036210000000), // 395 nm 19 | float3(0.014310000000, 0.000396000000, 0.067850010000), // 400 nm 20 | float3(0.023190000000, 0.000640000000, 0.110200000000), // 405 nm 21 | float3(0.043510000000, 0.001210000000, 0.207400000000), // 410 nm 22 | float3(0.077630000000, 0.002180000000, 0.371300000000), // 415 nm 23 | float3(0.134380000000, 0.004000000000, 0.645600000000), // 420 nm 24 | float3(0.214770000000, 0.007300000000, 1.039050100000), // 425 nm 25 | float3(0.283900000000, 0.011600000000, 1.385600000000), // 430 nm 26 | float3(0.328500000000, 0.016840000000, 1.622960000000), // 435 nm 27 | float3(0.348280000000, 0.023000000000, 1.747060000000), // 440 nm 28 | float3(0.348060000000, 0.029800000000, 1.782600000000), // 445 nm 29 | float3(0.336200000000, 0.038000000000, 1.772110000000), // 450 nm 30 | float3(0.318700000000, 0.048000000000, 1.744100000000), // 455 nm 31 | float3(0.290800000000, 0.060000000000, 1.669200000000), // 460 nm 32 | float3(0.251100000000, 0.073900000000, 1.528100000000), // 465 nm 33 | float3(0.195360000000, 0.090980000000, 1.287640000000), // 470 nm 34 | float3(0.142100000000, 0.112600000000, 1.041900000000), // 475 nm 35 | float3(0.095640000000, 0.139020000000, 0.812950100000), // 480 nm 36 | float3(0.057950010000, 0.169300000000, 0.616200000000), // 485 nm 37 | float3(0.032010000000, 0.208020000000, 0.465180000000), // 490 nm 38 | float3(0.014700000000, 0.258600000000, 0.353300000000), // 495 nm 39 | float3(0.004900000000, 0.323000000000, 0.272000000000), // 500 nm 40 | float3(0.002400000000, 0.407300000000, 0.212300000000), // 505 nm 41 | float3(0.009300000000, 0.503000000000, 0.158200000000), // 510 nm 42 | float3(0.029100000000, 0.608200000000, 0.111700000000), // 515 nm 43 | float3(0.063270000000, 0.710000000000, 0.078249990000), // 520 nm 44 | float3(0.109600000000, 0.793200000000, 0.057250010000), // 525 nm 45 | float3(0.165500000000, 0.862000000000, 0.042160000000), // 530 nm 46 | float3(0.225749900000, 0.914850100000, 0.029840000000), // 535 nm 47 | float3(0.290400000000, 0.954000000000, 0.020300000000), // 540 nm 48 | float3(0.359700000000, 0.980300000000, 0.013400000000), // 545 nm 49 | float3(0.433449900000, 0.994950100000, 0.008749999000), // 550 nm 50 | float3(0.512050100000, 1.000000000000, 0.005749999000), // 555 nm 51 | float3(0.594500000000, 0.995000000000, 0.003900000000), // 560 nm 52 | float3(0.678400000000, 0.978600000000, 0.002749999000), // 565 nm 53 | float3(0.762100000000, 0.952000000000, 0.002100000000), // 570 nm 54 | float3(0.842500000000, 0.915400000000, 0.001800000000), // 575 nm 55 | float3(0.916300000000, 0.870000000000, 0.001650001000), // 580 nm 56 | float3(0.978600000000, 0.816300000000, 0.001400000000), // 585 nm 57 | float3(1.026300000000, 0.757000000000, 0.001100000000), // 590 nm 58 | float3(1.056700000000, 0.694900000000, 0.001000000000), // 595 nm 59 | float3(1.062200000000, 0.631000000000, 0.000800000000), // 600 nm 60 | float3(1.045600000000, 0.566800000000, 0.000600000000), // 605 nm 61 | float3(1.002600000000, 0.503000000000, 0.000340000000), // 610 nm 62 | float3(0.938400000000, 0.441200000000, 0.000240000000), // 615 nm 63 | float3(0.854449900000, 0.381000000000, 0.000190000000), // 620 nm 64 | float3(0.751400000000, 0.321000000000, 0.000100000000), // 625 nm 65 | float3(0.642400000000, 0.265000000000, 0.000049999990), // 630 nm 66 | float3(0.541900000000, 0.217000000000, 0.000030000000), // 635 nm 67 | float3(0.447900000000, 0.175000000000, 0.000020000000), // 640 nm 68 | float3(0.360800000000, 0.138200000000, 0.000010000000), // 645 nm 69 | float3(0.283500000000, 0.107000000000, 0.000000000000), // 650 nm 70 | float3(0.218700000000, 0.081600000000, 0.000000000000), // 655 nm 71 | float3(0.164900000000, 0.061000000000, 0.000000000000), // 660 nm 72 | float3(0.121200000000, 0.044580000000, 0.000000000000), // 665 nm 73 | float3(0.087400000000, 0.032000000000, 0.000000000000), // 670 nm 74 | float3(0.063600000000, 0.023200000000, 0.000000000000), // 675 nm 75 | float3(0.046770000000, 0.017000000000, 0.000000000000), // 680 nm 76 | float3(0.032900000000, 0.011920000000, 0.000000000000), // 685 nm 77 | float3(0.022700000000, 0.008210000000, 0.000000000000), // 690 nm 78 | float3(0.015840000000, 0.005723000000, 0.000000000000), // 695 nm 79 | float3(0.011359160000, 0.004102000000, 0.000000000000), // 700 nm 80 | float3(0.008110916000, 0.002929000000, 0.000000000000), // 705 nm 81 | float3(0.005790346000, 0.002091000000, 0.000000000000), // 710 nm 82 | float3(0.004106457000, 0.001484000000, 0.000000000000), // 715 nm 83 | float3(0.002899327000, 0.001047000000, 0.000000000000), // 720 nm 84 | float3(0.002049190000, 0.000740000000, 0.000000000000), // 725 nm 85 | float3(0.001439971000, 0.000520000000, 0.000000000000), // 730 nm 86 | float3(0.000999949300, 0.000361100000, 0.000000000000), // 735 nm 87 | float3(0.000690078600, 0.000249200000, 0.000000000000), // 740 nm 88 | float3(0.000476021300, 0.000171900000, 0.000000000000), // 745 nm 89 | float3(0.000332301100, 0.000120000000, 0.000000000000), // 750 nm 90 | float3(0.000234826100, 0.000084800000, 0.000000000000), // 755 nm 91 | float3(0.000166150500, 0.000060000000, 0.000000000000), // 760 nm 92 | float3(0.000117413000, 0.000042400000, 0.000000000000), // 765 nm 93 | float3(0.000083075270, 0.000030000000, 0.000000000000), // 770 nm 94 | float3(0.000058706520, 0.000021200000, 0.000000000000), // 775 nm 95 | float3(0.000041509940, 0.000014990000, 0.000000000000), // 780 nm 96 | float3(0.000029353260, 0.000010600000, 0.000000000000), // 785 nm 97 | float3(0.000020673830, 0.000007465700, 0.000000000000), // 790 nm 98 | float3(0.000014559770, 0.000005257800, 0.000000000000), // 795 nm 99 | float3(0.000010253980, 0.000003702900, 0.000000000000), // 800 nm 100 | float3(0.000007221456, 0.000002607800, 0.000000000000), // 805 nm 101 | float3(0.000005085868, 0.000001836600, 0.000000000000), // 810 nm 102 | float3(0.000003581652, 0.000001293400, 0.000000000000), // 815 nm 103 | float3(0.000002522525, 0.000000910930, 0.000000000000), // 820 nm 104 | float3(0.000001776509, 0.000000641530, 0.000000000000), // 825 nm 105 | float3(0.000001251141, 0.000000451810, 0.000000000000) // 830 nm 106 | }; 107 | 108 | // From Paul Malin (https://www.shadertoy.com/view/MstcD7) 109 | // Modified to output xyY as XYZ can be misleading 110 | // (does not interpolate linearly in chromaticity space) 111 | float3 wavelength_to_xyY(float wavelength) { 112 | float pos = (wavelength - standard_observer_1931_w_min) / (standard_observer_1931_w_max - standard_observer_1931_w_min); 113 | float index = pos * float(standard_observer_1931_length - 1); // -1 is a change from Paul's version. 114 | float floor_index = floor(index); 115 | float blend = clamp(index - floor_index, 0.0, 1.0); 116 | int index0 = int(floor_index); 117 | int index1 = index0 + 1; 118 | index1 = min(index1, standard_observer_1931_length - 1); 119 | return lerp(CIE_XYZ_to_xyY(standard_observer_1931[index0]), CIE_XYZ_to_xyY(standard_observer_1931[index1]), blend); 120 | } 121 | 122 | // Returns a lerp factor over p2 and p3 or -1 on miss 123 | float intersect_line_segment_2d(float2 p0, float2 dir, float2 p2, float2 p3) { 124 | float2 P = p2; 125 | float2 R = p3 - p2; 126 | float2 Q = p0; 127 | float2 S = dir; 128 | 129 | float2 N = float2(S.y, -S.x); 130 | float t = dot(Q-P, N) / dot(R, N); 131 | 132 | if (t == clamp(t, 0.0, 1.0) && dot(dir, p2 - p0) >= 0.0) { 133 | return t; 134 | } else { 135 | return -1.0; 136 | } 137 | } 138 | 139 | // Returns -1 for non-spectrals 140 | float CIE_xy_to_dominant_wavelength(float2 xy) { 141 | float2 white = white_D65_xy; 142 | float2 dir = xy - white; 143 | 144 | for (int i = 0; i + 1 < standard_observer_1931_length; ++i) { 145 | float2 locus_xy0 = CIE_XYZ_to_xyY(standard_observer_1931[i]).xy; 146 | float2 locus_xy1 = CIE_XYZ_to_xyY(standard_observer_1931[i + 1]).xy; 147 | 148 | float hit = intersect_line_segment_2d(white, dir, locus_xy0, locus_xy1); 149 | if (hit != -1.0) { 150 | return standard_observer_1931_w_min 151 | + (standard_observer_1931_w_max - standard_observer_1931_w_min) / float(standard_observer_1931_length - 1) 152 | * (float(i) + hit); 153 | } 154 | } 155 | 156 | return -1.0; 157 | } 158 | 159 | #endif // NOTORIOUS6_STANDARD_OBSERVER_HLSL 160 | -------------------------------------------------------------------------------- /shaders/inc/display_transform.hlsl: -------------------------------------------------------------------------------- 1 | #ifndef NOTORIOUS6_DISPLAY_TRANSFORM_HLSL 2 | #define NOTORIOUS6_DISPLAY_TRANSFORM_HLSL 3 | 4 | #include "ictcp.hlsl" 5 | #include "luv.hlsl" 6 | #include "oklab.hlsl" 7 | #include "lab.hlsl" 8 | #include "helmholtz_kohlrausch.hlsl" 9 | #include "ycbcr.hlsl" 10 | #include "ipt.hlsl" 11 | #include "bezold_brucke.hlsl" 12 | 13 | // The space to perform chroma attenuation in. More details in the `compress_stimulus` function. 14 | // Oklab works well, but fails at pure blues. 15 | // ICtCp seems to work pretty well all around. 16 | #define PERCEPTUAL_SPACE_OKLAB 0 17 | #define PERCEPTUAL_SPACE_ICTCP 1 18 | #define PERCEPTUAL_SPACE_IPT 2 19 | #define PERCEPTUAL_SPACE_NONE 3 20 | 21 | // Brightness compression curves: 22 | #define BRIGHTNESS_COMPRESSION_CURVE_REINHARD 0 23 | #define BRIGHTNESS_COMPRESSION_CURVE_SIRAGUSANO_SMITH 1 // :P 24 | 25 | // ---------------------------------------------------------------- 26 | // Configurable stuff: 27 | 28 | #define BRIGHTNESS_COMPRESSION_CURVE BRIGHTNESS_COMPRESSION_CURVE_SIRAGUSANO_SMITH 29 | 30 | // Choose the perceptual space for chroma attenuation. 31 | #define PERCEPTUAL_SPACE PERCEPTUAL_SPACE_IPT 32 | 33 | // Match target compressed brightness while attenuating chroma. 34 | // Important in the low end, as well as at the high end of blue and red. 35 | #define USE_BRIGHTNESS_LINEAR_CHROMA_ATTENUATION 1 36 | 37 | // Controls for manual desaturation of lighter than "white" stimulus (greens, yellows); 38 | // see comments in the code for more details. 39 | #define CHROMA_ATTENUATION_START 0.0 40 | #define CHROMA_ATTENUATION_EXPONENT_MIN 3.0 41 | #define CHROMA_ATTENUATION_EXPONENT_MAX 4.0 42 | 43 | // ---------------------------------------------------------------- 44 | 45 | #define USE_BEZOLD_BRUCKE_SHIFT 1 46 | #define BEZOLD_BRUCKE_BRUTE_FORCE 0 47 | #define BEZOLD_BRUCKE_SHIFT_RAMP 5 48 | #define USE_LONG_TAILED_CHROMA_ATTENUATION 1 49 | #define CHROMA_ATTENUATION_BIAS 1.03 50 | 51 | // Based on the selection, define `linear_to_perceptual` and `perceptual_to_linear` 52 | #if PERCEPTUAL_SPACE == PERCEPTUAL_SPACE_OKLAB 53 | #define linear_to_perceptual(col) sRGB_to_Oklab(col) 54 | #define perceptual_to_linear(col) Oklab_to_sRGB(col) 55 | #elif PERCEPTUAL_SPACE == PERCEPTUAL_SPACE_ICTCP 56 | #define linear_to_perceptual(col) BT709_to_ICtCp(col) 57 | #define perceptual_to_linear(col) ICtCp_to_BT709(col) 58 | #elif PERCEPTUAL_SPACE == PERCEPTUAL_SPACE_IPT 59 | #define linear_to_perceptual(col) XYZ_to_IPT(sRGB_to_XYZ(col)) 60 | #define perceptual_to_linear(col) XYZ_to_sRGB(IPT_to_XYZ(col)) 61 | #elif PERCEPTUAL_SPACE == PERCEPTUAL_SPACE_NONE 62 | #define linear_to_perceptual(col) (col) 63 | #define perceptual_to_linear(col) (col) 64 | #endif 65 | 66 | // Map brightness through a curve yielding values in 0..1, working with linear stimulus values. 67 | float compress_luminance(float v) { 68 | #if BRIGHTNESS_COMPRESSION_CURVE == BRIGHTNESS_COMPRESSION_CURVE_REINHARD 69 | // Reinhard 70 | const float k = 1.0; 71 | return pow(pow(v, k) / (pow(v, k) + 1.0), 1.0 / k); 72 | #elif BRIGHTNESS_COMPRESSION_CURVE == BRIGHTNESS_COMPRESSION_CURVE_SIRAGUSANO_SMITH 73 | // From Jed Smith: https://github.com/jedypod/open-display-transform/wiki/tech_tonescale, 74 | // based on stuff from Daniele Siragusano: https://community.acescentral.com/t/output-transform-tone-scale/3498/14 75 | // Reinhard with flare compensation. 76 | const float sx = 1.0; 77 | const float p = 1.2; 78 | const float sy = 1.0205; 79 | return saturate(sy * pow(v / (v + sx), p)); 80 | #endif 81 | } 82 | 83 | float3 display_transform_sRGB(float3 input_stimulus) { 84 | if (USE_BEZOLD_BRUCKE_SHIFT) { 85 | const float t = sRGB_to_luminance(input_stimulus) / BEZOLD_BRUCKE_SHIFT_RAMP; 86 | const float shift_amount = t / (t + 1.0); 87 | 88 | #if BEZOLD_BRUCKE_BRUTE_FORCE 89 | float3 stimulus = XYZ_to_sRGB(bezold_brucke_shift_XYZ_brute_force(sRGB_to_XYZ(input_stimulus), shift_amount)); 90 | #else 91 | float3 stimulus = XYZ_to_sRGB(bezold_brucke_shift_XYZ_with_lut(sRGB_to_XYZ(input_stimulus), shift_amount)); 92 | #endif 93 | 94 | input_stimulus = stimulus; 95 | } 96 | 97 | const HelmholtzKohlrauschEffect hk = hk_from_sRGB(input_stimulus); 98 | 99 | // Find the shader_input luminance adjusted by the Helmholtz-Kohlrausch effect. 100 | const float input_equiv_lum = srgb_to_equivalent_luminance(hk, input_stimulus); 101 | 102 | // The highest displayable intensity stimulus with the same chromaticity as the shader_input, 103 | // and its associated equivalent luminance. 104 | const float3 max_intensity_rgb = input_stimulus / max(input_stimulus.r, max(input_stimulus.g, input_stimulus.b)).xxx; 105 | float max_intensity_equiv_lum = srgb_to_equivalent_luminance(hk, max_intensity_rgb); 106 | //return max_intensity_equiv_lum.xxx - 1.0; 107 | //return saturate(max_intensity_rgb); 108 | 109 | const float max_output_scale = 1.0; 110 | 111 | // Compress the brightness. We will then adjust the chromatic shader_input stimulus to match this. 112 | // Note that this is not the non-linear "L*", but a 0..`max_output_scale` value as a multilpier 113 | // over the maximum achromatic luminance. 114 | const float compressed_achromatic_luminance = compress_luminance(input_equiv_lum / max_output_scale) * max_output_scale; 115 | //const float compressed_achromatic_luminance = smoothstep(0.1, 0.9, shader_input.uv.x); 116 | 117 | // Scale the chromatic stimulus so that its luminance matches `compressed_achromatic_luminance`. 118 | // TODO: Overly simplistic, and does not accurately map the brightness. 119 | // 120 | // This will create (mostly) matching brightness, but potentially out of gamut components. 121 | float3 compressed_rgb = (max_intensity_rgb / max_intensity_equiv_lum) * compressed_achromatic_luminance; 122 | 123 | // The achromatic stimulus we'll interpolate towards to fix out-of-gamut stimulus. 124 | const float clamped_compressed_achromatic_luminance = min(1.0, compressed_achromatic_luminance); 125 | 126 | // We now want to map the out-of-gamut stimulus back to what our device can display. 127 | // Since both the `compressed_rgb` and `clamped_compressed_achromatic_luminance` are of the same-ish 128 | // brightness, and `clamped_compressed_achromatic_luminance.xxx` is guaranteed to be inside the gamut, 129 | // we can trace a path from `compressed_rgb` towards `clamped_compressed_achromatic_luminance.xxx`, 130 | // and stop once we have intersected the target gamut. 131 | 132 | // This has the effect of removing chromatic content from the compressed stimulus, 133 | // and replacing that with achromatic content. If we do that naively, we run into 134 | // a perceptual hue shift due to the Abney effect. 135 | // 136 | // To counter, we first transform both vertices of the path we want to trace 137 | // into a perceptual space which preserves sensation of hue, then we trace 138 | // a straight line _inside that space_ until we intersect the gamut. 139 | 140 | const float3 perceptual = linear_to_perceptual(compressed_rgb); 141 | const float3 perceptual_white = linear_to_perceptual(clamped_compressed_achromatic_luminance.xxx); 142 | 143 | // Values lighter than "white" are already within the gamut, so our brightness compression is "done". 144 | // Perceptually they look wrong though, as they don't follow the desaturation that other stimulus does. 145 | // We fix that manually here by biasing the interpolation towards "white" at the end of the brightness range. 146 | // This "fixes" the yellows and greens. 147 | 148 | // We'll make the transition towards white smoother in areas of high chromatic strength. 149 | //float chroma_strength = length(sRGB_to_YCbCr(max_intensity_rgb).yz); 150 | float chroma_strength = LAB_to_Lch(XYZ_to_LAB(sRGB_to_XYZ(max_intensity_rgb))).y / 100.0 * 0.4; 151 | //float chroma_strength = 1; 152 | 153 | const float chroma_attenuation_start = CHROMA_ATTENUATION_START; 154 | const float chroma_attenuation_exponent = lerp(CHROMA_ATTENUATION_EXPONENT_MAX, CHROMA_ATTENUATION_EXPONENT_MIN, chroma_strength); 155 | const float chroma_attenuation_t = saturate( 156 | (compressed_achromatic_luminance - min(1, max_intensity_equiv_lum) * chroma_attenuation_start) 157 | / ((CHROMA_ATTENUATION_BIAS * max_output_scale - min(1, max_intensity_equiv_lum) * chroma_attenuation_start)) 158 | ); 159 | 160 | #if USE_LONG_TAILED_CHROMA_ATTENUATION 161 | float chroma_attenuation = asin(pow(chroma_attenuation_t, 3.0)) / M_PI * 2; 162 | 163 | // Window this with a soft falloff 164 | { 165 | const float compressed_achromatic_luminance2 = compress_luminance(0.125 * input_equiv_lum / max_output_scale) * max_output_scale; 166 | const float chroma_attenuation_t2 = saturate( 167 | (compressed_achromatic_luminance2 - min(1, max_intensity_equiv_lum) * 0.5) 168 | / ((max_output_scale - min(1, max_intensity_equiv_lum) * 0.5)) 169 | ); 170 | 171 | chroma_attenuation = lerp(chroma_attenuation, 1.0, 172 | 1.0 - saturate(1.0 - pow(chroma_attenuation_t2, 4)) 173 | ); 174 | } 175 | #else 176 | const float chroma_attenuation = pow(chroma_attenuation_t, chroma_attenuation_exponent); 177 | #endif 178 | 179 | { 180 | const float3 perceptual_mid = lerp(perceptual, perceptual_white, chroma_attenuation); 181 | compressed_rgb = perceptual_to_linear(perceptual_mid); 182 | 183 | const HelmholtzKohlrauschEffect hk = hk_from_sRGB(compressed_rgb); 184 | 185 | #if USE_BRIGHTNESS_LINEAR_CHROMA_ATTENUATION 186 | for (int i = 0; i < 2; ++i) { 187 | const float current_brightness = srgb_to_equivalent_luminance(hk, compressed_rgb); 188 | compressed_rgb *= compressed_achromatic_luminance / max(1e-10, current_brightness); 189 | } 190 | #endif 191 | } 192 | 193 | // At this stage we still have out of gamut colors. 194 | // This takes a silly twist now. So far we've been careful to preserve hue... 195 | // Now we're going to let the channels clip, but apply a per-channel roll-off. 196 | // This sacrificies hue accuracy and brightness to retain saturation. 197 | 198 | if (true) { 199 | compressed_rgb = max(compressed_rgb, 0.0.xxx); 200 | 201 | const float p = 12.0; 202 | compressed_rgb = compressed_rgb * pow(pow(compressed_rgb, p.xxx) + 1.0, -1.0 / p.xxx); 203 | 204 | const float max_comp = max(compressed_rgb.r, max(compressed_rgb.g, compressed_rgb.b)); 205 | const float max_comp_dist = max(max_comp - compressed_rgb.r, max(max_comp - compressed_rgb.g, max_comp - compressed_rgb.b)); 206 | 207 | // Rescale so we can reach 100% white. Avoid rescaling very highly saturated colors, 208 | // as that would reintroduce discontinuities. 209 | compressed_rgb /= pow(lerp(0.5, 1.0, max_comp_dist), 1.0 / p); 210 | } 211 | 212 | //return hk_equivalent_luminance(compressed_rgb).xxx; 213 | //return compressed_achromatic_luminance.xxx; 214 | 215 | return compressed_rgb; 216 | } 217 | 218 | #endif // NOTORIOUS6_DISPLAY_TRANSFORM_HLSL 219 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /shaders/brightness-hue-preserving.glsl: -------------------------------------------------------------------------------- 1 | // TL;DR: Compress brightness, preserve chroma by desaturation. 2 | 3 | #include "inc/prelude.glsl" 4 | #include "inc/ictcp.hlsl" 5 | #include "inc/luv.hlsl" 6 | #include "inc/oklab.hlsl" 7 | #include "inc/lab.hlsl" 8 | #include "inc/ycbcr.hlsl" 9 | 10 | #define HK_ADJUSTMENT_METHOD 1 // Force Nayatani 11 | #include "inc/helmholtz_kohlrausch.hlsl" 12 | 13 | // The space to perform chroma attenuation in. More details in the `compress_stimulus` function. 14 | // Oklab works best, with ICtCp being a close second. 15 | // LUV and None don't provide Abney correction. 16 | #define PERCEPTUAL_SPACE_OKLAB 0 17 | #define PERCEPTUAL_SPACE_ICTCP 2 18 | #define PERCEPTUAL_SPACE_NONE 3 19 | 20 | // Brightness compression curves: 21 | #define BRIGHTNESS_COMPRESSION_CURVE_REINHARD 0 22 | #define BRIGHTNESS_COMPRESSION_CURVE_SIRAGUSANO_SMITH 1 // :P 23 | 24 | // ---------------------------------------------------------------- 25 | // Configurable stuff: 26 | 27 | #define BRIGHTNESS_COMPRESSION_CURVE BRIGHTNESS_COMPRESSION_CURVE_SIRAGUSANO_SMITH 28 | 29 | // Choose the perceptual space for chroma attenuation. 30 | #define PERCEPTUAL_SPACE PERCEPTUAL_SPACE_OKLAB 31 | 32 | // Match target compressed brightness while attenuating chroma. 33 | // Important in the low end, as well as at the high end of blue and red. 34 | #define USE_BRIGHTNESS_LINEAR_CHROMA_ATTENUATION 1 35 | 36 | // The stimulus with the highest displayable brightness is not "white" 100% r, g, and b, 37 | // but depends on the Helmholtz-Kohlrausch effect. 38 | // That is somewhat problematic for us, as the display transform here is based on compressing 39 | // brightness to a range of up to a maximum achromatic signal of the output device. 40 | // If `ALLOW_BRIGHTNESS_ABOVE_WHITE` is 0, yellows and greens are never allowed to reach 41 | // full intensity, as that results in brightness above that of "white". 42 | // If `ALLOW_BRIGHTNESS_ABOVE_WHITE` is 1, the compressed stimulus is allowed to exceed 43 | // that range, at the cost of the output brightness curve having an inflection point, with the 44 | // brightness briefly exceeding max, and then going back to max as chroma attenuates. 45 | #define ALLOW_BRIGHTNESS_ABOVE_WHITE 0 46 | 47 | // if 1, the gamut will be trimmed at the "notorious 6" corners. 48 | // if 0, the whole gamut is used. 49 | // Not strictly necessary, but smooths-out boundaries where 50 | // achromatic stimulus begins to be added. 51 | #define TRIM_GAMUT_CORNERS 1 // 0 or 1 52 | #define GAMUT_CORNER_CUT_RADII float3(0.25, 0.25, 0.25) // 0..1 53 | 54 | // Controls for manual desaturation of lighter than "white" stimulus (greens, yellows); 55 | // see comments in the code for more details. 56 | #define CHROMA_ATTENUATION_START 0.0 57 | #define CHROMA_ATTENUATION_EXPONENT 4.0 58 | // ---------------------------------------------------------------- 59 | 60 | 61 | // Based on the selection, define `linear_to_perceptual` and `perceptual_to_linear` 62 | #if PERCEPTUAL_SPACE == PERCEPTUAL_SPACE_OKLAB 63 | #define linear_to_perceptual(col) sRGB_to_Oklab(col) 64 | #define perceptual_to_linear(col) Oklab_to_sRGB(col) 65 | #elif PERCEPTUAL_SPACE == PERCEPTUAL_SPACE_ICTCP 66 | #define linear_to_perceptual(col) BT709_to_ICtCp(col) 67 | #define perceptual_to_linear(col) ICtCp_to_BT709(col) 68 | #elif PERCEPTUAL_SPACE == PERCEPTUAL_SPACE_NONE 69 | #define linear_to_perceptual(col) (col) 70 | #define perceptual_to_linear(col) (col) 71 | #endif 72 | 73 | // Map brightness through a curve yielding values in 0..1, working with linear stimulus values. 74 | float compress_brightness(float v) { 75 | #if BRIGHTNESS_COMPRESSION_CURVE == BRIGHTNESS_COMPRESSION_CURVE_REINHARD 76 | // Reinhard 77 | return v / (v + 1.0); 78 | #elif BRIGHTNESS_COMPRESSION_CURVE == BRIGHTNESS_COMPRESSION_CURVE_SIRAGUSANO_SMITH 79 | // From Jed Smith: https://github.com/jedypod/open-display-transform/wiki/tech_tonescale, 80 | // based on stuff from Daniele Siragusano: https://community.acescentral.com/t/output-transform-tone-scale/3498/14 81 | // Reinhard with flare compensation. 82 | const float sx = 1.0; 83 | const float p = 1.2; 84 | const float sy = 1.0205; 85 | return saturate(sy * pow(v / (v + sx), p)); 86 | #endif 87 | } 88 | 89 | // A square with the (1, 0) and (0, 1) corners circularly trimmed. 90 | bool is_inside_2d_gamut_slice(float2 pos, float corner_radius) { 91 | float2 closest = clamp(pos, float2(0, corner_radius), float2(1 - corner_radius, 1)); 92 | float2 offset = pos - closest; 93 | return dot(offset, offset) <= corner_radius * corner_radius * 1.0001; 94 | } 95 | 96 | bool is_inside_target_gamut(float3 pos) { 97 | const float3 rgb_corner_radii = GAMUT_CORNER_CUT_RADII; 98 | 99 | return true 100 | #if TRIM_GAMUT_CORNERS 101 | // Trim red except where green is high or blue is high 102 | && (is_inside_2d_gamut_slice(pos.rg, rgb_corner_radii.r) && is_inside_2d_gamut_slice(pos.rb, rgb_corner_radii.r)) 103 | // Trim green except where red is high or blue is high 104 | && (is_inside_2d_gamut_slice(pos.gr, rgb_corner_radii.g) && is_inside_2d_gamut_slice(pos.gb, rgb_corner_radii.g)) 105 | // Trim blue except where red is high or green is high 106 | && (is_inside_2d_gamut_slice(pos.br, rgb_corner_radii.b) && is_inside_2d_gamut_slice(pos.bg, rgb_corner_radii.b)) 107 | #else 108 | // Just a box. 109 | && (pos.x <= 1.0 && pos.y <= 1.0 && pos.z <= 1.0) 110 | && (pos.x >= 0.0 && pos.y >= 0.0 && pos.z >= 0.0) 111 | #endif 112 | ; 113 | } 114 | 115 | float3 compress_stimulus(ShaderInput shader_input) { 116 | const HelmholtzKohlrauschEffect hk = hk_from_sRGB(shader_input.stimulus); 117 | 118 | // Find the shader_input brightness adjusted by the Helmholtz-Kohlrausch effect. 119 | const float input_brightness = srgb_to_equivalent_luminance(hk, shader_input.stimulus); 120 | 121 | // The highest displayable intensity stimulus with the same chromaticity as the shader_input, 122 | // and its associated brightness. 123 | const float3 max_intensity_rgb = shader_input.stimulus / max(shader_input.stimulus.r, max(shader_input.stimulus.g, shader_input.stimulus.b)).xxx; 124 | float max_intensity_brightness = srgb_to_equivalent_luminance(hk, max_intensity_rgb); 125 | //return max_intensity_brightness.xxx - 1.0; 126 | //return max_intensity_rgb; 127 | 128 | #if ALLOW_BRIGHTNESS_ABOVE_WHITE 129 | // The `max_intensity_rgb` stimulus can potentially be lighter than "white". 130 | // 131 | // This is by how much the output brightness will be allowed to exceed 132 | // the brightness of the highest luminance achromatic stimulus of the target gamut. 133 | float max_output_scale = max(1.0, max_intensity_brightness); 134 | #else 135 | float max_output_scale = 1.0; 136 | #endif 137 | 138 | // Compress the brightness. We will then adjust the chromatic shader_input stimulus to match this. 139 | // Note that this is not the non-linear "L*", but a 0..`max_output_scale` value as a multilpier 140 | // over the maximum achromatic luminance. 141 | const float compressed_achromatic_luminance = compress_brightness(input_brightness / max_output_scale) * max_output_scale; 142 | 143 | // Scale the chromatic stimulus so that its luminance matches `compressed_achromatic_luminance`. 144 | // TODO: Overly simplistic, and does not accurately map the brightness. 145 | // 146 | // This will create (mostly) matching brightness, but potentially out of gamut components. 147 | float3 compressed_rgb = (max_intensity_rgb / max_intensity_brightness) * compressed_achromatic_luminance; 148 | 149 | // The achromatic stimulus we'll interpolate towards to fix out-of-gamut stimulus. 150 | const float clamped_compressed_achromatic_luminance = min(1.0, compressed_achromatic_luminance); 151 | 152 | // We now want to map the out-of-gamut stimulus back to what our device can display. 153 | // Since both the `compressed_rgb` and `clamped_compressed_achromatic_luminance` are of the same-ish 154 | // brightness, and `clamped_compressed_achromatic_luminance.xxx` is guaranteed to be inside the gamut, 155 | // we can trace a path from `compressed_rgb` towards `clamped_compressed_achromatic_luminance.xxx`, 156 | // and stop once we have intersected the target gamut. 157 | 158 | // This has the effect of removing chromatic content from the compressed stimulus, 159 | // and replacing that with achromatic content. If we do that naively, we run into 160 | // a perceptual hue shift due to the Abney effect. 161 | // 162 | // To counter, we first transform both vertices of the path we want to trace 163 | // into a perceptual space which preserves sensation of hue, then we trace 164 | // a straight line _inside that space_ until we intersect the gamut. 165 | 166 | const float3 perceptual = linear_to_perceptual(compressed_rgb); 167 | const float3 perceptual_white = linear_to_perceptual(clamped_compressed_achromatic_luminance.xxx); 168 | 169 | // Values lighter than "white" are already within the gamut, so our brightness compression is "done". 170 | // Perceptually they look wrong though, as they don't follow the desaturation that other stimulus does. 171 | // We fix that manually here by biasing the interpolation towards "white" at the end of the brightness range. 172 | // This "fixes" the yellows and greens. 173 | const float chroma_attenuation = pow( 174 | saturate( 175 | (compressed_achromatic_luminance - max_output_scale * CHROMA_ATTENUATION_START) 176 | / (max_output_scale * (1.0 - CHROMA_ATTENUATION_START)) 177 | ), CHROMA_ATTENUATION_EXPONENT 178 | ); 179 | 180 | // The gamut (and the line) is deformed by the perceptual space, making its shape non-trivial. 181 | // We also potentially use a trimmed gamut to reduce the presence of the "Notorious 6", 182 | // making the gamut shape difficult to intersect analytically. 183 | // 184 | // The search here is performed in a pretty brute-force way, by performing a binary search. 185 | // The number of iterations is chosen in a very conservative way, and could be reduced. 186 | 187 | // Start and end points of our binary search. We'll refine those as we go. 188 | float s0 = chroma_attenuation; 189 | float s1 = 1; 190 | 191 | { 192 | float3 perceptual_mid = lerp(perceptual, perceptual_white, s0); 193 | compressed_rgb = perceptual_to_linear(perceptual_mid); 194 | const HelmholtzKohlrauschEffect hk = hk_from_sRGB(compressed_rgb); 195 | 196 | #if USE_BRIGHTNESS_LINEAR_CHROMA_ATTENUATION 197 | for (int i = 0; i < 2; ++i) { 198 | const float current_brightness = srgb_to_equivalent_luminance(hk, compressed_rgb); 199 | compressed_rgb *= clamped_compressed_achromatic_luminance / max(1e-10, current_brightness); 200 | } 201 | #endif 202 | } 203 | 204 | if (!is_inside_target_gamut(compressed_rgb)) { 205 | for (int i = 0; i < 24; ++i) { 206 | float3 perceptual_mid = lerp(perceptual, perceptual_white, lerp(s0, s1, 0.5)); 207 | compressed_rgb = perceptual_to_linear(perceptual_mid); 208 | const HelmholtzKohlrauschEffect hk = hk_from_sRGB(compressed_rgb); 209 | 210 | #if USE_BRIGHTNESS_LINEAR_CHROMA_ATTENUATION 211 | const float current_brightness = srgb_to_equivalent_luminance(hk, compressed_rgb); 212 | compressed_rgb *= clamped_compressed_achromatic_luminance / max(1e-10, current_brightness); 213 | #endif 214 | 215 | // Note: allow to exceed the gamut when `max_output_scale` > 1.0. 216 | // If we don't, we get a sharp cut to "white" with ALLOW_BRIGHTNESS_ABOVE_WHITE. 217 | if (is_inside_target_gamut(compressed_rgb / max_output_scale)) { 218 | // Mid point inside gamut. Step back. 219 | s1 = lerp(s0, s1, 0.5); 220 | } else { 221 | // Mid point outside gamut. Step forward. 222 | s0 = lerp(s0, s1, 0.5); 223 | } 224 | } 225 | } 226 | 227 | #if ALLOW_BRIGHTNESS_ABOVE_WHITE 228 | // HACK: if `ALLOW_BRIGHTNESS_ABOVE_WHITE` is enabled, we may still have a stimulus 229 | // value outside of the target gamut. We could clip here, but this works too. 230 | compressed_rgb /= max(1.0, max(compressed_rgb.r, max(compressed_rgb.g, compressed_rgb.b))); 231 | #endif 232 | 233 | //return hk_equivalent_luminance(compressed_rgb).xxx; 234 | //return compressed_achromatic_luminance.xxx; 235 | 236 | return compressed_rgb; 237 | } 238 | -------------------------------------------------------------------------------- /src/app_state.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | fbo::Fbo, 3 | image_pool::*, 4 | lut_lib::{AnyLutsChanged, LutDesc, LutLib}, 5 | shader::ShaderKey, 6 | shader_lib::{AnyShadersChanged, ShaderLib}, 7 | texture::Texture, 8 | }; 9 | use anyhow::Context; 10 | use glutin::event::{ElementState, KeyboardInput, MouseButton, VirtualKeyCode, WindowEvent}; 11 | use jpeg_encoder::{ColorType, Encoder}; 12 | use std::{ 13 | ffi::{c_void, CString}, 14 | path::{Path, PathBuf}, 15 | sync::Arc, 16 | }; 17 | use turbosloth::LazyCache; 18 | 19 | #[derive(Default)] 20 | struct InteractionState { 21 | dragging_ev: bool, 22 | last_cursor_position: [f64; 2], 23 | } 24 | 25 | pub struct PendingImageCapture { 26 | ev: f64, 27 | file_path: PathBuf, 28 | image_index: usize, 29 | shader_index: usize, 30 | } 31 | 32 | pub struct AppState { 33 | image_pool: ImagePool, 34 | current_image: usize, 35 | shader_lib: ShaderLib, 36 | lut_lib: LutLib, 37 | current_shader: usize, 38 | _lazy_cache: Arc, 39 | shaders: Vec, 40 | interaction: InteractionState, 41 | pub pending_image_capture: Vec, 42 | pub ev: f64, 43 | } 44 | 45 | trait ModuloWrappingOps: Sized { 46 | fn modulo_wrapping_inc(self, modulo: Self) -> Self; 47 | fn modulo_wrapping_dec(self, modulo: Self) -> Self; 48 | } 49 | 50 | impl ModuloWrappingOps for usize { 51 | fn modulo_wrapping_inc(self, modulo: Self) -> Self { 52 | (self + 1) % modulo 53 | } 54 | 55 | fn modulo_wrapping_dec(self, modulo: Self) -> Self { 56 | (self + modulo.saturating_sub(1)) % modulo 57 | } 58 | } 59 | 60 | pub enum NeedsRedraw { 61 | Yes, 62 | No, 63 | } 64 | 65 | impl AppState { 66 | pub fn new(input_path: PathBuf, gl: &gl::Gl) -> anyhow::Result { 67 | let image_pool = ImagePool::new(input_path)?; 68 | let lazy_cache = LazyCache::create(); 69 | 70 | let mut shader_lib = ShaderLib::new(&lazy_cache, gl); 71 | 72 | let shaders_folder = "shaders"; 73 | let shaders: Vec = std::fs::read_dir(shaders_folder) 74 | .context("Reading the shaders/ directory")? 75 | .filter_map(|entry| { 76 | let path = entry.ok()?.path(); 77 | (path.is_file() && path.extension() == Some(std::ffi::OsStr::new("glsl"))).then( 78 | || { 79 | shader_lib.add_shader(format!( 80 | "{}/{}", 81 | shaders_folder, 82 | path.file_name().unwrap().to_string_lossy() 83 | )) 84 | }, 85 | ) 86 | }) 87 | .collect(); 88 | 89 | let mut lut_lib = LutLib::new(&lazy_cache); 90 | lut_lib.add_lut( 91 | LutDesc { 92 | width: 64, 93 | internal_format: gl::RG32F, 94 | name: "bezold_brucke_lut".into(), 95 | shader_path: "shaders/lut/bezold_brucke_lut.glsl".into(), 96 | }, 97 | gl, 98 | ); 99 | 100 | Ok(Self { 101 | image_pool, 102 | current_image: 0, 103 | shader_lib, 104 | lut_lib, 105 | current_shader: 0, 106 | _lazy_cache: lazy_cache, 107 | shaders, 108 | interaction: Default::default(), 109 | pending_image_capture: Default::default(), 110 | ev: 0.0, 111 | }) 112 | } 113 | 114 | pub fn update(&mut self, gl: &gl::Gl) -> NeedsRedraw { 115 | let luts_changed = self.lut_lib.compile_all(gl); 116 | let shaders_changed = self.shader_lib.compile_all(gl); 117 | 118 | if matches!(luts_changed, AnyLutsChanged::Yes) 119 | || matches!(shaders_changed, AnyShadersChanged::Yes) 120 | { 121 | NeedsRedraw::Yes 122 | } else { 123 | NeedsRedraw::No 124 | } 125 | } 126 | 127 | pub fn process_batched_requests(&mut self, gl: &gl::Gl) -> anyhow::Result<()> { 128 | for pending in self.pending_image_capture.drain(..) { 129 | let texture = match self.image_pool.get_texture(pending.image_index, gl) { 130 | Some(texture) => texture, 131 | None => continue, 132 | }; 133 | 134 | let fbo = Fbo::new(gl, texture.size); 135 | fbo.bind(gl); 136 | 137 | let shader = self 138 | .shader_lib 139 | .get_shader_gl_handle(&self.shaders[pending.shader_index]) 140 | .expect("get_shader_gl_handle"); 141 | 142 | draw_texture(gl, texture, shader, texture.size, pending.ev, &self.lut_lib); 143 | Self::capture_screenshot(gl, texture, &pending.file_path)?; 144 | log::info!("Saved {:?}", pending.file_path); 145 | 146 | fbo.destroy(gl); 147 | } 148 | 149 | Ok(()) 150 | } 151 | 152 | pub fn draw_frame(&mut self, gl: &gl::Gl, physical_window_size: [usize; 2]) { 153 | let texture = self.image_pool.get_texture(self.current_image, gl); 154 | 155 | unsafe { 156 | let shader = self 157 | .shader_lib 158 | .get_shader_gl_handle(&self.shaders[self.current_shader]); 159 | 160 | if let Some((texture, shader)) = texture.zip(shader) { 161 | let fbo = Fbo::new(gl, texture.size); 162 | fbo.bind(gl); 163 | 164 | draw_texture(gl, texture, shader, texture.size, self.ev, &self.lut_lib); 165 | 166 | let width_frac: f64 = texture.size[0] as f64 / physical_window_size[0] as f64; 167 | let height_frac: f64 = texture.size[1] as f64 / physical_window_size[1] as f64; 168 | let fit_frac = width_frac.max(height_frac); 169 | let width_frac = width_frac / fit_frac; 170 | let height_frac = height_frac / fit_frac; 171 | 172 | let width = (physical_window_size[0] as f64 * width_frac) as i32; 173 | let height = (physical_window_size[1] as f64 * height_frac) as i32; 174 | let x_offset = (physical_window_size[0] as i32 - width) / 2; 175 | let y_offset = (physical_window_size[1] as i32 - height) / 2; 176 | 177 | fbo.unbind(gl); 178 | fbo.bind_read(gl); 179 | 180 | gl.ClearColor(0.1, 0.1, 0.1, 1.0); 181 | gl.Clear(gl::COLOR_BUFFER_BIT); 182 | gl.BlitFramebuffer( 183 | 0, 184 | 0, 185 | texture.size[0] as _, 186 | texture.size[1] as _, 187 | x_offset, 188 | y_offset, 189 | x_offset + width, 190 | y_offset + height, 191 | gl::COLOR_BUFFER_BIT, 192 | gl::LINEAR, 193 | ); 194 | 195 | fbo.unbind_read(gl); 196 | fbo.destroy(gl); 197 | } else { 198 | if shader.is_none() { 199 | gl.ClearColor(0.5, 0.0, 0.0, 1.0); 200 | } else { 201 | gl.ClearColor(0.0, 0.0, 0.0, 1.0); 202 | } 203 | 204 | gl.Clear(gl::COLOR_BUFFER_BIT); 205 | } 206 | } 207 | } 208 | 209 | fn capture_screenshot(gl: &gl::Gl, texture: &Texture, file_path: &Path) -> anyhow::Result<()> { 210 | let mut pixels = vec![0u8; texture.size.into_iter().product::() * 4]; 211 | 212 | if let Some(parent_dir) = file_path.parent() { 213 | std::fs::create_dir_all(parent_dir)?; 214 | } 215 | 216 | unsafe { 217 | gl.PixelStorei(gl::UNPACK_ALIGNMENT, 1); 218 | gl.ReadPixels( 219 | 0, 220 | 0, 221 | texture.size[0] as _, 222 | texture.size[1] as _, 223 | gl::RGBA, 224 | gl::UNSIGNED_BYTE, 225 | pixels.as_mut_ptr() as *mut c_void, 226 | ); 227 | } 228 | 229 | // Flip it 230 | { 231 | let mut pixels = pixels.as_mut_slice(); 232 | let row_bytes = texture.size[0] * 4; 233 | while pixels.len() >= row_bytes * 2 { 234 | let (a, rest) = pixels.split_at_mut(row_bytes); 235 | pixels = rest; 236 | let (rest, b) = pixels.split_at_mut(pixels.len() - row_bytes); 237 | pixels = rest; 238 | a.swap_with_slice(b); 239 | } 240 | } 241 | 242 | // Create new encoder that writes to a file with maximum quality (100) 243 | let encoder = Encoder::new_file(file_path, 90) 244 | .with_context(|| format!("Failed to create {:?}", file_path))?; 245 | 246 | encoder 247 | .encode( 248 | &pixels, 249 | texture.size[0] as _, 250 | texture.size[1] as _, 251 | ColorType::Rgba, 252 | ) 253 | .context("encoder.encode")?; 254 | 255 | Ok(()) 256 | } 257 | 258 | fn handle_keyboard_input(&mut self, input: KeyboardInput) -> NeedsRedraw { 259 | if !matches!(input.state, glutin::event::ElementState::Pressed) { 260 | return NeedsRedraw::No; 261 | } 262 | 263 | match input.virtual_keycode { 264 | Some(VirtualKeyCode::Left) => { 265 | self.current_image = self 266 | .current_image 267 | .modulo_wrapping_dec(self.image_pool.image_count()); 268 | NeedsRedraw::Yes 269 | } 270 | Some(VirtualKeyCode::Right) => { 271 | self.current_image = self 272 | .current_image 273 | .modulo_wrapping_inc(self.image_pool.image_count()); 274 | NeedsRedraw::Yes 275 | } 276 | Some(VirtualKeyCode::Up) => { 277 | self.current_shader = self.current_shader.modulo_wrapping_inc(self.shaders.len()); 278 | NeedsRedraw::Yes 279 | } 280 | Some(VirtualKeyCode::Down) => { 281 | self.current_shader = self.current_shader.modulo_wrapping_dec(self.shaders.len()); 282 | NeedsRedraw::Yes 283 | } 284 | Some(VirtualKeyCode::F12) => { 285 | self.pending_image_capture = vec![PendingImageCapture { 286 | ev: self.ev, 287 | file_path: "screenshot.jpg".into(), 288 | image_index: self.current_image, 289 | shader_index: self.current_shader, 290 | }]; 291 | NeedsRedraw::Yes 292 | } 293 | _ => NeedsRedraw::No, 294 | } 295 | } 296 | 297 | pub fn handle_window_event(&mut self, event: WindowEvent) -> NeedsRedraw { 298 | match event { 299 | WindowEvent::KeyboardInput { input, .. } => self.handle_keyboard_input(input), 300 | WindowEvent::CursorMoved { position, .. } => { 301 | let mut needs_redraw = NeedsRedraw::No; 302 | 303 | if self.interaction.dragging_ev { 304 | self.ev += (self.interaction.last_cursor_position[1] - position.y) / 100.0; 305 | needs_redraw = NeedsRedraw::Yes; 306 | } 307 | 308 | self.interaction.last_cursor_position = [position.x, position.y]; 309 | needs_redraw 310 | } 311 | WindowEvent::MouseInput { state, button, .. } => { 312 | if matches!(button, MouseButton::Left) { 313 | self.interaction.dragging_ev = matches!(state, ElementState::Pressed); 314 | } 315 | 316 | NeedsRedraw::No 317 | } 318 | _ => NeedsRedraw::No, 319 | } 320 | } 321 | 322 | pub fn current_shader(&self) -> String { 323 | self.shaders[self.current_shader].name() 324 | } 325 | 326 | pub fn current_image_name(&self) -> Option { 327 | self.image_pool 328 | .get_image_path(self.current_image) 329 | .and_then(|path| Some(path.file_name()?.to_string_lossy().as_ref().to_owned())) 330 | } 331 | 332 | pub fn request_batch( 333 | &mut self, 334 | ev_min: f64, 335 | ev_max: f64, 336 | ev_step: f64, 337 | shader_name: &str, 338 | ) -> anyhow::Result<()> { 339 | let root_dir = &PathBuf::from("batch"); 340 | let shader_index = self 341 | .shaders 342 | .iter() 343 | .position(|shader| shader.name() == shader_name) 344 | .ok_or_else(|| anyhow::anyhow!("Unknown shader {:?}", shader_name))?; 345 | 346 | let mut batch = { 347 | let slf: &AppState = self; 348 | let image_count = self.image_pool.image_count(); 349 | (0..image_count) 350 | .flat_map(|image_index| { 351 | slf.image_pool 352 | .get_image_path(image_index) 353 | .into_iter() 354 | .flat_map(move |image_path| { 355 | let step_count = 356 | ((ev_max - ev_min) / ev_step + 0.5).ceil().max(1.0) as usize; 357 | 358 | (0..step_count).map(move |step_index| { 359 | let ev = ev_min + ev_step * step_index as f64; 360 | 361 | PendingImageCapture { 362 | ev, 363 | file_path: root_dir 364 | .join(image_path.file_name().unwrap()) 365 | .join(format!("{:03} - EV {}.jpg", step_index, ev)), 366 | image_index, 367 | shader_index, 368 | } 369 | }) 370 | }) 371 | }) 372 | .collect::>() 373 | }; 374 | 375 | self.pending_image_capture.append(&mut batch); 376 | 377 | Ok(()) 378 | } 379 | } 380 | 381 | fn draw_texture( 382 | gl: &gl::Gl, 383 | texture: &Texture, 384 | shader_program: u32, 385 | size: [usize; 2], 386 | ev: f64, 387 | lut_lib: &LutLib, 388 | ) { 389 | unsafe { 390 | gl.Viewport(0, 0, size[0] as _, size[1] as _); 391 | 392 | gl.UseProgram(shader_program); 393 | 394 | let mut tex_unit = 0; 395 | 396 | { 397 | gl.ActiveTexture(gl::TEXTURE0); 398 | gl.BindTexture(gl::TEXTURE_2D, texture.id); 399 | let loc = 400 | gl.GetUniformLocation(shader_program, "input_texture\0".as_ptr() as *const i8); 401 | if loc != -1 { 402 | gl.Uniform1i(loc, tex_unit); 403 | tex_unit += 1; 404 | } 405 | } 406 | 407 | for (lut_desc, lut_texture) in lut_lib.iter() { 408 | let uniform_name = CString::new(lut_desc.name.clone()).unwrap(); 409 | let loc = gl.GetUniformLocation(shader_program, uniform_name.as_ptr() as *const i8); 410 | if loc != -1 { 411 | // log::info!("Binding lut {:?}", lut_desc.name); 412 | gl.ActiveTexture(gl::TEXTURE0 + tex_unit as gl::types::GLenum); 413 | gl.BindTexture(lut_texture.ty, lut_texture.id); 414 | gl.Uniform1i(loc, tex_unit); 415 | tex_unit += 1; 416 | } 417 | } 418 | 419 | /*for (lut_desc, lut_texture) in lut_lib.iter() { 420 | let uniform_name = CString::new(lut_desc.name.clone()).unwrap(); 421 | let loc = gl.GetUniformLocation(shader_program, uniform_name.as_ptr() as *const i8); 422 | if loc != -1 { 423 | gl.Uniform1i(loc, img_unit); 424 | gl.BindImageTexture( 425 | img_unit as _, 426 | lut_texture.id, 427 | 0, 428 | gl::FALSE, 429 | 0, 430 | gl::READ_ONLY, 431 | lut_texture.internal_format, 432 | ); 433 | img_unit += 1; 434 | } 435 | }*/ 436 | 437 | { 438 | let loc = gl.GetUniformLocation(shader_program, "input_ev\0".as_ptr() as *const i8); 439 | if loc != -1 { 440 | gl.Uniform1f(loc, ev as f32); 441 | } 442 | } 443 | 444 | gl.DrawArrays(gl::TRIANGLES, 0, 3); 445 | gl.UseProgram(0); 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "0.7.18" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "android_glue" 22 | version = "0.2.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" 25 | 26 | [[package]] 27 | name = "ansi_term" 28 | version = "0.12.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 31 | dependencies = [ 32 | "winapi 0.3.9", 33 | ] 34 | 35 | [[package]] 36 | name = "anyhow" 37 | version = "1.0.52" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3" 40 | 41 | [[package]] 42 | name = "async-channel" 43 | version = "1.6.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" 46 | dependencies = [ 47 | "concurrent-queue", 48 | "event-listener", 49 | "futures-core", 50 | ] 51 | 52 | [[package]] 53 | name = "async-executor" 54 | version = "1.4.1" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" 57 | dependencies = [ 58 | "async-task", 59 | "concurrent-queue", 60 | "fastrand", 61 | "futures-lite", 62 | "once_cell", 63 | "slab", 64 | ] 65 | 66 | [[package]] 67 | name = "async-fs" 68 | version = "1.5.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "8b3ca4f8ff117c37c278a2f7415ce9be55560b846b5bc4412aaa5d29c1c3dae2" 71 | dependencies = [ 72 | "async-lock", 73 | "blocking", 74 | "futures-lite", 75 | ] 76 | 77 | [[package]] 78 | name = "async-io" 79 | version = "1.6.0" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" 82 | dependencies = [ 83 | "concurrent-queue", 84 | "futures-lite", 85 | "libc", 86 | "log", 87 | "once_cell", 88 | "parking", 89 | "polling", 90 | "slab", 91 | "socket2", 92 | "waker-fn", 93 | "winapi 0.3.9", 94 | ] 95 | 96 | [[package]] 97 | name = "async-lock" 98 | version = "2.4.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" 101 | dependencies = [ 102 | "event-listener", 103 | ] 104 | 105 | [[package]] 106 | name = "async-net" 107 | version = "1.6.1" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "5373304df79b9b4395068fb080369ec7178608827306ce4d081cba51cac551df" 110 | dependencies = [ 111 | "async-io", 112 | "blocking", 113 | "futures-lite", 114 | ] 115 | 116 | [[package]] 117 | name = "async-process" 118 | version = "1.3.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6" 121 | dependencies = [ 122 | "async-io", 123 | "blocking", 124 | "cfg-if 1.0.0", 125 | "event-listener", 126 | "futures-lite", 127 | "libc", 128 | "once_cell", 129 | "signal-hook", 130 | "winapi 0.3.9", 131 | ] 132 | 133 | [[package]] 134 | name = "async-task" 135 | version = "4.0.3" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" 138 | 139 | [[package]] 140 | name = "async-trait" 141 | version = "0.1.52" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" 144 | dependencies = [ 145 | "proc-macro2", 146 | "quote", 147 | "syn", 148 | ] 149 | 150 | [[package]] 151 | name = "atomic-waker" 152 | version = "1.0.0" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" 155 | 156 | [[package]] 157 | name = "atty" 158 | version = "0.2.14" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 161 | dependencies = [ 162 | "hermit-abi", 163 | "libc", 164 | "winapi 0.3.9", 165 | ] 166 | 167 | [[package]] 168 | name = "autocfg" 169 | version = "1.0.1" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 172 | 173 | [[package]] 174 | name = "bit_field" 175 | version = "0.10.1" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" 178 | 179 | [[package]] 180 | name = "bitflags" 181 | version = "1.3.2" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 184 | 185 | [[package]] 186 | name = "block" 187 | version = "0.1.6" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 190 | 191 | [[package]] 192 | name = "blocking" 193 | version = "1.1.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "046e47d4b2d391b1f6f8b407b1deb8dee56c1852ccd868becf2710f601b5f427" 196 | dependencies = [ 197 | "async-channel", 198 | "async-task", 199 | "atomic-waker", 200 | "fastrand", 201 | "futures-lite", 202 | "once_cell", 203 | ] 204 | 205 | [[package]] 206 | name = "bumpalo" 207 | version = "3.8.0" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" 210 | 211 | [[package]] 212 | name = "bytes" 213 | version = "1.1.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 216 | 217 | [[package]] 218 | name = "cache-padded" 219 | version = "1.2.0" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" 222 | 223 | [[package]] 224 | name = "calloop" 225 | version = "0.9.3" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "bf2eec61efe56aa1e813f5126959296933cf0700030e4314786c48779a66ab82" 228 | dependencies = [ 229 | "log", 230 | "nix", 231 | ] 232 | 233 | [[package]] 234 | name = "cc" 235 | version = "1.0.72" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" 238 | 239 | [[package]] 240 | name = "cfg-if" 241 | version = "0.1.10" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 244 | 245 | [[package]] 246 | name = "cfg-if" 247 | version = "1.0.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 250 | 251 | [[package]] 252 | name = "cgl" 253 | version = "0.3.2" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" 256 | dependencies = [ 257 | "libc", 258 | ] 259 | 260 | [[package]] 261 | name = "clap" 262 | version = "2.34.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 265 | dependencies = [ 266 | "ansi_term", 267 | "atty", 268 | "bitflags", 269 | "strsim 0.8.0", 270 | "textwrap", 271 | "unicode-width", 272 | "vec_map", 273 | ] 274 | 275 | [[package]] 276 | name = "cocoa" 277 | version = "0.24.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" 280 | dependencies = [ 281 | "bitflags", 282 | "block", 283 | "cocoa-foundation", 284 | "core-foundation 0.9.2", 285 | "core-graphics 0.22.3", 286 | "foreign-types", 287 | "libc", 288 | "objc", 289 | ] 290 | 291 | [[package]] 292 | name = "cocoa-foundation" 293 | version = "0.1.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" 296 | dependencies = [ 297 | "bitflags", 298 | "block", 299 | "core-foundation 0.9.2", 300 | "core-graphics-types", 301 | "foreign-types", 302 | "libc", 303 | "objc", 304 | ] 305 | 306 | [[package]] 307 | name = "colored" 308 | version = "1.9.3" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" 311 | dependencies = [ 312 | "atty", 313 | "lazy_static", 314 | "winapi 0.3.9", 315 | ] 316 | 317 | [[package]] 318 | name = "concurrent-queue" 319 | version = "1.2.2" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" 322 | dependencies = [ 323 | "cache-padded", 324 | ] 325 | 326 | [[package]] 327 | name = "core-foundation" 328 | version = "0.7.0" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" 331 | dependencies = [ 332 | "core-foundation-sys 0.7.0", 333 | "libc", 334 | ] 335 | 336 | [[package]] 337 | name = "core-foundation" 338 | version = "0.9.2" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" 341 | dependencies = [ 342 | "core-foundation-sys 0.8.3", 343 | "libc", 344 | ] 345 | 346 | [[package]] 347 | name = "core-foundation-sys" 348 | version = "0.7.0" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" 351 | 352 | [[package]] 353 | name = "core-foundation-sys" 354 | version = "0.8.3" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 357 | 358 | [[package]] 359 | name = "core-graphics" 360 | version = "0.19.2" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" 363 | dependencies = [ 364 | "bitflags", 365 | "core-foundation 0.7.0", 366 | "foreign-types", 367 | "libc", 368 | ] 369 | 370 | [[package]] 371 | name = "core-graphics" 372 | version = "0.22.3" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" 375 | dependencies = [ 376 | "bitflags", 377 | "core-foundation 0.9.2", 378 | "core-graphics-types", 379 | "foreign-types", 380 | "libc", 381 | ] 382 | 383 | [[package]] 384 | name = "core-graphics-types" 385 | version = "0.1.1" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" 388 | dependencies = [ 389 | "bitflags", 390 | "core-foundation 0.9.2", 391 | "foreign-types", 392 | "libc", 393 | ] 394 | 395 | [[package]] 396 | name = "core-video-sys" 397 | version = "0.1.4" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" 400 | dependencies = [ 401 | "cfg-if 0.1.10", 402 | "core-foundation-sys 0.7.0", 403 | "core-graphics 0.19.2", 404 | "libc", 405 | "objc", 406 | ] 407 | 408 | [[package]] 409 | name = "crunchy" 410 | version = "0.2.2" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 413 | 414 | [[package]] 415 | name = "cty" 416 | version = "0.2.2" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" 419 | 420 | [[package]] 421 | name = "darling" 422 | version = "0.13.1" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" 425 | dependencies = [ 426 | "darling_core", 427 | "darling_macro", 428 | ] 429 | 430 | [[package]] 431 | name = "darling_core" 432 | version = "0.13.1" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" 435 | dependencies = [ 436 | "fnv", 437 | "ident_case", 438 | "proc-macro2", 439 | "quote", 440 | "strsim 0.10.0", 441 | "syn", 442 | ] 443 | 444 | [[package]] 445 | name = "darling_macro" 446 | version = "0.13.1" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" 449 | dependencies = [ 450 | "darling_core", 451 | "quote", 452 | "syn", 453 | ] 454 | 455 | [[package]] 456 | name = "dispatch" 457 | version = "0.2.0" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" 460 | 461 | [[package]] 462 | name = "dlib" 463 | version = "0.5.0" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" 466 | dependencies = [ 467 | "libloading", 468 | ] 469 | 470 | [[package]] 471 | name = "downcast-rs" 472 | version = "1.2.0" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" 475 | 476 | [[package]] 477 | name = "event-listener" 478 | version = "2.5.1" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" 481 | 482 | [[package]] 483 | name = "exr" 484 | version = "1.5.3" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "e8af5ef47e2ed89d23d0ecbc1b681b30390069de70260937877514377fc24feb" 487 | dependencies = [ 488 | "bit_field", 489 | "flume", 490 | "half", 491 | "lebe", 492 | "miniz_oxide", 493 | "smallvec", 494 | "threadpool", 495 | "zune-inflate", 496 | ] 497 | 498 | [[package]] 499 | name = "fastrand" 500 | version = "1.6.0" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2" 503 | dependencies = [ 504 | "instant", 505 | ] 506 | 507 | [[package]] 508 | name = "filetime" 509 | version = "0.2.15" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" 512 | dependencies = [ 513 | "cfg-if 1.0.0", 514 | "libc", 515 | "redox_syscall", 516 | "winapi 0.3.9", 517 | ] 518 | 519 | [[package]] 520 | name = "flume" 521 | version = "0.10.9" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "24c3fd473b3a903a62609e413ed7538f99e10b665ecb502b5e481a95283f8ab4" 524 | dependencies = [ 525 | "futures-core", 526 | "futures-sink", 527 | "nanorand", 528 | "pin-project", 529 | "spin", 530 | ] 531 | 532 | [[package]] 533 | name = "fnv" 534 | version = "1.0.7" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 537 | 538 | [[package]] 539 | name = "foreign-types" 540 | version = "0.3.2" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 543 | dependencies = [ 544 | "foreign-types-shared", 545 | ] 546 | 547 | [[package]] 548 | name = "foreign-types-shared" 549 | version = "0.1.1" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 552 | 553 | [[package]] 554 | name = "fsevent" 555 | version = "0.4.0" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" 558 | dependencies = [ 559 | "bitflags", 560 | "fsevent-sys", 561 | ] 562 | 563 | [[package]] 564 | name = "fsevent-sys" 565 | version = "2.0.1" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" 568 | dependencies = [ 569 | "libc", 570 | ] 571 | 572 | [[package]] 573 | name = "fuchsia-zircon" 574 | version = "0.3.3" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 577 | dependencies = [ 578 | "bitflags", 579 | "fuchsia-zircon-sys", 580 | ] 581 | 582 | [[package]] 583 | name = "fuchsia-zircon-sys" 584 | version = "0.3.3" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 587 | 588 | [[package]] 589 | name = "futures-core" 590 | version = "0.3.19" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" 593 | 594 | [[package]] 595 | name = "futures-io" 596 | version = "0.3.19" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" 599 | 600 | [[package]] 601 | name = "futures-lite" 602 | version = "1.12.0" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" 605 | dependencies = [ 606 | "fastrand", 607 | "futures-core", 608 | "futures-io", 609 | "memchr", 610 | "parking", 611 | "pin-project-lite", 612 | "waker-fn", 613 | ] 614 | 615 | [[package]] 616 | name = "futures-sink" 617 | version = "0.3.19" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" 620 | 621 | [[package]] 622 | name = "getrandom" 623 | version = "0.2.3" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 626 | dependencies = [ 627 | "cfg-if 1.0.0", 628 | "js-sys", 629 | "libc", 630 | "wasi", 631 | "wasm-bindgen", 632 | ] 633 | 634 | [[package]] 635 | name = "gl" 636 | version = "0.1.0" 637 | dependencies = [ 638 | "gl_generator", 639 | ] 640 | 641 | [[package]] 642 | name = "gl_generator" 643 | version = "0.14.0" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" 646 | dependencies = [ 647 | "khronos_api", 648 | "log", 649 | "xml-rs", 650 | ] 651 | 652 | [[package]] 653 | name = "glutin" 654 | version = "0.28.0" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "00ea9dbe544bc8a657c4c4a798c2d16cd01b549820e47657297549d28371f6d2" 657 | dependencies = [ 658 | "android_glue", 659 | "cgl", 660 | "cocoa", 661 | "core-foundation 0.9.2", 662 | "glutin_egl_sys", 663 | "glutin_emscripten_sys", 664 | "glutin_gles2_sys", 665 | "glutin_glx_sys", 666 | "glutin_wgl_sys", 667 | "lazy_static", 668 | "libloading", 669 | "log", 670 | "objc", 671 | "osmesa-sys", 672 | "parking_lot", 673 | "wayland-client", 674 | "wayland-egl", 675 | "winapi 0.3.9", 676 | "winit", 677 | ] 678 | 679 | [[package]] 680 | name = "glutin_egl_sys" 681 | version = "0.1.5" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "2abb6aa55523480c4adc5a56bbaa249992e2dddb2fc63dc96e04a3355364c211" 684 | dependencies = [ 685 | "gl_generator", 686 | "winapi 0.3.9", 687 | ] 688 | 689 | [[package]] 690 | name = "glutin_emscripten_sys" 691 | version = "0.1.1" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "80de4146df76e8a6c32b03007bc764ff3249dcaeb4f675d68a06caf1bac363f1" 694 | 695 | [[package]] 696 | name = "glutin_gles2_sys" 697 | version = "0.1.5" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "e8094e708b730a7c8a1954f4f8a31880af00eb8a1c5b5bf85d28a0a3c6d69103" 700 | dependencies = [ 701 | "gl_generator", 702 | "objc", 703 | ] 704 | 705 | [[package]] 706 | name = "glutin_glx_sys" 707 | version = "0.1.7" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "7e393c8fc02b807459410429150e9c4faffdb312d59b8c038566173c81991351" 710 | dependencies = [ 711 | "gl_generator", 712 | "x11-dl", 713 | ] 714 | 715 | [[package]] 716 | name = "glutin_wgl_sys" 717 | version = "0.1.5" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "3da5951a1569dbab865c6f2a863efafff193a93caf05538d193e9e3816d21696" 720 | dependencies = [ 721 | "gl_generator", 722 | ] 723 | 724 | [[package]] 725 | name = "half" 726 | version = "2.2.1" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" 729 | dependencies = [ 730 | "crunchy", 731 | ] 732 | 733 | [[package]] 734 | name = "heck" 735 | version = "0.3.3" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 738 | dependencies = [ 739 | "unicode-segmentation", 740 | ] 741 | 742 | [[package]] 743 | name = "hermit-abi" 744 | version = "0.1.19" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 747 | dependencies = [ 748 | "libc", 749 | ] 750 | 751 | [[package]] 752 | name = "hotwatch" 753 | version = "0.4.6" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "39301670a6f5798b75f36a1b149a379a50df5aa7c71be50f4b41ec6eab445cb8" 756 | dependencies = [ 757 | "log", 758 | "notify", 759 | ] 760 | 761 | [[package]] 762 | name = "ident_case" 763 | version = "1.0.1" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 766 | 767 | [[package]] 768 | name = "inotify" 769 | version = "0.7.1" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" 772 | dependencies = [ 773 | "bitflags", 774 | "inotify-sys", 775 | "libc", 776 | ] 777 | 778 | [[package]] 779 | name = "inotify-sys" 780 | version = "0.1.5" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" 783 | dependencies = [ 784 | "libc", 785 | ] 786 | 787 | [[package]] 788 | name = "instant" 789 | version = "0.1.12" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 792 | dependencies = [ 793 | "cfg-if 1.0.0", 794 | "js-sys", 795 | "wasm-bindgen", 796 | "web-sys", 797 | ] 798 | 799 | [[package]] 800 | name = "iovec" 801 | version = "0.1.4" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 804 | dependencies = [ 805 | "libc", 806 | ] 807 | 808 | [[package]] 809 | name = "itoa" 810 | version = "0.4.8" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 813 | 814 | [[package]] 815 | name = "jni-sys" 816 | version = "0.3.0" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 819 | 820 | [[package]] 821 | name = "jpeg-encoder" 822 | version = "0.4.1" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "cfb34c7ff960aaee786db93e7bb779912555a632e8043ae92b68e868c0ec15c4" 825 | 826 | [[package]] 827 | name = "js-sys" 828 | version = "0.3.55" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" 831 | dependencies = [ 832 | "wasm-bindgen", 833 | ] 834 | 835 | [[package]] 836 | name = "kernel32-sys" 837 | version = "0.2.2" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 840 | dependencies = [ 841 | "winapi 0.2.8", 842 | "winapi-build", 843 | ] 844 | 845 | [[package]] 846 | name = "khronos_api" 847 | version = "3.1.0" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" 850 | 851 | [[package]] 852 | name = "lazy_static" 853 | version = "1.4.0" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 856 | 857 | [[package]] 858 | name = "lazycell" 859 | version = "1.3.0" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 862 | 863 | [[package]] 864 | name = "lebe" 865 | version = "0.5.2" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" 868 | 869 | [[package]] 870 | name = "libc" 871 | version = "0.2.112" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" 874 | 875 | [[package]] 876 | name = "libloading" 877 | version = "0.7.2" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" 880 | dependencies = [ 881 | "cfg-if 1.0.0", 882 | "winapi 0.3.9", 883 | ] 884 | 885 | [[package]] 886 | name = "lock_api" 887 | version = "0.4.5" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" 890 | dependencies = [ 891 | "scopeguard", 892 | ] 893 | 894 | [[package]] 895 | name = "log" 896 | version = "0.4.14" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 899 | dependencies = [ 900 | "cfg-if 1.0.0", 901 | ] 902 | 903 | [[package]] 904 | name = "malloc_buf" 905 | version = "0.0.6" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 908 | dependencies = [ 909 | "libc", 910 | ] 911 | 912 | [[package]] 913 | name = "memchr" 914 | version = "2.4.1" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 917 | 918 | [[package]] 919 | name = "memmap2" 920 | version = "0.3.1" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "00b6c2ebff6180198788f5db08d7ce3bc1d0b617176678831a7510825973e357" 923 | dependencies = [ 924 | "libc", 925 | ] 926 | 927 | [[package]] 928 | name = "memoffset" 929 | version = "0.6.5" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 932 | dependencies = [ 933 | "autocfg", 934 | ] 935 | 936 | [[package]] 937 | name = "minimal-lexical" 938 | version = "0.2.1" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 941 | 942 | [[package]] 943 | name = "miniz_oxide" 944 | version = "0.6.2" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" 947 | dependencies = [ 948 | "adler", 949 | ] 950 | 951 | [[package]] 952 | name = "mio" 953 | version = "0.6.23" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" 956 | dependencies = [ 957 | "cfg-if 0.1.10", 958 | "fuchsia-zircon", 959 | "fuchsia-zircon-sys", 960 | "iovec", 961 | "kernel32-sys", 962 | "libc", 963 | "log", 964 | "miow 0.2.2", 965 | "net2", 966 | "slab", 967 | "winapi 0.2.8", 968 | ] 969 | 970 | [[package]] 971 | name = "mio" 972 | version = "0.8.0" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" 975 | dependencies = [ 976 | "libc", 977 | "log", 978 | "miow 0.3.7", 979 | "ntapi", 980 | "winapi 0.3.9", 981 | ] 982 | 983 | [[package]] 984 | name = "mio-extras" 985 | version = "2.0.6" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" 988 | dependencies = [ 989 | "lazycell", 990 | "log", 991 | "mio 0.6.23", 992 | "slab", 993 | ] 994 | 995 | [[package]] 996 | name = "miow" 997 | version = "0.2.2" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" 1000 | dependencies = [ 1001 | "kernel32-sys", 1002 | "net2", 1003 | "winapi 0.2.8", 1004 | "ws2_32-sys", 1005 | ] 1006 | 1007 | [[package]] 1008 | name = "miow" 1009 | version = "0.3.7" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 1012 | dependencies = [ 1013 | "winapi 0.3.9", 1014 | ] 1015 | 1016 | [[package]] 1017 | name = "nanorand" 1018 | version = "0.6.1" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "729eb334247daa1803e0a094d0a5c55711b85571179f5ec6e53eccfdf7008958" 1021 | dependencies = [ 1022 | "getrandom", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "ndk" 1027 | version = "0.5.0" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "96d868f654c72e75f8687572699cdabe755f03effbb62542768e995d5b8d699d" 1030 | dependencies = [ 1031 | "bitflags", 1032 | "jni-sys", 1033 | "ndk-sys", 1034 | "num_enum", 1035 | "thiserror", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "ndk-glue" 1040 | version = "0.5.0" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "fc291b8de2095cba8dab7cf381bf582ff4c17a09acf854c32e46545b08085d28" 1043 | dependencies = [ 1044 | "lazy_static", 1045 | "libc", 1046 | "log", 1047 | "ndk", 1048 | "ndk-macro", 1049 | "ndk-sys", 1050 | ] 1051 | 1052 | [[package]] 1053 | name = "ndk-macro" 1054 | version = "0.3.0" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" 1057 | dependencies = [ 1058 | "darling", 1059 | "proc-macro-crate", 1060 | "proc-macro2", 1061 | "quote", 1062 | "syn", 1063 | ] 1064 | 1065 | [[package]] 1066 | name = "ndk-sys" 1067 | version = "0.2.2" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" 1070 | 1071 | [[package]] 1072 | name = "net2" 1073 | version = "0.2.37" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" 1076 | dependencies = [ 1077 | "cfg-if 0.1.10", 1078 | "libc", 1079 | "winapi 0.3.9", 1080 | ] 1081 | 1082 | [[package]] 1083 | name = "nix" 1084 | version = "0.22.0" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "cf1e25ee6b412c2a1e3fcb6a4499a5c1bfe7f43e014bdce9a6b6666e5aa2d187" 1087 | dependencies = [ 1088 | "bitflags", 1089 | "cc", 1090 | "cfg-if 1.0.0", 1091 | "libc", 1092 | "memoffset", 1093 | ] 1094 | 1095 | [[package]] 1096 | name = "nom" 1097 | version = "7.1.0" 1098 | source = "registry+https://github.com/rust-lang/crates.io-index" 1099 | checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" 1100 | dependencies = [ 1101 | "memchr", 1102 | "minimal-lexical", 1103 | "version_check", 1104 | ] 1105 | 1106 | [[package]] 1107 | name = "notify" 1108 | version = "4.0.17" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257" 1111 | dependencies = [ 1112 | "bitflags", 1113 | "filetime", 1114 | "fsevent", 1115 | "fsevent-sys", 1116 | "inotify", 1117 | "libc", 1118 | "mio 0.6.23", 1119 | "mio-extras", 1120 | "walkdir", 1121 | "winapi 0.3.9", 1122 | ] 1123 | 1124 | [[package]] 1125 | name = "notorious6" 1126 | version = "0.1.0" 1127 | dependencies = [ 1128 | "anyhow", 1129 | "bytes", 1130 | "exr", 1131 | "gl", 1132 | "glutin", 1133 | "hotwatch", 1134 | "jpeg-encoder", 1135 | "lazy_static", 1136 | "log", 1137 | "parking_lot", 1138 | "radiant", 1139 | "relative-path", 1140 | "shader-prepper", 1141 | "simple_logger", 1142 | "smol", 1143 | "structopt", 1144 | "turbosloth", 1145 | ] 1146 | 1147 | [[package]] 1148 | name = "ntapi" 1149 | version = "0.3.6" 1150 | source = "registry+https://github.com/rust-lang/crates.io-index" 1151 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 1152 | dependencies = [ 1153 | "winapi 0.3.9", 1154 | ] 1155 | 1156 | [[package]] 1157 | name = "num_cpus" 1158 | version = "1.13.1" 1159 | source = "registry+https://github.com/rust-lang/crates.io-index" 1160 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 1161 | dependencies = [ 1162 | "hermit-abi", 1163 | "libc", 1164 | ] 1165 | 1166 | [[package]] 1167 | name = "num_enum" 1168 | version = "0.5.6" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "720d3ea1055e4e4574c0c0b0f8c3fd4f24c4cdaf465948206dea090b57b526ad" 1171 | dependencies = [ 1172 | "num_enum_derive", 1173 | ] 1174 | 1175 | [[package]] 1176 | name = "num_enum_derive" 1177 | version = "0.5.6" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "0d992b768490d7fe0d8586d9b5745f6c49f557da6d81dc982b1d167ad4edbb21" 1180 | dependencies = [ 1181 | "proc-macro-crate", 1182 | "proc-macro2", 1183 | "quote", 1184 | "syn", 1185 | ] 1186 | 1187 | [[package]] 1188 | name = "objc" 1189 | version = "0.2.7" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 1192 | dependencies = [ 1193 | "malloc_buf", 1194 | ] 1195 | 1196 | [[package]] 1197 | name = "once_cell" 1198 | version = "1.9.0" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" 1201 | 1202 | [[package]] 1203 | name = "osmesa-sys" 1204 | version = "0.1.2" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b" 1207 | dependencies = [ 1208 | "shared_library", 1209 | ] 1210 | 1211 | [[package]] 1212 | name = "parking" 1213 | version = "2.0.0" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" 1216 | 1217 | [[package]] 1218 | name = "parking_lot" 1219 | version = "0.11.2" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 1222 | dependencies = [ 1223 | "instant", 1224 | "lock_api", 1225 | "parking_lot_core", 1226 | ] 1227 | 1228 | [[package]] 1229 | name = "parking_lot_core" 1230 | version = "0.8.5" 1231 | source = "registry+https://github.com/rust-lang/crates.io-index" 1232 | checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" 1233 | dependencies = [ 1234 | "cfg-if 1.0.0", 1235 | "instant", 1236 | "libc", 1237 | "redox_syscall", 1238 | "smallvec", 1239 | "winapi 0.3.9", 1240 | ] 1241 | 1242 | [[package]] 1243 | name = "percent-encoding" 1244 | version = "2.1.0" 1245 | source = "registry+https://github.com/rust-lang/crates.io-index" 1246 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 1247 | 1248 | [[package]] 1249 | name = "pin-project" 1250 | version = "1.0.10" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" 1253 | dependencies = [ 1254 | "pin-project-internal", 1255 | ] 1256 | 1257 | [[package]] 1258 | name = "pin-project-internal" 1259 | version = "1.0.10" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" 1262 | dependencies = [ 1263 | "proc-macro2", 1264 | "quote", 1265 | "syn", 1266 | ] 1267 | 1268 | [[package]] 1269 | name = "pin-project-lite" 1270 | version = "0.2.8" 1271 | source = "registry+https://github.com/rust-lang/crates.io-index" 1272 | checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" 1273 | 1274 | [[package]] 1275 | name = "pkg-config" 1276 | version = "0.3.24" 1277 | source = "registry+https://github.com/rust-lang/crates.io-index" 1278 | checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" 1279 | 1280 | [[package]] 1281 | name = "polling" 1282 | version = "2.2.0" 1283 | source = "registry+https://github.com/rust-lang/crates.io-index" 1284 | checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" 1285 | dependencies = [ 1286 | "cfg-if 1.0.0", 1287 | "libc", 1288 | "log", 1289 | "wepoll-ffi", 1290 | "winapi 0.3.9", 1291 | ] 1292 | 1293 | [[package]] 1294 | name = "proc-macro-crate" 1295 | version = "1.1.0" 1296 | source = "registry+https://github.com/rust-lang/crates.io-index" 1297 | checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" 1298 | dependencies = [ 1299 | "thiserror", 1300 | "toml", 1301 | ] 1302 | 1303 | [[package]] 1304 | name = "proc-macro-error" 1305 | version = "1.0.4" 1306 | source = "registry+https://github.com/rust-lang/crates.io-index" 1307 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1308 | dependencies = [ 1309 | "proc-macro-error-attr", 1310 | "proc-macro2", 1311 | "quote", 1312 | "syn", 1313 | "version_check", 1314 | ] 1315 | 1316 | [[package]] 1317 | name = "proc-macro-error-attr" 1318 | version = "1.0.4" 1319 | source = "registry+https://github.com/rust-lang/crates.io-index" 1320 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1321 | dependencies = [ 1322 | "proc-macro2", 1323 | "quote", 1324 | "version_check", 1325 | ] 1326 | 1327 | [[package]] 1328 | name = "proc-macro2" 1329 | version = "1.0.36" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 1332 | dependencies = [ 1333 | "unicode-xid", 1334 | ] 1335 | 1336 | [[package]] 1337 | name = "quote" 1338 | version = "1.0.14" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" 1341 | dependencies = [ 1342 | "proc-macro2", 1343 | ] 1344 | 1345 | [[package]] 1346 | name = "radiant" 1347 | version = "0.3.0" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "8fe058d33033bb65dbf0adcfd4a03f4ce4964426ec9c73e054ebf2f7e605ccd3" 1350 | 1351 | [[package]] 1352 | name = "rand_core" 1353 | version = "0.6.3" 1354 | source = "registry+https://github.com/rust-lang/crates.io-index" 1355 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 1356 | 1357 | [[package]] 1358 | name = "raw-window-handle" 1359 | version = "0.4.2" 1360 | source = "registry+https://github.com/rust-lang/crates.io-index" 1361 | checksum = "fba75eee94a9d5273a68c9e1e105d9cffe1ef700532325788389e5a83e2522b7" 1362 | dependencies = [ 1363 | "cty", 1364 | ] 1365 | 1366 | [[package]] 1367 | name = "redox_syscall" 1368 | version = "0.2.10" 1369 | source = "registry+https://github.com/rust-lang/crates.io-index" 1370 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 1371 | dependencies = [ 1372 | "bitflags", 1373 | ] 1374 | 1375 | [[package]] 1376 | name = "regex" 1377 | version = "1.5.4" 1378 | source = "registry+https://github.com/rust-lang/crates.io-index" 1379 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 1380 | dependencies = [ 1381 | "aho-corasick", 1382 | "memchr", 1383 | "regex-syntax", 1384 | ] 1385 | 1386 | [[package]] 1387 | name = "regex-syntax" 1388 | version = "0.6.25" 1389 | source = "registry+https://github.com/rust-lang/crates.io-index" 1390 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 1391 | 1392 | [[package]] 1393 | name = "relative-path" 1394 | version = "1.6.0" 1395 | source = "registry+https://github.com/rust-lang/crates.io-index" 1396 | checksum = "73d4caf086b102ab49d0525b721594a555ab55c6556086bbe52a430ad26c3bd7" 1397 | 1398 | [[package]] 1399 | name = "same-file" 1400 | version = "1.0.6" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1403 | dependencies = [ 1404 | "winapi-util", 1405 | ] 1406 | 1407 | [[package]] 1408 | name = "scoped-tls" 1409 | version = "1.0.0" 1410 | source = "registry+https://github.com/rust-lang/crates.io-index" 1411 | checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" 1412 | 1413 | [[package]] 1414 | name = "scopeguard" 1415 | version = "1.1.0" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1418 | 1419 | [[package]] 1420 | name = "serde" 1421 | version = "1.0.133" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a" 1424 | 1425 | [[package]] 1426 | name = "shader-prepper" 1427 | version = "0.3.0-pre.1" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "998c7255e5600ddf2f503e0b3de47e7109a9de490fa1a01119db40bfa4dd729c" 1430 | dependencies = [ 1431 | "lazy_static", 1432 | "regex", 1433 | "thiserror", 1434 | ] 1435 | 1436 | [[package]] 1437 | name = "shared_library" 1438 | version = "0.1.9" 1439 | source = "registry+https://github.com/rust-lang/crates.io-index" 1440 | checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" 1441 | dependencies = [ 1442 | "lazy_static", 1443 | "libc", 1444 | ] 1445 | 1446 | [[package]] 1447 | name = "signal-hook" 1448 | version = "0.3.13" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d" 1451 | dependencies = [ 1452 | "libc", 1453 | "signal-hook-registry", 1454 | ] 1455 | 1456 | [[package]] 1457 | name = "signal-hook-registry" 1458 | version = "1.4.0" 1459 | source = "registry+https://github.com/rust-lang/crates.io-index" 1460 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1461 | dependencies = [ 1462 | "libc", 1463 | ] 1464 | 1465 | [[package]] 1466 | name = "simd-adler32" 1467 | version = "0.3.4" 1468 | source = "registry+https://github.com/rust-lang/crates.io-index" 1469 | checksum = "14a5df39617d7c8558154693a1bb8157a4aab8179209540cc0b10e5dc24e0b18" 1470 | 1471 | [[package]] 1472 | name = "simple_logger" 1473 | version = "1.16.0" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "45b60258a35dc3cb8a16890b8fd6723349bfa458d7960e25e633f1b1c19d7b5e" 1476 | dependencies = [ 1477 | "atty", 1478 | "colored", 1479 | "log", 1480 | "time", 1481 | "winapi 0.3.9", 1482 | ] 1483 | 1484 | [[package]] 1485 | name = "slab" 1486 | version = "0.4.5" 1487 | source = "registry+https://github.com/rust-lang/crates.io-index" 1488 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" 1489 | 1490 | [[package]] 1491 | name = "smallvec" 1492 | version = "1.7.0" 1493 | source = "registry+https://github.com/rust-lang/crates.io-index" 1494 | checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" 1495 | 1496 | [[package]] 1497 | name = "smithay-client-toolkit" 1498 | version = "0.15.3" 1499 | source = "registry+https://github.com/rust-lang/crates.io-index" 1500 | checksum = "1325f292209cee78d5035530932422a30aa4c8fda1a16593ac083c1de211e68a" 1501 | dependencies = [ 1502 | "bitflags", 1503 | "calloop", 1504 | "dlib", 1505 | "lazy_static", 1506 | "log", 1507 | "memmap2", 1508 | "nix", 1509 | "pkg-config", 1510 | "wayland-client", 1511 | "wayland-cursor", 1512 | "wayland-protocols", 1513 | ] 1514 | 1515 | [[package]] 1516 | name = "smol" 1517 | version = "1.2.5" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "85cf3b5351f3e783c1d79ab5fc604eeed8b8ae9abd36b166e8b87a089efd85e4" 1520 | dependencies = [ 1521 | "async-channel", 1522 | "async-executor", 1523 | "async-fs", 1524 | "async-io", 1525 | "async-lock", 1526 | "async-net", 1527 | "async-process", 1528 | "blocking", 1529 | "futures-lite", 1530 | "once_cell", 1531 | ] 1532 | 1533 | [[package]] 1534 | name = "socket2" 1535 | version = "0.4.2" 1536 | source = "registry+https://github.com/rust-lang/crates.io-index" 1537 | checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" 1538 | dependencies = [ 1539 | "libc", 1540 | "winapi 0.3.9", 1541 | ] 1542 | 1543 | [[package]] 1544 | name = "spin" 1545 | version = "0.9.2" 1546 | source = "registry+https://github.com/rust-lang/crates.io-index" 1547 | checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" 1548 | dependencies = [ 1549 | "lock_api", 1550 | ] 1551 | 1552 | [[package]] 1553 | name = "strsim" 1554 | version = "0.8.0" 1555 | source = "registry+https://github.com/rust-lang/crates.io-index" 1556 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 1557 | 1558 | [[package]] 1559 | name = "strsim" 1560 | version = "0.10.0" 1561 | source = "registry+https://github.com/rust-lang/crates.io-index" 1562 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1563 | 1564 | [[package]] 1565 | name = "structopt" 1566 | version = "0.3.25" 1567 | source = "registry+https://github.com/rust-lang/crates.io-index" 1568 | checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" 1569 | dependencies = [ 1570 | "clap", 1571 | "lazy_static", 1572 | "structopt-derive", 1573 | ] 1574 | 1575 | [[package]] 1576 | name = "structopt-derive" 1577 | version = "0.4.18" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 1580 | dependencies = [ 1581 | "heck", 1582 | "proc-macro-error", 1583 | "proc-macro2", 1584 | "quote", 1585 | "syn", 1586 | ] 1587 | 1588 | [[package]] 1589 | name = "syn" 1590 | version = "1.0.84" 1591 | source = "registry+https://github.com/rust-lang/crates.io-index" 1592 | checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b" 1593 | dependencies = [ 1594 | "proc-macro2", 1595 | "quote", 1596 | "unicode-xid", 1597 | ] 1598 | 1599 | [[package]] 1600 | name = "textwrap" 1601 | version = "0.11.0" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 1604 | dependencies = [ 1605 | "unicode-width", 1606 | ] 1607 | 1608 | [[package]] 1609 | name = "thiserror" 1610 | version = "1.0.30" 1611 | source = "registry+https://github.com/rust-lang/crates.io-index" 1612 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 1613 | dependencies = [ 1614 | "thiserror-impl", 1615 | ] 1616 | 1617 | [[package]] 1618 | name = "thiserror-impl" 1619 | version = "1.0.30" 1620 | source = "registry+https://github.com/rust-lang/crates.io-index" 1621 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 1622 | dependencies = [ 1623 | "proc-macro2", 1624 | "quote", 1625 | "syn", 1626 | ] 1627 | 1628 | [[package]] 1629 | name = "threadpool" 1630 | version = "1.8.1" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" 1633 | dependencies = [ 1634 | "num_cpus", 1635 | ] 1636 | 1637 | [[package]] 1638 | name = "time" 1639 | version = "0.3.5" 1640 | source = "registry+https://github.com/rust-lang/crates.io-index" 1641 | checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad" 1642 | dependencies = [ 1643 | "itoa", 1644 | "libc", 1645 | "time-macros", 1646 | ] 1647 | 1648 | [[package]] 1649 | name = "time-macros" 1650 | version = "0.2.3" 1651 | source = "registry+https://github.com/rust-lang/crates.io-index" 1652 | checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" 1653 | 1654 | [[package]] 1655 | name = "toml" 1656 | version = "0.5.8" 1657 | source = "registry+https://github.com/rust-lang/crates.io-index" 1658 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 1659 | dependencies = [ 1660 | "serde", 1661 | ] 1662 | 1663 | [[package]] 1664 | name = "turbosloth" 1665 | version = "0.2.0" 1666 | source = "git+https://github.com/h3r2tic/turbosloth.git?rev=92030af#92030af160fe7dcf4089fe728e8990b7eafe82fb" 1667 | dependencies = [ 1668 | "async-trait", 1669 | "futures-lite", 1670 | "wyhash", 1671 | ] 1672 | 1673 | [[package]] 1674 | name = "unicode-segmentation" 1675 | version = "1.8.0" 1676 | source = "registry+https://github.com/rust-lang/crates.io-index" 1677 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 1678 | 1679 | [[package]] 1680 | name = "unicode-width" 1681 | version = "0.1.9" 1682 | source = "registry+https://github.com/rust-lang/crates.io-index" 1683 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 1684 | 1685 | [[package]] 1686 | name = "unicode-xid" 1687 | version = "0.2.2" 1688 | source = "registry+https://github.com/rust-lang/crates.io-index" 1689 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1690 | 1691 | [[package]] 1692 | name = "vec_map" 1693 | version = "0.8.2" 1694 | source = "registry+https://github.com/rust-lang/crates.io-index" 1695 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1696 | 1697 | [[package]] 1698 | name = "version_check" 1699 | version = "0.9.4" 1700 | source = "registry+https://github.com/rust-lang/crates.io-index" 1701 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1702 | 1703 | [[package]] 1704 | name = "waker-fn" 1705 | version = "1.1.0" 1706 | source = "registry+https://github.com/rust-lang/crates.io-index" 1707 | checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" 1708 | 1709 | [[package]] 1710 | name = "walkdir" 1711 | version = "2.3.2" 1712 | source = "registry+https://github.com/rust-lang/crates.io-index" 1713 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" 1714 | dependencies = [ 1715 | "same-file", 1716 | "winapi 0.3.9", 1717 | "winapi-util", 1718 | ] 1719 | 1720 | [[package]] 1721 | name = "wasi" 1722 | version = "0.10.2+wasi-snapshot-preview1" 1723 | source = "registry+https://github.com/rust-lang/crates.io-index" 1724 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 1725 | 1726 | [[package]] 1727 | name = "wasm-bindgen" 1728 | version = "0.2.78" 1729 | source = "registry+https://github.com/rust-lang/crates.io-index" 1730 | checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" 1731 | dependencies = [ 1732 | "cfg-if 1.0.0", 1733 | "wasm-bindgen-macro", 1734 | ] 1735 | 1736 | [[package]] 1737 | name = "wasm-bindgen-backend" 1738 | version = "0.2.78" 1739 | source = "registry+https://github.com/rust-lang/crates.io-index" 1740 | checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" 1741 | dependencies = [ 1742 | "bumpalo", 1743 | "lazy_static", 1744 | "log", 1745 | "proc-macro2", 1746 | "quote", 1747 | "syn", 1748 | "wasm-bindgen-shared", 1749 | ] 1750 | 1751 | [[package]] 1752 | name = "wasm-bindgen-macro" 1753 | version = "0.2.78" 1754 | source = "registry+https://github.com/rust-lang/crates.io-index" 1755 | checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" 1756 | dependencies = [ 1757 | "quote", 1758 | "wasm-bindgen-macro-support", 1759 | ] 1760 | 1761 | [[package]] 1762 | name = "wasm-bindgen-macro-support" 1763 | version = "0.2.78" 1764 | source = "registry+https://github.com/rust-lang/crates.io-index" 1765 | checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" 1766 | dependencies = [ 1767 | "proc-macro2", 1768 | "quote", 1769 | "syn", 1770 | "wasm-bindgen-backend", 1771 | "wasm-bindgen-shared", 1772 | ] 1773 | 1774 | [[package]] 1775 | name = "wasm-bindgen-shared" 1776 | version = "0.2.78" 1777 | source = "registry+https://github.com/rust-lang/crates.io-index" 1778 | checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" 1779 | 1780 | [[package]] 1781 | name = "wayland-client" 1782 | version = "0.29.3" 1783 | source = "registry+https://github.com/rust-lang/crates.io-index" 1784 | checksum = "7e256a731597b4e264d2f342e44f3708814103fbab144676fa077b6d9f3e2966" 1785 | dependencies = [ 1786 | "bitflags", 1787 | "downcast-rs", 1788 | "libc", 1789 | "nix", 1790 | "scoped-tls", 1791 | "wayland-commons", 1792 | "wayland-scanner", 1793 | "wayland-sys", 1794 | ] 1795 | 1796 | [[package]] 1797 | name = "wayland-commons" 1798 | version = "0.29.3" 1799 | source = "registry+https://github.com/rust-lang/crates.io-index" 1800 | checksum = "96f28d05d154a6ae7a183f2d29906ccceae794047b3a97d35a627f483ed05ee2" 1801 | dependencies = [ 1802 | "nix", 1803 | "once_cell", 1804 | "smallvec", 1805 | "wayland-sys", 1806 | ] 1807 | 1808 | [[package]] 1809 | name = "wayland-cursor" 1810 | version = "0.29.3" 1811 | source = "registry+https://github.com/rust-lang/crates.io-index" 1812 | checksum = "bcb1afc06470809fea80281128ea2ed21b730589fe5f5ef0478eb8633bc1b003" 1813 | dependencies = [ 1814 | "nix", 1815 | "wayland-client", 1816 | "xcursor", 1817 | ] 1818 | 1819 | [[package]] 1820 | name = "wayland-egl" 1821 | version = "0.29.3" 1822 | source = "registry+https://github.com/rust-lang/crates.io-index" 1823 | checksum = "d805575be40ef52bd6496a6796d6c8e3c49da06ee2382a0a30aa5d2909280869" 1824 | dependencies = [ 1825 | "wayland-client", 1826 | "wayland-sys", 1827 | ] 1828 | 1829 | [[package]] 1830 | name = "wayland-protocols" 1831 | version = "0.29.3" 1832 | source = "registry+https://github.com/rust-lang/crates.io-index" 1833 | checksum = "5b5199e12af7708dfb4eb6ea2f10b089d21b4b437cfde44b018ad11b093101b6" 1834 | dependencies = [ 1835 | "bitflags", 1836 | "wayland-client", 1837 | "wayland-commons", 1838 | "wayland-scanner", 1839 | ] 1840 | 1841 | [[package]] 1842 | name = "wayland-scanner" 1843 | version = "0.29.3" 1844 | source = "registry+https://github.com/rust-lang/crates.io-index" 1845 | checksum = "2cfd5edf014d2bcfd13607f6461acc15677676eeca58df0af7c4856be5faabf1" 1846 | dependencies = [ 1847 | "proc-macro2", 1848 | "quote", 1849 | "xml-rs", 1850 | ] 1851 | 1852 | [[package]] 1853 | name = "wayland-sys" 1854 | version = "0.29.3" 1855 | source = "registry+https://github.com/rust-lang/crates.io-index" 1856 | checksum = "beb0eb50984d3efb0642b58ee2f458a62d765563bebd94049b6f9f40979f12aa" 1857 | dependencies = [ 1858 | "dlib", 1859 | "lazy_static", 1860 | "pkg-config", 1861 | ] 1862 | 1863 | [[package]] 1864 | name = "web-sys" 1865 | version = "0.3.55" 1866 | source = "registry+https://github.com/rust-lang/crates.io-index" 1867 | checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" 1868 | dependencies = [ 1869 | "js-sys", 1870 | "wasm-bindgen", 1871 | ] 1872 | 1873 | [[package]] 1874 | name = "wepoll-ffi" 1875 | version = "0.1.2" 1876 | source = "registry+https://github.com/rust-lang/crates.io-index" 1877 | checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" 1878 | dependencies = [ 1879 | "cc", 1880 | ] 1881 | 1882 | [[package]] 1883 | name = "winapi" 1884 | version = "0.2.8" 1885 | source = "registry+https://github.com/rust-lang/crates.io-index" 1886 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1887 | 1888 | [[package]] 1889 | name = "winapi" 1890 | version = "0.3.9" 1891 | source = "registry+https://github.com/rust-lang/crates.io-index" 1892 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1893 | dependencies = [ 1894 | "winapi-i686-pc-windows-gnu", 1895 | "winapi-x86_64-pc-windows-gnu", 1896 | ] 1897 | 1898 | [[package]] 1899 | name = "winapi-build" 1900 | version = "0.1.1" 1901 | source = "registry+https://github.com/rust-lang/crates.io-index" 1902 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1903 | 1904 | [[package]] 1905 | name = "winapi-i686-pc-windows-gnu" 1906 | version = "0.4.0" 1907 | source = "registry+https://github.com/rust-lang/crates.io-index" 1908 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1909 | 1910 | [[package]] 1911 | name = "winapi-util" 1912 | version = "0.1.5" 1913 | source = "registry+https://github.com/rust-lang/crates.io-index" 1914 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1915 | dependencies = [ 1916 | "winapi 0.3.9", 1917 | ] 1918 | 1919 | [[package]] 1920 | name = "winapi-x86_64-pc-windows-gnu" 1921 | version = "0.4.0" 1922 | source = "registry+https://github.com/rust-lang/crates.io-index" 1923 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1924 | 1925 | [[package]] 1926 | name = "winit" 1927 | version = "0.26.0" 1928 | source = "registry+https://github.com/rust-lang/crates.io-index" 1929 | checksum = "70466a5f4825cc88c92963591b06dbc255420bffe19d847bfcda475e82d079c0" 1930 | dependencies = [ 1931 | "bitflags", 1932 | "block", 1933 | "cocoa", 1934 | "core-foundation 0.9.2", 1935 | "core-graphics 0.22.3", 1936 | "core-video-sys", 1937 | "dispatch", 1938 | "instant", 1939 | "lazy_static", 1940 | "libc", 1941 | "log", 1942 | "mio 0.8.0", 1943 | "ndk", 1944 | "ndk-glue", 1945 | "ndk-sys", 1946 | "objc", 1947 | "parking_lot", 1948 | "percent-encoding", 1949 | "raw-window-handle", 1950 | "smithay-client-toolkit", 1951 | "wasm-bindgen", 1952 | "wayland-client", 1953 | "wayland-protocols", 1954 | "web-sys", 1955 | "winapi 0.3.9", 1956 | "x11-dl", 1957 | ] 1958 | 1959 | [[package]] 1960 | name = "ws2_32-sys" 1961 | version = "0.2.1" 1962 | source = "registry+https://github.com/rust-lang/crates.io-index" 1963 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1964 | dependencies = [ 1965 | "winapi 0.2.8", 1966 | "winapi-build", 1967 | ] 1968 | 1969 | [[package]] 1970 | name = "wyhash" 1971 | version = "0.5.0" 1972 | source = "registry+https://github.com/rust-lang/crates.io-index" 1973 | checksum = "baf6e163c25e3fac820b4b453185ea2dea3b6a3e0a721d4d23d75bd33734c295" 1974 | dependencies = [ 1975 | "rand_core", 1976 | ] 1977 | 1978 | [[package]] 1979 | name = "x11-dl" 1980 | version = "2.19.1" 1981 | source = "registry+https://github.com/rust-lang/crates.io-index" 1982 | checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59" 1983 | dependencies = [ 1984 | "lazy_static", 1985 | "libc", 1986 | "pkg-config", 1987 | ] 1988 | 1989 | [[package]] 1990 | name = "xcursor" 1991 | version = "0.3.4" 1992 | source = "registry+https://github.com/rust-lang/crates.io-index" 1993 | checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" 1994 | dependencies = [ 1995 | "nom", 1996 | ] 1997 | 1998 | [[package]] 1999 | name = "xml-rs" 2000 | version = "0.8.4" 2001 | source = "registry+https://github.com/rust-lang/crates.io-index" 2002 | checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" 2003 | 2004 | [[package]] 2005 | name = "zune-inflate" 2006 | version = "0.2.42" 2007 | source = "registry+https://github.com/rust-lang/crates.io-index" 2008 | checksum = "c473377c11c4a3ac6a2758f944cd336678e9c977aa0abf54f6450cf77e902d6d" 2009 | dependencies = [ 2010 | "simd-adler32", 2011 | ] 2012 | --------------------------------------------------------------------------------