├── .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 | 
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 |
--------------------------------------------------------------------------------