#[repr(u32)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PrimitiveType {
#[default]
Rect = 0,
Circle = 1,
Ellipse = 2,
Shadow = 3,
InnerShadow = 4,
CircleShadow = 5,
CircleInnerShadow = 6,
Text = 7,
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum FillType {
#[default]
Solid = 0,
LinearGradient = 1,
RadialGradient = 2,
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum GlassType {
UltraThin = 0,
Thin = 1,
#[default]
Regular = 2,
Thick = 3,
Chrome = 4,
Simple = 5,
}
#[repr(u32)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ClipType {
#[default]
None = 0,
Rect = 1,
Circle = 2,
Ellipse = 3,
Polygon = 4,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GpuPrimitive {
pub bounds: [f32; 4],
pub corner_radius: [f32; 4],
pub color: [f32; 4],
pub color2: [f32; 4],
pub border: [f32; 4],
pub border_color: [f32; 4],
pub shadow: [f32; 4],
pub shadow_color: [f32; 4],
pub clip_bounds: [f32; 4],
pub clip_radius: [f32; 4],
pub gradient_params: [f32; 4],
pub rotation: [f32; 4],
pub local_affine: [f32; 4],
pub perspective: [f32; 4],
pub sdf_3d: [f32; 4],
pub light: [f32; 4],
pub filter_a: [f32; 4],
pub filter_b: [f32; 4],
pub mask_params: [f32; 4],
pub mask_info: [f32; 4],
pub corner_shape: [f32; 4],
pub clip_fade: [f32; 4],
pub type_info: [u32; 4],
}
impl Default for GpuPrimitive {
fn default() -> Self {
Self {
bounds: [0.0, 0.0, 100.0, 100.0],
corner_radius: [0.0; 4],
color: [1.0, 1.0, 1.0, 1.0],
color2: [1.0, 1.0, 1.0, 1.0],
border: [0.0; 4],
border_color: [0.0, 0.0, 0.0, 1.0],
shadow: [0.0; 4],
shadow_color: [0.0, 0.0, 0.0, 0.0],
clip_bounds: [-10000.0, -10000.0, 100000.0, 100000.0],
clip_radius: [0.0; 4],
gradient_params: [0.0, 0.0, 1.0, 0.0],
rotation: [0.0, 1.0, 0.0, 1.0],
local_affine: [1.0, 0.0, 0.0, 1.0],
perspective: [0.0, 1.0, 0.0, 0.0],
sdf_3d: [0.0, 0.3, 32.0, 0.0],
light: [0.0, -1.0, 0.5, 0.8],
filter_a: [0.0, 0.0, 0.0, 0.0], filter_b: [1.0, 1.0, 1.0, 0.0], mask_params: [0.0; 4],
mask_info: [0.0; 4], corner_shape: [1.0; 4],
clip_fade: [0.0; 4],
type_info: [0; 4], }
}
}
impl GpuPrimitive {
pub fn rect(x: f32, y: f32, width: f32, height: f32) -> Self {
Self {
bounds: [x, y, width, height],
type_info: [PrimitiveType::Rect as u32, FillType::Solid as u32, 0, 0],
..Default::default()
}
}
pub fn circle(cx: f32, cy: f32, radius: f32) -> Self {
Self {
bounds: [cx - radius, cy - radius, radius * 2.0, radius * 2.0],
type_info: [PrimitiveType::Circle as u32, FillType::Solid as u32, 0, 0],
..Default::default()
}
}
pub fn with_rotation(mut self, angle_rad: f32) -> Self {
self.rotation[0] = angle_rad.sin();
self.rotation[1] = angle_rad.cos();
self
}
pub fn with_3d_rotation(mut self, rx_rad: f32, ry_rad: f32, perspective_d: f32) -> Self {
self.rotation[2] = ry_rad.sin();
self.rotation[3] = ry_rad.cos();
self.perspective[0] = rx_rad.sin();
self.perspective[1] = rx_rad.cos();
self.perspective[2] = perspective_d;
self
}
pub fn with_3d_shape(mut self, shape_type: f32, depth: f32) -> Self {
self.perspective[3] = shape_type;
self.sdf_3d[0] = depth;
self
}
pub fn with_light(mut self, dir: [f32; 3], intensity: f32) -> Self {
self.light = [dir[0], dir[1], dir[2], intensity];
self
}
pub fn with_3d_material(mut self, ambient: f32, specular_power: f32) -> Self {
self.sdf_3d[1] = ambient;
self.sdf_3d[2] = specular_power;
self
}
pub fn with_3d_translate_z(mut self, z: f32) -> Self {
self.sdf_3d[3] = z;
self
}
pub fn with_color(mut self, r: f32, g: f32, b: f32, a: f32) -> Self {
self.color = [r, g, b, a];
self
}
pub fn with_corner_radius(mut self, radius: f32) -> Self {
self.corner_radius = [radius; 4];
self
}
pub fn with_corner_radii(mut self, tl: f32, tr: f32, br: f32, bl: f32) -> Self {
self.corner_radius = [tl, tr, br, bl];
self
}
pub fn with_border(mut self, width: f32, r: f32, g: f32, b: f32, a: f32) -> Self {
self.border = [width, 0.0, 0.0, 0.0];
self.border_color = [r, g, b, a];
self
}
#[allow(clippy::too_many_arguments)]
pub fn with_shadow(
mut self,
offset_x: f32,
offset_y: f32,
blur: f32,
spread: f32,
r: f32,
g: f32,
b: f32,
a: f32,
) -> Self {
self.shadow = [offset_x, offset_y, blur, spread];
self.shadow_color = [r, g, b, a];
self
}
#[allow(clippy::too_many_arguments)]
pub fn with_linear_gradient(
mut self,
r1: f32,
g1: f32,
b1: f32,
a1: f32,
r2: f32,
g2: f32,
b2: f32,
a2: f32,
) -> Self {
self.color = [r1, g1, b1, a1];
self.color2 = [r2, g2, b2, a2];
self.type_info[1] = FillType::LinearGradient as u32;
self
}
#[allow(clippy::too_many_arguments)]
pub fn with_radial_gradient(
mut self,
r1: f32,
g1: f32,
b1: f32,
a1: f32,
r2: f32,
g2: f32,
b2: f32,
a2: f32,
) -> Self {
self.color = [r1, g1, b1, a1];
self.color2 = [r2, g2, b2, a2];
self.type_info[1] = FillType::RadialGradient as u32;
self
}
pub fn with_clip_rect(mut self, x: f32, y: f32, width: f32, height: f32) -> Self {
self.clip_bounds = [x, y, width, height];
self.clip_radius = [0.0; 4];
self.type_info[2] = ClipType::Rect as u32;
self
}
#[allow(clippy::too_many_arguments)]
pub fn with_clip_rounded_rect(
mut self,
x: f32,
y: f32,
width: f32,
height: f32,
tl: f32,
tr: f32,
br: f32,
bl: f32,
) -> Self {
self.clip_bounds = [x, y, width, height];
self.clip_radius = [tl, tr, br, bl];
self.type_info[2] = ClipType::Rect as u32;
self
}
pub fn with_clip_circle(mut self, cx: f32, cy: f32, radius: f32) -> Self {
self.clip_bounds = [cx, cy, radius, radius];
self.clip_radius = [radius, radius, 0.0, 0.0];
self.type_info[2] = ClipType::Circle as u32;
self
}
pub fn with_clip_ellipse(mut self, cx: f32, cy: f32, rx: f32, ry: f32) -> Self {
self.clip_bounds = [cx, cy, rx, ry];
self.clip_radius = [rx, ry, 0.0, 0.0];
self.type_info[2] = ClipType::Ellipse as u32;
self
}
pub fn with_no_clip(mut self) -> Self {
self.clip_bounds = [-10000.0, -10000.0, 100000.0, 100000.0];
self.clip_radius = [0.0; 4];
self.type_info[2] = ClipType::None as u32;
self
}
pub fn with_z_layer(mut self, layer: u32) -> Self {
self.type_info[3] = layer;
self
}
pub fn z_layer(&self) -> u32 {
self.type_info[3]
}
pub fn set_z_layer(&mut self, layer: u32) {
self.type_info[3] = layer;
}
pub fn from_glyph(glyph: &GpuGlyph) -> Self {
let is_color_flag = if glyph.flags[0] > 0.5 { 1u32 } else { 0u32 };
Self {
bounds: glyph.bounds,
corner_radius: [0.0; 4],
color: glyph.color,
color2: [0.0; 4],
border: [0.0; 4],
border_color: [0.0; 4],
shadow: [0.0; 4],
shadow_color: [0.0; 4],
clip_bounds: glyph.clip_bounds,
clip_radius: [0.0; 4],
gradient_params: glyph.uv_bounds,
rotation: [0.0, 1.0, 0.0, 1.0],
local_affine: [1.0, 0.0, 0.0, 1.0],
perspective: [0.0, 1.0, 0.0, 0.0],
sdf_3d: [0.0, 0.3, 32.0, 0.0],
light: [0.0, -1.0, 0.5, 0.8],
filter_a: [0.0, 0.0, 0.0, 0.0],
filter_b: [1.0, 1.0, 1.0, 0.0],
mask_params: [0.0; 4],
mask_info: [0.0; 4],
corner_shape: [1.0; 4],
clip_fade: glyph.clip_fade,
type_info: [
PrimitiveType::Text as u32,
is_color_flag,
ClipType::None as u32,
0,
],
}
}
#[allow(clippy::too_many_arguments)]
pub fn text_glyph(
x: f32,
y: f32,
width: f32,
height: f32,
uv_min_x: f32,
uv_min_y: f32,
uv_max_x: f32,
uv_max_y: f32,
color: [f32; 4],
) -> Self {
Self {
bounds: [x, y, width, height],
corner_radius: [0.0; 4],
color,
color2: [0.0; 4],
border: [0.0; 4],
border_color: [0.0; 4],
shadow: [0.0; 4],
shadow_color: [0.0; 4],
clip_bounds: [-10000.0, -10000.0, 100000.0, 100000.0],
clip_radius: [0.0; 4],
gradient_params: [uv_min_x, uv_min_y, uv_max_x, uv_max_y],
rotation: [0.0, 1.0, 0.0, 1.0],
local_affine: [1.0, 0.0, 0.0, 1.0],
perspective: [0.0, 1.0, 0.0, 0.0],
sdf_3d: [0.0, 0.3, 32.0, 0.0],
light: [0.0, -1.0, 0.5, 0.8],
filter_a: [0.0, 0.0, 0.0, 0.0],
filter_b: [1.0, 1.0, 1.0, 0.0],
mask_params: [0.0; 4],
mask_info: [0.0; 4],
corner_shape: [1.0; 4],
clip_fade: [0.0; 4],
type_info: [PrimitiveType::Text as u32, 0, ClipType::None as u32, 0],
}
}
}
pub const MAX_GROUP_SHAPES: usize = 16;
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct ShapeDesc {
pub offset: [f32; 4],
pub params: [f32; 4],
pub half_ext: [f32; 4],
pub color: [f32; 4],
}
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GpuGlassPrimitive {
pub bounds: [f32; 4],
pub corner_radius: [f32; 4],
pub tint_color: [f32; 4],
pub params: [f32; 4],
pub params2: [f32; 4],
pub type_info: [u32; 4],
pub clip_bounds: [f32; 4],
pub clip_radius: [f32; 4],
pub border_color: [f32; 4],
}
impl Default for GpuGlassPrimitive {
fn default() -> Self {
Self {
bounds: [0.0, 0.0, 100.0, 100.0],
corner_radius: [0.0; 4],
tint_color: [1.0, 1.0, 1.0, 0.1], params: [20.0, 1.0, 1.0, 0.0], params2: [0.8, -std::f32::consts::FRAC_PI_4, 0.0, 0.0],
type_info: [GlassType::Regular as u32, 0, 0, ClipType::None as u32],
clip_bounds: [-10000.0, -10000.0, 100000.0, 100000.0],
clip_radius: [0.0; 4],
border_color: [0.0, 0.0, 0.0, 0.0], }
}
}
impl GpuGlassPrimitive {
pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
Self {
bounds: [x, y, width, height],
..Default::default()
}
}
pub fn circle(center_x: f32, center_y: f32, radius: f32) -> Self {
let diameter = radius * 2.0;
Self {
bounds: [center_x - radius, center_y - radius, diameter, diameter],
corner_radius: [radius; 4],
..Default::default()
}
}
pub fn with_corner_radius(mut self, radius: f32) -> Self {
self.corner_radius = [radius; 4];
self
}
pub fn with_corner_radius_per_corner(mut self, tl: f32, tr: f32, br: f32, bl: f32) -> Self {
self.corner_radius = [tl, tr, br, bl];
self
}
pub fn with_tint(mut self, r: f32, g: f32, b: f32, a: f32) -> Self {
self.tint_color = [r, g, b, a];
self
}
pub fn with_blur(mut self, radius: f32) -> Self {
self.params[0] = radius;
self
}
pub fn with_saturation(mut self, saturation: f32) -> Self {
self.params[1] = saturation;
self
}
pub fn with_brightness(mut self, brightness: f32) -> Self {
self.params[2] = brightness;
self
}
pub fn with_noise(mut self, amount: f32) -> Self {
self.params[3] = amount;
self
}
pub fn with_glass_type(mut self, glass_type: GlassType) -> Self {
self.type_info[0] = glass_type as u32;
self
}
pub fn ultra_thin(mut self) -> Self {
self.type_info[0] = GlassType::UltraThin as u32;
self.params[0] = 10.0; self
}
pub fn thin(mut self) -> Self {
self.type_info[0] = GlassType::Thin as u32;
self.params[0] = 15.0;
self
}
pub fn regular(mut self) -> Self {
self.type_info[0] = GlassType::Regular as u32;
self.params[0] = 20.0;
self
}
pub fn thick(mut self) -> Self {
self.type_info[0] = GlassType::Thick as u32;
self.params[0] = 30.0;
self
}
pub fn chrome(mut self) -> Self {
self.type_info[0] = GlassType::Chrome as u32;
self.params[0] = 25.0;
self.params[1] = 0.8; self
}
pub fn with_border_thickness(mut self, thickness: f32) -> Self {
self.params2[0] = thickness;
self
}
pub fn with_light_angle(mut self, angle_radians: f32) -> Self {
self.params2[1] = angle_radians;
self
}
pub fn with_light_angle_degrees(mut self, angle_degrees: f32) -> Self {
self.params2[1] = angle_degrees * std::f32::consts::PI / 180.0;
self
}
pub fn with_shadow(mut self, blur: f32, opacity: f32) -> Self {
self.params2[2] = blur;
self.params2[3] = opacity;
self
}
pub fn with_shadow_offset(
mut self,
blur: f32,
opacity: f32,
offset_x: f32,
offset_y: f32,
) -> Self {
self.params2[2] = blur;
self.params2[3] = opacity;
self.type_info[1] = offset_x.to_bits();
self.type_info[2] = offset_y.to_bits();
self
}
pub fn with_refraction(mut self, strength: f32) -> Self {
self.type_info[3] = (-strength).to_bits();
self
}
pub fn flat(mut self) -> Self {
self.type_info[3] = (-0.0_f32).to_bits();
self
}
pub fn with_clip_rect(mut self, x: f32, y: f32, width: f32, height: f32) -> Self {
self.clip_bounds = [x, y, width, height];
self.clip_radius = [0.0; 4];
self
}
pub fn with_clip_rounded_rect(
mut self,
x: f32,
y: f32,
width: f32,
height: f32,
radius: f32,
) -> Self {
self.clip_bounds = [x, y, width, height];
self.clip_radius = [radius; 4];
self
}
#[allow(clippy::too_many_arguments)]
pub fn with_clip_rounded_rect_per_corner(
mut self,
x: f32,
y: f32,
width: f32,
height: f32,
tl: f32,
tr: f32,
br: f32,
bl: f32,
) -> Self {
self.clip_bounds = [x, y, width, height];
self.clip_radius = [tl, tr, br, bl];
self
}
pub fn with_no_clip(mut self) -> Self {
self.clip_bounds = [-10000.0, -10000.0, 100000.0, 100000.0];
self.clip_radius = [0.0; 4];
self
}
pub fn with_border_color(mut self, r: f32, g: f32, b: f32, a: f32) -> Self {
self.border_color = [r, g, b, a];
self
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GpuGlyph {
pub bounds: [f32; 4],
pub uv_bounds: [f32; 4],
pub color: [f32; 4],
pub clip_bounds: [f32; 4],
pub clip_fade: [f32; 4],
pub flags: [f32; 4],
}
impl Default for GpuGlyph {
fn default() -> Self {
Self {
bounds: [0.0; 4],
uv_bounds: [0.0, 0.0, 1.0, 1.0],
color: [0.0, 0.0, 0.0, 1.0],
clip_bounds: [-10000.0, -10000.0, 100000.0, 100000.0],
clip_fade: [0.0; 4], flags: [0.0; 4], }
}
}
impl GpuGlyph {
pub fn with_clip_rect(mut self, x: f32, y: f32, width: f32, height: f32) -> Self {
self.clip_bounds = [x, y, width, height];
self
}
pub fn with_no_clip(mut self) -> Self {
self.clip_bounds = [-10000.0, -10000.0, 100000.0, 100000.0];
self
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Uniforms {
pub viewport_size: [f32; 2],
pub _padding: [f32; 2],
}
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GlassUniforms {
pub viewport_size: [f32; 2],
pub time: f32,
pub _padding: f32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct CompositeUniforms {
pub opacity: f32,
pub blend_mode: u32,
pub _padding: [f32; 2],
}
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct LayerCompositeUniforms {
pub source_rect: [f32; 4],
pub dest_rect: [f32; 4],
pub viewport_size: [f32; 2],
pub opacity: f32,
pub blend_mode: u32,
pub clip_bounds: [f32; 4],
pub clip_radius: [f32; 4],
pub clip_type: u32,
pub perspective_d: f32,
pub sin_rx: f32,
pub cos_rx: f32,
pub sin_ry: f32,
pub cos_ry: f32,
pub _pad: [f32; 2],
}
impl LayerCompositeUniforms {
pub fn new(
layer_size: (u32, u32),
dest_x: f32,
dest_y: f32,
viewport_size: (f32, f32),
opacity: f32,
blend_mode: blinc_core::BlendMode,
) -> Self {
Self {
source_rect: [0.0, 0.0, 1.0, 1.0], dest_rect: [dest_x, dest_y, layer_size.0 as f32, layer_size.1 as f32],
viewport_size: [viewport_size.0, viewport_size.1],
opacity,
blend_mode: blend_mode as u32,
clip_bounds: [0.0, 0.0, viewport_size.0, viewport_size.1], clip_radius: [0.0, 0.0, 0.0, 0.0],
clip_type: 0, perspective_d: 0.0,
sin_rx: 0.0,
cos_rx: 1.0,
sin_ry: 0.0,
cos_ry: 1.0,
_pad: [0.0; 2],
}
}
pub fn with_source_rect(
source_rect: [f32; 4],
dest_rect: [f32; 4],
viewport_size: (f32, f32),
opacity: f32,
blend_mode: blinc_core::BlendMode,
) -> Self {
Self {
source_rect,
dest_rect,
viewport_size: [viewport_size.0, viewport_size.1],
opacity,
blend_mode: blend_mode as u32,
clip_bounds: [0.0, 0.0, viewport_size.0, viewport_size.1], clip_radius: [0.0, 0.0, 0.0, 0.0],
clip_type: 0, perspective_d: 0.0,
sin_rx: 0.0,
cos_rx: 1.0,
sin_ry: 0.0,
cos_ry: 1.0,
_pad: [0.0; 2],
}
}
pub fn with_clip(mut self, bounds: [f32; 4], radius: [f32; 4]) -> Self {
self.clip_bounds = bounds;
self.clip_radius = radius;
self.clip_type = 1;
self
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct PathUniforms {
pub viewport_size: [f32; 2],
pub opacity: f32,
pub _pad0: f32,
pub transform: [[f32; 4]; 3],
pub clip_bounds: [f32; 4],
pub clip_radius: [f32; 4],
pub clip_type: u32,
pub use_gradient_texture: u32,
pub use_image_texture: u32,
pub use_glass_effect: u32,
pub image_uv_bounds: [f32; 4],
pub glass_params: [f32; 4],
pub glass_tint: [f32; 4],
}
impl Default for PathUniforms {
fn default() -> Self {
Self {
viewport_size: [800.0, 600.0],
opacity: 1.0,
_pad0: 0.0,
transform: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
],
clip_bounds: [-10000.0, -10000.0, 100000.0, 100000.0],
clip_radius: [0.0; 4],
clip_type: ClipType::None as u32,
use_gradient_texture: 0, use_image_texture: 0, use_glass_effect: 0, image_uv_bounds: [0.0, 0.0, 1.0, 1.0], glass_params: [20.0, 1.0, 0.5, 0.9], glass_tint: [1.0, 1.0, 1.0, 0.3], }
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub struct BlurUniforms {
pub texel_size: [f32; 2],
pub radius: f32,
pub iteration: u32,
pub blur_alpha: u32,
pub _pad1: f32,
pub _pad2: f32,
pub _pad3: f32,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct ColorMatrixUniforms {
pub row0: [f32; 4],
pub row1: [f32; 4],
pub row2: [f32; 4],
pub row3: [f32; 4],
pub offset: [f32; 4],
}
impl Default for ColorMatrixUniforms {
fn default() -> Self {
Self {
row0: [1.0, 0.0, 0.0, 0.0],
row1: [0.0, 1.0, 0.0, 0.0],
row2: [0.0, 0.0, 1.0, 0.0],
row3: [0.0, 0.0, 0.0, 1.0],
offset: [0.0, 0.0, 0.0, 0.0],
}
}
}
impl ColorMatrixUniforms {
pub fn from_matrix(matrix: &[f32; 20]) -> Self {
Self {
row0: [matrix[0], matrix[1], matrix[2], matrix[3]],
row1: [matrix[5], matrix[6], matrix[7], matrix[8]],
row2: [matrix[10], matrix[11], matrix[12], matrix[13]],
row3: [matrix[15], matrix[16], matrix[17], matrix[18]],
offset: [matrix[4], matrix[9], matrix[14], matrix[19]],
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct DropShadowUniforms {
pub offset: [f32; 2],
pub blur_radius: f32,
pub spread: f32,
pub color: [f32; 4],
pub texel_size: [f32; 2],
pub _pad: [f32; 2],
}
impl Default for DropShadowUniforms {
fn default() -> Self {
Self {
offset: [4.0, 4.0],
blur_radius: 8.0,
spread: 0.0,
color: [0.0, 0.0, 0.0, 0.5], texel_size: [1.0 / 800.0, 1.0 / 600.0],
_pad: [0.0, 0.0],
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GlowUniforms {
pub color: [f32; 4],
pub blur: f32,
pub range: f32,
pub opacity: f32,
pub _pad0: f32,
pub texel_size: [f32; 2],
pub _pad1: [f32; 2],
}
impl Default for GlowUniforms {
fn default() -> Self {
Self {
color: [1.0, 1.0, 1.0, 1.0], blur: 8.0,
range: 4.0,
opacity: 1.0,
_pad0: 0.0,
texel_size: [1.0 / 800.0, 1.0 / 600.0],
_pad1: [0.0, 0.0],
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct MaskImageUniforms {
pub mask_mode: u32,
pub _pad: [f32; 3],
}
#[derive(Clone, Default)]
pub struct PathBatch {
pub vertices: Vec<crate::path::PathVertex>,
pub indices: Vec<u32>,
pub clip_bounds: [f32; 4],
pub clip_radius: [f32; 4],
pub clip_type: u32,
pub use_gradient_texture: bool,
pub gradient_stops: Option<Vec<blinc_core::GradientStop>>,
pub use_image_texture: bool,
pub image_source: Option<String>,
pub image_uv_bounds: [f32; 4],
pub use_glass_effect: bool,
pub glass_params: [f32; 4],
pub glass_tint: [f32; 4],
}
#[derive(Clone, Debug)]
pub enum LayerCommand {
Push {
config: blinc_core::LayerConfig,
},
Pop,
Sample {
id: blinc_core::LayerId,
source: blinc_core::Rect,
dest: blinc_core::Rect,
},
}
#[derive(Clone, Debug)]
pub struct LayerCommandEntry {
pub primitive_index: usize,
pub command: LayerCommand,
}
pub struct PrimitiveBatch {
pub primitives: Vec<GpuPrimitive>,
pub foreground_primitives: Vec<GpuPrimitive>,
pub glass_primitives: Vec<GpuGlassPrimitive>,
pub nested_glass_primitives: Vec<GpuGlassPrimitive>,
pub glyphs: Vec<GpuGlyph>,
pub paths: PathBatch,
pub foreground_paths: PathBatch,
pub layer_commands: Vec<LayerCommandEntry>,
pub viewports_3d: Vec<Viewport3D>,
pub particle_viewports: Vec<ParticleViewport3D>,
pub aux_data: Vec<[f32; 4]>,
pub dynamic_images: Vec<DynamicImage>,
}
#[derive(Clone)]
pub struct DynamicImage {
pub data: Vec<u8>,
pub width: u32,
pub height: u32,
pub dest: blinc_core::Rect,
pub opacity: f32,
pub corner_radius: f32,
}
impl PrimitiveBatch {
pub fn new() -> Self {
Self {
primitives: Vec::new(),
foreground_primitives: Vec::new(),
glass_primitives: Vec::new(),
nested_glass_primitives: Vec::new(),
glyphs: Vec::new(),
paths: PathBatch::default(),
foreground_paths: PathBatch::default(),
layer_commands: Vec::new(),
viewports_3d: Vec::new(),
particle_viewports: Vec::new(),
aux_data: Vec::new(),
dynamic_images: Vec::new(),
}
}
pub fn clear(&mut self) {
self.primitives.clear();
self.foreground_primitives.clear();
self.glass_primitives.clear();
self.nested_glass_primitives.clear();
self.glyphs.clear();
self.paths = PathBatch::default();
self.foreground_paths = PathBatch::default();
self.layer_commands.clear();
self.viewports_3d.clear();
self.particle_viewports.clear();
self.aux_data.clear();
self.dynamic_images.clear();
}
pub fn push_viewport_3d(&mut self, viewport: Viewport3D) {
self.viewports_3d.push(viewport);
}
pub fn has_3d_viewports(&self) -> bool {
!self.viewports_3d.is_empty()
}
pub fn push_particle_viewport(&mut self, viewport: ParticleViewport3D) {
self.particle_viewports.push(viewport);
}
pub fn has_particle_viewports(&self) -> bool {
!self.particle_viewports.is_empty()
}
pub fn push_layer_command(&mut self, command: LayerCommand) {
self.layer_commands.push(LayerCommandEntry {
primitive_index: self.primitives.len(),
command,
});
}
pub fn has_layer_commands(&self) -> bool {
!self.layer_commands.is_empty()
}
pub fn has_layer_effects(&self) -> bool {
self.layer_commands.iter().any(|entry| {
if let LayerCommand::Push { config } = &entry.command {
!config.effects.is_empty() || config.blend_mode != blinc_core::BlendMode::Normal
} else {
false
}
})
}
pub fn push(&mut self, primitive: GpuPrimitive) {
self.primitives.push(primitive);
}
pub fn push_foreground(&mut self, primitive: GpuPrimitive) {
self.foreground_primitives.push(primitive);
}
pub fn push_glass(&mut self, glass: GpuGlassPrimitive) {
self.glass_primitives.push(glass);
}
pub fn push_nested_glass(&mut self, glass: GpuGlassPrimitive) {
self.nested_glass_primitives.push(glass);
}
pub fn push_glyph(&mut self, glyph: GpuGlyph) {
self.glyphs.push(glyph);
}
pub fn push_glyph_as_primitive(&mut self, glyph: GpuGlyph) {
self.foreground_primitives
.push(GpuPrimitive::from_glyph(&glyph));
}
pub fn convert_glyphs_to_primitives(&mut self) {
for glyph in self.glyphs.drain(..) {
self.foreground_primitives
.push(GpuPrimitive::from_glyph(&glyph));
}
}
pub fn get_unified_foreground_primitives(&self) -> Vec<GpuPrimitive> {
let mut result = self.foreground_primitives.clone();
for glyph in &self.glyphs {
result.push(GpuPrimitive::from_glyph(glyph));
}
result
}
pub fn push_path(&mut self, tessellated: crate::path::TessellatedPath) {
if tessellated.is_empty() {
return;
}
let base_vertex = self.paths.vertices.len() as u32;
self.paths.vertices.extend(tessellated.vertices);
self.paths
.indices
.extend(tessellated.indices.iter().map(|i| i + base_vertex));
}
pub fn push_foreground_path(&mut self, tessellated: crate::path::TessellatedPath) {
if tessellated.is_empty() {
return;
}
let base_vertex = self.foreground_paths.vertices.len() as u32;
self.foreground_paths.vertices.extend(tessellated.vertices);
self.foreground_paths
.indices
.extend(tessellated.indices.iter().map(|i| i + base_vertex));
}
pub fn push_path_with_clip(
&mut self,
tessellated: crate::path::TessellatedPath,
clip_bounds: [f32; 4],
clip_radius: [f32; 4],
clip_type: ClipType,
) {
if tessellated.is_empty() {
return;
}
self.paths.clip_bounds = clip_bounds;
self.paths.clip_radius = clip_radius;
self.paths.clip_type = clip_type as u32;
let base_vertex = self.paths.vertices.len() as u32;
self.paths.vertices.extend(tessellated.vertices);
self.paths
.indices
.extend(tessellated.indices.iter().map(|i| i + base_vertex));
}
pub fn push_foreground_path_with_clip(
&mut self,
tessellated: crate::path::TessellatedPath,
clip_bounds: [f32; 4],
clip_radius: [f32; 4],
clip_type: ClipType,
) {
if tessellated.is_empty() {
return;
}
self.foreground_paths.clip_bounds = clip_bounds;
self.foreground_paths.clip_radius = clip_radius;
self.foreground_paths.clip_type = clip_type as u32;
let base_vertex = self.foreground_paths.vertices.len() as u32;
self.foreground_paths.vertices.extend(tessellated.vertices);
self.foreground_paths
.indices
.extend(tessellated.indices.iter().map(|i| i + base_vertex));
}
pub fn push_path_with_brush_info(
&mut self,
mut tessellated: crate::path::TessellatedPath,
clip_bounds: [f32; 4],
clip_radius: [f32; 4],
clip_type: ClipType,
brush_info: &crate::path::PathBrushInfo,
) {
if tessellated.is_empty() {
return;
}
let clip_type_u = clip_type as u32;
for v in &mut tessellated.vertices {
v.clip_bounds = clip_bounds;
v.clip_radius = clip_radius;
v.clip_type = clip_type_u;
}
self.paths.clip_bounds = clip_bounds;
self.paths.clip_radius = clip_radius;
self.paths.clip_type = clip_type_u;
self.paths.use_gradient_texture = brush_info.needs_gradient_texture;
self.paths.gradient_stops = brush_info.gradient_stops.clone();
self.paths.use_image_texture =
matches!(brush_info.brush_type, crate::path::PathBrushType::Image);
self.paths.image_source = brush_info.image_source.clone();
self.paths.image_uv_bounds = [0.0, 0.0, 1.0, 1.0]; self.paths.use_glass_effect =
matches!(brush_info.brush_type, crate::path::PathBrushType::Glass);
self.paths.glass_params = brush_info.glass_params;
self.paths.glass_tint = [
brush_info.glass_tint.r,
brush_info.glass_tint.g,
brush_info.glass_tint.b,
brush_info.glass_tint.a,
];
let base_vertex = self.paths.vertices.len() as u32;
self.paths.vertices.extend(tessellated.vertices);
self.paths
.indices
.extend(tessellated.indices.iter().map(|i| i + base_vertex));
}
pub fn push_foreground_path_with_brush_info(
&mut self,
mut tessellated: crate::path::TessellatedPath,
clip_bounds: [f32; 4],
clip_radius: [f32; 4],
clip_type: ClipType,
brush_info: &crate::path::PathBrushInfo,
) {
if tessellated.is_empty() {
return;
}
let clip_type_u = clip_type as u32;
for v in &mut tessellated.vertices {
v.clip_bounds = clip_bounds;
v.clip_radius = clip_radius;
v.clip_type = clip_type_u;
}
self.foreground_paths.clip_bounds = clip_bounds;
self.foreground_paths.clip_radius = clip_radius;
self.foreground_paths.clip_type = clip_type_u;
self.foreground_paths.use_gradient_texture = brush_info.needs_gradient_texture;
self.foreground_paths.gradient_stops = brush_info.gradient_stops.clone();
self.foreground_paths.use_image_texture =
matches!(brush_info.brush_type, crate::path::PathBrushType::Image);
self.foreground_paths.image_source = brush_info.image_source.clone();
self.foreground_paths.image_uv_bounds = [0.0, 0.0, 1.0, 1.0];
self.foreground_paths.use_glass_effect =
matches!(brush_info.brush_type, crate::path::PathBrushType::Glass);
self.foreground_paths.glass_params = brush_info.glass_params;
self.foreground_paths.glass_tint = [
brush_info.glass_tint.r,
brush_info.glass_tint.g,
brush_info.glass_tint.b,
brush_info.glass_tint.a,
];
let base_vertex = self.foreground_paths.vertices.len() as u32;
self.foreground_paths.vertices.extend(tessellated.vertices);
self.foreground_paths
.indices
.extend(tessellated.indices.iter().map(|i| i + base_vertex));
}
pub fn is_empty(&self) -> bool {
self.primitives.is_empty()
&& self.foreground_primitives.is_empty()
&& self.glass_primitives.is_empty()
&& self.glyphs.is_empty()
&& self.paths.vertices.is_empty()
&& self.foreground_paths.vertices.is_empty()
&& self.viewports_3d.is_empty()
}
pub fn has_paths(&self) -> bool {
!self.paths.vertices.is_empty() || !self.foreground_paths.vertices.is_empty()
}
pub fn path_vertex_count(&self) -> usize {
self.paths.vertices.len()
}
pub fn path_index_count(&self) -> usize {
self.paths.indices.len()
}
pub fn foreground_path_vertex_count(&self) -> usize {
self.foreground_paths.vertices.len()
}
pub fn foreground_path_index_count(&self) -> usize {
self.foreground_paths.indices.len()
}
pub fn primitive_count(&self) -> usize {
self.primitives.len()
}
pub fn foreground_primitive_count(&self) -> usize {
self.foreground_primitives.len()
}
pub fn glass_count(&self) -> usize {
self.glass_primitives.len()
}
pub fn glyph_count(&self) -> usize {
self.glyphs.len()
}
pub fn max_z_layer(&self) -> u32 {
self.primitives
.iter()
.chain(self.foreground_primitives.iter())
.map(|p| p.z_layer())
.max()
.unwrap_or(0)
}
pub fn primitives_for_layer(&self, z_layer: u32) -> Vec<GpuPrimitive> {
self.primitives
.iter()
.filter(|p| p.z_layer() == z_layer)
.cloned()
.collect()
}
pub fn primitives_for_layer_excluding_effects(
&self,
z_layer: u32,
effect_indices: &std::collections::HashSet<usize>,
) -> Vec<GpuPrimitive> {
self.primitives
.iter()
.enumerate()
.filter(|(i, p)| p.z_layer() == z_layer && !effect_indices.contains(i))
.map(|(_, p)| *p)
.collect()
}
pub fn effect_layer_indices(&self) -> std::collections::HashSet<usize> {
let mut indices = std::collections::HashSet::new();
let mut stack: Vec<(usize, &blinc_core::LayerConfig)> = Vec::new();
for entry in &self.layer_commands {
match &entry.command {
LayerCommand::Push { config } => {
stack.push((entry.primitive_index, config));
}
LayerCommand::Pop => {
if let Some((start_idx, config)) = stack.pop() {
if !config.effects.is_empty()
|| config.blend_mode != blinc_core::BlendMode::Normal
{
for i in start_idx..entry.primitive_index {
indices.insert(i);
}
}
}
}
LayerCommand::Sample { .. } => {}
}
}
indices
}
pub fn foreground_primitives_for_layer(&self, z_layer: u32) -> Vec<GpuPrimitive> {
self.foreground_primitives
.iter()
.filter(|p| p.z_layer() == z_layer)
.cloned()
.collect()
}
pub fn has_z_layers(&self) -> bool {
self.max_z_layer() > 0
}
pub fn merge(&mut self, other: PrimitiveBatch) {
let primitive_offset = self.primitives.len();
self.primitives.extend(other.primitives);
self.foreground_primitives
.extend(other.foreground_primitives);
self.glass_primitives.extend(other.glass_primitives);
self.glyphs.extend(other.glyphs);
let base_vertex = self.paths.vertices.len() as u32;
self.paths.vertices.extend(other.paths.vertices);
self.paths
.indices
.extend(other.paths.indices.iter().map(|i| i + base_vertex));
let fg_base_vertex = self.foreground_paths.vertices.len() as u32;
self.foreground_paths
.vertices
.extend(other.foreground_paths.vertices);
self.foreground_paths.indices.extend(
other
.foreground_paths
.indices
.iter()
.map(|i| i + fg_base_vertex),
);
for mut entry in other.layer_commands {
entry.primitive_index += primitive_offset;
self.layer_commands.push(entry);
}
self.viewports_3d.extend(other.viewports_3d);
self.aux_data.extend(other.aux_data);
}
}
impl Default for PrimitiveBatch {
fn default() -> Self {
Self::new()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Sdf3DUniform {
pub camera_pos: [f32; 4],
pub camera_dir: [f32; 4],
pub camera_up: [f32; 4],
pub camera_right: [f32; 4],
pub resolution: [f32; 2],
pub time: f32,
pub fov: f32,
pub max_steps: u32,
pub max_distance: f32,
pub epsilon: f32,
pub _padding: f32,
pub uv_offset: [f32; 2],
pub uv_scale: [f32; 2],
}
impl Default for Sdf3DUniform {
fn default() -> Self {
Self {
camera_pos: [0.0, 2.0, 5.0, 1.0],
camera_dir: [0.0, 0.0, -1.0, 0.0],
camera_up: [0.0, 1.0, 0.0, 0.0],
camera_right: [1.0, 0.0, 0.0, 0.0],
resolution: [800.0, 600.0],
time: 0.0,
fov: 0.8,
max_steps: 128,
max_distance: 100.0,
epsilon: 0.001,
_padding: 0.0,
uv_offset: [0.0, 0.0],
uv_scale: [1.0, 1.0],
}
}
}
#[derive(Clone, Debug)]
pub struct Viewport3D {
pub shader_wgsl: String,
pub uniforms: Sdf3DUniform,
pub bounds: [f32; 4],
pub lights: Vec<blinc_core::Light>,
}
#[derive(Clone, Debug)]
pub struct ParticleViewport3D {
pub emitter: crate::particles::GpuEmitter,
pub forces: Vec<crate::particles::GpuForce>,
pub max_particles: u32,
pub bounds: [f32; 4],
pub camera_pos: [f32; 3],
pub camera_target: [f32; 3],
pub camera_up: [f32; 3],
pub fov: f32,
pub time: f32,
pub delta_time: f32,
pub blend_mode: u32,
pub playing: bool,
}
impl Default for ParticleViewport3D {
fn default() -> Self {
Self {
emitter: crate::particles::GpuEmitter::default(),
forces: Vec::new(),
max_particles: 10000,
bounds: [0.0, 0.0, 800.0, 600.0],
camera_pos: [0.0, 2.0, 5.0],
camera_target: [0.0, 0.0, 0.0],
camera_up: [0.0, 1.0, 0.0],
fov: 0.8,
time: 0.0,
delta_time: 0.016,
blend_mode: 0,
playing: true,
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
#[deprecated(note = "Use GpuPrimitive instead")]
pub struct GpuRect {
pub position: [f32; 2],
pub size: [f32; 2],
pub color: [f32; 4],
pub corner_radius: [f32; 4],
pub border_width: f32,
pub border_color: [f32; 4],
pub _padding: [f32; 3],
}