mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
gpu invert node demo
This commit is contained in:
27
node-graph/gcore-shader/Cargo.toml
Normal file
27
node-graph/gcore-shader/Cargo.toml
Normal file
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "graphene-core-shader"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
description = "Graphene nodes compiled to shaders"
|
||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib", "cdylib"]
|
||||
|
||||
[features]
|
||||
gpu = ["glam/libm"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace dependencies
|
||||
spirv-std = { workspace = true }
|
||||
bytemuck = { workspace = true }
|
||||
glam = { workspace = true, features = [
|
||||
"scalar-math", "bytemuck"
|
||||
] }
|
||||
|
||||
[lints.rust]
|
||||
# the spirv target is not in the list of common cfgs so must be added manually
|
||||
unexpected_cfgs = { level = "warn", check-cfg = [
|
||||
'cfg(target_arch, values("spirv"))',
|
||||
] }
|
||||
782
node-graph/gcore-shader/src/color.rs
Normal file
782
node-graph/gcore-shader/src/color.rs
Normal file
@@ -0,0 +1,782 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use core::hash::Hash;
|
||||
use glam::Vec4;
|
||||
#[cfg(target_arch = "spirv")]
|
||||
use spirv_std::num_traits::Euclid;
|
||||
#[cfg(target_arch = "spirv")]
|
||||
use spirv_std::num_traits::float::Float;
|
||||
|
||||
// -----------------------------------------------------
|
||||
// custom color start
|
||||
// -----------------------------------------------------
|
||||
|
||||
impl From<Vec4> for Color {
|
||||
fn from(value: Vec4) -> Self {
|
||||
Color {
|
||||
red: value.x,
|
||||
green: value.y,
|
||||
blue: value.z,
|
||||
alpha: value.w,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for Vec4 {
|
||||
fn from(value: Color) -> Self {
|
||||
Vec4::new(value.red, value.green, value.blue, value.alpha)
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
// custom color end
|
||||
// -----------------------------------------------------
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)]
|
||||
pub struct SRGBA8 {
|
||||
red: u8,
|
||||
green: u8,
|
||||
blue: u8,
|
||||
alpha: u8,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)]
|
||||
pub struct Luma(pub f32);
|
||||
|
||||
/// Structure that represents a color.
|
||||
/// Internally alpha is stored as `f32` that ranges from `0.0` (transparent) to `1.0` (opaque).
|
||||
/// The other components (RGB) are stored as `f32` that range from `0.0` up to `f32::MAX`,
|
||||
/// the values encode the brightness of each channel proportional to the light intensity in cd/m² (nits) in HDR, and `0.0` (black) to `1.0` (white) in SDR color.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Pod, Zeroable)]
|
||||
pub struct Color {
|
||||
red: f32,
|
||||
green: f32,
|
||||
blue: f32,
|
||||
alpha: f32,
|
||||
}
|
||||
|
||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||
impl Hash for Color {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.red.to_bits().hash(state);
|
||||
self.green.to_bits().hash(state);
|
||||
self.blue.to_bits().hash(state);
|
||||
self.alpha.to_bits().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
pub const BLACK: Color = Color::from_rgbf32_unchecked(0., 0., 0.);
|
||||
pub const WHITE: Color = Color::from_rgbf32_unchecked(1., 1., 1.);
|
||||
pub const RED: Color = Color::from_rgbf32_unchecked(1., 0., 0.);
|
||||
pub const GREEN: Color = Color::from_rgbf32_unchecked(0., 1., 0.);
|
||||
pub const BLUE: Color = Color::from_rgbf32_unchecked(0., 0., 1.);
|
||||
pub const YELLOW: Color = Color::from_rgbf32_unchecked(1., 1., 0.);
|
||||
pub const CYAN: Color = Color::from_rgbf32_unchecked(0., 1., 1.);
|
||||
pub const MAGENTA: Color = Color::from_rgbf32_unchecked(1., 0., 1.);
|
||||
pub const TRANSPARENT: Color = Self {
|
||||
red: 0.,
|
||||
green: 0.,
|
||||
blue: 0.,
|
||||
alpha: 0.,
|
||||
};
|
||||
|
||||
/// Returns `Some(Color)` if `red`, `green`, `blue` and `alpha` have a valid value. Negative numbers (including `-0.0`), NaN, and infinity are not valid values and return `None`.
|
||||
/// Alpha values greater than `1.0` are not valid.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use graphene_core::raster::color::Color;
|
||||
/// let color = Color::from_rgbaf32(0.3, 0.14, 0.15, 0.92).unwrap();
|
||||
/// assert!(color.components() == (0.3, 0.14, 0.15, 0.92));
|
||||
///
|
||||
/// let color = Color::from_rgbaf32(1., 1., 1., f32::NAN);
|
||||
/// assert!(color == None);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn from_rgbaf32(red: f32, green: f32, blue: f32, alpha: f32) -> Option<Color> {
|
||||
if alpha > 1. || [red, green, blue, alpha].iter().any(|c| c.is_sign_negative() || !c.is_finite()) {
|
||||
return None;
|
||||
}
|
||||
let color = Color { red, green, blue, alpha };
|
||||
Some(color)
|
||||
}
|
||||
|
||||
/// Return an opaque `Color` from given `f32` RGB channels.
|
||||
#[inline(always)]
|
||||
pub const fn from_rgbf32_unchecked(red: f32, green: f32, blue: f32) -> Color {
|
||||
Color { red, green, blue, alpha: 1. }
|
||||
}
|
||||
|
||||
/// Return an opaque `Color` from given `f32` RGB channels.
|
||||
#[inline(always)]
|
||||
pub const fn from_rgbaf32_unchecked(red: f32, green: f32, blue: f32, alpha: f32) -> Color {
|
||||
Color { red, green, blue, alpha }
|
||||
}
|
||||
|
||||
/// Return an opaque `Color` from given `f32` RGB channels.
|
||||
#[inline(always)]
|
||||
pub fn from_unassociated_alpha(red: f32, green: f32, blue: f32, alpha: f32) -> Color {
|
||||
Color::from_rgbaf32_unchecked(red * alpha, green * alpha, blue * alpha, alpha)
|
||||
}
|
||||
|
||||
/// Return an opaque SDR `Color` given RGB channels from `0` to `255`, premultiplied by alpha.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use graphene_core::raster::color::Color;
|
||||
/// let color = Color::from_rgb8_srgb(0x72, 0x67, 0x62);
|
||||
/// let color2 = Color::from_rgba8_srgb(0x72, 0x67, 0x62, 0xFF);
|
||||
/// assert_eq!(color, color2)
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn from_rgb8_srgb(red: u8, green: u8, blue: u8) -> Color {
|
||||
Color::from_rgba8_srgb(red, green, blue, 255)
|
||||
}
|
||||
|
||||
// TODO: Should this be premult?
|
||||
/// Return an SDR `Color` given RGBA channels from `0` to `255`, premultiplied by alpha.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use graphene_core::raster::color::Color;
|
||||
/// let color = Color::from_rgba8_srgb(0x72, 0x67, 0x62, 0x61);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn from_rgba8_srgb(red: u8, green: u8, blue: u8, alpha: u8) -> Color {
|
||||
let map_range = |int_color| int_color as f32 / 255.;
|
||||
|
||||
let red = map_range(red);
|
||||
let green = map_range(green);
|
||||
let blue = map_range(blue);
|
||||
let alpha = map_range(alpha);
|
||||
Color { red, green, blue, alpha }.to_linear_srgb().map_rgb(|channel| channel * alpha)
|
||||
}
|
||||
|
||||
/// Create a [Color] from a hue, saturation, lightness and alpha (all between 0 and 1)
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use graphene_core::raster::color::Color;
|
||||
/// let color = Color::from_hsla(0.5, 0.2, 0.3, 1.);
|
||||
/// ```
|
||||
pub fn from_hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Color {
|
||||
let temp1 = if lightness < 0.5 {
|
||||
lightness * (saturation + 1.)
|
||||
} else {
|
||||
lightness + saturation - lightness * saturation
|
||||
};
|
||||
let temp2 = 2. * lightness - temp1;
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
let rem = |x: f32| x.rem_euclid(1.);
|
||||
#[cfg(target_arch = "spirv")]
|
||||
let rem = |x: f32| x.rem_euclid(&1.);
|
||||
|
||||
let mut red = rem(hue + 1. / 3.);
|
||||
let mut green = rem(hue);
|
||||
let mut blue = rem(hue - 1. / 3.);
|
||||
|
||||
fn map_channel(channel: &mut f32, temp2: f32, temp1: f32) {
|
||||
*channel = if *channel * 6. < 1. {
|
||||
temp2 + (temp1 - temp2) * 6. * *channel
|
||||
} else if *channel * 2. < 1. {
|
||||
temp1
|
||||
} else if *channel * 3. < 2. {
|
||||
temp2 + (temp1 - temp2) * (2. / 3. - *channel) * 6.
|
||||
} else {
|
||||
temp2
|
||||
}
|
||||
.clamp(0., 1.);
|
||||
}
|
||||
map_channel(&mut red, temp2, temp1);
|
||||
map_channel(&mut green, temp2, temp1);
|
||||
map_channel(&mut blue, temp2, temp1);
|
||||
|
||||
Color { red, green, blue, alpha }
|
||||
}
|
||||
|
||||
/// Return the `red` component.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use graphene_core::raster::color::Color;
|
||||
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
|
||||
/// assert!(color.r() == 0.114);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn r(&self) -> f32 {
|
||||
self.red
|
||||
}
|
||||
|
||||
/// Return the `green` component.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use graphene_core::raster::color::Color;
|
||||
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
|
||||
/// assert!(color.g() == 0.103);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn g(&self) -> f32 {
|
||||
self.green
|
||||
}
|
||||
|
||||
/// Return the `blue` component.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use graphene_core::raster::color::Color;
|
||||
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
|
||||
/// assert!(color.b() == 0.98);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn b(&self) -> f32 {
|
||||
self.blue
|
||||
}
|
||||
|
||||
/// Return the `alpha` component without checking its expected `0.0` to `1.0` range.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use graphene_core::raster::color::Color;
|
||||
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
|
||||
/// assert!(color.a() == 0.97);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn a(&self) -> f32 {
|
||||
self.alpha
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn average_rgb_channels(&self) -> f32 {
|
||||
(self.red + self.green + self.blue) / 3.
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn minimum_rgb_channels(&self) -> f32 {
|
||||
self.red.min(self.green).min(self.blue)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn maximum_rgb_channels(&self) -> f32 {
|
||||
self.red.max(self.green).max(self.blue)
|
||||
}
|
||||
|
||||
// From https://stackoverflow.com/a/56678483/775283
|
||||
#[inline(always)]
|
||||
pub fn luminance_srgb(&self) -> f32 {
|
||||
0.2126 * self.red + 0.7152 * self.green + 0.0722 * self.blue
|
||||
}
|
||||
|
||||
// From https://en.wikipedia.org/wiki/Luma_(video)#Rec._601_luma_versus_Rec._709_luma_coefficients
|
||||
#[inline(always)]
|
||||
pub fn luminance_rec_601(&self) -> f32 {
|
||||
0.299 * self.red + 0.587 * self.green + 0.114 * self.blue
|
||||
}
|
||||
|
||||
// From https://en.wikipedia.org/wiki/Luma_(video)#Rec._601_luma_versus_Rec._709_luma_coefficients
|
||||
#[inline(always)]
|
||||
pub fn luminance_rec_601_rounded(&self) -> f32 {
|
||||
0.3 * self.red + 0.59 * self.green + 0.11 * self.blue
|
||||
}
|
||||
|
||||
// From https://stackoverflow.com/a/56678483/775283
|
||||
#[inline(always)]
|
||||
pub fn luminance_perceptual(&self) -> f32 {
|
||||
let luminance = self.luminance_srgb();
|
||||
|
||||
if luminance <= 0.008856 {
|
||||
(luminance * 903.3) / 100.
|
||||
} else {
|
||||
(luminance.cbrt() * 116. - 16.) / 100.
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn from_luminance(luminance: f32) -> Color {
|
||||
Color {
|
||||
red: luminance,
|
||||
green: luminance,
|
||||
blue: luminance,
|
||||
alpha: 1.,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn with_luminance(&self, luminance: f32) -> Color {
|
||||
let delta = luminance - self.luminance_rec_601_rounded();
|
||||
self.map_rgb(|c| (c + delta).clamp(0., 1.))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn saturation(&self) -> f32 {
|
||||
let max = (self.red).max(self.green).max(self.blue);
|
||||
let min = (self.red).min(self.green).min(self.blue);
|
||||
|
||||
max - min
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn with_saturation(&self, saturation: f32) -> Color {
|
||||
let [hue, _, lightness, alpha] = self.to_hsla();
|
||||
Color::from_hsla(hue, saturation, lightness, alpha)
|
||||
}
|
||||
|
||||
pub fn with_alpha(&self, alpha: f32) -> Color {
|
||||
Color {
|
||||
red: self.red,
|
||||
green: self.green,
|
||||
blue: self.blue,
|
||||
alpha,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_red(&self, red: f32) -> Color {
|
||||
Color {
|
||||
red,
|
||||
green: self.green,
|
||||
blue: self.blue,
|
||||
alpha: self.alpha,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_green(&self, green: f32) -> Color {
|
||||
Color {
|
||||
red: self.red,
|
||||
green,
|
||||
blue: self.blue,
|
||||
alpha: self.alpha,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_blue(&self, blue: f32) -> Color {
|
||||
Color {
|
||||
red: self.red,
|
||||
green: self.green,
|
||||
blue,
|
||||
alpha: self.alpha,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn blend_normal(_c_b: f32, c_s: f32) -> f32 {
|
||||
c_s
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn blend_multiply(c_b: f32, c_s: f32) -> f32 {
|
||||
c_s * c_b
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn blend_darken(c_b: f32, c_s: f32) -> f32 {
|
||||
c_s.min(c_b)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn blend_color_burn(c_b: f32, c_s: f32) -> f32 {
|
||||
if c_b == 1. {
|
||||
1.
|
||||
} else if c_s == 0. {
|
||||
0.
|
||||
} else {
|
||||
1. - ((1. - c_b) / c_s).min(1.)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn blend_linear_burn(c_b: f32, c_s: f32) -> f32 {
|
||||
c_b + c_s - 1.
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn blend_darker_color(&self, other: Color) -> Color {
|
||||
if self.average_rgb_channels() <= other.average_rgb_channels() { *self } else { other }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn blend_screen(c_b: f32, c_s: f32) -> f32 {
|
||||
1. - (1. - c_s) * (1. - c_b)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn blend_lighten(c_b: f32, c_s: f32) -> f32 {
|
||||
c_s.max(c_b)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn blend_color_dodge(c_b: f32, c_s: f32) -> f32 {
|
||||
if c_s == 1. { 1. } else { (c_b / (1. - c_s)).min(1.) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn blend_linear_dodge(c_b: f32, c_s: f32) -> f32 {
|
||||
c_b + c_s
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn blend_lighter_color(&self, other: Color) -> Color {
|
||||
if self.average_rgb_channels() >= other.average_rgb_channels() { *self } else { other }
|
||||
}
|
||||
|
||||
pub fn blend_softlight(c_b: f32, c_s: f32) -> f32 {
|
||||
if c_s <= 0.5 {
|
||||
c_b - (1. - 2. * c_s) * c_b * (1. - c_b)
|
||||
} else {
|
||||
let d: fn(f32) -> f32 = |x| if x <= 0.25 { ((16. * x - 12.) * x + 4.) * x } else { x.sqrt() };
|
||||
c_b + (2. * c_s - 1.) * (d(c_b) - c_b)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blend_hardlight(c_b: f32, c_s: f32) -> f32 {
|
||||
if c_s <= 0.5 {
|
||||
Color::blend_multiply(2. * c_s, c_b)
|
||||
} else {
|
||||
Color::blend_screen(2. * c_s - 1., c_b)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blend_vivid_light(c_b: f32, c_s: f32) -> f32 {
|
||||
if c_s <= 0.5 {
|
||||
Color::blend_color_burn(2. * c_s, c_b)
|
||||
} else {
|
||||
Color::blend_color_dodge(2. * c_s - 1., c_b)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blend_linear_light(c_b: f32, c_s: f32) -> f32 {
|
||||
if c_s <= 0.5 {
|
||||
Color::blend_linear_burn(2. * c_s, c_b)
|
||||
} else {
|
||||
Color::blend_linear_dodge(2. * c_s - 1., c_b)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blend_pin_light(c_b: f32, c_s: f32) -> f32 {
|
||||
if c_s <= 0.5 {
|
||||
Color::blend_darken(2. * c_s, c_b)
|
||||
} else {
|
||||
Color::blend_lighten(2. * c_s - 1., c_b)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blend_hard_mix(c_b: f32, c_s: f32) -> f32 {
|
||||
if Color::blend_linear_light(c_b, c_s) < 0.5 { 0. } else { 1. }
|
||||
}
|
||||
|
||||
pub fn blend_difference(c_b: f32, c_s: f32) -> f32 {
|
||||
(c_b - c_s).abs()
|
||||
}
|
||||
|
||||
pub fn blend_exclusion(c_b: f32, c_s: f32) -> f32 {
|
||||
c_b + c_s - 2. * c_b * c_s
|
||||
}
|
||||
|
||||
pub fn blend_subtract(c_b: f32, c_s: f32) -> f32 {
|
||||
c_b - c_s
|
||||
}
|
||||
|
||||
pub fn blend_divide(c_b: f32, c_s: f32) -> f32 {
|
||||
if c_b == 0. { 1. } else { c_b / c_s }
|
||||
}
|
||||
|
||||
pub fn blend_hue(&self, c_s: Color) -> Color {
|
||||
let sat_b = self.saturation();
|
||||
let lum_b = self.luminance_rec_601();
|
||||
c_s.with_saturation(sat_b).with_luminance(lum_b)
|
||||
}
|
||||
|
||||
pub fn blend_saturation(&self, c_s: Color) -> Color {
|
||||
let sat_s = c_s.saturation();
|
||||
let lum_b = self.luminance_rec_601();
|
||||
|
||||
self.with_saturation(sat_s).with_luminance(lum_b)
|
||||
}
|
||||
|
||||
pub fn blend_color(&self, c_s: Color) -> Color {
|
||||
let lum_b = self.luminance_rec_601();
|
||||
|
||||
c_s.with_luminance(lum_b)
|
||||
}
|
||||
|
||||
pub fn blend_luminosity(&self, c_s: Color) -> Color {
|
||||
let lum_s = c_s.luminance_rec_601();
|
||||
|
||||
self.with_luminance(lum_s)
|
||||
}
|
||||
|
||||
/// Return the all components as a tuple, first component is red, followed by green, followed by blue, followed by alpha.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use graphene_core::raster::color::Color;
|
||||
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
|
||||
/// assert_eq!(color.components(), (0.114, 0.103, 0.98, 0.97));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn components(&self) -> (f32, f32, f32, f32) {
|
||||
(self.red, self.green, self.blue, self.alpha)
|
||||
}
|
||||
|
||||
/// Return the all components as a u8 slice, first component is red, followed by green, followed by blue, followed by alpha. Use this if the [`Color`] is in linear space.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use graphene_core::raster::color::Color;
|
||||
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
|
||||
/// // TODO: Add test
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn to_rgba8_srgb(&self) -> [u8; 4] {
|
||||
let gamma = self.to_gamma_srgb();
|
||||
[(gamma.red * 255.) as u8, (gamma.green * 255.) as u8, (gamma.blue * 255.) as u8, (gamma.alpha * 255.) as u8]
|
||||
}
|
||||
|
||||
// https://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
|
||||
/// Convert a [Color] to a hue, saturation, lightness and alpha (all between 0 and 1)
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use graphene_core::raster::color::Color;
|
||||
/// let color = Color::from_hsla(0.5, 0.2, 0.3, 1.).to_hsla();
|
||||
/// ```
|
||||
pub fn to_hsla(&self) -> [f32; 4] {
|
||||
let min_channel = self.red.min(self.green).min(self.blue);
|
||||
let max_channel = self.red.max(self.green).max(self.blue);
|
||||
|
||||
let lightness = (min_channel + max_channel) / 2.;
|
||||
let saturation = if min_channel == max_channel {
|
||||
0.
|
||||
} else if lightness <= 0.5 {
|
||||
(max_channel - min_channel) / (max_channel + min_channel)
|
||||
} else {
|
||||
(max_channel - min_channel) / (2. - max_channel - min_channel)
|
||||
};
|
||||
let hue = if self.red >= self.green && self.red >= self.blue {
|
||||
(self.green - self.blue) / (max_channel - min_channel)
|
||||
} else if self.green >= self.red && self.green >= self.blue {
|
||||
2. + (self.blue - self.red) / (max_channel - min_channel)
|
||||
} else {
|
||||
4. + (self.red - self.green) / (max_channel - min_channel)
|
||||
} / 6.;
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
let hue = hue.rem_euclid(1.);
|
||||
#[cfg(target_arch = "spirv")]
|
||||
let hue = hue.rem_euclid(&1.);
|
||||
|
||||
[hue, saturation, lightness, self.alpha]
|
||||
}
|
||||
|
||||
// TODO: Readd formatting
|
||||
|
||||
/// Creates a color from a 8-character RGBA hex string (without a # prefix).
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use graphene_core::raster::color::Color;
|
||||
/// let color = Color::from_rgba_str("7C67FA61").unwrap();
|
||||
/// ```
|
||||
pub fn from_rgba_str(color_str: &str) -> Option<Color> {
|
||||
if color_str.len() != 8 {
|
||||
return None;
|
||||
}
|
||||
let r = u8::from_str_radix(&color_str[0..2], 16).ok()?;
|
||||
let g = u8::from_str_radix(&color_str[2..4], 16).ok()?;
|
||||
let b = u8::from_str_radix(&color_str[4..6], 16).ok()?;
|
||||
let a = u8::from_str_radix(&color_str[6..8], 16).ok()?;
|
||||
|
||||
Some(Color::from_rgba8_srgb(r, g, b, a))
|
||||
}
|
||||
|
||||
/// Creates a color from a 6-character RGB hex string (without a # prefix).
|
||||
///
|
||||
/// ```
|
||||
/// use graphene_core::raster::color::Color;
|
||||
/// let color = Color::from_rgb_str("7C67FA").unwrap();
|
||||
/// ```
|
||||
pub fn from_rgb_str(color_str: &str) -> Option<Color> {
|
||||
if color_str.len() != 6 {
|
||||
return None;
|
||||
}
|
||||
let r = u8::from_str_radix(&color_str[0..2], 16).ok()?;
|
||||
let g = u8::from_str_radix(&color_str[2..4], 16).ok()?;
|
||||
let b = u8::from_str_radix(&color_str[4..6], 16).ok()?;
|
||||
|
||||
Some(Color::from_rgb8_srgb(r, g, b))
|
||||
}
|
||||
|
||||
/// Linearly interpolates between two colors based on t.
|
||||
///
|
||||
/// T must be between 0 and 1.
|
||||
#[inline(always)]
|
||||
pub fn lerp(&self, other: &Color, t: f32) -> Self {
|
||||
assert!((0. ..=1.).contains(&t));
|
||||
Color::from_rgbaf32_unchecked(
|
||||
self.red + ((other.red - self.red) * t),
|
||||
self.green + ((other.green - self.green) * t),
|
||||
self.blue + ((other.blue - self.blue) * t),
|
||||
self.alpha + ((other.alpha - self.alpha) * t),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn gamma(&self, gamma: f32) -> Color {
|
||||
let gamma = gamma.max(0.0001);
|
||||
|
||||
// From https://www.dfstudios.co.uk/articles/programming/image-programming-algorithms/image-processing-algorithms-part-6-gamma-correction/
|
||||
let inverse_gamma = 1. / gamma;
|
||||
self.map_rgb(|c: f32| c.powf(inverse_gamma))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn to_linear_srgb(&self) -> Self {
|
||||
Self {
|
||||
red: Self::srgb_to_linear(self.red),
|
||||
green: Self::srgb_to_linear(self.green),
|
||||
blue: Self::srgb_to_linear(self.blue),
|
||||
alpha: self.alpha,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn to_gamma_srgb(&self) -> Self {
|
||||
Self {
|
||||
red: Self::linear_to_srgb(self.red),
|
||||
green: Self::linear_to_srgb(self.green),
|
||||
blue: Self::linear_to_srgb(self.blue),
|
||||
alpha: self.alpha,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn srgb_to_linear(channel: f32) -> f32 {
|
||||
if channel <= 0.04045 { channel / 12.92 } else { ((channel + 0.055) / 1.055).powf(2.4) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn linear_to_srgb(channel: f32) -> f32 {
|
||||
if channel <= 0.0031308 { channel * 12.92 } else { 1.055 * channel.powf(1. / 2.4) - 0.055 }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn map_rgba<F: Fn(f32) -> f32>(&self, f: F) -> Self {
|
||||
Self::from_rgbaf32_unchecked(f(self.r()), f(self.g()), f(self.b()), f(self.a()))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn map_rgb<F: Fn(f32) -> f32>(&self, f: F) -> Self {
|
||||
Self::from_rgbaf32_unchecked(f(self.r()), f(self.g()), f(self.b()), self.a())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn apply_opacity(&self, opacity: f32) -> Self {
|
||||
Self::from_rgbaf32_unchecked(self.r() * opacity, self.g() * opacity, self.b() * opacity, self.a() * opacity)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn to_associated_alpha(&self, alpha: f32) -> Self {
|
||||
Self {
|
||||
red: self.red * alpha,
|
||||
green: self.green * alpha,
|
||||
blue: self.blue * alpha,
|
||||
alpha: self.alpha * alpha,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn to_unassociated_alpha(&self) -> Self {
|
||||
if self.alpha == 0. {
|
||||
return *self;
|
||||
}
|
||||
let unmultiply = 1. / self.alpha;
|
||||
Self {
|
||||
red: self.red * unmultiply,
|
||||
green: self.green * unmultiply,
|
||||
blue: self.blue * unmultiply,
|
||||
alpha: self.alpha,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn blend_rgb<F: Fn(f32, f32) -> f32>(&self, other: Color, f: F) -> Self {
|
||||
let background = self.to_unassociated_alpha();
|
||||
Color {
|
||||
red: f(background.red, other.red).clamp(0., 1.),
|
||||
green: f(background.green, other.green).clamp(0., 1.),
|
||||
blue: f(background.blue, other.blue).clamp(0., 1.),
|
||||
alpha: other.alpha,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn alpha_blend(&self, other: Color) -> Self {
|
||||
let inv_alpha = 1. - other.alpha;
|
||||
Self {
|
||||
red: self.red * inv_alpha + other.red,
|
||||
green: self.green * inv_alpha + other.green,
|
||||
blue: self.blue * inv_alpha + other.blue,
|
||||
alpha: self.alpha * inv_alpha + other.alpha,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn alpha_add(&self, other: Color) -> Self {
|
||||
Self {
|
||||
alpha: (self.alpha + other.alpha).clamp(0., 1.),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn alpha_subtract(&self, other: Color) -> Self {
|
||||
Self {
|
||||
alpha: (self.alpha - other.alpha).clamp(0., 1.),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn alpha_multiply(&self, other: Color) -> Self {
|
||||
Self {
|
||||
alpha: (self.alpha * other.alpha).clamp(0., 1.),
|
||||
..*self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hsl_roundtrip() {
|
||||
for (red, green, blue) in [
|
||||
(24, 98, 118),
|
||||
(69, 11, 89),
|
||||
(54, 82, 38),
|
||||
(47, 76, 50),
|
||||
(25, 15, 73),
|
||||
(62, 57, 33),
|
||||
(55, 2, 18),
|
||||
(12, 3, 82),
|
||||
(91, 16, 98),
|
||||
(91, 39, 82),
|
||||
(97, 53, 32),
|
||||
(76, 8, 91),
|
||||
(54, 87, 19),
|
||||
(56, 24, 88),
|
||||
(14, 82, 34),
|
||||
(61, 86, 31),
|
||||
(73, 60, 75),
|
||||
(95, 79, 88),
|
||||
(13, 34, 4),
|
||||
(82, 84, 84),
|
||||
(255, 255, 178),
|
||||
] {
|
||||
let col = Color::from_rgb8_srgb(red, green, blue);
|
||||
let [hue, saturation, lightness, alpha] = col.to_hsla();
|
||||
let result = Color::from_hsla(hue, saturation, lightness, alpha);
|
||||
assert!((col.r() - result.r()) < f32::EPSILON * 100.);
|
||||
assert!((col.g() - result.g()) < f32::EPSILON * 100.);
|
||||
assert!((col.b() - result.b()) < f32::EPSILON * 100.);
|
||||
assert!((col.a() - result.a()) < f32::EPSILON * 100.);
|
||||
}
|
||||
}
|
||||
14
node-graph/gcore-shader/src/fullscreen_vertex.rs
Normal file
14
node-graph/gcore-shader/src/fullscreen_vertex.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use glam::{Vec2, Vec4};
|
||||
use spirv_std::spirv;
|
||||
|
||||
/// webgpu NDC is like OpenGL: (-1.0 .. 1.0, -1.0 .. 1.0, 0.0 .. 1.0)
|
||||
/// https://www.w3.org/TR/webgpu/#coordinate-systems
|
||||
const FULLSCREEN_VERTICES: [Vec2; 3] = [Vec2::new(-1., -1.), Vec2::new(-1., 3.), Vec2::new(3., -1.)];
|
||||
|
||||
#[spirv(vertex)]
|
||||
pub fn fullscreen_vertex(#[spirv(vertex_index)] vertex_index: u32, #[spirv(position)] gl_position: &mut Vec4) {
|
||||
// broken on edition 2024 branch
|
||||
// let vertex = unsafe { *FULLSCREEN_VERTICES.index_unchecked(vertex_index as usize) };
|
||||
let vertex = FULLSCREEN_VERTICES[vertex_index as usize];
|
||||
*gl_position = Vec4::from((vertex, 0., 1.));
|
||||
}
|
||||
39
node-graph/gcore-shader/src/gpu_invert.rs
Normal file
39
node-graph/gcore-shader/src/gpu_invert.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use crate::color::Color;
|
||||
|
||||
// exact copy of the invert node
|
||||
// #[node_macro::node(category("Raster: Adjustment"))]
|
||||
fn invert_copy(
|
||||
// _: impl Ctx,
|
||||
// #[implementations(
|
||||
// Color,
|
||||
// ImageFrameTable<Color>,
|
||||
// GradientStops,
|
||||
// )]
|
||||
// mut input: T,
|
||||
color: Color,
|
||||
) -> Color {
|
||||
// input.adjust(|color| {
|
||||
let color = color.to_gamma_srgb();
|
||||
|
||||
let color = color.map_rgb(|c| color.a() - c);
|
||||
|
||||
color.to_linear_srgb()
|
||||
// });
|
||||
// input
|
||||
}
|
||||
|
||||
pub mod gpu_invert_shader {
|
||||
use crate::color::Color;
|
||||
use crate::gpu_invert::invert_copy;
|
||||
use glam::{Vec4, Vec4Swizzles};
|
||||
use spirv_std::image::sample_with::lod;
|
||||
use spirv_std::image::{Image2d, ImageWithMethods};
|
||||
use spirv_std::spirv;
|
||||
|
||||
#[spirv(fragment)]
|
||||
pub fn gpu_invert_fragment(#[spirv(frag_coord)] frag_coord: Vec4, #[spirv(descriptor_set = 0, binding = 0)] texture: &Image2d, color_out: &mut Vec4) {
|
||||
let color = Color::from(texture.fetch_with(frag_coord.xy().as_uvec2(), lod(0)));
|
||||
let color = invert_copy(color);
|
||||
*color_out = Vec4::from(color);
|
||||
}
|
||||
}
|
||||
5
node-graph/gcore-shader/src/lib.rs
Normal file
5
node-graph/gcore-shader/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#![no_std]
|
||||
|
||||
pub mod color;
|
||||
pub mod fullscreen_vertex;
|
||||
pub mod gpu_invert;
|
||||
@@ -15,6 +15,8 @@ gpu-executor = { path = "../gpu-executor" }
|
||||
|
||||
# Workspace dependencies
|
||||
graphene-core = { workspace = true, features = ["std", "alloc", "gpu", "wgpu"] }
|
||||
# required for cargo watch to pick up changes
|
||||
graphene-core-shader = { path = "../gcore-shader" }
|
||||
dyn-any = { workspace = true, features = ["log-bad-types", "rc", "glam"] }
|
||||
node-macro = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
@@ -40,3 +42,7 @@ half = "2.4.1"
|
||||
|
||||
# Optional dependencies
|
||||
nvtx = { version = "1.3", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
cargo-gpu = { workspace = true }
|
||||
env_logger = { workspace = true }
|
||||
|
||||
34
node-graph/wgpu-executor/build.rs
Normal file
34
node-graph/wgpu-executor/build.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use cargo_gpu::CompileResultNagaExt;
|
||||
use cargo_gpu::naga::back::wgsl::WriterFlags;
|
||||
use cargo_gpu::naga::valid::Capabilities;
|
||||
use cargo_gpu::spirv_builder::{MetadataPrintout, SpirvMetadata};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
env_logger::builder().init();
|
||||
|
||||
let shader_crate = PathBuf::from("../gcore-shader");
|
||||
|
||||
// install the toolchain and build the `rustc_codegen_spirv` codegen backend with it
|
||||
let backend = cargo_gpu::Install::from_shader_crate(shader_crate.clone()).run()?;
|
||||
|
||||
// build the shader crate
|
||||
let mut builder = backend.to_spirv_builder(shader_crate, "spirv-unknown-vulkan1.2");
|
||||
builder.print_metadata = MetadataPrintout::DependencyOnly;
|
||||
builder.spirv_metadata = SpirvMetadata::Full;
|
||||
builder.shader_crate_features.default_features = false;
|
||||
builder.shader_crate_features.features = vec![String::from("gpu")];
|
||||
let spv_result = builder.build()?;
|
||||
|
||||
// transpile the spv binaries to wgsl
|
||||
let wgsl_result = spv_result.naga_transpile(Capabilities::empty())?.to_wgsl(WriterFlags::empty())?;
|
||||
let path_to_wgsl = wgsl_result.module.unwrap_single();
|
||||
|
||||
// emit path to wgsl into env var, used in `quad.rs` like this:
|
||||
// > include_str!(env!("WGSL_SHADER_PATH"))
|
||||
println!("cargo::rustc-env=WGSL_SHADER_PATH={}", path_to_wgsl.display());
|
||||
|
||||
// you could also generate some rust source code into the `std::env::var("OUT_DIR")` dir
|
||||
// and use `include!(concat!(env!("OUT_DIR"), "/shader_symbols.rs"));` to include it
|
||||
Ok(())
|
||||
}
|
||||
150
node-graph/wgpu-executor/src/gcore_shader_nodes.rs
Normal file
150
node-graph/wgpu-executor/src/gcore_shader_nodes.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
use crate::{Context, WgpuExecutor};
|
||||
use graphene_core::Ctx;
|
||||
use graphene_core::application_io::{ImageTexture, TextureFrameTable};
|
||||
use graphene_core::instances::Instance;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
use wgpu::{
|
||||
BindGroupDescriptor, BindGroupEntry, BindingResource, ColorTargetState, Device, Face, FragmentState, FrontFace, LoadOp, Operations, PolygonMode, PrimitiveState, PrimitiveTopology, Queue,
|
||||
RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, ShaderModuleDescriptor, ShaderSource, StoreOp, TextureDescriptor, TextureDimension, TextureFormat,
|
||||
TextureViewDescriptor, VertexState,
|
||||
};
|
||||
|
||||
const WGSL_SHADER: &str = include_str!(env!("WGSL_SHADER_PATH"));
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn gpu_invert<'a: 'n>(_: impl Ctx, input: TextureFrameTable, executor: &'a WgpuExecutor) -> TextureFrameTable {
|
||||
let Context { device, queue, .. } = &executor.context;
|
||||
// this should be cached
|
||||
let graphics_pipeline = GraphitePerPixelGraphicsPipeline::new(device, "gpu_invertgpu_invert_shadergpu_invert_fragment");
|
||||
graphics_pipeline.run(input, queue)
|
||||
}
|
||||
|
||||
pub struct GraphitePerPixelGraphicsPipeline {
|
||||
device: Arc<Device>,
|
||||
render_pipeline_f32: wgpu::RenderPipeline,
|
||||
render_pipeline_f16: wgpu::RenderPipeline,
|
||||
render_pipeline_srgb8: wgpu::RenderPipeline,
|
||||
}
|
||||
|
||||
impl GraphitePerPixelGraphicsPipeline {
|
||||
pub fn new(device: &Arc<Device>, fragment_shader_name: &str) -> Self {
|
||||
let shader_module = device.create_shader_module(ShaderModuleDescriptor {
|
||||
label: Some("graphite wgsl shader"),
|
||||
source: ShaderSource::Wgsl(Cow::Borrowed(WGSL_SHADER)),
|
||||
});
|
||||
let create_render_pipeline = |format| {
|
||||
device.create_render_pipeline(&RenderPipelineDescriptor {
|
||||
label: Some("gpu_invert"),
|
||||
layout: None,
|
||||
vertex: VertexState {
|
||||
module: &shader_module,
|
||||
entry_point: Some("fullscreen_vertexfullscreen_vertex"),
|
||||
compilation_options: Default::default(),
|
||||
buffers: &[],
|
||||
},
|
||||
primitive: PrimitiveState {
|
||||
topology: PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: FrontFace::Ccw,
|
||||
cull_mode: Some(Face::Back),
|
||||
unclipped_depth: false,
|
||||
polygon_mode: PolygonMode::Fill,
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: Default::default(),
|
||||
fragment: Some(FragmentState {
|
||||
module: &shader_module,
|
||||
entry_point: Some(fragment_shader_name),
|
||||
compilation_options: Default::default(),
|
||||
targets: &[Some(ColorTargetState {
|
||||
format,
|
||||
blend: None,
|
||||
write_mask: Default::default(),
|
||||
})],
|
||||
}),
|
||||
multiview: None,
|
||||
cache: None,
|
||||
})
|
||||
};
|
||||
Self {
|
||||
device: device.clone(),
|
||||
render_pipeline_f32: create_render_pipeline(TextureFormat::Rgba32Float),
|
||||
render_pipeline_f16: create_render_pipeline(TextureFormat::Rgba16Float),
|
||||
render_pipeline_srgb8: create_render_pipeline(TextureFormat::Rgba8UnormSrgb),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, format: TextureFormat) -> &wgpu::RenderPipeline {
|
||||
match format {
|
||||
TextureFormat::Rgba32Float => &self.render_pipeline_f32,
|
||||
TextureFormat::Rgba16Float => &self.render_pipeline_f16,
|
||||
TextureFormat::Rgba8UnormSrgb => &self.render_pipeline_srgb8,
|
||||
_ => panic!("unsupported"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&self, input: TextureFrameTable, queue: &Arc<Queue>) -> TextureFrameTable {
|
||||
let device = &self.device;
|
||||
let mut cmd = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("gpu_invert") });
|
||||
let out = input
|
||||
.instance_ref_iter()
|
||||
.map(|instance| {
|
||||
let view_in = instance.instance.texture.create_view(&TextureViewDescriptor::default());
|
||||
let format = instance.instance.texture.format();
|
||||
let pipeline = self.get(format);
|
||||
|
||||
let bind_group = device.create_bind_group(&BindGroupDescriptor {
|
||||
label: Some("gpu_invert bind group"),
|
||||
// `get_bind_group_layout` allocates unnecessary memory, we could create it manually to not do that
|
||||
layout: &pipeline.get_bind_group_layout(0),
|
||||
entries: &[BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: BindingResource::TextureView(&view_in),
|
||||
}],
|
||||
});
|
||||
|
||||
let tex_out = device.create_texture(&TextureDescriptor {
|
||||
label: Some("gpu_invert_out"),
|
||||
size: instance.instance.texture.size(),
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
format,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[format],
|
||||
});
|
||||
|
||||
let view_out = tex_out.create_view(&TextureViewDescriptor::default());
|
||||
let mut rp = cmd.begin_render_pass(&RenderPassDescriptor {
|
||||
label: Some("gpu_invert rp"),
|
||||
color_attachments: &[Some(RenderPassColorAttachment {
|
||||
view: &view_out,
|
||||
resolve_target: None,
|
||||
ops: Operations {
|
||||
// should be dont_care but wgpu doesn't expose that
|
||||
load: LoadOp::Clear(wgpu::Color::BLACK),
|
||||
store: StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
rp.set_pipeline(&pipeline);
|
||||
rp.set_bind_group(0, Some(&bind_group), &[]);
|
||||
rp.draw(0..3, 0..1);
|
||||
|
||||
Instance {
|
||||
instance: ImageTexture { texture: Arc::new(tex_out) },
|
||||
transform: *instance.transform,
|
||||
alpha_blending: *instance.alpha_blending,
|
||||
source_node_id: *instance.source_node_id,
|
||||
}
|
||||
})
|
||||
.collect::<TextureFrameTable>();
|
||||
queue.submit([cmd.finish()]);
|
||||
out
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
mod context;
|
||||
mod executor;
|
||||
mod gcore_shader_nodes;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
pub use context::Context;
|
||||
|
||||
Reference in New Issue
Block a user