Autoformat slang code

This commit is contained in:
Kovid Goyal 2026-08-10 22:15:10 +05:30
parent 24a7a9d50b
commit 953ac19e77
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
40 changed files with 1264 additions and 1209 deletions

View file

@ -41,7 +41,7 @@ for x in os.listdir(base):
if file.startswith('wayland-') and os.path.basename(root) == 'glfw':
continue
ext = os.path.splitext(file)[1]
if ext in ('.c', '.h', '.m'):
if ext in ('.c', '.h', '.m', '.slang'):
clang_files.append(os.path.join(root, file))
CACHE_DIR = os.path.join(base, '.cache', 'autoformat')

View file

@ -124,7 +124,7 @@ This is in a :file:`.slang` file. It must define a function called
``fragment_main()`` whose signature is:
.. literalinclude:: ../kitty/shaders/custom/sample.slang
:start-at: public float4 fragment_main(
:start-after: START_FUNCTION_SIGNATURE
:end-before: END_FUNCTION_SIGNATURE
The two structs passed into this function have the definition shown below:

View file

@ -6,14 +6,16 @@ module alpha_blend;
// Alpha blend two colors returning the resulting color pre-multiplied by its alpha
// and its alpha. See https://en.wikipedia.org/wiki/Alpha_compositing
public float4 alpha_blend(float4 over, float4 under) {
public float4
alpha_blend(float4 over, float4 under) {
float alpha = lerp(under.a, 1.0f, over.a);
float3 combined_color = lerp(under.rgb * under.a, over.rgb, over.a);
return float4(combined_color, alpha);
}
// Same as alpha_blend() except that it assumes over and under are both premultiplied.
public float4 alpha_blend_premul(float4 over, float4 under) {
public float4
alpha_blend_premul(float4 over, float4 under) {
float inv_over_alpha = 1.0f - over.a;
float alpha = over.a + under.a * inv_over_alpha;
return float4(over.rgb + under.rgb * inv_over_alpha, alpha);
@ -21,8 +23,8 @@ public float4 alpha_blend_premul(float4 over, float4 under) {
// Same as alpha_blend_premul with under_alpha = 1 outputs a blended color
// with alpha 1 which is effectively pre-multiplied since alpha is 1
public float4 alpha_blend_premul(float4 over, float3 under) {
public float4
alpha_blend_premul(float4 over, float3 under) {
float inv_over_alpha = 1.0f - over.a;
return float4(over.rgb + under.rgb * inv_over_alpha, 1.0f);
}

View file

@ -12,8 +12,7 @@ import utils;
#define NUM_COLORS 256
// Inputs {{{
public struct CellRenderData
{
public struct CellRenderData {
public float use_cell_bg_for_selection_fg, use_cell_fg_for_selection_fg, use_cell_for_selection_bg;
public uint default_fg, highlight_fg, highlight_bg, main_cursor_fg, main_cursor_bg, url_color, url_style, inverted, extra_cursor_fg, extra_cursor_bg;
@ -45,19 +44,20 @@ public ConstantBuffer<GammaLUT> glt;
#define gamma_lut glt.gamma_lut
//
static const int fg_index_map[3] = {0, 1, 0};
static const int fg_index_map[3] = { 0, 1, 0 };
static const uint2 cell_pos_map[4] = {
uint2(1u, 0u), // right, top
uint2(1u, 1u), // right, bottom
uint2(0u, 1u), // left, bottom
uint2(0u, 0u) // left, top
uint2(1u, 0u), // right, top
uint2(1u, 1u), // right, bottom
uint2(0u, 1u), // left, bottom
uint2(0u, 0u) // left, top
};
static const uint cursor_shape_map[] = { // maps cursor shape to foreground sprite index
0u, // NO_CURSOR
0u, // BLOCK (this is rendered as background)
2u, // BEAM
3u, // UNDERLINE
4u // UNFOCUSED
static const uint cursor_shape_map[] = {
// maps cursor shape to foreground sprite index
0u, // NO_CURSOR
0u, // BLOCK (this is rendered as background)
2u, // BEAM
3u, // UNDERLINE
4u // UNFOCUSED
};
// }}}
@ -69,7 +69,8 @@ public static const uint BIT_MASK = 1u;
// Linear space luminance values
public static const float3 Y = float3(0.2126, 0.7152, 0.0722);
public float3 color_to_vec(uint c) {
public float3
color_to_vec(uint c) {
uint r, g, b;
r = (c >> 16) & BYTE_MASK;
g = (c >> 8) & BYTE_MASK;
@ -77,21 +78,25 @@ public float3 color_to_vec(uint c) {
return float3(gamma_lut[r], gamma_lut[g], gamma_lut[b]);
}
public float one_if_equal_zero_otherwise(float a, float b) {
public float
one_if_equal_zero_otherwise(float a, float b) {
return (1.0f - zero_or_one(abs(float(a) - float(b))));
}
// We need an integer variant to accommodate GPU driver bugs, see
// https://github.com/kovidgoyal/kitty/issues/9072
public uint one_if_equal_zero_otherwise(int a, int b) {
public uint
one_if_equal_zero_otherwise(int a, int b) {
return (1u - uint(zero_or_one(abs(float(a) - float(b)))));
}
public uint one_if_equal_zero_otherwise(uint a, uint b) {
public uint
one_if_equal_zero_otherwise(uint a, uint b) {
return (1u - uint(zero_or_one(abs(float(a) - float(b)))));
}
public uint resolve_color(uint c, uint defval) {
public uint
resolve_color(uint c, uint defval) {
int t = int(c & BYTE_MASK);
uint is_one = one_if_equal_zero_otherwise(t, 1);
uint is_two = one_if_equal_zero_otherwise(t, 2);
@ -99,30 +104,32 @@ public uint resolve_color(uint c, uint defval) {
return is_one * color_table[(c >> 8) & BYTE_MASK] + is_two * (c >> 8) + is_neither_one_nor_two * defval;
}
public float3 to_color(uint c, uint defval) {
public float3
to_color(uint c, uint defval) {
return color_to_vec(resolve_color(c, defval));
}
[ForceInline]
float3 q_func(float type_val, uint which, float3 val) {
float3
q_func(float type_val, uint which, float3 val) {
return one_if_equal_zero_otherwise(type_val, float(which)) * val;
}
float3 resolve_dynamic_color(uint c, float3 special_val, float3 defval) {
float3
resolve_dynamic_color(uint c, float3 special_val, float3 defval) {
float type_val = float((c >> 24) & BYTE_MASK);
return (
q_func(type_val, COLOR_IS_RGB, color_to_vec(c)) +
q_func(type_val, COLOR_IS_INDEX, color_to_vec(color_table[c & BYTE_MASK])) +
q_func(type_val, COLOR_IS_SPECIAL, special_val) +
q_func(type_val, COLOR_NOT_SET, defval)
);
q_func(type_val, COLOR_IS_RGB, color_to_vec(c)) + q_func(type_val, COLOR_IS_INDEX, color_to_vec(color_table[c & BYTE_MASK])) +
q_func(type_val, COLOR_IS_SPECIAL, special_val) + q_func(type_val, COLOR_NOT_SET, defval));
}
public float contrast_ratio(float under_luminance, float over_luminance) {
public float
contrast_ratio(float under_luminance, float over_luminance) {
return clamp((max(under_luminance, over_luminance) + 0.05f) / (min(under_luminance, over_luminance) + 0.05f), 1.f, 21.f);
}
public float contrast_ratio(float3 a, float3 b) {
public float
contrast_ratio(float3 a, float3 b) {
return contrast_ratio(dot(a, Y), dot(b, Y));
}
@ -130,25 +137,23 @@ public struct ColorPair {
public float3 bg, fg;
};
float contrast_ratio(ColorPair a) {
float
contrast_ratio(ColorPair a) {
return contrast_ratio(a.bg, a.fg);
}
ColorPair if_less_than_pair(float a, float b, ColorPair thenval, ColorPair elseval) {
return ColorPair(
if_less_than(a, b, thenval.bg, elseval.bg),
if_less_than(a, b, thenval.fg, elseval.fg)
);
ColorPair
if_less_than_pair(float a, float b, ColorPair thenval, ColorPair elseval) {
return ColorPair(if_less_than(a, b, thenval.bg, elseval.bg), if_less_than(a, b, thenval.fg, elseval.fg));
}
ColorPair if_one_then_pair(float condition, ColorPair thenval, ColorPair elseval) {
return ColorPair(
if_one_then(condition, thenval.bg, elseval.bg),
if_one_then(condition, thenval.fg, elseval.fg)
);
ColorPair
if_one_then_pair(float condition, ColorPair thenval, ColorPair elseval) {
return ColorPair(if_one_then(condition, thenval.bg, elseval.bg), if_one_then(condition, thenval.fg, elseval.fg));
}
ColorPair resolve_extra_cursor_colors_for_special_cursor(float3 cell_bg, float3 cell_fg) {
ColorPair
resolve_extra_cursor_colors_for_special_cursor(float3 cell_bg, float3 cell_fg) {
ColorPair cell = ColorPair(cell_fg, cell_bg);
ColorPair base = ColorPair(color_to_vec(crd.default_fg), color_to_vec(crd.bg_colors0));
float cr = contrast_ratio(cell);
@ -157,37 +162,34 @@ ColorPair resolve_extra_cursor_colors_for_special_cursor(float3 cell_bg, float3
return if_less_than_pair(cr, 2.5f, higher_contrast_pair, cell);
}
ColorPair resolve_extra_cursor_colors(float3 cell_bg, float3 cell_fg, ColorPair main_cursor) {
ColorPair
resolve_extra_cursor_colors(float3 cell_bg, float3 cell_fg, ColorPair main_cursor) {
ColorPair ans = ColorPair(
resolve_dynamic_color(crd.extra_cursor_bg, main_cursor.bg, main_cursor.bg),
resolve_dynamic_color(crd.extra_cursor_fg, cell_bg, main_cursor.fg)
);
resolve_dynamic_color(crd.extra_cursor_bg, main_cursor.bg, main_cursor.bg), resolve_dynamic_color(crd.extra_cursor_fg, cell_bg, main_cursor.fg));
ColorPair special = resolve_extra_cursor_colors_for_special_cursor(cell_bg, cell_fg);
return if_one_then_pair(zero_or_one(abs(float(crd.extra_cursor_bg & BYTE_MASK) - float(COLOR_IS_SPECIAL))), ans, special);
}
uint is_cursor(uint x, uint y) {
uint
is_cursor(uint x, uint y) {
uint clamped_x = clamp(x, crd.cursor_x1, crd.cursor_x2);
uint clamped_y = clamp(y, crd.cursor_y1, crd.cursor_y2);
return one_if_equal_zero_otherwise(x, clamped_x) * one_if_equal_zero_otherwise(y, clamped_y);
}
float background_opacity_for(uint bg, uint colorval, float opacity_if_matched) {
float
background_opacity_for(uint bg, uint colorval, float opacity_if_matched) {
float not_matched = step(1.0, abs(float(colorval) - float(bg)));
return not_matched + opacity_if_matched * (1.0 - not_matched);
}
float calc_background_opacity(uint bg) {
float
calc_background_opacity(uint bg) {
return (
background_opacity_for(bg, crd.bg_colors0, crd.bg_opacities0) *
background_opacity_for(bg, crd.bg_colors1, crd.bg_opacities1) *
background_opacity_for(bg, crd.bg_colors2, crd.bg_opacities2) *
background_opacity_for(bg, crd.bg_colors3, crd.bg_opacities3) *
background_opacity_for(bg, crd.bg_colors4, crd.bg_opacities4) *
background_opacity_for(bg, crd.bg_colors5, crd.bg_opacities5) *
background_opacity_for(bg, crd.bg_colors6, crd.bg_opacities6) *
background_opacity_for(bg, crd.bg_colors7, crd.bg_opacities7)
);
background_opacity_for(bg, crd.bg_colors0, crd.bg_opacities0) * background_opacity_for(bg, crd.bg_colors1, crd.bg_opacities1) *
background_opacity_for(bg, crd.bg_colors2, crd.bg_opacities2) * background_opacity_for(bg, crd.bg_colors3, crd.bg_opacities3) *
background_opacity_for(bg, crd.bg_colors4, crd.bg_opacities4) * background_opacity_for(bg, crd.bg_colors5, crd.bg_opacities5) *
background_opacity_for(bg, crd.bg_colors6, crd.bg_opacities6) * background_opacity_for(bg, crd.bg_colors7, crd.bg_opacities7));
}
// }}}
@ -205,20 +207,19 @@ public struct CellData {
// plus the intermediate data the cell foreground pass needs.
public struct BackgroundOutput {
public float4 position;
public float3 background_rgb; // final background color (linear, post cursor/selection)
public float bg_alpha; // final alpha, post special-cell opacity (pre draw_bg_bitfield mask)
public float3 background_rgb; // final background color (linear, post cursor/selection)
public float bg_alpha; // final alpha, post special-cell opacity (pre draw_bg_bitfield mask)
public float cell_has_default_bg;
public float3 bg; // original background color, pre selection/cursor (used for selection fg)
public float3 foreground; // resolved foreground color, pre selection/override
public float3 bg; // original background color, pre selection/cursor (used for selection fg)
public float3 foreground; // resolved foreground color, pre selection/override
public uint fg_as_uint;
public uint text_attrs;
public uint is_reversed;
public CellData cell_data;
};
public BackgroundOutput compute_background(
uint3 colors, uint2 sprite_idx, uint is_selected, uint instance_id, uint vertex_id
) {
public BackgroundOutput
compute_background(uint3 colors, uint2 sprite_idx, uint is_selected, uint instance_id, uint vertex_id) {
BackgroundOutput bo;
// set cell color indices {{{
@ -234,7 +235,7 @@ public BackgroundOutput compute_background(
bg_as_uint = has_mark * color_table[NUM_COLORS + mark - 1] + (BIT_MASK - has_mark) * bg_as_uint;
float cell_has_default_bg = 1.f - step(1.f, abs(float(bg_as_uint - crd.bg_colors0))); // 1 if has default bg else 0
float3 bg = color_to_vec(bg_as_uint);
float3 cell_bg = bg; // preserve original bg for the selection foreground color
float3 cell_bg = bg; // preserve original bg for the selection foreground color
uint fg_as_uint = resolve_color(colors[fg_index], default_colors[fg_index]);
fg_as_uint = has_mark * color_table[NUM_COLORS + MARK_MASK + mark] + (1u - has_mark) * fg_as_uint;
float3 foreground = color_to_vec(fg_as_uint);
@ -290,7 +291,7 @@ public BackgroundOutput compute_background(
float effective_cursor_opacity = max(crd.cursor_opacity, bg_alpha);
// is_special_cell is either 0 or 1
float is_special_cell = cell_data.has_block_cursor + float(is_selected & BIT_MASK);
is_special_cell += float(is_reversed); // reverse video cells should be opaque as well
is_special_cell += float(is_reversed); // reverse video cells should be opaque as well
is_special_cell = zero_or_one(is_special_cell);
cell_has_default_bg = if_one_then(is_special_cell, 0., cell_has_default_bg);
@ -319,7 +320,8 @@ public BackgroundOutput compute_background(
// same as the cell background pass but without the draw_bg_bitfield masking, so
// the padding is always drawn. The premultiplied alpha ensures padding matches
// its neighboring cell under a semi-transparent OS window.
public float4 padding_background_premul(uint3 colors, uint2 sprite_idx, uint is_selected, uint instance_id) {
public float4
padding_background_premul(uint3 colors, uint2 sprite_idx, uint is_selected, uint instance_id) {
BackgroundOutput bo = compute_background(colors, sprite_idx, is_selected, instance_id, 0u);
return vec4_premul(bo.background_rgb, bo.bg_alpha);
}

View file

@ -5,10 +5,10 @@
import alpha_blend;
// Constants and Macros
#define left 0
#define top 1
#define right 2
#define bottom 3
#define left 0
#define top 1
#define right 2
#define bottom 3
#define tex_left 0.0
#define tex_top 0.0
#define tex_right 1.0
@ -17,12 +17,7 @@ import alpha_blend;
#define x_axis 0
#define y_axis 1
static const float2 tex_map[4] = {
float2(tex_left, tex_top),
float2(tex_left, tex_bottom),
float2(tex_right, tex_bottom),
float2(tex_right, tex_top)
};
static const float2 tex_map[4] = { float2(tex_left, tex_top), float2(tex_left, tex_bottom), float2(tex_right, tex_bottom), float2(tex_right, tex_top) };
struct VertexOutput {
@ -31,11 +26,13 @@ struct VertexOutput {
};
// Helper Functions
float scale_factor(float window_size, float image_size) {
float
scale_factor(float window_size, float image_size) {
return window_size / image_size;
}
float tiling_factor(int i, float4 sizes, float tiled) {
float
tiling_factor(int i, float4 sizes, float tiled) {
int window = i;
int image = i + 2;
return tiled * scale_factor(sizes[window], sizes[image]) + (1.0 - tiled);
@ -43,12 +40,8 @@ float tiling_factor(int i, float4 sizes, float tiled) {
// Main Vertex Shader Entry Point
[shader("vertex")]
VertexOutput vertex_main(
uint vertex_id : SV_VertexID,
uniform float tiled,
uniform float4 sizes,
uniform float4 positions,
) {
VertexOutput
vertex_main(uint vertex_id: SV_VertexID, uniform float tiled, uniform float4 sizes, uniform float4 positions, ) {
const float2 pos_map[4] = {
float2(positions[left], positions[top]),
float2(positions[left], positions[bottom]),
@ -60,10 +53,7 @@ VertexOutput vertex_main(
VertexOutput output;
// Calculate outputs
float2 tex_coords = tex_map[vertex_id];
output.texcoord = float2(
tex_coords[x_axis] * tiling_factor(x_axis, sizes, tiled),
tex_coords[y_axis] * tiling_factor(y_axis, sizes, tiled)
);
output.texcoord = float2(tex_coords[x_axis] * tiling_factor(x_axis, sizes, tiled), tex_coords[y_axis] * tiling_factor(y_axis, sizes, tiled));
output.position = float4(pos_map[vertex_id], 0.0, 1.0);
return output;
}
@ -73,7 +63,9 @@ uniform Sampler2D image;
// Main Fragment Shader Entry Point
[shader("fragment")]
float4 fragment_main(float2 texcoord: TEXCOORD, uniform float4 background) : SV_Target {
float4
fragment_main(float2 texcoord: TEXCOORD, uniform float4 background)
: SV_Target {
// Sample the texture using Slang's intrinsic texture object syntax
float4 color = image.Sample(texcoord);
// Compute final color with alpha blending

View file

@ -16,18 +16,13 @@ public struct BlitOutput {
#define bottom 3
// Static constant array mapping vertex IDs
static const int2 vertex_pos_map[4] = {
{right, top},
{right, bottom},
{left, bottom},
{left, top}
};
static const int2 vertex_pos_map[4] = { { right, top }, { right, bottom }, { left, bottom }, { left, top } };
public BlitOutput get_coords_for_blit(uint vertex_id, float4 src_rect, float4 dest_rect) {
public BlitOutput
get_coords_for_blit(uint vertex_id, float4 src_rect, float4 dest_rect) {
int2 pos = vertex_pos_map[vertex_id];
BlitOutput output;
output.texcoord = float2(src_rect[pos.x], src_rect[pos.y]);
output.position = float2(dest_rect[pos.x], dest_rect[pos.y]);
return output;
}

View file

@ -14,19 +14,18 @@ struct VSOutput {
[shader("vertex")]
VSOutput vertex_main(
uint vertex_id : SV_VertexID,
uniform float4 src_rect,
uniform float4 dest_rect,
) {
VSOutput
vertex_main(uint vertex_id: SV_VertexID, uniform float4 src_rect, uniform float4 dest_rect, ) {
BlitOutput ans = get_coords_for_blit(vertex_id, src_rect, dest_rect);
return {ans.texcoord, float4(ans.position[0], ans.position[1], 0.0, 1.0)};
return { ans.texcoord, float4(ans.position[0], ans.position[1], 0.0, 1.0) };
}
uniform Sampler2D image;
[shader("fragment")]
float4 fragment_main(float2 texcoord : TEXCOORD) : SV_Target {
float4
fragment_main(float2 texcoord: TEXCOORD)
: SV_Target {
float4 color_premul = image.Sample(texcoord);
return vec4_premul(linear2srgb(color_premul.rgb / color_premul.a), color_premul.a);
}

View file

@ -34,8 +34,7 @@ struct VertexInput {
};
// Vertex shader output structure
struct VertexOutput
{
struct VertexOutput {
float4 color_premul : COLOR_PREMUL;
float4 position : SV_Position;
};
@ -47,28 +46,26 @@ static const int RIGHT = 2;
static const int BOTTOM = 3;
static const uint FF = 0xff;
static const uint2 pos_map[4] = {
uint2(RIGHT, TOP),
uint2(RIGHT, BOTTOM),
uint2(LEFT, BOTTOM),
uint2(LEFT, TOP)
};
static const uint2 pos_map[4] = { uint2(RIGHT, TOP), uint2(RIGHT, BOTTOM), uint2(LEFT, BOTTOM), uint2(LEFT, TOP) };
float to_color(uint c) {
float
to_color(uint c) {
return gamma_lut[c & FF];
}
float is_integer_value(uint c, int x) {
float
is_integer_value(uint c, int x) {
return 1. - step(0.5, abs(float(c) - float(x)));
}
float3 as_color_vector(uint c, int shift) {
float3
as_color_vector(uint c, int shift) {
return float3(to_color(c >> shift), to_color(c >> (shift - 8)), to_color(c >> (shift - 16)));
}
[shader("vertex")]
VertexOutput vertex_main(float4 rect, uint rect_color, uniform float background_opacity, uint vertex_id : SV_VertexID)
{
VertexOutput
vertex_main(float4 rect, uint rect_color, uniform float background_opacity, uint vertex_id: SV_VertexID) {
VertexOutput output;
uint2 pos = pos_map[vertex_id];
@ -83,10 +80,9 @@ VertexOutput vertex_main(float4 rect, uint rect_color, uniform float background_
color3 = if_one_then(is_window_bg, window_bg, color3);
// Actual border quads and tab bar edge strips must be always drawn opaque
float is_not_a_border = zero_or_one(abs(
(float(rc) - ACTIVE_BORDER_COLOR) * (float(rc) - INACTIVE_BORDER_COLOR) * (float(rc) - BELL_BORDER_COLOR) *
(float(rc) - TAB_BAR_EDGE_LEFT_COLOR) * (float(rc) - TAB_BAR_EDGE_RIGHT_COLOR)
));
float is_not_a_border = zero_or_one(
abs((float(rc) - ACTIVE_BORDER_COLOR) * (float(rc) - INACTIVE_BORDER_COLOR) * (float(rc) - BELL_BORDER_COLOR) * (float(rc) - TAB_BAR_EDGE_LEFT_COLOR) *
(float(rc) - TAB_BAR_EDGE_RIGHT_COLOR)));
float final_opacity = if_one_then(is_not_a_border, background_opacity, 1.);
output.color_premul = vec4_premul(color3, final_opacity);
@ -95,6 +91,8 @@ VertexOutput vertex_main(float4 rect, uint rect_color, uniform float background_
}
[shader("fragment")]
float4 fragment_main(float4 color_premul : COLOR_PREMUL) : SV_Target {
float4
fragment_main(float4 color_premul: COLOR_PREMUL)
: SV_Target {
return color_premul;
}

View file

@ -33,7 +33,7 @@ struct VertexOutput {
float3 background;
float4 effective_background_premul;
float effective_text_alpha;
float effective_text_alpha;
float3 sprite_pos;
float3 underline_pos;
float3 cursor_pos;
@ -42,11 +42,12 @@ struct VertexOutput {
float3 cell_foreground;
float4 cursor_color_premult;
float3 decoration_fg;
float colored_sprite;
float colored_sprite;
};
// Foreground utility functions {{{
uint3 to_sprite_coords(uint idx) {
uint3
to_sprite_coords(uint idx) {
uint sprites_per_page = crd.sprites_xnum * crd.sprites_ynum;
uint z = idx / sprites_per_page;
uint num_on_last_page = idx - sprites_per_page * z;
@ -55,23 +56,26 @@ uint3 to_sprite_coords(uint idx) {
return uint3(x, y, z);
}
float3 to_sprite_pos(uint2 pos, uint idx) {
float3
to_sprite_pos(uint2 pos, uint idx) {
uint3 c = to_sprite_coords(idx);
float2 s_xpos = float2(float(c.x), float(c.x) + 1.0f) * (1.0f / float(crd.sprites_xnum));
float2 s_ypos = float2(float(c.y), float(c.y) + 1.0f) * (1.0f / float(crd.sprites_ynum));
uint texture_height_px = (crd.cell_height + 1u) * crd.sprites_ynum;
float row_height = 1.0f / float(texture_height_px);
s_ypos[1] -= row_height; // skip the decorations_exclude row
s_ypos[1] -= row_height; // skip the decorations_exclude row
return float3(s_xpos[pos.x], s_ypos[pos.y], float(c.z));
}
uint to_underline_exclusion_pos(uint2 sprite_idx) {
uint
to_underline_exclusion_pos(uint2 sprite_idx) {
uint3 c = to_sprite_coords(sprite_idx[0]);
uint cell_top_px = c.y * (crd.cell_height + 1u);
return cell_top_px + crd.cell_height;
}
uint read_sprite_decorations_idx(uint2 sprite_idx) {
uint
read_sprite_decorations_idx(uint2 sprite_idx) {
int idx = int(sprite_idx[0] & SPRITE_INDEX_MASK);
int width, height;
sprite_decorations_map.GetDimensions(width, height);
@ -81,29 +85,32 @@ uint read_sprite_decorations_idx(uint2 sprite_idx) {
return sprite_decorations_map[int2(x, y)].r;
}
uint2 get_decorations_indices(uint2 sprite_idx, uint in_url /* [0, 1] */, uint text_attrs) {
uint2
get_decorations_indices(uint2 sprite_idx, uint in_url /* [0, 1] */, uint text_attrs) {
uint decorations_idx = read_sprite_decorations_idx(sprite_idx);
uint has_decorations = uint(zero_or_one(float(decorations_idx)));
uint strike_style = ((text_attrs >> STRIKE_SHIFT) & BIT_MASK); // 0 or 1
uint strike_idx = decorations_idx * strike_style;
uint underline_style = ((text_attrs >> DECORATION_SHIFT) & DECORATION_MASK);
underline_style = in_url * crd.url_style + (1u - in_url) * underline_style; // [0, 5]
uint has_underline = uint(step(0.5f, float(underline_style))); // [0, 1]
uint has_underline = uint(step(0.5f, float(underline_style))); // [0, 1]
return has_decorations * uint2(strike_idx, has_underline * (decorations_idx + underline_style));
}
// }}}
// Override foreground colors {{{
float3 fg_override_luminance(float colored_sprite, float under_luminance, float over_lumininace, float3 under, float3 over) {
float3
fg_override_luminance(float colored_sprite, float under_luminance, float over_lumininace, float3 under, float3 over) {
// If the difference in luminance is too small,
// force the foreground color to be black or white.
float diff_luminance = abs(under_luminance - over_lumininace);
float override_level = (1.f - colored_sprite) * step(diff_luminance, crd.fg_override_threshold);
float original_level = 1.f - override_level;
return original_level * over + override_level * float3(step(under_luminance, 0.5f));
float override_level = (1.f - colored_sprite) * step(diff_luminance, crd.fg_override_threshold);
float original_level = 1.f - override_level;
return original_level * over + override_level * float3(step(under_luminance, 0.5f));
}
float3 fg_override_contrast(float under_luminance, float over_luminance, float3 under, float3 over) {
float3
fg_override_contrast(float under_luminance, float over_luminance, float3 under, float3 over) {
float ratio = contrast_ratio(under_luminance, over_luminance);
float3 diff = abs(under - over);
float3 over_hsluv = rgbToHsluv(over);
@ -119,7 +126,8 @@ float3 fg_override_contrast(float under_luminance, float over_luminance, float3
return lerp(result, over, fallback_condition);
}
float3 override_foreground_color(float3 over, float3 under, float colored_sprite) {
float3
override_foreground_color(float3 over, float3 under, float colored_sprite) {
float under_luminance = dot(under, Y);
float over_lumininace = dot(over.rgb, Y);
if (FG_OVERRIDE_ALGO == 1) return fg_override_luminance(colored_sprite, under_luminance, over_lumininace, under, over);
@ -128,14 +136,14 @@ float3 override_foreground_color(float3 over, float3 under, float colored_sprite
// }}}
[shader("vertex")]
VertexOutput vertex_main(
VertexOutput
vertex_main(
[[vk::location(0)]] uint3 colors,
[[vk::location(1)]] uint2 sprite_idx,
[[vk::location(2)]] uint is_selected,
uint vertex_id : SV_VertexID,
uint instance_id : SV_InstanceID,
uniform uint draw_bg_bitfield,
) {
[[vk::location(2)]] uint is_selected,
uint vertex_id: SV_VertexID,
uint instance_id: SV_InstanceID,
uniform uint draw_bg_bitfield, ) {
VertexOutput vo;
BackgroundOutput bo = compute_background(colors, sprite_idx, is_selected, instance_id, vertex_id);
@ -153,8 +161,7 @@ VertexOutput vertex_main(
vo.colored_sprite = float((sprite_idx[0] & SPRITE_COLORED_MASK) >> SPRITE_COLORED_SHIFT);
float has_dim = float((text_attrs >> DIM_SHIFT) & BIT_MASK), has_blink = float((text_attrs >> BLINK_SHIFT) & BIT_MASK);
vo.effective_text_alpha = crd.inactive_text_alpha * if_one_then(has_dim, crd.dim_opacity, 1.0) * if_one_then(
has_blink, crd.blink_opacity, 1.0);
vo.effective_text_alpha = crd.inactive_text_alpha * if_one_then(has_dim, crd.dim_opacity, 1.0) * if_one_then(has_blink, crd.blink_opacity, 1.0);
float in_url = float((is_selected >> 1) & BIT_MASK);
vo.decoration_fg = if_one_then(in_url, color_to_vec(crd.url_color), to_color(colors[2], fg_as_uint));
// Selection
@ -203,21 +210,25 @@ uniform Sampler2DArray sprites;
// Scaling factor for the extra text-alpha adjustment for luminance-difference.
static const float text_gamma_scaling = 0.5;
float clamp_to_unit_float(float x) {
float
clamp_to_unit_float(float x) {
// Clamp value to suitable output range
return clamp(x, 0.0f, 1.0f);
}
float4 foreground_contrast_new(float4 over, float3 under, float text_contrast, float text_gamma_adjustment) {
float4
foreground_contrast_new(float4 over, float3 under, float text_contrast, float text_gamma_adjustment) {
float under_luminance = dot(under, Y);
float over_lumininace = dot(over.rgb, Y);
// Apply additional gamma-adjustment scaled by the luminance difference, the darker the foreground the more adjustment we apply.
// A multiplicative contrast is also available to increase saturation.
over.a = clamp_to_unit_float(lerp(over.a, pow(over.a, text_gamma_adjustment), (1 - over_lumininace + under_luminance) * text_gamma_scaling) * text_contrast);
over.a =
clamp_to_unit_float(lerp(over.a, pow(over.a, text_gamma_adjustment), (1 - over_lumininace + under_luminance) * text_gamma_scaling) * text_contrast);
return over;
}
float4 foreground_contrast_old(float4 over, float3 under) {
float4
foreground_contrast_old(float4 over, float3 under) {
// Simulation of gamma-incorrect blending
float under_luminance = dot(under, Y);
float over_lumininace = dot(over.rgb, Y);
@ -225,27 +236,37 @@ float4 foreground_contrast_old(float4 over, float3 under) {
//
// linear2srgb(over * overA2 + under * (1 - overA2)) = linear2srgb(over) * over.a + linear2srgb(under) * (1 - over.a)
// ^ gamma correct blending with new alpha ^ gamma incorrect blending with old alpha
over.a = clamp_to_unit_float((srgb2linear(linear2srgb(over_lumininace) * over.a + linear2srgb(under_luminance) * (1.0f - over.a)) - under_luminance) / (over_lumininace - under_luminance));
over.a = clamp_to_unit_float(
(srgb2linear(linear2srgb(over_lumininace) * over.a + linear2srgb(under_luminance) * (1.0f - over.a)) - under_luminance) /
(over_lumininace - under_luminance));
return over;
}
float4 foreground_contrast(float4 over, float3 under, float text_contrast, float text_gamma_adjustment) {
float4
foreground_contrast(float4 over, float3 under, float text_contrast, float text_gamma_adjustment) {
if (TEXT_NEW_GAMMA) return foreground_contrast_new(over, under, text_contrast, text_gamma_adjustment);
return foreground_contrast_old(over, under);
}
float4 load_text_foreground_color(float3 sprite_pos, float colored_sprite, float3 cell_foreground) {
float4
load_text_foreground_color(float3 sprite_pos, float colored_sprite, float3 cell_foreground) {
// For colored sprites use the color from the sprite rather than the text foreground
// Return non-premultiplied foreground color
float4 text_fg = sprites.Sample(sprite_pos);
return float4(lerp(cell_foreground, text_fg.xyz, colored_sprite), text_fg.w);
}
float4 calculate_premul_foreground_from_sprites(
float3 sprite_pos, float3 underline_pos, float3 cursor_pos, float3 strike_pos, uint underline_exclusion_pos,
float4 text_fg, float3 decoration_fg, float4 cursor_color_premult,
float effective_text_alpha,
) {
float4
calculate_premul_foreground_from_sprites(
float3 sprite_pos,
float3 underline_pos,
float3 cursor_pos,
float3 strike_pos,
uint underline_exclusion_pos,
float4 text_fg,
float3 decoration_fg,
float4 cursor_color_premult,
float effective_text_alpha, ) {
// Return premul foreground color from decorations (cursor, underline, strikethrough)
int width, height, layer;
sprites.GetDimensions(width, height, layer);
@ -261,24 +282,20 @@ float4 calculate_premul_foreground_from_sprites(
float combined_alpha = min(text_fg.w + strike_alpha, 1.0);
float4 ans = alpha_blend(
float4(text_fg.rgb, combined_alpha * effective_text_alpha),
float4(decoration_fg, underline_alpha * effective_text_alpha)
);
float4 ans = alpha_blend(float4(text_fg.rgb, combined_alpha * effective_text_alpha), float4(decoration_fg, underline_alpha * effective_text_alpha));
return lerp(ans, cursor_color_premult, cursor_alpha * cursor_color_premult.w);
}
float4 adjust_foreground_contrast_with_background(float4 text_fg, float3 bg, float text_contrast, float text_gamma_adjustment) {
float4
adjust_foreground_contrast_with_background(float4 text_fg, float3 bg, float text_contrast, float text_gamma_adjustment) {
return foreground_contrast(text_fg, bg, text_contrast, text_gamma_adjustment);
}
[shader("fragment")]
float4 fragment_main(
VertexOutput vo,
uniform float text_contrast,
uniform float text_gamma_adjustment,
) : SV_Target {
float4
fragment_main(VertexOutput vo, uniform float text_contrast, uniform float text_gamma_adjustment, )
: SV_Target {
float4 ans_premul = 0;
if (!ONLY_FOREGROUND) ans_premul = vo.effective_background_premul;
@ -287,8 +304,15 @@ float4 fragment_main(
float4 text_fg = load_text_foreground_color(vo.sprite_pos, vo.colored_sprite, vo.cell_foreground);
text_fg = adjust_foreground_contrast_with_background(text_fg, vo.background, text_contrast, text_gamma_adjustment);
float4 text_fg_premul = calculate_premul_foreground_from_sprites(
vo.sprite_pos, vo.underline_pos, vo.cursor_pos, vo.strike_pos, vo.underline_exclusion_pos,
text_fg, vo.decoration_fg, vo.cursor_color_premult, vo.effective_text_alpha);
vo.sprite_pos,
vo.underline_pos,
vo.cursor_pos,
vo.strike_pos,
vo.underline_exclusion_pos,
text_fg,
vo.decoration_fg,
vo.cursor_color_premult,
vo.effective_text_alpha);
if (ONLY_FOREGROUND) ans_premul = text_fg_premul;
else ans_premul = alpha_blend_premul(text_fg_premul, ans_premul);
}

View file

@ -10,30 +10,30 @@ import kitty_custom_shader_types;
// Golden spiral samples [x, y, weight] — weight is inverse of distance
static const float3 samples[24] = {
float3(0.1693761725038636, 0.9855514761735895, 1.0),
float3(-1.333070830962943, 0.4721463328627773, 0.7071067811865475),
float3(-0.8464394909806497, -1.51113870578065, 0.5773502691896258),
float3(1.554155680728463, -1.2588090085709776, 0.5),
float3(1.681364377589461, 1.4741145918052656, 0.4472135954999579),
float3(-1.2795157692199817, 2.088741103228784, 0.4082482904638631),
float3(0.1693761725038636, 0.9855514761735895, 1.0),
float3(-1.333070830962943, 0.4721463328627773, 0.7071067811865475),
float3(-0.8464394909806497, -1.51113870578065, 0.5773502691896258),
float3(1.554155680728463, -1.2588090085709776, 0.5),
float3(1.681364377589461, 1.4741145918052656, 0.4472135954999579),
float3(-1.2795157692199817, 2.088741103228784, 0.4082482904638631),
float3(-2.4575847530631187, -0.9799373355024756, 0.3779644730092272),
float3(0.5874641440200847, -2.7667464429345077, 0.35355339059327373),
float3(2.997715703369726, 0.11704939884745152, 0.3333333333333333),
float3(0.41360842451688395, 3.1351121305574803, 0.31622776601683794),
float3(-3.167149933769243, 0.9844599011770256, 0.30151134457776363),
float3(0.5874641440200847, -2.7667464429345077, 0.35355339059327373),
float3(2.997715703369726, 0.11704939884745152, 0.3333333333333333),
float3(0.41360842451688395, 3.1351121305574803, 0.31622776601683794),
float3(-3.167149933769243, 0.9844599011770256, 0.30151134457776363),
float3(-1.5736713846521535, -3.0860263079123245, 0.2886751345948129),
float3(2.888202648340422, -2.1583061557896213, 0.2773500981126146),
float3(2.7150778983300325, 2.5745586041105715, 0.2672612419124244),
float3(-2.1504069972377464, 3.2211410627650165, 0.2581988897471611),
float3(2.888202648340422, -2.1583061557896213, 0.2773500981126146),
float3(2.7150778983300325, 2.5745586041105715, 0.2672612419124244),
float3(-2.1504069972377464, 3.2211410627650165, 0.2581988897471611),
float3(-3.6548858794907493, -1.6253643308191343, 0.25),
float3(1.0130775986052671, -3.9967078676335834, 0.24253562503633297),
float3(4.229723673607257, 0.33081361055181563, 0.23570226039551587),
float3(0.40107790291173834, 4.340407413572593, 0.22941573387056174),
float3(-4.319124570236028, 1.159811599693438, 0.22360679774997896),
float3(-1.9209044802827355, -4.160543952132907, 0.2182178902359924),
float3(3.8639122286635708, -2.6589814382925123, 0.21320071635561041),
float3(3.3486228404946234, 3.4331800232609, 0.20851441405707477),
float3(-2.8769733643574344, 3.9652268864187157, 0.20412414523193154)
float3(1.0130775986052671, -3.9967078676335834, 0.24253562503633297),
float3(4.229723673607257, 0.33081361055181563, 0.23570226039551587),
float3(0.40107790291173834, 4.340407413572593, 0.22941573387056174),
float3(-4.319124570236028, 1.159811599693438, 0.22360679774997896),
float3(-1.9209044802827355, -4.160543952132907, 0.2182178902359924),
float3(3.8639122286635708, -2.6589814382925123, 0.21320071635561041),
float3(3.3486228404946234, 3.4331800232609, 0.20851441405707477),
float3(-2.8769733643574344, 3.9652268864187157, 0.20412414523193154)
};
// minimum luminance for a pixel to contribute bloom
@ -41,15 +41,13 @@ static const float BLOOM_THRESHOLD = 0.2f;
// strength of the bloom contribution
static const float BLOOM_STRENGTH = 0.2f;
float lum(float4 c) {
float
lum(float4 c) {
return 0.299f * c.r + 0.587f * c.g + 0.114f * c.b;
}
public float4 fragment_main(
float4 color,
KittyTextures t,
KittyCustomShaderData d
) {
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
float2 uv = t.pos;
// One pixel in backbuffer UV space; scale by sqrt(2) to match original step

View file

@ -9,15 +9,12 @@
import kitty_custom_shader_types;
static const float WARP = 0.25f; // simulates curvature of CRT monitor
static const float SCAN = 0.50f; // simulates darkness between scanlines
static const float4 TINT = float4(0, 0.8, 0.6, 0); // tint color set the fourth component to 1 to apply it fully
static const float WARP = 0.25f; // simulates curvature of CRT monitor
static const float SCAN = 0.50f; // simulates darkness between scanlines
static const float4 TINT = float4(0, 0.8, 0.6, 0); // tint color set the fourth component to 1 to apply it fully
public float4 fragment_main(
float4 color,
KittyTextures t,
KittyCustomShaderData d
) {
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
// UV within the current viewport (0..1)
float2 uv = t.pos;
@ -26,8 +23,12 @@ public float4 fragment_main(
dc *= dc;
// Warp UV to simulate CRT screen curvature
uv.x -= 0.5f; uv.x *= 1.0f + (dc.y * (0.3f * WARP)); uv.x += 0.5f;
uv.y -= 0.5f; uv.y *= 1.0f + (dc.x * (0.4f * WARP)); uv.y += 0.5f;
uv.x -= 0.5f;
uv.x *= 1.0f + (dc.y * (0.3f * WARP));
uv.x += 0.5f;
uv.y -= 0.5f;
uv.y *= 1.0f + (dc.x * (0.4f * WARP));
uv.y += 0.5f;
// Original Y pixel coordinate within the viewport (for scanline phase)
float fragCoordY = (t.pos.y - t.viewport.y) * float(d.viewport_size_pixels.y);

View file

@ -9,49 +9,54 @@
import kitty_custom_shader_types;
static const float4 TRAIL_COLOR = float4(1.0, 0.725, 0.161, 1.0);
static const float4 TRAIL_COLOR_ACCENT = float4(1.0, 0.0, 0.0, 1.0);
static const float4 TRAIL_COLOR = float4(1.0, 0.725, 0.161, 1.0);
static const float4 TRAIL_COLOR_ACCENT = float4(1.0, 0.0, 0.0, 1.0);
// Trail lifetime in seconds.
static const float DURATION = 0.5;
static const float DURATION = 0.5;
// pow(1 - x, 10) via repeated squaring — branchless ease-out.
static float ease_out10(float x) {
float y = 1.0 - x;
static float
ease_out10(float x) {
float y = 1.0 - x;
float y2 = y * y;
float y4 = y2 * y2;
return y4 * y4 * y2;
}
// Smooth blend curve for the progress value.
static float blend_smooth(float t) {
static float
blend_smooth(float t) {
float s = t * t;
return s / (2.0 * (s - t) + 1.0);
}
// SDF of an axis-aligned box. p = query point, center = box centre, hext = half-extents.
static float sdBox(float2 p, float2 center, float2 hext) {
static float
sdBox(float2 p, float2 center, float2 hext) {
float2 d = abs(p - center) - hext;
return length(max(d, float2(0.0))) + min(max(d.x, d.y), 0.0);
}
// Single edge contribution for the quadrilateral SDF (signed winding accumulation).
static float seg(float2 p, float2 a, float2 b, inout float s, float d) {
float2 e = b - a;
float2 w = p - a;
static float
seg(float2 p, float2 a, float2 b, inout float s, float d) {
float2 e = b - a;
float2 w = p - a;
float2 proj = a + e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);
d = min(d, dot(p - proj, p - proj));
float c0 = step(0.0, p.y - a.y);
float c1 = 1.0 - step(0.0, p.y - b.y);
float c2 = 1.0 - step(0.0, e.x * w.y - e.y * w.x);
float allCond = c0 * c1 * c2;
float allCond = c0 * c1 * c2;
float noneCond = (1.0 - c0) * (1.0 - c1) * (1.0 - c2);
s *= lerp(1.0, -1.0, step(0.5, allCond + noneCond));
return d;
}
// SDF of an arbitrary quadrilateral (uses winding number via four edge contributions).
static float sdQuad(float2 p, float2 v0, float2 v1, float2 v2, float2 v3) {
static float
sdQuad(float2 p, float2 v0, float2 v1, float2 v2, float2 v3) {
float s = 1.0;
float d = dot(p - v0, p - v0);
d = seg(p, v0, v3, s, d);
@ -63,17 +68,15 @@ static float sdQuad(float2 p, float2 v0, float2 v1, float2 v2, float2 v3) {
// Returns 0 for diagonal movement (up-left / down-right), 1 otherwise.
// Selects which corner of the parallelogram aligns with the movement direction.
static float movementVertexFactor(float2 a, float2 b) {
static float
movementVertexFactor(float2 a, float2 b) {
float c1 = step(b.x, a.x) * step(a.y, b.y);
float c2 = step(a.x, b.x) * step(b.y, a.y);
return 1.0 - max(c1, c2);
}
public float4 fragment_main(
float4 color,
KittyTextures t,
KittyCustomShaderData d
) {
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
if (d.cursor_trail_color.a <= 0.0) return color;
float age = clamp((d.timestamp - d.cursor_trail_change_time) / DURATION, 0.0, 1.0);
@ -84,39 +87,38 @@ public float4 fragment_main(
// Work in pixel space for correct aspect-ratio distance measurements.
float2 pixelDims = float2(d.viewport_size_pixels) * t.viewport.zw;
float2 px = t.pos * pixelDims;
float2 px = t.pos * pixelDims;
// Current cursor rect in pixel space (edge: .x=left .y=right .z=top .w=bottom, y-up).
float curLeft = d.cursor_trail_edge.x * pixelDims.x;
float curRight = d.cursor_trail_edge.y * pixelDims.x;
float curTop = d.cursor_trail_edge.z * pixelDims.y;
float curLeft = d.cursor_trail_edge.x * pixelDims.x;
float curRight = d.cursor_trail_edge.y * pixelDims.x;
float curTop = d.cursor_trail_edge.z * pixelDims.y;
float curBottom = d.cursor_trail_edge.w * pixelDims.y;
float curWidth = curRight - curLeft;
float curHeight = curTop - curBottom;
float curWidth = curRight - curLeft;
float curHeight = curTop - curBottom;
float2 curCenter = float2((curLeft + curRight) * 0.5, (curTop + curBottom) * 0.5);
float prevLeft = d.cursor_trail_prev_edge.x * pixelDims.x;
float prevTop = d.cursor_trail_prev_edge.z * pixelDims.y;
float prevLeft = d.cursor_trail_prev_edge.x * pixelDims.x;
float prevTop = d.cursor_trail_prev_edge.z * pixelDims.y;
float prevBottom = d.cursor_trail_prev_edge.w * pixelDims.y;
float2 prevCenter = float2(
(d.cursor_trail_prev_edge.x + d.cursor_trail_prev_edge.y) * 0.5 * pixelDims.x,
(d.cursor_trail_prev_edge.z + d.cursor_trail_prev_edge.w) * 0.5 * pixelDims.y
);
(d.cursor_trail_prev_edge.z + d.cursor_trail_prev_edge.w) * 0.5 * pixelDims.y);
float lineLength = length(curCenter - prevCenter);
// Alpha fades to 0 at the previous-cursor end, reaching full effect at the current cursor.
float alphaModifier = min(length(px - curCenter) / (lineLength * easedProgress), 1.0);
// Build parallelogram vertices in pixel space.
float vf = movementVertexFactor(curCenter, prevCenter);
float vf = movementVertexFactor(curCenter, prevCenter);
float ivf = 1.0 - vf;
float2 v0 = float2(lerp(curLeft, curRight, vf), curBottom);
float2 v0 = float2(lerp(curLeft, curRight, vf), curBottom);
float2 v1 = float2(lerp(curLeft, curRight, ivf), curTop);
float2 v2 = float2(prevLeft + curWidth * ivf, prevTop);
float2 v3 = float2(prevLeft + curWidth * vf, prevBottom);
float2 v2 = float2(prevLeft + curWidth * ivf, prevTop);
float2 v3 = float2(prevLeft + curWidth * vf, prevBottom);
float sdfCursor = sdBox(px, curCenter, float2(curWidth, curHeight) * 0.5);
float sdfTrail = sdQuad(px, v0, v1, v2, v3);
float sdfTrail = sdQuad(px, v0, v1, v2, v3);
// ~2-pixel anti-aliasing range in pixel space.
float aaRange = 2.0;

View file

@ -20,8 +20,12 @@ import kitty_custom_shader_types;
// Returns true when uv is inside the convex quad c0→c1→c2→c3 (clockwise, y-up).
// Corner order from cursor_trail_corners_*: 0=top-right, 1=bottom-right, 2=bottom-left, 3=top-left.
bool inside_trail_quad(float2 uv, float2 c0, float2 c1, float2 c2, float2 c3) {
float2 e0 = c1 - c0; float2 e1 = c2 - c1; float2 e2 = c3 - c2; float2 e3 = c0 - c3;
bool
inside_trail_quad(float2 uv, float2 c0, float2 c1, float2 c2, float2 c3) {
float2 e0 = c1 - c0;
float2 e1 = c2 - c1;
float2 e2 = c3 - c2;
float2 e3 = c0 - c3;
float s0 = e0.x * (uv.y - c0.y) - e0.y * (uv.x - c0.x);
float s1 = e1.x * (uv.y - c1.y) - e1.y * (uv.x - c1.x);
float s2 = e2.x * (uv.y - c2.y) - e2.y * (uv.x - c2.x);
@ -29,27 +33,24 @@ bool inside_trail_quad(float2 uv, float2 c0, float2 c1, float2 c2, float2 c3) {
return s0 <= 0.0 && s1 <= 0.0 && s2 <= 0.0 && s3 <= 0.0;
}
public float4 fragment_main(
float4 color,
KittyTextures t,
KittyCustomShaderData d
) {
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
float opacity = d.cursor_trail_color.a;
if (opacity <= 0.0) return color;
float2 uv = t.pos;
float2 c0 = float2(d.cursor_trail_corners_x.x, d.cursor_trail_corners_y.x); // top-right
float2 c1 = float2(d.cursor_trail_corners_x.y, d.cursor_trail_corners_y.y); // bottom-right
float2 c2 = float2(d.cursor_trail_corners_x.z, d.cursor_trail_corners_y.z); // bottom-left
float2 c3 = float2(d.cursor_trail_corners_x.w, d.cursor_trail_corners_y.w); // top-left
float2 c0 = float2(d.cursor_trail_corners_x.x, d.cursor_trail_corners_y.x); // top-right
float2 c1 = float2(d.cursor_trail_corners_x.y, d.cursor_trail_corners_y.y); // bottom-right
float2 c2 = float2(d.cursor_trail_corners_x.z, d.cursor_trail_corners_y.z); // bottom-left
float2 c3 = float2(d.cursor_trail_corners_x.w, d.cursor_trail_corners_y.w); // top-left
if (!inside_trail_quad(uv, c0, c1, c2, c3)) return color;
// Mask out the cursor's current rectangle so the real cursor renders cleanly on top.
// cursor_trail_edge: .x=left, .y=right, .z=top, .w=bottom (UV, y-up)
float inside_cursor = step(d.cursor_trail_edge.x, uv.x) * step(uv.x, d.cursor_trail_edge.y)
* step(d.cursor_trail_edge.w, uv.y) * step(uv.y, d.cursor_trail_edge.z);
float inside_cursor =
step(d.cursor_trail_edge.x, uv.x) * step(uv.x, d.cursor_trail_edge.y) * step(d.cursor_trail_edge.w, uv.y) * step(uv.y, d.cursor_trail_edge.z);
opacity *= 1.0 - inside_cursor;
if (opacity <= 0.0) return color;

View file

@ -7,27 +7,29 @@
import kitty_custom_shader_types;
static const float3 BOLT_COLOR = float3(1.00, 0.82, 0.72);
static const float3 GLOW_COLOR = float3(0.74, 0.00, 0.00);
static const float3 BOLT_COLOR = float3(1.00, 0.82, 0.72);
static const float3 GLOW_COLOR = float3(0.74, 0.00, 0.00);
// Bolt lifetime in seconds.
static const float DURATION = 0.14;
static const float DURATION = 0.14;
// Sideways displacement relative to bolt length.
static const float JAGGEDNESS = 0.085;
static const float JAGGEDNESS = 0.085;
// Core stroke width in pixels.
static const float CORE_WIDTH = 1.15;
static const float CORE_WIDTH = 1.15;
// Glow radius in pixels.
static const float GLOW_WIDTH = 5.5;
static const float GLOW_WIDTH = 5.5;
// Number of linear segments that make up the main bolt.
static const int BOLT_SEGMENTS = 12;
static const int BOLT_SEGMENTS = 12;
static float hash11(float p) {
static float
hash11(float p) {
p = frac(p * 0.1031);
p *= p + 33.33;
p *= p + p;
return frac(p);
}
static float segDist(float2 p, float2 a, float2 b) {
static float
segDist(float2 p, float2 a, float2 b) {
float2 ab = b - a;
float t = clamp(dot(p - a, ab) / max(dot(ab, ab), 1e-7), 0.0, 1.0);
return length(p - (a + ab * t));
@ -35,23 +37,25 @@ static float segDist(float2 p, float2 a, float2 b) {
// A point on the piecewise-linear bolt. Endpoints stay fixed; interior nodes
// are displaced perpendicular to the travel direction.
static float2 boltPoint(float2 a, float2 b, float t, float seed) {
float2 delta = b - a;
float boltLength = length(delta);
float2 perp = float2(-delta.y, delta.x) / max(boltLength, 1e-5);
float node = floor(t * float(BOLT_SEGMENTS) + 0.5);
float rnd = hash11(node * 17.17 + seed) * 2.0 - 1.0;
float fade = sin(3.14159265 * t);
static float2
boltPoint(float2 a, float2 b, float t, float seed) {
float2 delta = b - a;
float boltLength = length(delta);
float2 perp = float2(-delta.y, delta.x) / max(boltLength, 1e-5);
float node = floor(t * float(BOLT_SEGMENTS) + 0.5);
float rnd = hash11(node * 17.17 + seed) * 2.0 - 1.0;
float fade = sin(3.14159265 * t);
return lerp(a, b, t) + perp * rnd * boltLength * JAGGEDNESS * fade;
}
// Minimum distance from p to the main bolt's piecewise-linear path.
static float mainBoltDist(float2 p, float2 a, float2 b, float seed) {
float minD = 1e5;
static float
mainBoltDist(float2 p, float2 a, float2 b, float seed) {
float minD = 1e5;
float2 prev = a;
[ForceUnroll]
for (int i = 1; i <= BOLT_SEGMENTS; i++) {
float t = float(i) / float(BOLT_SEGMENTS);
float t = float(i) / float(BOLT_SEGMENTS);
float2 cur = boltPoint(a, b, t, seed);
minD = min(minD, segDist(p, prev, cur));
prev = cur;
@ -60,49 +64,41 @@ static float mainBoltDist(float2 p, float2 a, float2 b, float seed) {
}
// Three small forks growing out of alternating points on the main bolt.
static float forkDist(float2 p, float2 a, float2 b, float seed) {
float minD = 1e5;
float2 delta = b - a;
float boltLength = length(delta);
float2 perp = float2(-delta.y, delta.x) / max(boltLength, 1e-5);
float2 dir = normalize(delta);
static float
forkDist(float2 p, float2 a, float2 b, float seed) {
float minD = 1e5;
float2 delta = b - a;
float boltLength = length(delta);
float2 perp = float2(-delta.y, delta.x) / max(boltLength, 1e-5);
float2 dir = normalize(delta);
[ForceUnroll]
for (int i = 0; i < 3; i++) {
float fi = float(i);
float t = 0.28 + fi * 0.22;
float2 root = boltPoint(a, b, t, seed);
float side = lerp(-1.0, 1.0, step(0.5, hash11(seed + fi * 41.0)));
float reach = boltLength * (0.055 + 0.035 * hash11(seed + fi * 53.0));
float fi = float(i);
float t = 0.28 + fi * 0.22;
float2 root = boltPoint(a, b, t, seed);
float side = lerp(-1.0, 1.0, step(0.5, hash11(seed + fi * 41.0)));
float reach = boltLength * (0.055 + 0.035 * hash11(seed + fi * 53.0));
float2 along = dir * reach * (hash11(seed + fi * 67.0) - 0.35);
float2 tip = root + perp * side * reach + along;
float2 elbow = lerp(root, tip, 0.52)
- perp * side * reach * (0.12 + 0.18 * hash11(seed + fi * 79.0));
float2 tip = root + perp * side * reach + along;
float2 elbow = lerp(root, tip, 0.52) - perp * side * reach * (0.12 + 0.18 * hash11(seed + fi * 79.0));
minD = min(minD, segDist(p, root, elbow));
minD = min(minD, segDist(p, elbow, tip));
}
return minD;
}
public float4 fragment_main(
float4 color,
KittyTextures t,
KittyCustomShaderData d
) {
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
float age = clamp((d.timestamp - d.cursor_trail_change_time) / DURATION, 0.0, 1.0);
if (age >= 1.0) return color;
float2 pixelDims = float2(d.viewport_size_pixels) * t.viewport.zw;
// Cursor centres in pixel space (edge: .x=left .y=right .z=top .w=bottom, y-up).
float2 curCenter = float2(
(d.cursor_trail_edge.x + d.cursor_trail_edge.y) * 0.5,
(d.cursor_trail_edge.z + d.cursor_trail_edge.w) * 0.5
) * pixelDims;
float2 prevCenter = float2(
(d.cursor_trail_prev_edge.x + d.cursor_trail_prev_edge.y) * 0.5,
(d.cursor_trail_prev_edge.z + d.cursor_trail_prev_edge.w) * 0.5
) * pixelDims;
float2 curCenter = float2((d.cursor_trail_edge.x + d.cursor_trail_edge.y) * 0.5, (d.cursor_trail_edge.z + d.cursor_trail_edge.w) * 0.5) * pixelDims;
float2 prevCenter =
float2((d.cursor_trail_prev_edge.x + d.cursor_trail_prev_edge.y) * 0.5, (d.cursor_trail_prev_edge.z + d.cursor_trail_prev_edge.w) * 0.5) * pixelDims;
float cursorWidth = (d.cursor_trail_edge.y - d.cursor_trail_edge.x) * pixelDims.x;
@ -114,25 +110,25 @@ public float4 fragment_main(
float mainD = mainBoltDist(px, prevCenter, curCenter, seed);
float forkD = forkDist(px, prevCenter, curCenter, seed);
float pixel = max(fwidth(mainD), 0.65);
float core = 1.0 - smoothstep(CORE_WIDTH, CORE_WIDTH + pixel, mainD);
float pixel = max(fwidth(mainD), 0.65);
float core = 1.0 - smoothstep(CORE_WIDTH, CORE_WIDTH + pixel, mainD);
float forkCore = 1.0 - smoothstep(CORE_WIDTH * 0.72, CORE_WIDTH * 0.72 + pixel, forkD);
float glow = exp(-mainD * mainD / (GLOW_WIDTH * GLOW_WIDTH));
float glow = exp(-mainD * mainD / (GLOW_WIDTH * GLOW_WIDTH));
float forkGlow = exp(-forkD * forkD / (GLOW_WIDTH * GLOW_WIDTH * 0.55));
// Flash hard, then rapidly lose the core, leaving only glow.
float flash = 1.0 - smoothstep(0.0, 1.0, age);
float flash = 1.0 - smoothstep(0.0, 1.0, age);
flash *= flash;
float flicker = 0.82 + 0.18 * sin(age * 95.0 + seed);
float glowAmt = max(glow, forkGlow * 0.55) * flash * flicker * 0.72;
float coreAmt = max(core, forkCore * 0.82) * flash * flicker;
float flicker = 0.82 + 0.18 * sin(age * 95.0 + seed);
float glowAmt = max(glow, forkGlow * 0.55) * flash * flicker * 0.72;
float coreAmt = max(core, forkCore * 0.82) * flash * flicker;
float3 rgb = lerp(color.rgb, GLOW_COLOR, clamp(glowAmt, 0.0, 1.0));
rgb = lerp(rgb, BOLT_COLOR, clamp(coreAmt, 0.0, 1.0));
rgb = lerp(rgb, BOLT_COLOR, clamp(coreAmt, 0.0, 1.0));
// Keep the cursor crisp above the effect.
float insideCursor = step(d.cursor_trail_edge.x, t.pos.x) * step(t.pos.x, d.cursor_trail_edge.y)
* step(d.cursor_trail_edge.w, t.pos.y) * step(t.pos.y, d.cursor_trail_edge.z);
float insideCursor = step(d.cursor_trail_edge.x, t.pos.x) * step(t.pos.x, d.cursor_trail_edge.y) * step(d.cursor_trail_edge.w, t.pos.y) *
step(t.pos.y, d.cursor_trail_edge.z);
rgb = lerp(rgb, color.rgb, insideCursor);
return float4(rgb, color.a);

View file

@ -7,32 +7,36 @@
import kitty_custom_shader_types;
static const int NUM_PARTICLES = 128;
static const int NUM_PARTICLES = 128;
static const float BLACK_BLEND_THRESHOLD = 0.4f;
static const float CYCLE_TIME = 6.0f;
static const float PARTICLE_PHASE = 2.0f;
static const float CYCLE_TIME = 6.0f;
static const float PARTICLE_PHASE = 2.0f;
// Pseudo-random in [-1, 1]
float rand_val(float val, float seed) {
float
rand_val(float val, float seed) {
return cos(val * sin(val * seed) * seed);
}
float distance2(float2 a, float2 b) {
float
distance2(float2 a, float2 b) {
float2 d = a - b;
return dot(d, d);
}
// Diagonal red-and-white barber-pole pattern — branchless
float3 barberpole(float2 pos, float2 rocketpos) {
float3
barberpole(float2 pos, float2 rocketpos) {
float d = fmod(((pos.x - rocketpos.x) + (pos.y - rocketpos.y)) * 20.0f, 2.0f);
return lerp(float3(1.0f), float3(1.0f, 0.0f, 0.0f), step(1.0f, d));
}
// Draw a single rocket at rocketpos in the supplied coordinate space — branchless
float3 rocket(float2 pos, float2 rocketpos) {
float3 col = float3(0.0f);
float absx = abs(rocketpos.x - pos.x);
float absy = abs(rocketpos.y - pos.y);
float3
rocket(float2 pos, float2 rocketpos) {
float3 col = float3(0.0f);
float absx = abs(rocketpos.x - pos.x);
float absy = abs(rocketpos.y - pos.y);
// Wooden stick
float stick = step(absx, 0.01f) * step(absy, 0.22f);
@ -43,9 +47,9 @@ float3 rocket(float2 pos, float2 rocketpos) {
col = lerp(col, barberpole(pos, rocketpos), pole);
// Pointed nose cone
float pointw = (rocketpos.y - pos.y - 0.25f) * -0.7f;
float pointw = (rocketpos.y - pos.y - 0.25f) * -0.7f;
float has_point = step(0.1f, rocketpos.y - pos.y);
float f_point = smoothstep(pointw - 0.001f, pointw + 0.001f, absx);
float f_point = smoothstep(pointw - 0.001f, pointw + 0.001f, absx);
col = lerp(col, lerp(float3(1.0f, 0.0f, 0.0f), col, f_point), has_point);
// Ambient shadow gradient
@ -59,36 +63,35 @@ float3 rocket(float2 pos, float2 rocketpos) {
// Particle direction for iteration i is the unit vector at angle i radians,
// precomputed as (cos(i), sin(i)) — equivalent to iteratively applying the
// original 1-radian rotation matrix but eliminates inter-iteration data dependency.
float3 drawParticles(float2 pos, float3 particolor, float time,
float2 cpos, float gravity, float seed, float timelength) {
float3
drawParticles(float2 pos, float3 particolor, float time, float2 cpos, float gravity, float seed, float timelength) {
float3 col = float3(0.0f);
[ForceUnroll]
for (int i = 0; i < NUM_PARTICLES; i++) {
float fi = float(i + 1); // 1-indexed to match original
float d = rand_val(fi, seed);
float fade = (fi / float(NUM_PARTICLES)) * time;
float2 pp = float2(cos(float(i)), sin(float(i))); // unit direction at angle i
float fi = float(i + 1); // 1-indexed to match original
float d = rand_val(fi, seed);
float fade = (fi / float(NUM_PARTICLES)) * time;
float2 pp = float2(cos(float(i)), sin(float(i))); // unit direction at angle i
float2 particpos = cpos + time * pp * d;
col = lerp(particolor / fade, col,
smoothstep(0.0f, 0.0001f, distance2(particpos, pos)));
col = lerp(particolor / fade, col, smoothstep(0.0f, 0.0001f, distance2(particpos, pos)));
}
col *= smoothstep(0.0f, 1.0f, (timelength - time) / timelength);
return col;
}
// Returns rocket or particles depending on which phase of the cycle we are in
float3 drawFireworks(float time, float2 uv, float3 particolor, float seed) {
float3
drawFireworks(float time, float2 uv, float3 particolor, float seed) {
float3 col = float3(0.0f);
if (time <= 0.0f) return col;
float tmod = fmod(time, CYCLE_TIME);
float tmod = fmod(time, CYCLE_TIME);
float cycle_id = ceil(time / CYCLE_TIME);
if (tmod > PARTICLE_PHASE) {
float2 cpos = float2(rand_val(cycle_id, seed), -0.5f);
col = drawParticles(uv, particolor, tmod - PARTICLE_PHASE,
cpos, 0.5f, cycle_id, seed);
col = drawParticles(uv, particolor, tmod - PARTICLE_PHASE, cpos, 0.5f, cycle_id, seed);
} else {
float rx = 3.0f * rand_val(cycle_id, seed);
float ry = 3.0f * (-0.5f + (PARTICLE_PHASE - tmod));
@ -97,30 +100,27 @@ float3 drawFireworks(float time, float2 uv, float3 particolor, float seed) {
return col;
}
public float4 fragment_main(
float4 color,
KittyTextures t,
KittyCustomShaderData d
) {
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
float2 pixelDims = float2(d.viewport_size_pixels) * t.viewport.zw;
float aspect = pixelDims.x / pixelDims.y;
float aspect = pixelDims.x / pixelDims.y;
// Map to [-aspect, aspect] × [-1, 1] with y=1 at top (matching the original)
float2 uv = 1.0f - 2.0f * t.pos;
uv.x *= aspect;
uv.y = -uv.y;
uv.x *= aspect;
uv.y = -uv.y;
float3 col = float3(0.1f, 0.1f, 0.2f);
col += 0.1f * uv.y; // subtle sky gradient
col += 0.1f * uv.y; // subtle sky gradient
float time = d.timestamp;
col += drawFireworks(time, uv, float3(1.0f, 0.1f, 0.1f), 1.0f);
col += drawFireworks(time, uv, float3(1.0f, 0.1f, 0.1f), 1.0f);
col += drawFireworks(time - 2.0f, uv, float3(0.0f, 1.0f, 0.5f), 2.0f);
col += drawFireworks(time - 4.0f, uv, float3(1.0f, 1.0f, 0.1f), 3.0f);
float4 terminalColor = t.backbuffer.Sample(t.pos);
float alpha = step(length(terminalColor.rgb), BLACK_BLEND_THRESHOLD);
float3 blended = lerp(terminalColor.rgb, col * 0.3f, alpha);
float alpha = step(length(terminalColor.rgb), BLACK_BLEND_THRESHOLD);
float3 blended = lerp(terminalColor.rgb, col * 0.3f, alpha);
return float4(blended, terminalColor.a);
}

View file

@ -8,87 +8,82 @@
import kitty_custom_shader_types;
static const int NUM_EXPLOSIONS = 3;
static const int NUM_PARTICLES = 42;
static const int NUM_EXPLOSIONS = 3;
static const int NUM_PARTICLES = 42;
static const float BLACK_BLEND_THRESHOLD = 0.4f;
static const float TIME_SCALE = 0.5f;
static const float PI = 3.14159265359f;
static const float TIME_SCALE = 0.5f;
static const float PI = 3.14159265359f;
// Noise constant from Dave Hoskins — not user-tunable so kept as plain static const
static const float3 MOD3 = float3(0.1031f, 0.11369f, 0.13787f);
float3 hash31(float p) {
float3
hash31(float p) {
float3 p3 = frac(float3(p) * MOD3);
p3 += dot(p3, p3.yzx + 19.19f);
return frac(float3((p3.x + p3.y) * p3.z,
(p3.x + p3.z) * p3.y,
(p3.y + p3.z) * p3.x));
return frac(float3((p3.x + p3.y) * p3.z, (p3.x + p3.z) * p3.y, (p3.y + p3.z) * p3.x));
}
float hash12(float2 p) {
float
hash12(float2 p) {
float3 p3 = frac(float3(p.xyx) * MOD3);
p3 += dot(p3, p3.yzx + 19.19f);
return frac((p3.x + p3.y) * p3.z);
}
float light(float2 uv, float2 pos, float size) {
uv -= pos;
float
light(float2 uv, float2 pos, float size) {
uv -= pos;
size *= size;
return size / dot(uv, uv);
}
float3 explosion(float2 uv, float2 p, float seed, float t) {
float3 col = float3(0.0f);
float3 en = hash31(seed);
float3
explosion(float2 uv, float2 p, float seed, float t) {
float3 col = float3(0.0f);
float3 en = hash31(seed);
float3 baseCol = en;
// startP, pt, and b are loop-invariant; computed once outside
float2 startP = p - float2(0.0f, t * t * 0.1f);
float pt = 1.0f - pow(t - 1.0f, 2.0f);
float pt = 1.0f - pow(t - 1.0f, 2.0f);
// B(en.x, en.y, en.z, t) macro expansion
float b = smoothstep(en.x - en.z, en.x + en.z, t)
* smoothstep(en.y + en.z, en.y - en.z, t);
float sizeBase = lerp(0.01f, 0.005f, smoothstep(0.0f, 0.1f, pt))
* smoothstep(1.0f, 0.1f, pt);
float b = smoothstep(en.x - en.z, en.x + en.z, t) * smoothstep(en.y + en.z, en.y - en.z, t);
float sizeBase = lerp(0.01f, 0.005f, smoothstep(0.0f, 0.1f, pt)) * smoothstep(1.0f, 0.1f, pt);
[ForceUnroll]
for (int i = 0; i < NUM_PARTICLES; i++) {
float3 n = hash31(float(i)) - 0.5f;
float3 n = hash31(float(i)) - 0.5f;
float2 endP = startP + normalize(n.xy) * n.z - float2(0.0f, t * 0.2f);
float2 pos = lerp(p, endP, pt);
float2 pos = lerp(p, endP, pt);
float sparkle = sin((pt + n.z) * 21.0f) * 0.5f + 0.5f;
sparkle = pow(sparkle, pow(en.x, 3.0f) * 50.0f)
* lerp(0.01f, 0.01f, en.y * n.y);
sparkle = pow(sparkle, pow(en.x, 3.0f) * 50.0f) * lerp(0.01f, 0.01f, en.y * n.y);
col += baseCol * light(uv, pos, sizeBase + sparkle * b);
}
return col;
}
float3 rainbow(float3 c, float time) {
float3
rainbow(float3 c, float time) {
float avg = (c.r + c.g + c.b) / 3.0f;
c = avg + (c - avg) * sin(float3(0.0f, 0.333f, 0.666f) + time);
c += sin(float3(0.4f, 0.3f, 0.3f) * time
+ float3(1.1244f, 3.43215f, 6.435f))
* float3(0.4f, 0.1f, 0.5f);
c = avg + (c - avg) * sin(float3(0.0f, 0.333f, 0.666f) + time);
c += sin(float3(0.4f, 0.3f, 0.3f) * time + float3(1.1244f, 3.43215f, 6.435f)) * float3(0.4f, 0.1f, 0.5f);
return c;
}
public float4 fragment_main(
float4 color,
KittyTextures t,
KittyCustomShaderData d
) {
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
float2 pixelDims = float2(d.viewport_size_pixels) * t.viewport.zw;
float aspect = pixelDims.x / pixelDims.y;
float time = fmod(d.timestamp, 3600.0f) * TIME_SCALE;
float aspect = pixelDims.x / pixelDims.y;
float time = fmod(d.timestamp, 3600.0f) * TIME_SCALE;
// Aspect-corrected UV with y=0 at top so gravity points downward
float2 uv = t.pos;
uv.x -= 0.5f;
uv.x *= aspect;
uv.y = -uv.y + 1.0f;
uv.y = -uv.y + 1.0f;
float3 c = float3(0.0f);
@ -96,7 +91,7 @@ public float4 fragment_main(
for (int i = 0; i < NUM_EXPLOSIONS; i++) {
float et = time + float(i) * 1234.45235f;
float id = floor(et);
et -= id;
et -= id;
float2 p = hash31(id).xy;
p.x -= 0.5f;
@ -106,8 +101,8 @@ public float4 fragment_main(
c = rainbow(c, d.timestamp);
float4 terminalColor = t.backbuffer.Sample(t.pos);
float alpha = step(length(terminalColor.rgb), BLACK_BLEND_THRESHOLD);
float3 blended = lerp(terminalColor.rgb, c * 0.3f, alpha);
float alpha = step(length(terminalColor.rgb), BLACK_BLEND_THRESHOLD);
float3 blended = lerp(terminalColor.rgb, c * 0.3f, alpha);
return float4(blended, terminalColor.a);
}

View file

@ -19,16 +19,13 @@ static const float BORDER_GLOW = 0.55;
// Dim factor applied to pixels outside the active window (1.0 = unchanged, 0.0 = black).
static const float INACTIVE_DIM = 0.3;
public float4 fragment_main(
float4 color,
KittyTextures t,
KittyCustomShaderData d
) {
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
float4 g = d.active_window_geometry;
// Pass through unchanged if no active window geometry is known.
if (g.z <= 0.0 || g.w <= 0.0) return color;
float2 uv = t.pos;
float2 uv = t.pos;
float2 winMin = g.xy;
float2 winMax = g.xy + g.zw;
@ -39,8 +36,8 @@ public float4 fragment_main(
if (inWindow) {
// Minimum distance from this pixel to any edge of the active window.
float2 edgeDists = min(uv - winMin, winMax - uv);
float nearestEdge = min(edgeDists.x, edgeDists.y);
float2 edgeDists = min(uv - winMin, winMax - uv);
float nearestEdge = min(edgeDists.x, edgeDists.y);
// Additive border glow: strongest at the edge, falls off over BORDER_WIDTH.
float borderGlow = smoothstep(BORDER_WIDTH, 0.0, nearestEdge) * BORDER_GLOW;

View file

@ -8,65 +8,68 @@
import kitty_custom_shader_types;
static const int ITERATIONS = 40;
static const float SPEED = 0.5f;
static const float STRIP_CHARS_MIN = 7.0f;
static const float STRIP_CHARS_MAX = 40.0f;
static const int ITERATIONS = 40;
static const float SPEED = 0.5f;
static const float STRIP_CHARS_MIN = 7.0f;
static const float STRIP_CHARS_MAX = 40.0f;
static const float STRIP_CHAR_HEIGHT = 0.15f;
static const float STRIP_CHAR_WIDTH = 0.10f;
static const float ZCELL_SIZE = STRIP_CHAR_HEIGHT * STRIP_CHARS_MAX;
static const float XYCELL_SIZE = 12.0f * STRIP_CHAR_WIDTH;
static const int BLOCK_SIZE = 10;
static const int BLOCK_GAP = 2;
static const float WALK_SPEED = 0.5f * XYCELL_SIZE;
static const float STRIP_CHAR_WIDTH = 0.10f;
static const float ZCELL_SIZE = STRIP_CHAR_HEIGHT * STRIP_CHARS_MAX;
static const float XYCELL_SIZE = 12.0f * STRIP_CHAR_WIDTH;
static const int BLOCK_SIZE = 10;
static const int BLOCK_GAP = 2;
static const float WALK_SPEED = 0.5f * XYCELL_SIZE;
static const float BLOCKS_BEFORE_TURN = 3.0f;
static const float PI = 3.14159265359f;
static const float PI = 3.14159265359f;
// ---- random ----
float hash(float v) {
float
hash(float v) {
return frac(sin(v) * 43758.5453123f);
}
float hash(float2 v) {
float
hash(float2 v) {
return hash(dot(v, float2(5.3983f, 5.4427f)));
}
float2 hash2(float2 v) {
float2
hash2(float2 v) {
// GLSL: v * mat2(127.1, 311.7, 269.5, 183.3) [column-major]
// Slang float2x2 is row-major; transpose the GLSL column-major layout.
v = mul(v, float2x2(127.1f, 269.5f,
311.7f, 183.3f));
v = mul(v, float2x2(127.1f, 269.5f, 311.7f, 183.3f));
return frac(sin(v) * 43758.5453123f);
}
float4 hash4(float2 v) {
float4
hash4(float2 v) {
// GLSL: v * mat4x2(127.1,311.7, 269.5,183.3, 113.5,271.9, 246.1,124.6)
// mat4x2 = 4 cols × 2 rows; transpose into float2x4 (2 rows × 4 cols).
float4 p = mul(v, float2x4(127.1f, 269.5f, 113.5f, 246.1f,
311.7f, 183.3f, 271.9f, 124.6f));
float4 p = mul(v, float2x4(127.1f, 269.5f, 113.5f, 246.1f, 311.7f, 183.3f, 271.9f, 124.6f));
return frac(sin(p) * 43758.5453123f);
}
float4 hash4(float3 v) {
float4
hash4(float3 v) {
// GLSL: v * mat4x3(127.1,311.7,74.7, 269.5,183.3,246.1, 113.5,271.9,124.6, 271.9,269.5,311.7)
// mat4x3 = 4 cols × 3 rows; transpose into float3x4 (3 rows × 4 cols).
float4 p = mul(v, float3x4(127.1f, 269.5f, 113.5f, 271.9f,
311.7f, 183.3f, 271.9f, 269.5f,
74.7f, 246.1f, 124.6f, 311.7f));
float4 p = mul(v, float3x4(127.1f, 269.5f, 113.5f, 271.9f, 311.7f, 183.3f, 271.9f, 269.5f, 74.7f, 246.1f, 124.6f, 311.7f));
return frac(sin(p) * 43758.5453123f);
}
// ---- symbols ----
float rune_line(float2 p, float2 a, float2 b) {
float
rune_line(float2 p, float2 a, float2 b) {
p -= a;
b -= a;
float h = clamp(dot(p, b) / dot(b, b), 0.0f, 1.0f);
return length(p - b * h);
}
float rune(float2 U, float2 seed, float highlight) {
float
rune(float2 U, float2 seed, float highlight) {
float d = 1e5f;
[ForceUnroll]
for (int i = 0; i < 4; i++) {
@ -75,29 +78,29 @@ float rune(float2 U, float2 seed, float highlight) {
// each rune touches the edge of its box on all 4 sides
// with [ForceUnroll] these become compile-time constant selects
pos.y = (i == 0) ? 0.0f : pos.y;
pos.y = (i == 0) ? 0.0f : pos.y;
pos.x = (i == 1) ? 0.999f : pos.x;
pos.x = (i == 2) ? 0.0f : pos.x;
pos.x = (i == 2) ? 0.0f : pos.x;
pos.y = (i == 3) ? 0.999f : pos.y;
float4 snaps = float4(2.0f, 3.0f, 2.0f, 3.0f);
pos = (floor(pos * snaps) + 0.5f) / snaps;
if (any(pos.xy != pos.zw))
d = min(d, rune_line(U, pos.xy, pos.zw + 0.001f));
if (any(pos.xy != pos.zw)) d = min(d, rune_line(U, pos.xy, pos.zw + 0.001f));
}
return smoothstep(0.1f, 0.0f, d) + highlight * smoothstep(0.4f, 0.0f, d);
}
float random_char(float2 outer, float2 inner, float highlight) {
float2 seed = float2(dot(outer, float2(269.5f, 183.3f)),
dot(outer, float2(113.5f, 271.9f)));
float
random_char(float2 outer, float2 inner, float highlight) {
float2 seed = float2(dot(outer, float2(269.5f, 183.3f)), dot(outer, float2(113.5f, 271.9f)));
return rune(inner, seed, highlight);
}
// ---- digital rain ----
float3 rain(float3 ro3, float3 rd3, float time) {
float3
rain(float3 ro3, float3 rd3, float time) {
float4 result = float4(0.0f, 0.0f, 0.0f, 0.0f);
float2 ro2 = ro3.xy;
@ -106,7 +109,7 @@ float3 rain(float3 ro3, float3 rd3, float time) {
bool prefer_dx = abs(rd2.x) > abs(rd2.y);
float t3_to_t2 = prefer_dx ? rd3.x / rd2.x : rd3.y / rd2.y;
int3 cell_side = int3(step(0.0f, rd3));
int3 cell_side = int3(step(0.0f, rd3));
int3 cell_shift = int3(sign(rd3));
float t2 = 0.0f;
@ -114,48 +117,44 @@ float3 rain(float3 ro3, float3 rd3, float time) {
[ForceUnroll]
for (int iter = 0; iter < ITERATIONS; iter++) {
int2 cell = next_cell;
float t2s = t2;
int2 cell = next_cell;
float t2s = t2;
float2 side = float2(next_cell + cell_side.xy) * XYCELL_SIZE;
float2 side = float2(next_cell + cell_side.xy) * XYCELL_SIZE;
float2 t2_side = (side - ro2) / rd2;
// branchless cell-side selection
bool pick_x = t2_side.x < t2_side.y;
t2 = pick_x ? t2_side.x : t2_side.y;
bool pick_x = t2_side.x < t2_side.y;
t2 = pick_x ? t2_side.x : t2_side.y;
next_cell.x += pick_x ? cell_shift.x : 0;
next_cell.y += pick_x ? 0 : cell_shift.y;
// skip gap cells
float2 cell_in_block = frac(float2(cell) / float(BLOCK_SIZE));
float gap = float(BLOCK_GAP) / float(BLOCK_SIZE);
if (cell_in_block.x < gap || cell_in_block.y < gap ||
(cell_in_block.x < (gap + 0.1f) && cell_in_block.y < (gap + 0.1f)))
continue;
float gap = float(BLOCK_GAP) / float(BLOCK_SIZE);
if (cell_in_block.x < gap || cell_in_block.y < gap || (cell_in_block.x < (gap + 0.1f) && cell_in_block.y < (gap + 0.1f))) continue;
float t3s = t2s / t3_to_t2;
float pos_z = ro3.z + rd3.z * t3s;
float xh = hash(float2(cell));
float t3s = t2s / t3_to_t2;
float pos_z = ro3.z + rd3.z * t3s;
float xh = hash(float2(cell));
float z_shift = xh * 11.0f - time * (0.5f + xh + xh * xh + pow(xh, 16.0f) * 3.0f);
float czs = floor(z_shift / STRIP_CHAR_HEIGHT);
z_shift = czs * STRIP_CHAR_HEIGHT;
int zcell = int(floor((pos_z - z_shift) / ZCELL_SIZE));
float czs = floor(z_shift / STRIP_CHAR_HEIGHT);
z_shift = czs * STRIP_CHAR_HEIGHT;
int zcell = int(floor((pos_z - z_shift) / ZCELL_SIZE));
[ForceUnroll]
for (int j = 0; j < 2; j++) {
float4 ch = hash4(float3(int3(cell, zcell)));
float4 ch = hash4(float3(int3(cell, zcell)));
float4 ch2 = frac(ch * float4(127.1f, 311.7f, 271.9f, 124.6f));
float chars_count = ch.w * (STRIP_CHARS_MAX - STRIP_CHARS_MIN) + STRIP_CHARS_MIN;
float chars_count = ch.w * (STRIP_CHARS_MAX - STRIP_CHARS_MIN) + STRIP_CHARS_MIN;
float target_length = chars_count * STRIP_CHAR_HEIGHT;
float target_rad = STRIP_CHAR_WIDTH * 0.5f;
float target_z = float(zcell) * ZCELL_SIZE + z_shift
+ ch.z * (ZCELL_SIZE - target_length);
float2 target = float2(cell) * XYCELL_SIZE + target_rad
+ ch.xy * (XYCELL_SIZE - target_rad * 2.0f);
float target_rad = STRIP_CHAR_WIDTH * 0.5f;
float target_z = float(zcell) * ZCELL_SIZE + z_shift + ch.z * (ZCELL_SIZE - target_length);
float2 target = float2(cell) * XYCELL_SIZE + target_rad + ch.xy * (XYCELL_SIZE - target_rad * 2.0f);
float2 sv = target - ro2;
float tmin = dot(sv, rd2);
float2 sv = target - ro2;
float tmin = dot(sv, rd2);
if (tmin >= t2s && tmin <= t2) {
float u = sv.x * rd2.y - sv.y * rd2.x;
if (abs(u) < target_rad) {
@ -163,28 +162,20 @@ float3 rain(float3 ro3, float3 rd3, float time) {
float z = ro3.z + rd3.z * tmin / t3_to_t2;
float v = (z - target_z) / target_length;
if (v >= 0.0f && v < 1.0f) {
float c = floor(v * chars_count);
float q = frac(v * chars_count);
float c = floor(v * chars_count);
float q = frac(v * chars_count);
float2 char_hash = hash2(float2(c + czs, ch2.x));
if (char_hash.x >= 0.1f || c == 0.0f) {
float time_factor = floor(
c == 0.0f ? time
: time * (ch2.z + ch2.w * ch2.w * 4.0f
* pow(char_hash.y, 4.0f)));
float a = random_char(float2(char_hash.x, time_factor),
float2(u, q),
max(1.0f, 3.0f - c * 0.5f) * 0.2f);
float time_factor = floor(c == 0.0f ? time : time * (ch2.z + ch2.w * ch2.w * 4.0f * pow(char_hash.y, 4.0f)));
float a = random_char(float2(char_hash.x, time_factor), float2(u, q), max(1.0f, 3.0f - c * 0.5f) * 0.2f);
a *= clamp((chars_count - 0.5f - c) * 0.5f, 0.0f, 1.0f);
if (a > 0.0f) {
float attenuation = 1.0f + pow(0.06f * tmin / t3_to_t2, 2.0f);
float3 col = (c == 0.0f
? float3(0.67f, 1.0f, 0.82f)
: float3(0.25f, 0.80f, 0.40f)) / attenuation;
float a1 = result.a;
result.a = a1 + (1.0f - a1) * a;
float3 col = (c == 0.0f ? float3(0.67f, 1.0f, 0.82f) : float3(0.25f, 0.80f, 0.40f)) / attenuation;
float a1 = result.a;
result.a = a1 + (1.0f - a1) * a;
result.xyz = (result.xyz * a1 + col * (1.0f - a1) * a) / result.a;
if (result.a > 0.98f)
return result.xyz;
if (result.a > 0.98f) return result.xyz;
}
}
}
@ -200,54 +191,52 @@ float3 rain(float3 ro3, float3 rd3, float time) {
// For M*v in GLSL with column-major constructor, Slang needs the same values
// re-read as row-major (i.e. transposed GLSL column-major == correct Slang row-major).
float2 rotate2D(float2 v, float angle) {
float2
rotate2D(float2 v, float angle) {
float s = sin(angle), c = cos(angle);
// GLSL: mat2(c,-s, s,c)*v → rows: (c,s),(-s,c)
return mul(float2x2(c, s, -s, c), v);
}
float3 rotateX(float3 v, float angle) {
float3
rotateX(float3 v, float angle) {
float s = sin(angle), c = cos(angle);
// GLSL: mat3(1,0,0, 0,c,-s, 0,s,c)*v → rows: (1,0,0),(0,c,s),(0,-s,c)
return mul(float3x3(1.0f, 0.0f, 0.0f,
0.0f, c, s,
0.0f, -s, c), v);
return mul(float3x3(1.0f, 0.0f, 0.0f, 0.0f, c, s, 0.0f, -s, c), v);
}
float3 rotateY(float3 v, float angle) {
float3
rotateY(float3 v, float angle) {
float s = sin(angle), c = cos(angle);
// GLSL: mat3(c,0,-s, 0,1,0, s,0,c)*v → rows: (c,0,s),(0,1,0),(-s,0,c)
return mul(float3x3( c, 0.0f, s,
0.0f, 1.0f, 0.0f,
-s, 0.0f, c), v);
return mul(float3x3(c, 0.0f, s, 0.0f, 1.0f, 0.0f, -s, 0.0f, c), v);
}
float3 rotateZ(float3 v, float angle) {
float3
rotateZ(float3 v, float angle) {
float s = sin(angle), c = cos(angle);
// GLSL: mat3(c,-s,0, s,c,0, 0,0,1)*v → rows: (c,s,0),(-s,c,0),(0,0,1)
return mul(float3x3( c, s, 0.0f,
-s, c, 0.0f,
0.0f, 0.0f, 1.0f), v);
return mul(float3x3(c, s, 0.0f, -s, c, 0.0f, 0.0f, 0.0f, 1.0f), v);
}
float smoothstep1(float x) { return smoothstep(0.0f, 1.0f, x); }
float
smoothstep1(float x) {
return smoothstep(0.0f, 1.0f, x);
}
// ---- entry point ----
public float4 fragment_main(
float4 color,
KittyTextures t,
KittyCustomShaderData d
) {
float2 uv = t.pos;
float time = fmod(d.timestamp, 300.0f) * SPEED;
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
float2 uv = t.pos;
float time = fmod(d.timestamp, 300.0f) * SPEED;
const float turn_rad = 0.25f / BLOCKS_BEFORE_TURN;
const float turn_rad = 0.25f / BLOCKS_BEFORE_TURN;
const float turn_abs_time = (PI * 0.5f * turn_rad) * 1.5f;
const float turn_time = turn_abs_time / (1.0f - 2.0f * turn_rad + turn_abs_time);
const float turn_time = turn_abs_time / (1.0f - 2.0f * turn_rad + turn_abs_time);
float level1_size = float(BLOCK_SIZE) * BLOCKS_BEFORE_TURN * XYCELL_SIZE;
float gap_size = float(BLOCK_GAP) * XYCELL_SIZE;
float gap_size = float(BLOCK_GAP) * XYCELL_SIZE;
float3 ro = float3(gap_size * 0.5f, gap_size * 0.5f, 0.0f);
float3 rd = float3(uv.x, 2.0f, uv.y);
@ -257,65 +246,90 @@ public float4 fragment_main(
float t1 = frac(t8 * 8.0f);
float2 prev, dir;
if (tq < 0.25f) { prev = float2(0.0f, 0.0f); dir = float2(0.0f, 1.0f); }
else if (tq < 0.50f) { prev = float2(0.0f, 1.0f); dir = float2(1.0f, 0.0f); }
else if (tq < 0.75f) { prev = float2(1.0f, 1.0f); dir = float2(0.0f, -1.0f); }
else { prev = float2(1.0f, 0.0f); dir = float2(-1.0f, 0.0f); }
if (tq < 0.25f) {
prev = float2(0.0f, 0.0f);
dir = float2(0.0f, 1.0f);
} else if (tq < 0.50f) {
prev = float2(0.0f, 1.0f);
dir = float2(1.0f, 0.0f);
} else if (tq < 0.75f) {
prev = float2(1.0f, 1.0f);
dir = float2(0.0f, -1.0f);
} else {
prev = float2(1.0f, 0.0f);
dir = float2(-1.0f, 0.0f);
}
float angle = floor(tq * 4.0f);
prev *= 4.0f;
const float first_turn_look_angle = 0.4f;
const float first_turn_look_angle = 0.4f;
const float second_turn_drift_angle = 0.5f;
const float fifth_turn_drift_angle = 0.25f;
const float fifth_turn_drift_angle = 0.25f;
float2 dirL = rotate2D(dir, -PI * 0.5f);
float2 dirR = -dirL;
float2 turn;
float turn_sign = 0.0f;
float up_down = 0.0f;
float rotate_on_turns = 1.0f;
float roll_on_turns = 1.0f;
float add_angel = 0.0f;
float turn_sign = 0.0f;
float up_down = 0.0f;
float rotate_on_turns = 1.0f;
float roll_on_turns = 1.0f;
float add_angel = 0.0f;
if (t8 < 0.125f) {
turn = dirL; turn_sign = -1.0f;
angle -= first_turn_look_angle * (
max(0.0f, t1 - (1.0f - turn_time * 2.0f)) / turn_time
- max(0.0f, t1 - (1.0f - turn_time)) / turn_time * 2.5f);
turn = dirL;
turn_sign = -1.0f;
angle -= first_turn_look_angle * (max(0.0f, t1 - (1.0f - turn_time * 2.0f)) / turn_time - max(0.0f, t1 - (1.0f - turn_time)) / turn_time * 2.5f);
roll_on_turns = 0.0f;
} else if (t8 < 0.250f) {
prev += dir; turn = dir; dir = dirL;
angle -= 1.0f; turn_sign = 1.0f;
add_angel += first_turn_look_angle * 0.5f
+ (-first_turn_look_angle * 0.5f + 1.0f + second_turn_drift_angle) * t1;
rotate_on_turns = 0.0f; roll_on_turns = 0.0f;
prev += dir;
turn = dir;
dir = dirL;
angle -= 1.0f;
turn_sign = 1.0f;
add_angel += first_turn_look_angle * 0.5f + (-first_turn_look_angle * 0.5f + 1.0f + second_turn_drift_angle) * t1;
rotate_on_turns = 0.0f;
roll_on_turns = 0.0f;
} else if (t8 < 0.375f) {
prev += dir + dirL; turn = dirR; turn_sign = 1.0f;
prev += dir + dirL;
turn = dirR;
turn_sign = 1.0f;
add_angel += second_turn_drift_angle * sqrt(1.0f - t1);
} else if (t8 < 0.500f) {
prev += dir + dir + dirL; turn = dirR; dir = dirR;
angle += 1.0f; turn_sign = 0.0f;
prev += dir + dir + dirL;
turn = dirR;
dir = dirR;
angle += 1.0f;
turn_sign = 0.0f;
up_down = sin(t1 * PI) * 0.37f;
} else if (t8 < 0.625f) {
prev += dir + dir; turn = dir; dir = dirR;
angle += 1.0f; turn_sign = -1.0f;
prev += dir + dir;
turn = dir;
dir = dirR;
angle += 1.0f;
turn_sign = -1.0f;
up_down = sin(-min(1.0f, t1 / (1.0f - turn_time)) * PI) * 0.37f;
} else if (t8 < 0.750f) {
prev += dir + dir + dirR; turn = dirL; turn_sign = -1.0f;
prev += dir + dir + dirR;
turn = dirL;
turn_sign = -1.0f;
add_angel -= (fifth_turn_drift_angle + 1.0f) * smoothstep1(t1);
rotate_on_turns = 0.0f; roll_on_turns = 0.0f;
rotate_on_turns = 0.0f;
roll_on_turns = 0.0f;
} else if (t8 < 0.875f) {
prev += dir + dir + dir + dirR; turn = dir; dir = dirL;
angle -= 1.0f; turn_sign = 1.0f;
add_angel -= fifth_turn_drift_angle
- smoothstep1(t1) * (fifth_turn_drift_angle * 2.0f + 1.0f);
rotate_on_turns = 0.0f; roll_on_turns = 0.0f;
prev += dir + dir + dir + dirR;
turn = dir;
dir = dirL;
angle -= 1.0f;
turn_sign = 1.0f;
add_angel -= fifth_turn_drift_angle - smoothstep1(t1) * (fifth_turn_drift_angle * 2.0f + 1.0f);
rotate_on_turns = 0.0f;
roll_on_turns = 0.0f;
} else {
prev += dir + dir + dir; turn = dirR; turn_sign = 1.0f;
angle += fifth_turn_drift_angle * (
1.5f * min(1.0f, (1.0f - t1) / turn_time)
- 0.5f * smoothstep1(1.0f - min(1.0f, t1 / (1.0f - turn_time))));
prev += dir + dir + dir;
turn = dirR;
turn_sign = 1.0f;
angle += fifth_turn_drift_angle * (1.5f * min(1.0f, (1.0f - t1) / turn_time) - 0.5f * smoothstep1(1.0f - min(1.0f, t1 / (1.0f - turn_time))));
}
if (d.mouse_button_pressed.x != 0) {
@ -334,9 +348,9 @@ public float4 fragment_main(
if (turn_sign == 0.0f) {
p = prev + dir * (turn_rad + t1);
} else if (t1 > (1.0f - turn_time)) {
float tr = (t1 - (1.0f - turn_time)) / turn_time;
float tr = (t1 - (1.0f - turn_time)) / turn_time;
float2 ctr = prev + dir * (1.0f - turn_rad) + turn * turn_rad;
p = ctr + turn_rad * rotate2D(dir, (tr - 1.0f) * turn_sign * PI * 0.5f);
p = ctr + turn_rad * rotate2D(dir, (tr - 1.0f) * turn_sign * PI * 0.5f);
angle += tr * turn_sign * rotate_on_turns;
rd = rotateY(rd, sin(tr * turn_sign * PI) * 0.2f * roll_on_turns);
} else {
@ -352,8 +366,8 @@ public float4 fragment_main(
float3 col = rain(ro, rd, time) * 0.25f;
float4 terminalColor = t.backbuffer.Sample(t.pos);
float mask = 1.2f - step(0.5f, dot(terminalColor.rgb, float3(1.0f, 1.0f, 1.0f)));
float3 blended = lerp(terminalColor.rgb * 1.2f, col, mask);
float mask = 1.2f - step(0.5f, dot(terminalColor.rgb, float3(1.0f, 1.0f, 1.0f)));
float3 blended = lerp(terminalColor.rgb * 1.2f, col, mask);
return float4(blended, terminalColor.a);
}

View file

@ -6,10 +6,7 @@
import kitty_custom_shader_types;
public float4 fragment_main(
float4 color,
KittyTextures t,
KittyCustomShaderData d
) {
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
return float4(1.0 - color.r, 1.0 - color.g, 1.0 - color.b, color.a);
}

View file

@ -39,12 +39,11 @@ static const float VERTICAL_FADE = 25.0f;
static const float CAMERA_DISTANCE = 9.7f;
// === PERSISTENCE & DECAY CONTROLS ===
static const float ACCUMULATION_DECAY = 0.03f; // Decay per frame: 0.0 = infinite, 0.005 = slow fade
static const float ACCUMULATION_DECAY = 0.03f; // Decay per frame: 0.0 = infinite, 0.005 = slow fade
static const float MOUSE_PERSIST_INTENSITY = 1.1f; // Brightness multiplier for persistent rays
// === STAR FIELD CONTROLS ===
static const float STAR_FIELD_ROTATIONS_SPEED =
(2 * PI) / (86400 * TIME_SCALE);
static const float STAR_FIELD_ROTATIONS_SPEED = (2 * PI) / (86400 * TIME_SCALE);
static const float STAR_BRIGHTNESS = 0.7f;
static const float STAR_SIZE = 1.6f;
static const float STAR_TWINKLE_SPEED = 0.05f;
@ -77,9 +76,9 @@ static const float DIP_LEVEL = 0.30f;
// === MOUSE TRAIL & RADIAL RAY SHAPE CONTROLS ===
static const float MOUSE_AURORA_INTENSITY = 0.15f;
static const float MOUSE_TRAIL_WIDTH = 0.06f;
static const float MOUSE_RAY_WIDTH = 0.015f; // Tangential ray width
static const float MOUSE_RAY_LENGTH = 0.11f; // Radial ray length along center alignment
static const float MOUSE_RAY_FREQUENCY = 160.0f; // Fine curtain thread streaking frequency
static const float MOUSE_RAY_WIDTH = 0.015f; // Tangential ray width
static const float MOUSE_RAY_LENGTH = 0.11f; // Radial ray length along center alignment
static const float MOUSE_RAY_FREQUENCY = 160.0f; // Fine curtain thread streaking frequency
static const float MOUSE_TRAIL_NOISE_SCALE = 3.0f;
static const float MOUSE_PRESS_BOOST = 0.3f;
static const float MOUSE_IGNORE_WHEN_HIDDEN = 0.0f;
@ -88,8 +87,7 @@ static const float DEBUG_MOUSE_OVERLAY = 0.0f;
static const float3 SKY_COLOR_TOP = float3(0.05f, 0.1f, 0.2f);
static const float3 SKY_COLOR_BOTTOM = float3(0.1f, 0.05f, 0.2f);
static const float2x2 TRI_ROT_MATRIX =
float2x2(0.95534f, 0.29552f, -0.29552f, 0.95534f);
static const float2x2 TRI_ROT_MATRIX = float2x2(0.95534f, 0.29552f, -0.29552f, 0.95534f);
static const float LATITUDE_DEGREES = 61.2f;
static const float LAT_RAD = LATITUDE_DEGREES * (3.14159265359f / 180.0f);
@ -97,471 +95,479 @@ static const float3 CELESTIAL_POLE = float3(0.0f, sin(LAT_RAD), cos(LAT_RAD));
static const float PERSPECTIVE_TILT = 0.12f;
static const int STAR_LAYERS = 4;
static const float STAR_LAYER_DENSITY[STAR_LAYERS] = {0.0005f, 0.0015f, 0.0045f, 0.0125f};
static const float STAR_LAYER_DENSITY[STAR_LAYERS] = { 0.0005f, 0.0015f, 0.0045f, 0.0125f };
static const float TWINKLE_FREQ_A = 1.618034f;
static const float TWINKLE_FREQ_B = 2.414214f;
static const int STAGE = 0; // internal use only
static const int STAGE = 0; // internal use only
// === TRIANGLE WAVE NOISE ===
float triWave(float x) { return clamp(abs(frac(x) - 0.5f), 0.01f, 0.49f); }
float2 triWave2(float2 p) {
return float2(triWave(p.x) + triWave(p.y), triWave(p.y + triWave(p.x)));
float
triWave(float x) {
return clamp(abs(frac(x) - 0.5f), 0.01f, 0.49f);
}
float triNoise2d(float2 p, float spd, float timestamp, float sharpness) {
float z = 1.8f;
float z2 = 2.5f;
float rz = 0.0f;
float2
triWave2(float2 p) {
return float2(triWave(p.x) + triWave(p.y), triWave(p.y + triWave(p.x)));
}
float c = cos(p.x * 0.06f);
float s = sin(p.x * 0.06f);
p = mul(float2x2(c, s, -s, c), p);
float
triNoise2d(float2 p, float spd, float timestamp, float sharpness) {
float z = 1.8f;
float z2 = 2.5f;
float rz = 0.0f;
float2 bp = p;
float c = cos(p.x * 0.06f);
float s = sin(p.x * 0.06f);
p = mul(float2x2(c, s, -s, c), p);
[ForceUnroll] for (int i = 0; i < 5; i++) {
float2 dg = triWave2(bp * 1.85f) * 0.75f;
float tc = cos(timestamp * spd);
float ts = sin(timestamp * spd);
dg = mul(float2x2(tc, ts, -ts, tc), dg);
p -= dg / z2;
float2 bp = p;
bp *= 1.3f;
z2 *= 0.45f;
z *= 0.42f;
p *= 1.21f + (rz - 1.0f) * 0.02f;
[ForceUnroll]
for (int i = 0; i < 5; i++) {
float2 dg = triWave2(bp * 1.85f) * 0.75f;
float tc = cos(timestamp * spd);
float ts = sin(timestamp * spd);
dg = mul(float2x2(tc, ts, -ts, tc), dg);
p -= dg / z2;
rz += triWave(p.x + triWave(p.y)) * z;
p = mul(TRI_ROT_MATRIX, p) * -1.0f;
}
bp *= 1.3f;
z2 *= 0.45f;
z *= 0.42f;
p *= 1.21f + (rz - 1.0f) * 0.02f;
return clamp(1.0f / pow(rz * sharpness, CURTAIN_SOFTNESS), 0.0f, 0.55f);
rz += triWave(p.x + triWave(p.y)) * z;
p = mul(TRI_ROT_MATRIX, p) * -1.0f;
}
return clamp(1.0f / pow(rz * sharpness, CURTAIN_SOFTNESS), 0.0f, 0.55f);
}
// === HASH FUNCTIONS ===
float hash21(float2 n) {
return frac(sin(dot(n, float2(12.9898f, 4.1414f))) * 43758.5453f);
float
hash21(float2 n) {
return frac(sin(dot(n, float2(12.9898f, 4.1414f))) * 43758.5453f);
}
uint pcgHash(uint v) {
v = v * 747796405u + 2891336453u;
uint w = ((v >> ((v >> 28u) + 4u)) ^ v) * 277803737u;
return (w >> 22u) ^ w;
uint
pcgHash(uint v) {
v = v * 747796405u + 2891336453u;
uint w = ((v >> ((v >> 28u) + 4u)) ^ v) * 277803737u;
return (w >> 22u) ^ w;
}
float epochRand(uint epoch, uint channel) {
uint seed = epoch * 0x9E3779B9u ^ (channel * 0x85EBCA6Bu + 0x27D4EB2Fu);
return float(pcgHash(seed)) * (1.0f / 4294967295.0f);
float
epochRand(uint epoch, uint channel) {
uint seed = epoch * 0x9E3779B9u ^ (channel * 0x85EBCA6Bu + 0x27D4EB2Fu);
return float(pcgHash(seed)) * (1.0f / 4294967295.0f);
}
float3 pcgHash33(float3 q) {
const uint STREAM_X = 0x9E3779B9u;
const uint STREAM_Y = 0x85EBCA6Bu;
const uint STREAM_Z = 0xC2B2AE35u;
float3
pcgHash33(float3 q) {
const uint STREAM_X = 0x9E3779B9u;
const uint STREAM_Y = 0x85EBCA6Bu;
const uint STREAM_Z = 0xC2B2AE35u;
uint3 p = uint3(int3(floor(q)));
uint3 p = uint3(int3(floor(q)));
const uint MULT = 0x5851F42Du;
const uint MULT = 0x5851F42Du;
uint h = p.x * MULT + p.y * 0x27D4EB2Fu + p.z * 0x165667B1u + STREAM_X;
h = ((h >> ((h >> 28u) + 4u)) ^ h) * 277803737u;
uint outX = (h >> 22u) ^ h;
uint h = p.x * MULT + p.y * 0x27D4EB2Fu + p.z * 0x165667B1u + STREAM_X;
h = ((h >> ((h >> 28u) + 4u)) ^ h) * 277803737u;
uint outX = (h >> 22u) ^ h;
h = p.y * MULT + p.z * 0x27D4EB2Fu + p.x * 0x165667B1u + STREAM_Y;
h = ((h >> ((h >> 28u) + 4u)) ^ h) * 277803737u;
uint outY = (h >> 22u) ^ h;
h = p.y * MULT + p.z * 0x27D4EB2Fu + p.x * 0x165667B1u + STREAM_Y;
h = ((h >> ((h >> 28u) + 4u)) ^ h) * 277803737u;
uint outY = (h >> 22u) ^ h;
h = p.z * MULT + p.x * 0x27D4EB2Fu + p.y * 0x165667B1u + STREAM_Z;
h = ((h >> ((h >> 28u) + 4u)) ^ h) * 277803737u;
uint outZ = (h >> 22u) ^ h;
h = p.z * MULT + p.x * 0x27D4EB2Fu + p.y * 0x165667B1u + STREAM_Z;
h = ((h >> ((h >> 28u) + 4u)) ^ h) * 277803737u;
uint outZ = (h >> 22u) ^ h;
return float3(outX, outY, outZ) * (1.0f / 4294967295.0f);
return float3(outX, outY, outZ) * (1.0f / 4294967295.0f);
}
// === PER-EPOCH RANDOM PARAMETERS ===
struct AuroraRandParams {
float timeOffset;
float2 startOffset;
float waveSpeedMul;
float scaleMul;
float sharpMul;
float colorJitter;
float intensityMul;
float timeOffset;
float2 startOffset;
float waveSpeedMul;
float scaleMul;
float sharpMul;
float colorJitter;
float intensityMul;
};
AuroraRandParams identityParams() {
AuroraRandParams rp;
rp.timeOffset = 0.0f;
rp.startOffset = float2(0.0f, 0.0f);
rp.waveSpeedMul = 1.0f;
rp.scaleMul = 1.0f;
rp.sharpMul = 1.0f;
rp.colorJitter = 0.0f;
rp.intensityMul = 1.0f;
return rp;
AuroraRandParams
identityParams() {
AuroraRandParams rp;
rp.timeOffset = 0.0f;
rp.startOffset = float2(0.0f, 0.0f);
rp.waveSpeedMul = 1.0f;
rp.scaleMul = 1.0f;
rp.sharpMul = 1.0f;
rp.colorJitter = 0.0f;
rp.intensityMul = 1.0f;
return rp;
}
AuroraRandParams epochParams(uint epoch) {
AuroraRandParams rp;
rp.timeOffset = epochRand(epoch, 1u) * 4096.0f;
rp.startOffset = float2(0.0f, 0.0f);
rp.waveSpeedMul = lerp(0.75f, 1.25f, epochRand(epoch, 6u));
rp.scaleMul = lerp(0.85f, 1.15f, epochRand(epoch, 7u));
rp.sharpMul = lerp(0.80f, 1.20f, epochRand(epoch, 8u));
rp.colorJitter = lerp(-0.6f, 0.6f, epochRand(epoch, 9u));
rp.intensityMul = lerp(0.85f, 1.15f, epochRand(epoch, 10u));
return rp;
AuroraRandParams
epochParams(uint epoch) {
AuroraRandParams rp;
rp.timeOffset = epochRand(epoch, 1u) * 4096.0f;
rp.startOffset = float2(0.0f, 0.0f);
rp.waveSpeedMul = lerp(0.75f, 1.25f, epochRand(epoch, 6u));
rp.scaleMul = lerp(0.85f, 1.15f, epochRand(epoch, 7u));
rp.sharpMul = lerp(0.80f, 1.20f, epochRand(epoch, 8u));
rp.colorJitter = lerp(-0.6f, 0.6f, epochRand(epoch, 9u));
rp.intensityMul = lerp(0.85f, 1.15f, epochRand(epoch, 10u));
return rp;
}
AuroraRandParams lerpParams(AuroraRandParams a, AuroraRandParams b, float t) {
AuroraRandParams rp;
rp.timeOffset = lerp(a.timeOffset, b.timeOffset, t);
rp.startOffset = lerp(a.startOffset, b.startOffset, t);
rp.waveSpeedMul = lerp(a.waveSpeedMul, b.waveSpeedMul, t);
rp.scaleMul = lerp(a.scaleMul, b.scaleMul, t);
rp.sharpMul = lerp(a.sharpMul, b.sharpMul, t);
rp.colorJitter = lerp(a.colorJitter, b.colorJitter, t);
rp.intensityMul = lerp(a.intensityMul, b.intensityMul, t);
return rp;
AuroraRandParams
lerpParams(AuroraRandParams a, AuroraRandParams b, float t) {
AuroraRandParams rp;
rp.timeOffset = lerp(a.timeOffset, b.timeOffset, t);
rp.startOffset = lerp(a.startOffset, b.startOffset, t);
rp.waveSpeedMul = lerp(a.waveSpeedMul, b.waveSpeedMul, t);
rp.scaleMul = lerp(a.scaleMul, b.scaleMul, t);
rp.sharpMul = lerp(a.sharpMul, b.sharpMul, t);
rp.colorJitter = lerp(a.colorJitter, b.colorJitter, t);
rp.intensityMul = lerp(a.intensityMul, b.intensityMul, t);
return rp;
}
AuroraRandParams getAuroraParams(float rawTimestamp, float randomFloat) {
if (RANDOMIZE <= 0.001f)
return identityParams();
uint launchOffset = uint(randomFloat * 100000.0f);
uint epoch = uint(floor(rawTimestamp / RESEED_INTERVAL)) + launchOffset;
return lerpParams(identityParams(), epochParams(epoch), RANDOMIZE);
AuroraRandParams
getAuroraParams(float rawTimestamp, float randomFloat) {
if (RANDOMIZE <= 0.001f) return identityParams();
uint launchOffset = uint(randomFloat * 100000.0f);
uint epoch = uint(floor(rawTimestamp / RESEED_INTERVAL)) + launchOffset;
return lerpParams(identityParams(), epochParams(epoch), RANDOMIZE);
}
float transitionBrightness(float rawTimestamp) {
if (RANDOMIZE <= 0.001f)
return 1.0f;
float ph = frac(rawTimestamp / RESEED_INTERVAL);
float dEpoch = min(ph, 1.0f - ph) * RESEED_INTERVAL;
float halfDip = TRANSITION_DIP * 0.5f;
if (dEpoch >= halfDip)
return 1.0f;
float u = dEpoch / max(halfDip, 1e-4f);
return lerp(DIP_LEVEL, 1.0f, smoothstep(0.0f, 1.0f, u));
float
transitionBrightness(float rawTimestamp) {
if (RANDOMIZE <= 0.001f) return 1.0f;
float ph = frac(rawTimestamp / RESEED_INTERVAL);
float dEpoch = min(ph, 1.0f - ph) * RESEED_INTERVAL;
float halfDip = TRANSITION_DIP * 0.5f;
if (dEpoch >= halfDip) return 1.0f;
float u = dEpoch / max(halfDip, 1e-4f);
return lerp(DIP_LEVEL, 1.0f, smoothstep(0.0f, 1.0f, u));
}
// === PROCEDURAL STARS ===
float magnitudeToBrightness(float hashVal) {
float mag = hashVal * STAR_BRIGHTNESS_VARIATION;
return pow(10.0f, -0.4f * mag);
float
magnitudeToBrightness(float hashVal) {
float mag = hashVal * STAR_BRIGHTNESS_VARIATION;
return pow(10.0f, -0.4f * mag);
}
float evalTwinkleWave(float3 pos, float seed, float horizonFactor) {
float variance = 1.0f - STAR_TWINKLE_CORRELATION;
float
evalTwinkleWave(float3 pos, float seed, float horizonFactor) {
float variance = 1.0f - STAR_TWINKLE_CORRELATION;
float seedPhaseA = seed * 6.2831853f * variance;
float seedPhaseB = seed * 3.14159265f * variance;
float seedPhaseC = seed * 4.71238898f * variance;
float freqMod = 1.0f + (frac(seed * 17.13f) - 0.5f) * 0.6f * variance;
float seedPhaseA = seed * 6.2831853f * variance;
float seedPhaseB = seed * 3.14159265f * variance;
float seedPhaseC = seed * 4.71238898f * variance;
float freqMod = 1.0f + (frac(seed * 17.13f) - 0.5f) * 0.6f * variance;
float spd = STAR_TWINKLE_SPEED * freqMod * horizonFactor;
float spd = STAR_TWINKLE_SPEED * freqMod * horizonFactor;
float waveA = sin(dot(pos, TWINKLE_WAVE_DIR_A) * spd + seedPhaseA);
float waveB = cos(dot(pos, TWINKLE_WAVE_DIR_B) * spd * STAR_TWINKLE_WAVE_B_MULT +
seedPhaseB + waveA * 0.7f);
float waveC = sin(dot(pos, TWINKLE_WAVE_DIR_C) * spd * STAR_TWINKLE_WAVE_C_MULT +
seedPhaseC);
float microFlash = pow(clamp(waveC * 0.5f + 0.5f, 0.0f, 1.0f), 3.0f);
float waveA = sin(dot(pos, TWINKLE_WAVE_DIR_A) * spd + seedPhaseA);
float waveB = cos(dot(pos, TWINKLE_WAVE_DIR_B) * spd * STAR_TWINKLE_WAVE_B_MULT + seedPhaseB + waveA * 0.7f);
float waveC = sin(dot(pos, TWINKLE_WAVE_DIR_C) * spd * STAR_TWINKLE_WAVE_C_MULT + seedPhaseC);
float microFlash = pow(clamp(waveC * 0.5f + 0.5f, 0.0f, 1.0f), 3.0f);
float combined = waveA * 0.45f + waveB * 0.35f + (microFlash - 0.2f) * 0.4f;
float combined = waveA * 0.45f + waveB * 0.35f + (microFlash - 0.2f) * 0.4f;
return max(0.0f, STAR_TWINKLE_BASE +
STAR_TWINKLE_AMPLITUDE * combined * horizonFactor);
return max(0.0f, STAR_TWINKLE_BASE + STAR_TWINKLE_AMPLITUDE * combined * horizonFactor);
}
float3 continuousChromaticTwinkle(float3 rd, float time, float seed) {
float3 turbulencePos = rd * STAR_TURBULENCE_SCALE;
float3 windDrift = time * STAR_WIND_DRIFT_SPEED;
float3 p = turbulencePos + windDrift;
float3
continuousChromaticTwinkle(float3 rd, float time, float seed) {
float3 turbulencePos = rd * STAR_TURBULENCE_SCALE;
float3 windDrift = time * STAR_WIND_DRIFT_SPEED;
float3 p = turbulencePos + windDrift;
float horizonFactor = 1.0f + 0.8f * saturate(STAR_DISPERSION_HORIZON_FACTOR - rd.y);
float dispersionScale = STAR_DISPERSION_SCALE * saturate(STAR_DISPERSION_HORIZON_FACTOR - rd.y);
float horizonFactor = 1.0f + 0.8f * saturate(STAR_DISPERSION_HORIZON_FACTOR - rd.y);
float dispersionScale = STAR_DISPERSION_SCALE * saturate(STAR_DISPERSION_HORIZON_FACTOR - rd.y);
float3 pR = p - STAR_DISPERSION_DIR * dispersionScale;
float3 pG = p;
float3 pB = p + STAR_DISPERSION_DIR * dispersionScale;
float3 pR = p - STAR_DISPERSION_DIR * dispersionScale;
float3 pG = p;
float3 pB = p + STAR_DISPERSION_DIR * dispersionScale;
float twR = evalTwinkleWave(pR, seed, horizonFactor);
float twG = evalTwinkleWave(pG, seed, horizonFactor);
float twB = evalTwinkleWave(pB, seed, horizonFactor);
float twR = evalTwinkleWave(pR, seed, horizonFactor);
float twG = evalTwinkleWave(pG, seed, horizonFactor);
float twB = evalTwinkleWave(pB, seed, horizonFactor);
return float3(twR, twG, twB);
return float3(twR, twG, twB);
}
float3 sampleStars(float3 rd, float2 res, float time) {
if (STAR_BRIGHTNESS <= 0.0f)
return float3(0.0f, 0.0f, 0.0f);
float3
sampleStars(float3 rd, float2 res, float time) {
if (STAR_BRIGHTNESS <= 0.0f) return float3(0.0f, 0.0f, 0.0f);
float rotSpeed = STAR_FIELD_ROTATIONS_SPEED;
float angle = time * rotSpeed;
float ca = cos(angle);
float sa = sin(angle);
float rotSpeed = STAR_FIELD_ROTATIONS_SPEED;
float angle = time * rotSpeed;
float ca = cos(angle);
float sa = sin(angle);
float3 rotRd = rd * ca + cross(CELESTIAL_POLE, rd) * sa +
CELESTIAL_POLE * dot(CELESTIAL_POLE, rd) * (1.0f - ca);
float3 rotRd = rd * ca + cross(CELESTIAL_POLE, rd) * sa + CELESTIAL_POLE * dot(CELESTIAL_POLE, rd) * (1.0f - ca);
float3 c = float3(0.0f, 0.0f, 0.0f);
float3 c = float3(0.0f, 0.0f, 0.0f);
[ForceUnroll] for (int i = 0; i < STAR_LAYERS; i++) {
float scale = (0.15f + float(i) * 0.02f) * res.x;
[ForceUnroll]
for (int i = 0; i < STAR_LAYERS; i++) {
float scale = (0.15f + float(i) * 0.02f) * res.x;
float3 layerOffset = float3(float(i) * 0.371f, float(i) * 0.519f, float(i) * 0.163f);
float3 scaledRd = rotRd * scale + layerOffset;
float3 layerOffset = float3(float(i) * 0.371f, float(i) * 0.519f, float(i) * 0.163f);
float3 scaledRd = rotRd * scale + layerOffset;
float3 q = frac(scaledRd) - 0.5f;
float3 id = floor(scaledRd);
float3 rn = pcgHash33(id);
float3 q = frac(scaledRd) - 0.5f;
float3 id = floor(scaledRd);
float3 rn = pcgHash33(id);
float threshold = STAR_LAYER_DENSITY[i] * STAR_DENSITY;
if (rn.x >= threshold)
continue;
float threshold = STAR_LAYER_DENSITY[i] * STAR_DENSITY;
if (rn.x >= threshold) continue;
float c2 = 1.0f - smoothstep(0.0f, STAR_SIZE, length(q));
float3 twinkleRGB = continuousChromaticTwinkle(rotRd, time, rn.y);
float starFlux = magnitudeToBrightness(rn.y);
float3 starCol = lerp(float3(1.0f, 0.49f, 0.1f), float3(0.75f, 0.9f, 1.0f), rn.z);
float c2 = 1.0f - smoothstep(0.0f, STAR_SIZE, length(q));
float3 twinkleRGB = continuousChromaticTwinkle(rotRd, time, rn.y);
float starFlux = magnitudeToBrightness(rn.y);
float3 starCol = lerp(float3(1.0f, 0.49f, 0.1f), float3(0.75f, 0.9f, 1.0f), rn.z);
c += c2 * starCol * starFlux * twinkleRGB;
}
return c * c * STAR_BRIGHTNESS;
c += c2 * starCol * starFlux * twinkleRGB;
}
return c * c * STAR_BRIGHTNESS;
}
// === MOUSE TRAIL FIELD ===
float mouseTrailField(float3 worldPos, float2 mouseWorld, float mouseValid,
float strength) {
if (strength <= 0.0f || mouseValid <= 0.5f)
return 0.0f;
float
mouseTrailField(float3 worldPos, float2 mouseWorld, float mouseValid, float strength) {
if (strength <= 0.0f || mouseValid <= 0.5f) return 0.0f;
float dist = length(worldPos.xz - mouseWorld);
float dist = length(worldPos.xz - mouseWorld);
float falloff = saturate(1.0f - dist / MOUSE_TRAIL_WIDTH);
falloff = falloff * falloff * (3.0f - 2.0f * falloff);
float falloff = saturate(1.0f - dist / MOUSE_TRAIL_WIDTH);
falloff = falloff * falloff * (3.0f - 2.0f * falloff);
return falloff * strength;
return falloff * strength;
}
// === ZENITH PROJECTION HELPER ===
float3 zenithRayDir(float2 centeredUV, float aspect, float horizonRadius,
float spinCos, float spinSin) {
float2 scaledUV = centeredUV * float2(aspect, 1.0f) * horizonRadius;
float r2 = dot(scaledUV, scaledUV);
float rd_y = 1.0f / sqrt(1.0f + r2);
float3 rd = normalize(float3(scaledUV.x, rd_y, scaledUV.y));
float3
zenithRayDir(float2 centeredUV, float aspect, float horizonRadius, float spinCos, float spinSin) {
float2 scaledUV = centeredUV * float2(aspect, 1.0f) * horizonRadius;
float r2 = dot(scaledUV, scaledUV);
float rd_y = 1.0f / sqrt(1.0f + r2);
float3 rd = normalize(float3(scaledUV.x, rd_y, scaledUV.y));
float ct = cos(PERSPECTIVE_TILT);
float st = sin(PERSPECTIVE_TILT);
rd.yz = float2(rd.y * ct - rd.z * st, rd.y * st + rd.z * ct);
float ct = cos(PERSPECTIVE_TILT);
float st = sin(PERSPECTIVE_TILT);
rd.yz = float2(rd.y * ct - rd.z * st, rd.y * st + rd.z * ct);
rd.xz = mul(float2x2(spinCos, spinSin, -spinSin, spinCos), rd.xz);
return rd;
rd.xz = mul(float2x2(spinCos, spinSin, -spinSin, spinCos), rd.xz);
return rd;
}
// === VOLUMETRIC AURORA RAY MARCH ===
float4 rayMarchAurora(float3 ro, float3 rd, float2 fragCoord, float noiseTime,
AuroraRandParams rp, float2 mouseWorld, float mouseValid,
float genStrength, float persistentTrail) {
float stepCount = MARCH_STEPS;
float4
rayMarchAurora(
float3 ro,
float3 rd,
float2 fragCoord,
float noiseTime,
AuroraRandParams rp,
float2 mouseWorld,
float mouseValid,
float genStrength,
float persistentTrail) {
float stepCount = MARCH_STEPS;
float effSpeed = WAVE_SPEED * rp.waveSpeedMul;
float effSharpness = CURTAIN_SHARPNESS * rp.sharpMul;
float effScale = AURORA_SCALE * rp.scaleMul;
float effSpeed = WAVE_SPEED * rp.waveSpeedMul;
float effSharpness = CURTAIN_SHARPNESS * rp.sharpMul;
float effScale = AURORA_SCALE * rp.scaleMul;
float3 accColor = float3(0.0f);
float accAlpha = 0.0f;
float3 accColor = float3(0.0f);
float accAlpha = 0.0f;
[ForceUnroll] for (int i = 0; i < 60; i++) {
if (float(i) >= stepCount)
break;
[ForceUnroll]
for (int i = 0; i < 60; i++) {
if (float(i) >= stepCount) break;
float of = 0.006f * hash21(fragCoord) * smoothstep(0.0f, 15.0f, float(i));
float pt = ((AURORA_ALTITUDE + pow(float(i), 1.4f) * 0.002f) - ro.y) /
(rd.y * 2.0f + 0.4f);
pt -= of;
float of = 0.006f * hash21(fragCoord) * smoothstep(0.0f, 15.0f, float(i));
float pt = ((AURORA_ALTITUDE + pow(float(i), 1.4f) * 0.002f) - ro.y) / (rd.y * 2.0f + 0.4f);
pt -= of;
if (pt < 0.0f)
continue;
if (pt < 0.0f) continue;
float3 bpos = ro + pt * rd;
float2 p = bpos.zx * effScale + rp.startOffset;
float density = triNoise2d(p, effSpeed, noiseTime, effSharpness);
float3 bpos = ro + pt * rd;
float2 p = bpos.zx * effScale + rp.startOffset;
float density = triNoise2d(p, effSpeed, noiseTime, effSharpness);
float trailField = mouseTrailField(bpos, mouseWorld, mouseValid, genStrength) + persistentTrail;
float trailNoise = triNoise2d(p * MOUSE_TRAIL_NOISE_SCALE + mouseWorld * 0.5f,
effSpeed * 1.5f, noiseTime, effSharpness * 0.8f);
density = saturate(density + trailField * (trailNoise + 0.6f));
float trailField = mouseTrailField(bpos, mouseWorld, mouseValid, genStrength) + persistentTrail;
float trailNoise = triNoise2d(p * MOUSE_TRAIL_NOISE_SCALE + mouseWorld * 0.5f, effSpeed * 1.5f, noiseTime, effSharpness * 0.8f);
density = saturate(density + trailField * (trailNoise + 0.6f));
float h = float(i) / MARCH_STEPS;
float3 colLow = float3(0.1f, 0.95f, 0.4f);
float3 colMid = float3(0.1f, 0.85f, 0.85f);
float3 colHigh = float3(0.65f, 0.25f, 0.95f);
float t1 = smoothstep(0.0f, 0.45f, h);
float t2 = smoothstep(0.35f, 1.0f, h);
float3 baseCol = lerp(lerp(colLow, colMid, t1), colHigh, t2);
baseCol += sin(float3(0.0f, 1.0f, 2.0f) + h * 6.0f + rp.colorJitter) * 0.06f;
baseCol = max(baseCol, float3(0.0f));
float h = float(i) / MARCH_STEPS;
float3 colLow = float3(0.1f, 0.95f, 0.4f);
float3 colMid = float3(0.1f, 0.85f, 0.85f);
float3 colHigh = float3(0.65f, 0.25f, 0.95f);
float t1 = smoothstep(0.0f, 0.45f, h);
float t2 = smoothstep(0.35f, 1.0f, h);
float3 baseCol = lerp(lerp(colLow, colMid, t1), colHigh, t2);
baseCol += sin(float3(0.0f, 1.0f, 2.0f) + h * 6.0f + rp.colorJitter) * 0.06f;
baseCol = max(baseCol, float3(0.0f));
float3 mouseCol = float3(0.15f, 0.75f, 1.0f);
float3 stepColor = lerp(baseCol, mouseCol, saturate(trailField * 1.5f));
float3 mouseCol = float3(0.15f, 0.75f, 1.0f);
float3 stepColor = lerp(baseCol, mouseCol, saturate(trailField * 1.5f));
float absorption = exp(-accAlpha * 2.0f);
accColor += stepColor * density * absorption;
accAlpha += density * 0.05f;
}
float absorption = exp(-accAlpha * 2.0f);
accColor += stepColor * density * absorption;
accAlpha += density * 0.05f;
}
accColor *= AURORA_INTENSITY * rp.intensityMul;
accColor *= AURORA_INTENSITY * rp.intensityMul;
return float4(accColor, saturate(accAlpha));
return float4(accColor, saturate(accAlpha));
}
// === SMOOTH CUTOFF ===
float smoothCutoff(float value, float floor_, float ceiling) {
float t = saturate((value - floor_) / max(ceiling - floor_, 0.001f));
return t * t * (3.0f - 2.0f * t);
float
smoothCutoff(float value, float floor_, float ceiling) {
float t = saturate((value - floor_) / max(ceiling - floor_, 0.001f));
return t * t * (3.0f - 2.0f * t);
}
// === ENTRY POINT ===
public
float4 fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
float2 pixelDims = float2(d.viewport_size_pixels) * t.viewport.zw;
float timestamp = fmod(d.timestamp, 86400.0f) * TIME_SCALE;
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
float2 pixelDims = float2(d.viewport_size_pixels) * t.viewport.zw;
float timestamp = fmod(d.timestamp, 86400.0f) * TIME_SCALE;
float2 uv = t.pos;
float aspect = pixelDims.x / max(pixelDims.y, 1.0f);
float2 uv = t.pos;
float aspect = pixelDims.x / max(pixelDims.y, 1.0f);
float2 mouseUV = (d.mouse_pos.xy - t.viewport.xy) / max(t.viewport.zw, float2(1e-5f, 1e-5f));
bool inWindow = all(mouseUV >= float2(0.0f)) && all(mouseUV <= float2(1.0f));
float mouseValid = float(inWindow);
if (MOUSE_IGNORE_WHEN_HIDDEN > 0.5f && d.mouse_pointer_hidden > 0.5f)
mouseValid = 0.0f;
float2 mouseUV = (d.mouse_pos.xy - t.viewport.xy) / max(t.viewport.zw, float2(1e-5f, 1e-5f));
bool inWindow = all(mouseUV >= float2(0.0f)) && all(mouseUV <= float2(1.0f));
float mouseValid = float(inWindow);
if (MOUSE_IGNORE_WHEN_HIDDEN > 0.5f && d.mouse_pointer_hidden > 0.5f) mouseValid = 0.0f;
// First group: Accumulate radial aurora rays cast from sky center into scratch texture 'a'
if (STAGE == 0) {
float prev = t.persist.Sample(uv).r;
float decayed = prev * saturate(1.0f - ACCUMULATION_DECAY);
// First group: Accumulate radial aurora rays cast from sky center into scratch texture 'a'
if (STAGE == 0) {
float prev = t.persist.Sample(uv).r;
float decayed = prev * saturate(1.0f - ACCUMULATION_DECAY);
float deposit = 0.0f;
if (mouseValid > 0.5f && d.mouse_button_pressed.x > 0.5f) {
// Aspect-corrected UVs relative to screen center (0.5, 0.5)
float2 cUV = (uv - 0.5f) * float2(aspect, 1.0f);
float2 mUV = (mouseUV - 0.5f) * float2(aspect, 1.0f);
float deposit = 0.0f;
if (mouseValid > 0.5f && d.mouse_button_pressed.x > 0.5f) {
// Aspect-corrected UVs relative to screen center (0.5, 0.5)
float2 cUV = (uv - 0.5f) * float2(aspect, 1.0f);
float2 mUV = (mouseUV - 0.5f) * float2(aspect, 1.0f);
// Radial direction vector from sky center through the mouse click
float mLen = length(mUV);
float2 mDir = (mLen > 0.0001f) ? (mUV / mLen) : float2(0.0f, 1.0f);
float2 mTangent = float2(-mDir.y, mDir.x); // Perpendicular to radial ray
// Radial direction vector from sky center through the mouse click
float mLen = length(mUV);
float2 mDir = (mLen > 0.0001f) ? (mUV / mLen) : float2(0.0f, 1.0f);
float2 mTangent = float2(-mDir.y, mDir.x); // Perpendicular to radial ray
// Difference vector from click point to current pixel
float2 diff = cUV - mUV;
// Difference vector from click point to current pixel
float2 diff = cUV - mUV;
// Project onto radial (along) and tangential (across) axes
float distAlongRay = dot(diff, mDir);
float distAcrossRay = dot(diff, mTangent);
// Project onto radial (along) and tangential (across) axes
float distAlongRay = dot(diff, mDir);
float distAcrossRay = dot(diff, mTangent);
// Asymmetric ray length extending outward from the click point
float yMax = (distAlongRay >= 0.0f) ? MOUSE_RAY_LENGTH : (MOUSE_RAY_LENGTH * 0.25f);
// Asymmetric ray length extending outward from the click point
float yMax = (distAlongRay >= 0.0f) ? MOUSE_RAY_LENGTH : (MOUSE_RAY_LENGTH * 0.25f);
float hDist = abs(distAcrossRay) / max(MOUSE_RAY_WIDTH, 0.001f);
float vDist = abs(distAlongRay) / max(yMax, 0.001f);
float hDist = abs(distAcrossRay) / max(MOUSE_RAY_WIDTH, 0.001f);
float vDist = abs(distAlongRay) / max(yMax, 0.001f);
// High-frequency curtain thread streaking along the ray width
float streaks1 = sin(distAcrossRay * MOUSE_RAY_FREQUENCY) * 0.35f + 0.65f;
float streaks2 = cos(distAcrossRay * MOUSE_RAY_FREQUENCY * 2.3f) * 0.25f + 0.75f;
// High-frequency curtain thread streaking along the ray width
float streaks1 = sin(distAcrossRay * MOUSE_RAY_FREQUENCY) * 0.35f + 0.65f;
float streaks2 = cos(distAcrossRay * MOUSE_RAY_FREQUENCY * 2.3f) * 0.25f + 0.75f;
float hFalloff = smoothstep(1.0f, 0.0f, hDist);
float vFalloff = smoothstep(1.0f, 0.0f, vDist);
float hFalloff = smoothstep(1.0f, 0.0f, hDist);
float vFalloff = smoothstep(1.0f, 0.0f, vDist);
deposit = hFalloff * vFalloff * streaks1 * streaks2 * MOUSE_PRESS_BOOST;
deposit = hFalloff * vFalloff * streaks1 * streaks2 * MOUSE_PRESS_BOOST;
}
return float4(saturate(decayed + deposit), 0.0f, 0.0f, 1.0f);
}
return float4(saturate(decayed + deposit), 0.0f, 0.0f, 1.0f);
}
// Second group: Copy scratch texture 'a' to 'persist'
if (STAGE == 1) { return t.a.Sample(uv); }
// Second group: Copy scratch texture 'a' to 'persist'
if (STAGE == 1) {
return t.a.Sample(uv);
}
// Third group: Main composite pass
AuroraRandParams rp = getAuroraParams(d.timestamp, INITIAL_RANDOM_SEED);
float epochBrightness = transitionBrightness(d.timestamp);
float noiseTime = timestamp + rp.timeOffset;
// Third group: Main composite pass
AuroraRandParams rp = getAuroraParams(d.timestamp, INITIAL_RANDOM_SEED);
float epochBrightness = transitionBrightness(d.timestamp);
float noiseTime = timestamp + rp.timeOffset;
float2 fragCoord = uv * pixelDims;
float2 fragCoord = uv * pixelDims;
float persistentTrail = t.persist.Sample(uv).r * MOUSE_PERSIST_INTENSITY;
float persistentTrail = t.persist.Sample(uv).r * MOUSE_PERSIST_INTENSITY;
float3 ro = float3(0.0f, 0.0f, -CAMERA_DISTANCE);
float2 centeredUV = uv - 0.5f;
float3 ro = float3(0.0f, 0.0f, -CAMERA_DISTANCE);
float2 centeredUV = uv - 0.5f;
float horizonRadius = 1.5f;
float horizonRadius = 1.5f;
float spinAngle = timestamp * 0.02f;
float cs = cos(spinAngle), ss = sin(spinAngle);
float spinAngle = timestamp * 0.02f;
float cs = cos(spinAngle), ss = sin(spinAngle);
float3 rd = zenithRayDir(centeredUV, aspect, horizonRadius, cs, ss);
float3 rd = zenithRayDir(centeredUV, aspect, horizonRadius, cs, ss);
float3 bgCol = lerp(SKY_COLOR_TOP, SKY_COLOR_BOTTOM, 1.0f - rd.y) * 0.63f;
float3 col = bgCol;
float3 bgCol = lerp(SKY_COLOR_TOP, SKY_COLOR_BOTTOM, 1.0f - rd.y) * 0.63f;
float3 col = bgCol;
float2 mouseWorld = float2(0.0f, 0.0f);
float2 mouseWorld = float2(0.0f, 0.0f);
if (mouseValid > 0.5f) {
float2 mCenteredUV = mouseUV - 0.5f;
float3 mrd = zenithRayDir(mCenteredUV, aspect, horizonRadius, cs, ss);
if (mouseValid > 0.5f) {
float2 mCenteredUV = mouseUV - 0.5f;
float3 mrd = zenithRayDir(mCenteredUV, aspect, horizonRadius, cs, ss);
float denom = mrd.y * 2.0f + 0.4f;
if (denom > 0.001f) {
float dist = (AURORA_ALTITUDE - ro.y) / denom;
mouseWorld = (ro + mrd * dist).xz;
float denom = mrd.y * 2.0f + 0.4f;
if (denom > 0.001f) {
float dist = (AURORA_ALTITUDE - ro.y) / denom;
mouseWorld = (ro + mrd * dist).xz;
}
}
}
float genStrength = MOUSE_AURORA_INTENSITY *
(1.0f + d.mouse_button_pressed.x * MOUSE_PRESS_BOOST);
float genStrength = MOUSE_AURORA_INTENSITY * (1.0f + d.mouse_button_pressed.x * MOUSE_PRESS_BOOST);
float4 aur = smoothstep(0.0f, 1.5f,
rayMarchAurora(ro, rd, fragCoord, noiseTime, rp,
mouseWorld, mouseValid, genStrength, persistentTrail));
col += sampleStars(rd, pixelDims, timestamp);
col = col * (1.0f - aur.a) + aur.rgb;
float4 aur = smoothstep(0.0f, 1.5f, rayMarchAurora(ro, rd, fragCoord, noiseTime, rp, mouseWorld, mouseValid, genStrength, persistentTrail));
col += sampleStars(rd, pixelDims, timestamp);
col = col * (1.0f - aur.a) + aur.rgb;
col *= EFFECT_INTENSITY * epochBrightness;
col *= EFFECT_INTENSITY * epochBrightness;
float effectLum = dot(col, float3(0.299f, 0.587f, 0.114f));
float gate = smoothCutoff(effectLum, EFFECT_FLOOR, EFFECT_CEILING);
col *= gate;
float effectLum = dot(col, float3(0.299f, 0.587f, 0.114f));
float gate = smoothCutoff(effectLum, EFFECT_FLOOR, EFFECT_CEILING);
col *= gate;
float4 terminalColor = t.backbuffer.Sample(t.pos);
float termLum = dot(terminalColor.rgb, float3(0.299f, 0.587f, 0.114f));
float isBlack = step(termLum, 0.001f);
float textPreserve = 1.0f - smoothstep(BLACK_BLEND_THRESHOLD,
BLACK_BLEND_THRESHOLD + 0.2f, termLum);
float blendFactor = lerp(textPreserve * EFFECT_INTENSITY, 1.0f, isBlack);
float3 blendedColor = terminalColor.rgb + col * blendFactor;
float4 terminalColor = t.backbuffer.Sample(t.pos);
float termLum = dot(terminalColor.rgb, float3(0.299f, 0.587f, 0.114f));
float isBlack = step(termLum, 0.001f);
float textPreserve = 1.0f - smoothstep(BLACK_BLEND_THRESHOLD, BLACK_BLEND_THRESHOLD + 0.2f, termLum);
float blendFactor = lerp(textPreserve * EFFECT_INTENSITY, 1.0f, isBlack);
float3 blendedColor = terminalColor.rgb + col * blendFactor;
if (DEBUG_MOUSE_OVERLAY > 0.5f && mouseValid > 0.5f) {
float ptProxy = (AURORA_ALTITUDE - ro.y) / (rd.y * 2.0f + 0.4f);
float g = mouseTrailField(ro + rd * ptProxy, mouseWorld, 1.0f, genStrength);
blendedColor =
lerp(blendedColor, float3(1.0f, 0.25f, 0.05f), saturate(g * 2.0f));
}
if (DEBUG_MOUSE_OVERLAY > 0.5f && mouseValid > 0.5f) {
float ptProxy = (AURORA_ALTITUDE - ro.y) / (rd.y * 2.0f + 0.4f);
float g = mouseTrailField(ro + rd * ptProxy, mouseWorld, 1.0f, genStrength);
blendedColor = lerp(blendedColor, float3(1.0f, 0.25f, 0.05f), saturate(g * 2.0f));
}
return float4(blendedColor, terminalColor.a);
return float4(blendedColor, terminalColor.a);
}

View file

@ -17,14 +17,10 @@ public struct BlitOutput {
#define bottom 3
// Static constant array mapping vertex IDs
static const int2 vertex_pos_map[4] = {
{right, top},
{right, bottom},
{left, bottom},
{left, top}
};
static const int2 vertex_pos_map[4] = { { right, top }, { right, bottom }, { left, bottom }, { left, top } };
BlitOutput get_coords_for_blit(uint vertex_id, float4 src_rect, float4 dest_rect) {
BlitOutput
get_coords_for_blit(uint vertex_id, float4 src_rect, float4 dest_rect) {
int2 pos = vertex_pos_map[vertex_id];
BlitOutput output;
output.texcoord = float2(src_rect[pos.x], src_rect[pos.y]);
@ -36,23 +32,27 @@ BlitOutput get_coords_for_blit(uint vertex_id, float4 src_rect, float4 dest_rect
public ConstantBuffer<KittyCustomShaderData> csd;
public float4 pipeline_vertex_main(uint vertex_id) {
public float4
pipeline_vertex_main(uint vertex_id) {
BlitOutput ans = get_coords_for_blit(vertex_id, csd.src_rect, csd.dest_rect);
return float4(ans.texcoord[0], ans.texcoord[1], ans.position[0], ans.position[1]);
}
float4 vec4_premul(float3 rgb, float a) {
float4
vec4_premul(float3 rgb, float a) {
return float4(rgb * a, a);
}
float linear2srgb(float x) { // scalar
float
linear2srgb(float x) { // scalar
float lower = 12.92 * x;
float upper = 1.055 * pow(x, 1.0f / 2.4f) - 0.055f;
return lerp(lower, upper, step(0.0031308f, x));
}
float3 linear2srgb(float3 x) { // vector
float3
linear2srgb(float3 x) { // vector
float3 lower = 12.92 * x;
float3 upper = 1.055 * pow(x, float3(1.0f / 2.4f)) - 0.055f;
@ -61,11 +61,11 @@ float3 linear2srgb(float3 x) { // vector
uniform Sampler2D backbuffer, a, b, persist;
public float4 pipeline_fragment_main(float2 backbuffer_pos, int group, float4 viewport, float animation_progress, bool convert_to_srgb) {
float4 color = backbuffer.Sample(backbuffer_pos); // pre multiplied linear RGB
color = float4(color.rgb / color.a, color.a); // un pre multiplied color passed through pipeline
KittyTextures t = {backbuffer, a, b, persist, backbuffer_pos, viewport, animation_progress, group};
public float4
pipeline_fragment_main(float2 backbuffer_pos, int group, float4 viewport, float animation_progress, bool convert_to_srgb) {
float4 color = backbuffer.Sample(backbuffer_pos); // pre multiplied linear RGB
color = float4(color.rgb / color.a, color.a); // un pre multiplied color passed through pipeline
KittyTextures t = { backbuffer, a, b, persist, backbuffer_pos, viewport, animation_progress, group };
// PIPELINE
return vec4_premul(color.rgb, color.a);
}

View file

@ -9,37 +9,34 @@
import kitty_custom_shader_types;
// Maximum wavefront radius at animation_progress=1 (aspect-corrected UV units).
static const float WAVE_SPEED = 0.45;
static const float WAVE_SPEED = 0.45;
// Spatial frequency of the rings (cycles per aspect-corrected UV unit).
static const float WAVE_FREQUENCY = 14.0;
static const float WAVE_FREQUENCY = 14.0;
// Starting half-width of the Gaussian envelope (expands as animation_progress increases).
static const float WAVE_WIDTH = 0.055;
static const float WAVE_WIDTH = 0.055;
// Peak radial UV displacement at the wavecrest.
static const float AMPLITUDE = 0.010;
static const float AMPLITUDE = 0.010;
// Scale applied to the wave gradient when constructing the surface normal.
static const float NORMAL_SCALE = 8.0;
static const float NORMAL_SCALE = 8.0;
// Blinn-Phong shininess exponent — higher values give a sharper, more mirror-like glint.
static const float SHININESS = 64.0;
static const float SHININESS = 64.0;
// Peak specular contribution at the wave crest facing the light.
static const float SPECULAR_STRENGTH = 0.40;
// Subtle diffuse shading that rounds each ring into a 3D ridge.
static const float DIFFUSE_STRENGTH = 0.10;
static const float DIFFUSE_STRENGTH = 0.10;
public float4 fragment_main(
float4 color,
KittyTextures t,
KittyCustomShaderData d
) {
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
float2 pixelDims = float2(d.viewport_size_pixels) * t.viewport.zw;
float aspect = pixelDims.x / pixelDims.y;
float aspect = pixelDims.x / pixelDims.y;
// d.mouse_pos.zw is the UV position of the last left-button press (y=0 at bottom).
float2 center = d.mouse_pos.zw;
// Aspect-corrected vector from the click center to this pixel.
float2 delta = (t.pos - center) * float2(aspect, 1.0);
float dist = length(delta);
float2 dir = delta / max(dist, 1e-5);
float dist = length(delta);
float2 dir = delta / max(dist, 1e-5);
// Wavefront radius advances linearly with animation_progress.
float front = WAVE_SPEED * t.animation_progress;
@ -49,7 +46,7 @@ public float4 fragment_main(
// Gaussian spatial envelope centered at the wavefront; grows in width over time.
float sigma = WAVE_WIDTH * (1.0 + t.animation_progress);
float env = exp(-behind * behind / (2.0 * sigma * sigma));
float env = exp(-behind * behind / (2.0 * sigma * sigma));
// Causality: suppress any energy ahead of the wavefront.
env *= 1.0 - smoothstep(-0.01, 0.02, behind);
@ -57,7 +54,7 @@ public float4 fragment_main(
// Temporal amplitude decay as the wave expands outward.
float decay = pow(1.0 - t.animation_progress, 0.7);
float k = WAVE_FREQUENCY * 6.28318530718;
float k = WAVE_FREQUENCY * 6.28318530718;
float wave = sin(behind * k);
// Analytic derivative of wave height h = wave * env * decay w.r.t. dist.
@ -77,22 +74,17 @@ public float4 fragment_main(
// Blinn-Phong half-vector.
float3 H = normalize(L + V);
float diffuse = max(dot(N, L), 0.0);
float diffuse = max(dot(N, L), 0.0);
float specular = pow(max(dot(N, H), 0.0), SHININESS);
// Radial UV displacement: push pixels away from center at crests, pull inward at troughs.
float2 warpedUV = clamp(
t.pos + dir * (wave * env * decay * AMPLITUDE),
float2(0.0), float2(1.0)
);
float2 warpedUV = clamp(t.pos + dir * (wave * env * decay * AMPLITUDE), float2(0.0), float2(1.0));
// Sample the terminal backbuffer at the displaced position.
float4 result = t.backbuffer.Sample(warpedUV);
// 3D lighting: diffuse rounds each ring into a ridge, specular gives directional glints.
float lighting = 1.0
+ diffuse * DIFFUSE_STRENGTH * env * decay
+ specular * SPECULAR_STRENGTH * env * decay;
float lighting = 1.0 + diffuse * DIFFUSE_STRENGTH * env * decay + specular * SPECULAR_STRENGTH * env * decay;
result.rgb *= lighting;
return result;

View file

@ -4,7 +4,11 @@
import kitty_custom_shader_types;
public float4 fragment_main(
static const float FACTOR = 2;
// START_FUNCTION_SIGNATURE
public float4
fragment_main(
// The color from the previous custom shader in this group or from the
// backbuffer if this is the first shader in the group
float4 color,
@ -13,8 +17,7 @@ public float4 fragment_main(
KittyTextures t,
// See types.slang for details on the data in this structure
KittyCustomShaderData d
)
{ // END_FUNCTION_SIGNATURE
return float4(color.r, min(max(0.1, color.g) * 2, 1), color.b, color.a);
KittyCustomShaderData d) {
// END_FUNCTION_SIGNATURE
return float4(color.r, min(max(0.1, color.g) * FACTOR, 1), color.b, color.a);
}

View file

@ -7,42 +7,36 @@
import kitty_custom_shader_types;
static const float SPOTLIGHT_RADIUS = 0.25; // spotlight radius in UV units
static const float SPOTLIGHT_SOFTNESS = 20.0; // edge sharpness; higher = sharper
static const float AMBIENT_LIGHT = 0.5; // minimum brightness outside the spotlight
static const float SPOTLIGHT_SPEED = 1.0; // oscillation speed in random-motion mode
static const float SPOTLIGHT_RADIUS = 0.25; // spotlight radius in UV units
static const float SPOTLIGHT_SOFTNESS = 20.0; // edge sharpness; higher = sharper
static const float AMBIENT_LIGHT = 0.5; // minimum brightness outside the spotlight
static const float SPOTLIGHT_SPEED = 1.0; // oscillation speed in random-motion mode
// Set to 1 to follow the mouse, 0 to use random oscillation
static const int USE_MOUSE = 1;
static const int USE_MOUSE = 1;
// When USE_MOUSE=1 and the mouse leaves the window:
// 0 = fall back to random oscillation
// 1 = clamp spotlight to the nearest window edge
// 2 = disable spotlight (pass through unchanged pixels)
static const int MOUSE_LEAVE_BEHAVIOR = 2;
static const int MOUSE_LEAVE_BEHAVIOR = 2;
// Set to 1 to pass through unchanged pixels when the mouse pointer is hidden
static const int HIDE_WHEN_POINTER_HIDDEN = 1;
static const int HIDE_WHEN_POINTER_HIDDEN = 1;
public float4 fragment_main(
float4 color,
KittyTextures t,
KittyCustomShaderData d
) {
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
if (HIDE_WHEN_POINTER_HIDDEN == 1 && d.mouse_pointer_hidden != 0.0) return color;
float2 uv = t.pos;
// Viewport pixel dimensions for aspect-ratio correction
float2 pixelDims = float2(d.viewport_size_pixels) * t.viewport.zw;
float aspectRatio = pixelDims.x / pixelDims.y;
float2 pixelDims = float2(d.viewport_size_pixels) * t.viewport.zw;
float aspectRatio = pixelDims.x / pixelDims.y;
// Random oscillating center (different frequencies and phases on each axis)
float time = d.timestamp * SPOTLIGHT_SPEED;
float2 randomCenter = float2(
0.5 + 0.4 * sin(time),
0.5 + 0.4 * sin(time * 1.3 + 3.14159)
);
float time = d.timestamp * SPOTLIGHT_SPEED;
float2 randomCenter = float2(0.5 + 0.4 * sin(time), 0.5 + 0.4 * sin(time * 1.3 + 3.14159));
// Mouse is valid only when both coords are inside the OS window [0, 1]
float2 mp = d.mouse_pos.xy;
float mouseInWindow = float(all(mp >= float2(0.0)) && all(mp <= float2(1.0)));
float2 mp = d.mouse_pos.xy;
float mouseInWindow = float(all(mp >= float2(0.0)) && all(mp <= float2(1.0)));
// Disable spotlight entirely when mouse leaves the window
if (USE_MOUSE == 1 && MOUSE_LEAVE_BEHAVIOR == 2 && mouseInWindow == 0.0) return color;
@ -51,18 +45,14 @@ public float4 fragment_main(
float2 outsideFallback = lerp(randomCenter, clamp(mp, float2(0.0), float2(1.0)), float(MOUSE_LEAVE_BEHAVIOR == 1 ? 1 : 0));
// Branchless center selection
float2 mouseCenter = lerp(outsideFallback, mp, mouseInWindow);
float2 mouseCenter = lerp(outsideFallback, mp, mouseInWindow);
float2 spotlightCenter = lerp(randomCenter, mouseCenter, float(USE_MOUSE));
// Euclidean distance in aspect-ratio-corrected space (produces a circular spotlight)
float distToCenter = length((uv - spotlightCenter) * float2(aspectRatio, 1.0));
// Soft spotlight edge
float spotlightIntensity = smoothstep(
SPOTLIGHT_RADIUS,
SPOTLIGHT_RADIUS - (1.0 / SPOTLIGHT_SOFTNESS),
distToCenter
);
float spotlightIntensity = smoothstep(SPOTLIGHT_RADIUS, SPOTLIGHT_RADIUS - (1.0 / SPOTLIGHT_SOFTNESS), distToCenter);
// Blend between ambient and full illumination
float3 result = color.rgb * lerp(float3(AMBIENT_LIGHT), float3(1.0), spotlightIntensity);

View file

@ -27,12 +27,13 @@ static const float EDGE_SOFTNESS = 0.04;
// Returns a value in [0, 1] for this pixel: 0 at the tab-bar edge of the
// content area, 1 at the opposite edge. The wipe sweeps from 0 toward 1.
float wipe_pixel_pos(float2 uv, float4 ca) {
float gap_left = ca.x;
float gap_right = 1.0 - (ca.x + ca.z);
float
wipe_pixel_pos(float2 uv, float4 ca) {
float gap_left = ca.x;
float gap_right = 1.0 - (ca.x + ca.z);
float gap_bottom = ca.y;
float gap_top = 1.0 - (ca.y + ca.w);
float max_gap = max(max(gap_left, gap_right), max(gap_bottom, gap_top));
float gap_top = 1.0 - (ca.y + ca.w);
float max_gap = max(max(gap_left, gap_right), max(gap_bottom, gap_top));
float pos;
if (max_gap == gap_top) {
@ -51,11 +52,8 @@ float wipe_pixel_pos(float2 uv, float4 ca) {
return clamp(pos, 0.0, 1.0);
}
public float4 fragment_main(
float4 color,
KittyTextures t,
KittyCustomShaderData d
) {
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
float4 ca = d.central_area;
if (ca.z <= 0.0 || ca.w <= 0.0) return color;
@ -73,20 +71,20 @@ public float4 fragment_main(
} else if (ANIMATION_TYPE == 2) {
// BLINDS: strips opening in sequence from the tab-bar side
float pixel_pos = wipe_pixel_pos(t.pos, ca);
float inv_n = 1.0 / float(NUM_BLINDS);
float inv_n = 1.0 / float(NUM_BLINDS);
float strip_idx = floor(pixel_pos * float(NUM_BLINDS));
float strip_start = strip_idx * inv_n;
// Progress local to this strip: 0 when it starts opening, 1 when fully open
float strip_p = clamp((p - strip_start) / inv_n, 0.0, 1.0);
float strip_p = clamp((p - strip_start) / inv_n, 0.0, 1.0);
// Position within the strip (0 at tab-bar side, 1 at far side)
float local = fract(pixel_pos * float(NUM_BLINDS));
float soft = 0.1;
float local = fract(pixel_pos * float(NUM_BLINDS));
float soft = 0.1;
brightness = 1.0 - smoothstep(strip_p - soft, strip_p + soft, local);
} else {
// IRIS: circular reveal expanding from the centre of the content area
float2 ca_center = ca.xy + ca.zw * 0.5;
float half_diag = length(ca.zw * 0.5);
float dist = length(t.pos - ca_center) / max(half_diag, 0.001);
float half_diag = length(ca.zw * 0.5);
float dist = length(t.pos - ca_center) / max(half_diag, 0.001);
brightness = 1.0 - smoothstep(p - EDGE_SOFTNESS, p + EDGE_SOFTNESS, dist);
}

View file

@ -12,17 +12,15 @@ static const float RESOLUTION = 4.0;
// strength of the effect
static const float STRENGTH = 0.5;
void scanline(inout float3 color, float2 pixel_pos) {
void
scanline(inout float3 color, float2 pixel_pos) {
float scanline = step(1.2, fmod(pixel_pos.y, RESOLUTION));
float grille = step(1.2, fmod(pixel_pos.x, RESOLUTION));
float grille = step(1.2, fmod(pixel_pos.x, RESOLUTION));
color *= max(1.0 - STRENGTH, scanline * grille);
}
public float4 fragment_main(
float4 color,
KittyTextures t,
KittyCustomShaderData d
) {
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
float2 pixel_pos = t.pos * float2(d.viewport_size_pixels);
float3 c = color.rgb;
scanline(c, pixel_pos);

View file

@ -4,7 +4,7 @@
public struct KittyCustomShaderData {
public float4 src_rect, dest_rect; // used by the vertex shader internally
public float4 src_rect, dest_rect; // used by the vertex shader internally
// global background/foreground colors from kitty options (linear RGB, alpha=1)
public float4 background, foreground;
@ -39,8 +39,8 @@ public struct KittyCustomShaderData {
// Corner order: 0=top-right, 1=bottom-right, 2=bottom-left, 3=top-left.
// Use animation_start cursor-trail-move / animation_stop cursor-trail-stop to activate.
// animation_progress is NOT tied to trail duration; use cursor_trail_state.x for opacity instead.
public float4 cursor_trail_corners_x; // UV x-coordinates of the 4 animated trail quad corners
public float4 cursor_trail_corners_y; // UV y-coordinates of the 4 animated trail quad corners
public float4 cursor_trail_corners_x; // UV x-coordinates of the 4 animated trail quad corners
public float4 cursor_trail_corners_y; // UV y-coordinates of the 4 animated trail quad corners
// .x=left, .y=right, .z=top, .w=bottom UV edges of the current cursor rectangle
public float4 cursor_trail_edge;
// .x=left, .y=right, .z=top, .w=bottom UV edges of the cursor rectangle before the most recent move.
@ -61,12 +61,12 @@ public struct KittyCustomShaderData {
// viewport currently being rendered too.
public uint2 viewport_size_pixels;
public float mouse_pointer_hidden; // 1.0 if the mouse pointer is currently hidden, 0.0 if visible
public float cursor_trail_state; // 1.0 if the cursor trail is actively animating, 0.0 otherwise
public float mouse_pointer_hidden; // 1.0 if the mouse pointer is currently hidden, 0.0 if visible
public float cursor_trail_state; // 1.0 if the cursor trail is actively animating, 0.0 otherwise
public float timestamp; // time in seconds since kitty was started (millisecond precision)
public float last_rendered_at; // time of previous render in seconds since kitty was started (millisecond precision)
public uint frame_counter; // cumulative frame counter increments by one every time a frame is fully rendered, first frame is zero
public float timestamp; // time in seconds since kitty was started (millisecond precision)
public float last_rendered_at; // time of previous render in seconds since kitty was started (millisecond precision)
public uint frame_counter; // cumulative frame counter increments by one every time a frame is fully rendered, first frame is zero
// Time (same epoch as `timestamp`) of the most recent cursor position change that triggered a trail.
// Zero until the cursor has moved at least twice. Pair with cursor_trail_prev_edge.
public float cursor_trail_change_time;
@ -84,7 +84,7 @@ public struct KittyTextures {
public Sampler2D b;
public Sampler2D persist;
public float2 pos; // the position of this pixel in the textures (UV coordinates)
public float2 pos; // the position of this pixel in the textures (UV coordinates)
// Some group related uniforms

View file

@ -11,87 +11,75 @@ import kitty_custom_shader_types;
static const float BLACK_BLEND_THRESHOLD = 0.4;
// Ray 1 source position (as fractions of viewport dimensions)
static const float RAY1_SRC_X = 0.7;
static const float RAY1_SRC_Y = 1.1;
static const float RAY1_SRC_X = 0.7;
static const float RAY1_SRC_Y = 1.1;
// Y component of ray 1 reference direction (X component is always 1.0)
static const float RAY1_DIR_Y = 0.116;
static const float RAY1_DIR_Y = 0.116;
static const float RAY1_SEED_A = 36.2214;
static const float RAY1_SEED_B = 21.11349;
static const float RAY1_SPEED = 1.1;
static const float RAY1_WEIGHT = 0.5; // contribution weight in final blend
static const float RAY1_SPEED = 1.1;
static const float RAY1_WEIGHT = 0.5; // contribution weight in final blend
// Ray 2 source position (as fractions of viewport dimensions)
static const float RAY2_SRC_X = 0.8;
static const float RAY2_SRC_Y = 1.2;
static const float RAY2_DIR_Y = -0.241;
static const float RAY2_SRC_X = 0.8;
static const float RAY2_SRC_Y = 1.2;
static const float RAY2_DIR_Y = -0.241;
static const float RAY2_SEED_A = 22.39910;
static const float RAY2_SEED_B = 18.0234;
static const float RAY2_SPEED = 0.9;
static const float RAY2_SPEED = 0.9;
static const float RAY2_WEIGHT = 0.4;
// Per-channel depth-attenuation: channel *= BASE + brightness * SCALE
static const float DEPTH_R_BASE = 0.05;
static const float DEPTH_R_BASE = 0.05;
static const float DEPTH_R_SCALE = 0.8;
static const float DEPTH_G_BASE = 0.15;
static const float DEPTH_G_BASE = 0.15;
static const float DEPTH_G_SCALE = 0.6;
static const float DEPTH_B_BASE = 0.3;
static const float DEPTH_B_BASE = 0.3;
static const float DEPTH_B_SCALE = 0.5;
// Overall strength of the ray overlay on dark terminal content
static const float OVERLAY_STRENGTH = 0.3;
float hash21(float2 p) {
float
hash21(float2 p) {
p = frac(p * float2(233.34, 851.73));
p += dot(p, p + 23.45);
return frac(p.x * p.y);
}
float rayStrength(
float2 raySource, float2 rayRefDir, float2 coord,
float seedA, float seedB, float speed,
float iTime, float resX
) {
float
rayStrength(float2 raySource, float2 rayRefDir, float2 coord, float seedA, float seedB, float speed, float iTime, float resX) {
float2 sourceToCoord = coord - raySource;
float cosAngle = dot(normalize(sourceToCoord), rayRefDir);
float dither = hash21(coord) * 0.015 - 0.0075;
float ray = clamp(
(0.45 + 0.15 * sin( cosAngle * seedA + iTime * speed)) +
(0.30 + 0.20 * cos(-cosAngle * seedB + iTime * speed)) + dither,
0.0, 1.0);
float cosAngle = dot(normalize(sourceToCoord), rayRefDir);
float dither = hash21(coord) * 0.015 - 0.0075;
float ray = clamp((0.45 + 0.15 * sin(cosAngle * seedA + iTime * speed)) + (0.30 + 0.20 * cos(-cosAngle * seedB + iTime * speed)) + dither, 0.0, 1.0);
float distFade = smoothstep(0.0, resX, resX - length(sourceToCoord));
return ray * lerp(0.5, 1.0, distFade);
}
public float4 fragment_main(
float4 color,
KittyTextures t,
KittyCustomShaderData d
) {
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
float2 iResolution = float2(d.viewport_size_pixels) * t.viewport.zw;
float iTime = d.timestamp;
float iTime = d.timestamp;
// Convert kitty UV (y=0 at bottom) to a top-left-origin pixel coordinate,
// matching the original shader's internal coordinate system.
float2 coord = float2(t.pos.x, 1.0 - t.pos.y) * iResolution;
float2 rayPos1 = float2(iResolution.x * RAY1_SRC_X, iResolution.y * RAY1_SRC_Y);
float2 rayPos1 = float2(iResolution.x * RAY1_SRC_X, iResolution.y * RAY1_SRC_Y);
float2 rayRefDir1 = normalize(float2(1.0, RAY1_DIR_Y));
float2 rayPos2 = float2(iResolution.x * RAY2_SRC_X, iResolution.y * RAY2_SRC_Y);
float2 rayPos2 = float2(iResolution.x * RAY2_SRC_X, iResolution.y * RAY2_SRC_Y);
float2 rayRefDir2 = normalize(float2(1.0, RAY2_DIR_Y));
float4 rays1 = float4(1.0, 1.0, 1.0, 0.0) *
rayStrength(rayPos1, rayRefDir1, coord,
RAY1_SEED_A, RAY1_SEED_B, RAY1_SPEED, iTime, iResolution.x);
float4 rays1 = float4(1.0, 1.0, 1.0, 0.0) * rayStrength(rayPos1, rayRefDir1, coord, RAY1_SEED_A, RAY1_SEED_B, RAY1_SPEED, iTime, iResolution.x);
float4 rays2 = float4(1.0, 1.0, 1.0, 0.0) *
rayStrength(rayPos2, rayRefDir2, coord,
RAY2_SEED_A, RAY2_SEED_B, RAY2_SPEED, iTime, iResolution.x);
float4 rays2 = float4(1.0, 1.0, 1.0, 0.0) * rayStrength(rayPos2, rayRefDir2, coord, RAY2_SEED_A, RAY2_SEED_B, RAY2_SPEED, iTime, iResolution.x);
float4 col = rays1 * RAY1_WEIGHT + rays2 * RAY2_WEIGHT;
// Attenuate toward the bottom (depth) with a blue-green tint
float brightness = t.pos.y; // 0 at screen bottom (deep), 1 at top (surface)
float brightness = t.pos.y; // 0 at screen bottom (deep), 1 at top (surface)
col.r *= DEPTH_R_BASE + brightness * DEPTH_R_SCALE;
col.g *= DEPTH_G_BASE + brightness * DEPTH_G_SCALE;
col.b *= DEPTH_B_BASE + brightness * DEPTH_B_SCALE;
@ -99,7 +87,7 @@ public float4 fragment_main(
float4 terminalColor = t.backbuffer.Sample(t.pos);
// Overlay rays onto dark (near-black) terminal pixels; preserve bright content
float alpha = step(length(terminalColor.rgb), BLACK_BLEND_THRESHOLD);
float alpha = step(length(terminalColor.rgb), BLACK_BLEND_THRESHOLD);
float3 blendedColor = lerp(terminalColor.rgb, col.rgb * OVERLAY_STRENGTH, alpha);
return float4(blendedColor, terminalColor.a);

View file

@ -6,31 +6,26 @@
import kitty_custom_shader_types;
static const float TAU = 6.28318530718;
static const int MAX_ITER = 6;
static const float3 WATER_COLOR = float3(0.5, 0.5, 0.5); // water overlay tint
static const float CAUSTIC_INTEN = 0.005; // caustic ripple intensity
static const float UV_DISPLACEMENT = 0.04; // UV warp magnitude from caustics
static const float TAU = 6.28318530718;
static const int MAX_ITER = 6;
static const float3 WATER_COLOR = float3(0.5, 0.5, 0.5); // water overlay tint
static const float CAUSTIC_INTEN = 0.005; // caustic ripple intensity
static const float UV_DISPLACEMENT = 0.04; // UV warp magnitude from caustics
public float4 fragment_main(
float4 color,
KittyTextures t,
KittyCustomShaderData d
) {
float wtime = d.timestamp * 0.5 + 23.0;
float2 uv = t.pos;
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
float wtime = d.timestamp * 0.5 + 23.0;
float2 uv = t.pos;
float2 p = fmod(uv * TAU, TAU) - 250.0;
float2 i = p;
float c = 1.0;
float c = 1.0;
[ForceUnroll]
for (int n = 0; n < MAX_ITER; n++) {
float tval = wtime * (1.0 - (3.5 / float(n + 1)));
i = p + float2(cos(tval - i.x) + sin(tval + i.y),
sin(tval - i.y) + cos(tval + i.x));
c += 1.0 / length(float2(p.x / (sin(i.x + tval) / CAUSTIC_INTEN),
p.y / (cos(i.y + tval) / CAUSTIC_INTEN)));
i = p + float2(cos(tval - i.x) + sin(tval + i.y), sin(tval - i.y) + cos(tval + i.x));
c += 1.0 / length(float2(p.x / (sin(i.x + tval) / CAUSTIC_INTEN), p.y / (cos(i.y + tval) / CAUSTIC_INTEN)));
}
c /= float(MAX_ITER);
c = 1.17 - pow(c, 1.4);

View file

@ -10,32 +10,25 @@ extern static const bool is_alpha_mask = false;
extern static const bool texture_is_not_premultiplied = true;
struct VSOutput
{
struct VSOutput {
float2 texcoord : TEXCOORD;
float4 position : SV_Position;
};
[shader("vertex")]
VSOutput vertex_main(
uint vertex_id : SV_VertexID,
uniform float4 src_rect,
uniform float4 dest_rect,
) {
VSOutput
vertex_main(uint vertex_id: SV_VertexID, uniform float4 src_rect, uniform float4 dest_rect, ) {
BlitOutput ans = get_coords_for_blit(vertex_id, src_rect, dest_rect);
return {ans.texcoord, float4(ans.position[0], ans.position[1], 0.0, 1.0)};
return { ans.texcoord, float4(ans.position[0], ans.position[1], 0.0, 1.0) };
}
uniform Sampler2D image;
[shader("fragment")]
float4 fragment_main(
float2 texcoord : TEXCOORD,
uniform float3 amask_fg,
uniform float4 amask_bg_premult,
uniform float extra_alpha
) : SV_Target {
float4
fragment_main(float2 texcoord: TEXCOORD, uniform float3 amask_fg, uniform float4 amask_bg_premult, uniform float extra_alpha)
: SV_Target {
float4 color = image.Sample(texcoord);
if (is_alpha_mask) {
color = float4(amask_fg, color.r);

View file

@ -5,48 +5,59 @@
module hsluv;
// Helper Functions
float divide(float num, float denom) {
float
divide(float num, float denom) {
return num / (abs(denom) + 1e-15) * sign(denom);
}
float3 divide(float3 num, float3 denom) {
float3
divide(float3 num, float3 denom) {
return num / (abs(denom) + 1e-15) * sign(denom);
}
float3 hsluv_intersectLineLine(float3 line1x, float3 line1y, float3 line2x, float3 line2y) {
float3
hsluv_intersectLineLine(float3 line1x, float3 line1y, float3 line2x, float3 line2y) {
return (line1y - line2y) / (line2x - line1x);
}
float3 hsluv_distanceFromPole(float3 pointx, float3 pointy) {
float3
hsluv_distanceFromPole(float3 pointx, float3 pointy) {
return sqrt(pointx * pointx + pointy * pointy);
}
float3 hsluv_lengthOfRayUntilIntersect(float theta, float3 x, float3 y) {
float3
hsluv_lengthOfRayUntilIntersect(float theta, float3 x, float3 y) {
float3 len = divide(y, sin(theta) - x * cos(theta));
len = lerp(len, (float3)1000.0, step(len, (float3)0.0));
return len;
}
float hsluv_maxSafeChromaForL(float L) {
float
hsluv_maxSafeChromaForL(float L) {
// Transposed from GLSL column-major constructor to Slang row-major layout
float3x3 m2 = float3x3(
3.2409699419045214, -1.5373831775700935, -0.49861076029300328,
-0.96924363628087983, 1.8759675015077207, 0.041555057407175613,
0.055630079696993609,-0.20397695888897657, 1.0569715142428786
);
3.2409699419045214,
-1.5373831775700935,
-0.49861076029300328,
-0.96924363628087983,
1.8759675015077207,
0.041555057407175613,
0.055630079696993609,
-0.20397695888897657,
1.0569715142428786);
float sub0 = L + 16.0;
float sub1 = sub0 * sub0 * sub0 * 0.000000641;
float sub2 = lerp(L / 903.2962962962963, sub1, step(0.0088564516790356308, sub1));
float3 top1 = (284517.0 * m2[0] - 94839.0 * m2[2]) * sub2;
float3 top1 = (284517.0 * m2[0] - 94839.0 * m2[2]) * sub2;
float3 bottom = (632260.0 * m2[2] - 126452.0 * m2[1]) * sub2;
float3 top2 = (838422.0 * m2[2] + 769860.0 * m2[1] + 731718.0 * m2[0]) * L * sub2;
float3 top2 = (838422.0 * m2[2] + 769860.0 * m2[1] + 731718.0 * m2[0]) * L * sub2;
float3 bounds0x = top1 / bottom;
float3 bounds0y = top2 / bottom;
float3 bounds1x = top1 / (bottom + 126452.0);
float3 bounds1x = top1 / (bottom + 126452.0);
float3 bounds1y = (top2 - 769860.0 * L) / (bottom + 126452.0);
float3 xs0 = hsluv_intersectLineLine(bounds0x, bounds0y, -1.0 / bounds0x, (float3)0.0);
@ -55,87 +66,100 @@ float hsluv_maxSafeChromaForL(float L) {
float3 lengths0 = hsluv_distanceFromPole(xs0, bounds0y + xs0 * bounds0x);
float3 lengths1 = hsluv_distanceFromPole(xs1, bounds1y + xs1 * bounds1x);
return min(lengths0.x,
min(lengths1.x,
min(lengths0.y,
min(lengths1.y,
min(lengths0.z,
lengths1.z)))));
return min(lengths0.x, min(lengths1.x, min(lengths0.y, min(lengths1.y, min(lengths0.z, lengths1.z)))));
}
float hsluv_maxChromaForLH(float L, float H) {
float
hsluv_maxChromaForLH(float L, float H) {
float hrad = radians(H);
// Transposed from GLSL column-major constructor to Slang row-major layout
float3x3 m2 = float3x3(
3.2409699419045214, -1.5373831775700935, -0.49861076029300328,
-0.96924363628087983, 1.8759675015077207, 0.041555057407175613,
0.055630079696993609,-0.20397695888897657, 1.0569715142428786
);
3.2409699419045214,
-1.5373831775700935,
-0.49861076029300328,
-0.96924363628087983,
1.8759675015077207,
0.041555057407175613,
0.055630079696993609,
-0.20397695888897657,
1.0569715142428786);
float sub1 = pow(L + 16.0, 3.0) / 1560896.0;
float sub2 = lerp(L / 903.2962962962963, sub1, step(0.0088564516790356308, sub1));
float3 top1 = (284517.0 * m2[0] - 94839.0 * m2[2]) * sub2;
float3 top1 = (284517.0 * m2[0] - 94839.0 * m2[2]) * sub2;
float3 bottom = (632260.0 * m2[2] - 126452.0 * m2[1]) * sub2;
float3 top2 = (838422.0 * m2[2] + 769860.0 * m2[1] + 731718.0 * m2[0]) * L * sub2;
float3 top2 = (838422.0 * m2[2] + 769860.0 * m2[1] + 731718.0 * m2[0]) * L * sub2;
float3 bound0x = top1 / bottom;
float3 bound0y = top2 / bottom;
float3 bound1x = top1 / (bottom + 126452.0);
float3 bound1x = top1 / (bottom + 126452.0);
float3 bound1y = (top2 - 769860.0 * L) / (bottom + 126452.0);
float3 lengths0 = hsluv_lengthOfRayUntilIntersect(hrad, bound0x, bound0y);
float3 lengths1 = hsluv_lengthOfRayUntilIntersect(hrad, bound1x, bound1y);
return min(lengths0.x,
min(lengths1.x,
min(lengths0.y,
min(lengths1.y,
min(lengths0.z,
lengths1.z)))));
return min(lengths0.x, min(lengths1.x, min(lengths0.y, min(lengths1.y, min(lengths0.z, lengths1.z)))));
}
float3 hsluv_fromLinear(float3 c) {
float3
hsluv_fromLinear(float3 c) {
return lerp(c * 12.92, 1.055 * pow(max(c, (float3)0), (float3)(1.0 / 2.4)) - 0.055, step(0.0031308, c));
}
float3 hsluv_toLinear(float3 c) {
float3
hsluv_toLinear(float3 c) {
return lerp(c / 12.92, pow(max((c + 0.055) / (1.0 + 0.055), (float3)0), (float3)2.4), step(0.04045, c));
}
float hsluv_yToL(float Y) {
float
hsluv_yToL(float Y) {
return lerp(Y * 903.2962962962963, 116.0 * pow(max(Y, 0), 1.0 / 3.0) - 16.0, step(0.0088564516790356308, Y));
}
float hsluv_lToY(float L) {
float
hsluv_lToY(float L) {
return lerp(L / 903.2962962962963, pow((max(L, 0) + 16.0) / 116.0, 3.0), step(8.0, L));
}
float3 xyzToRgb(float3 tuple) {
float3
xyzToRgb(float3 tuple) {
// Transposed layout from GLSL column-major matrix construction
const float3x3 m = float3x3(
3.2409699419045214, -0.96924363628087983, 0.055630079696993609,
-1.5373831775700935, 1.8759675015077207, -0.20397695888897657,
-0.49861076029300328, 0.041555057407175613, 1.0569715142428786
);
3.2409699419045214,
-0.96924363628087983,
0.055630079696993609,
-1.5373831775700935,
1.8759675015077207,
-0.20397695888897657,
-0.49861076029300328,
0.041555057407175613,
1.0569715142428786);
// GLSL `tuple * m` is transformed to Slang/HLSL `mul(m, tuple)`
return hsluv_fromLinear(mul(m, tuple));
}
float3 rgbToXyz(float3 tuple) {
float3
rgbToXyz(float3 tuple) {
// Transposed layout from GLSL column-major matrix construction
const float3x3 m = float3x3(
0.41239079926595948, 0.21263900587151036, 0.019330818715591851,
0.35758433938387796, 0.71516867876775593, 0.11919477979462599,
0.18048078840183429, 0.072192315360733715, 0.95053215224966058
);
0.41239079926595948,
0.21263900587151036,
0.019330818715591851,
0.35758433938387796,
0.71516867876775593,
0.11919477979462599,
0.18048078840183429,
0.072192315360733715,
0.95053215224966058);
// GLSL `tuple * m` is transformed to Slang/HLSL `mul(m, tuple)`
return mul(m, hsluv_toLinear(tuple));
}
float3 xyzToLuv(float3 tuple) {
float3
xyzToLuv(float3 tuple) {
float X = tuple.x;
float Y = tuple.y;
float Z = tuple.z;
@ -143,14 +167,11 @@ float3 xyzToLuv(float3 tuple) {
float L = hsluv_yToL(Y);
float div = 1.0 / max(dot(tuple, float3(1, 15, 3)), 1e-15);
return float3(
1.0,
(52.0 * (X * div) - 2.57179),
(117.0 * (Y * div) - 6.08816)
) * L;
return float3(1.0, (52.0 * (X * div) - 2.57179), (117.0 * (Y * div) - 6.08816)) * L;
}
float3 luvToXyz(float3 tuple) {
float3
luvToXyz(float3 tuple) {
float L = tuple.x;
float U = divide(tuple.y, 13.0 * L) + 0.19783000664283681;
@ -163,7 +184,8 @@ float3 luvToXyz(float3 tuple) {
return float3(X, Y, Z);
}
float3 luvToLch(float3 tuple) {
float3
luvToLch(float3 tuple) {
float L = tuple.x;
float U = tuple.y;
float V = tuple.z;
@ -175,42 +197,45 @@ float3 luvToLch(float3 tuple) {
return float3(L, C, H);
}
float3 lchToLuv(float3 tuple) {
float3
lchToLuv(float3 tuple) {
float hrad = radians(tuple.z);
return float3(
tuple.x,
cos(hrad) * tuple.y,
sin(hrad) * tuple.y
);
return float3(tuple.x, cos(hrad) * tuple.y, sin(hrad) * tuple.y);
}
float3 hsluvToLch(float3 tuple) {
float3
hsluvToLch(float3 tuple) {
tuple.y *= hsluv_maxChromaForLH(tuple.z, tuple.x) * 0.01;
return tuple.zyx;
}
float3 lchToHsluv(float3 tuple) {
float3
lchToHsluv(float3 tuple) {
tuple.y = divide(tuple.y, hsluv_maxChromaForLH(tuple.x, tuple.z) * 0.01);
return tuple.zyx;
}
float3 lchToRgb(float3 tuple) {
float3
lchToRgb(float3 tuple) {
return xyzToRgb(luvToXyz(lchToLuv(tuple)));
}
float3 rgbToLch(float3 tuple) {
float3
rgbToLch(float3 tuple) {
return luvToLch(xyzToLuv(rgbToXyz(tuple)));
}
public float3 hsluvToRgb(float3 tuple) {
public float3
hsluvToRgb(float3 tuple) {
return lchToRgb(hsluvToLch(tuple));
}
public float3 rgbToHsluv(float3 tuple) {
public float3
rgbToHsluv(float3 tuple) {
return lchToHsluv(rgbToLch(tuple));
}
float3 luvToRgb(float3 tuple) {
float3
luvToRgb(float3 tuple) {
return xyzToRgb(luvToXyz(tuple));
}

View file

@ -5,7 +5,8 @@
module linear2srgb;
// Scalar sRGB to Linear conversion
public float srgb2linear(float x) {
public float
srgb2linear(float x) {
float lower = x / 12.92;
float upper = pow((x + 0.055f) / 1.055f, 2.4f);
@ -14,7 +15,8 @@ public float srgb2linear(float x) {
}
// Scalar Linear to sRGB conversion
public float linear2srgb(float x) {
public float
linear2srgb(float x) {
float lower = 12.92 * x;
float upper = 1.055 * pow(x, 1.0f / 2.4f) - 0.055f;
@ -22,7 +24,8 @@ public float linear2srgb(float x) {
}
// Vector Linear to sRGB conversion
public float3 linear2srgb(float3 x) {
public float3
linear2srgb(float3 x) {
float3 lower = 12.92 * x;
float3 upper = 1.055 * pow(x, float3(1.0f / 2.4f)) - 0.055f;
@ -30,9 +33,9 @@ public float3 linear2srgb(float3 x) {
}
// Vector sRGB to Linear conversion
public float3 srgb2linear(float3 c) {
public float3
srgb2linear(float3 c) {
// You can call the scalar version per-component,
// or pass the whole vector if you overload it for float3.
return float3(srgb2linear(c.r), srgb2linear(c.g), srgb2linear(c.b));
}

View file

@ -23,10 +23,10 @@ import background;
// Corner indicator per quad vertex, matching cell_pos_map in background.slang.
// .x: 0 -> left, 1 -> right ; .y: 0 -> top, 1 -> bottom
static const uint2 pad_pos_map[4] = {
uint2(1u, 0u), // right, top
uint2(1u, 1u), // right, bottom
uint2(0u, 1u), // left, bottom
uint2(0u, 0u) // left, top
uint2(1u, 0u), // right, top
uint2(1u, 1u), // right, bottom
uint2(0u, 1u), // left, bottom
uint2(0u, 0u) // left, top
};
struct VertexOutput {
@ -35,12 +35,13 @@ struct VertexOutput {
};
[shader("vertex")]
VertexOutput vertex_main(
VertexOutput
vertex_main(
[[vk::location(0)]] uint3 colors,
[[vk::location(1)]] uint2 sprite_idx,
[[vk::location(2)]] uint is_selected,
uint vertex_id : SV_VertexID,
uint instance_id : SV_InstanceID,
[[vk::location(2)]] uint is_selected,
uint vertex_id: SV_VertexID,
uint instance_id: SV_InstanceID,
// The true cell index (into the cell grid) for strip 0's first instance.
uniform uint base_instance,
uniform uint instance_step,
@ -62,18 +63,17 @@ VertexOutput vertex_main(
// Strip-1 parameters. Equal to strip-0 values for single-strip draws so
// the lerp below is always correct regardless of strip count.
uniform uint base_instance2,
uniform float2 across2,
) {
uniform float2 across2, ) {
VertexOutput vo;
// Branchless strip selector: 0.0 for strip 0, 1.0 for strip 1.
float strip_f = float(instance_id / along_count);
uint strip_i = instance_id % along_count;
uint strip_i = instance_id % along_count;
// Select the base cell and across extents for whichever strip this
// instance belongs to, using lerp so no divergent branch is emitted.
float fbase = lerp(float(base_instance), float(base_instance2), strip_f);
uint real_id = uint(fbase) + strip_i * instance_step;
float fbase = lerp(float(base_instance), float(base_instance2), strip_f);
uint real_id = uint(fbase) + strip_i * instance_step;
float2 chosen_across = lerp(across, across2, strip_f);
vo.color_premul = padding_background_premul(colors, sprite_idx, is_selected, real_id);
@ -89,7 +89,7 @@ VertexOutput vertex_main(
float a = lerp(float(p.x), float(p.y), h);
// 1 when this is the first/last instance within its strip, else 0.
float is_first = 1.0 - min(float(strip_i), 1.0);
float is_last = 1.0 - min(float(along_count - 1u - strip_i), 1.0);
float is_last = 1.0 - min(float(along_count - 1u - strip_i), 1.0);
float along = lerp(along_lo, along_hi, s);
// Extend the first cell's near corner and the last cell's far corner out to
@ -104,6 +104,8 @@ VertexOutput vertex_main(
}
[shader("fragment")]
float4 fragment_main(float4 color_premul : COLOR_PREMUL) : SV_Target {
float4
fragment_main(float4 color_premul: COLOR_PREMUL)
: SV_Target {
return color_premul;
}

View file

@ -9,23 +9,21 @@ import alpha_blend;
#define right 2
#define bottom 3
static const int2 vertex_pos_map[4] = {
int2(right, top),
int2(right, bottom),
int2(left, bottom),
int2(left, top)
};
static const int2 vertex_pos_map[4] = { int2(right, top), int2(right, bottom), int2(left, bottom), int2(left, top) };
static const float4 dest_rect = float4(-1, 1, 1, -1);
[shader("vertex")]
float4 vertex_main(uint vertex_id : SV_VertexID) : SV_Position {
float4
vertex_main(uint vertex_id: SV_VertexID)
: SV_Position {
int2 pos = vertex_pos_map[vertex_id];
return float4(dest_rect[pos.x], dest_rect[pos.y], 0, 1);
}
// Signed distance function for a rounded rectangle
float rounded_rectangle_sdf(float2 p, float2 b, float r) {
float
rounded_rectangle_sdf(float2 p, float2 b, float r) {
// signed distance field
// first term is used for points outside the rectangle
float2 q = abs(p) - b;
@ -33,13 +31,15 @@ float rounded_rectangle_sdf(float2 p, float2 b, float r) {
}
[shader("fragment")]
float4 fragment_main(
float4
fragment_main(
uniform float4 rect,
uniform float2 params,
uniform float4 color,
uniform float4 background_color,
float4 frag_coord : SV_Position // SV_Position provides gl_FragCoord equivalent in Slang
) : SV_Target {
float4 frag_coord: SV_Position // SV_Position provides gl_FragCoord equivalent in Slang
)
: SV_Target {
float2 size = rect.zw; // GLSL .ba maps to .zw or .ba in Slang (using .zw is typical)
float2 origin = rect.xy;
float thickness = params[0];

View file

@ -12,18 +12,16 @@ struct VSOutput {
[shader("vertex")]
VSOutput vertex_main(
uint vertex_id : SV_VertexID,
uniform float4 src_rect,
uniform float4 dest_rect,
) {
VSOutput
vertex_main(uint vertex_id: SV_VertexID, uniform float4 src_rect, uniform float4 dest_rect, ) {
BlitOutput ans = get_coords_for_blit(vertex_id, src_rect, dest_rect);
return {ans.texcoord, float4(ans.position[0], ans.position[1], 0.0, 1.0)};
return { ans.texcoord, float4(ans.position[0], ans.position[1], 0.0, 1.0) };
}
uniform Sampler2D image;
float3 safe_unpremult_to_linear(float4 s) {
float3
safe_unpremult_to_linear(float4 s) {
// Avoid division by zero by replacing 0.0 alpha with 1.0.
// If alpha is 0.0, the division is safe, and lerp masks the result to 0.0 anyway.
float safe_alpha = lerp(1.0f, s.a, step(0.00001f, s.a));
@ -33,34 +31,37 @@ float3 safe_unpremult_to_linear(float4 s) {
}
[shader("fragment")]
float4 fragment_main(float2 texcoord : TEXCOORD, uniform float2 src_size) : SV_Target {
float4
fragment_main(float2 texcoord: TEXCOORD, uniform float2 src_size)
: SV_Target {
float4 s00, s10, s01, s11;
float2 texel_size = 1.0 / src_size;
// Use Slang target switches to dynamically compile for specific backend features
__target_switch
{
// Modern backends with full texture gathering capabilities
case spirv: case hlsl: case metal: {
float2 gather_coord = texcoord - (0.5 * texel_size);
__target_switch {
// Modern backends with full texture gathering capabilities
case spirv:
case hlsl:
case metal: {
float2 gather_coord = texcoord - (0.5 * texel_size);
float4 r_gather = image.GatherRed(gather_coord);
float4 g_gather = image.GatherGreen(gather_coord);
float4 b_gather = image.GatherBlue(gather_coord);
float4 a_gather = image.GatherAlpha(gather_coord);
float4 r_gather = image.GatherRed(gather_coord);
float4 g_gather = image.GatherGreen(gather_coord);
float4 b_gather = image.GatherBlue(gather_coord);
float4 a_gather = image.GatherAlpha(gather_coord);
s00 = float4(r_gather.w, g_gather.w, b_gather.w, a_gather.w); // Bottom-Left
s10 = float4(r_gather.z, g_gather.z, b_gather.z, a_gather.z); // Bottom-Right
s01 = float4(r_gather.x, g_gather.x, b_gather.x, a_gather.x); // Top-Left
s11 = float4(r_gather.y, g_gather.y, b_gather.y, a_gather.y); // Top-Right
}
// Fallback for older targets or legacy GLSL versions
default: {
s00 = image.Sample(texcoord + float2(-0.25, -0.25) * texel_size);
s10 = image.Sample(texcoord + float2( 0.25, -0.25) * texel_size);
s01 = image.Sample(texcoord + float2(-0.25, 0.25) * texel_size);
s11 = image.Sample(texcoord + float2( 0.25, 0.25) * texel_size);
}
s00 = float4(r_gather.w, g_gather.w, b_gather.w, a_gather.w); // Bottom-Left
s10 = float4(r_gather.z, g_gather.z, b_gather.z, a_gather.z); // Bottom-Right
s01 = float4(r_gather.x, g_gather.x, b_gather.x, a_gather.x); // Top-Left
s11 = float4(r_gather.y, g_gather.y, b_gather.y, a_gather.y); // Top-Right
}
// Fallback for older targets or legacy GLSL versions
default: {
s00 = image.Sample(texcoord + float2(-0.25, -0.25) * texel_size);
s10 = image.Sample(texcoord + float2(0.25, -0.25) * texel_size);
s01 = image.Sample(texcoord + float2(-0.25, 0.25) * texel_size);
s11 = image.Sample(texcoord + float2(0.25, 0.25) * texel_size);
}
}
// Unpremultiply and convert to linear for each sample

View file

@ -406,8 +406,8 @@ def parse_slang_text(src_code: str, path: str = '') -> SlangFile:
entry_points.append(EntryPoint(Stage.vertex, name))
case 'fragment' | 'pixel':
entry_points.append(EntryPoint(Stage.fragment, name))
found_entry_point = ''
break
found_entry_point = ''
else:
match words[0]:
case 'module':

View file

@ -4,29 +4,28 @@
// Main Vertex Shader Entry Point
[shader("vertex")]
float4 vertex_main(
uint vertex_id : SV_VertexID,
float4
vertex_main(
uint vertex_id: SV_VertexID,
uniform float4 edges, // [ left, top, right, bottom ]
) : SV_Position {
)
: SV_Position {
// Extract boundaries from the edges vector
float left = edges[0];
float top = edges[1];
float right = edges[2];
float left = edges[0];
float top = edges[1];
float right = edges[2];
float bottom = edges[3];
// Static mapping table for vertex positions
const float2 pos_map[4] = {
float2(left, top),
float2(left, bottom),
float2(right, bottom),
float2(right, top)
};
const float2 pos_map[4] = { float2(left, top), float2(left, bottom), float2(right, bottom), float2(right, top) };
// Calculate final position
return float4(pos_map[vertex_id], 0.0, 1.0);
}
[shader("fragment")]
float4 fragment_main(uniform float4 tint_color) : SV_Target {
float4
fragment_main(uniform float4 tint_color)
: SV_Target {
return tint_color;
}

View file

@ -2,15 +2,15 @@
// Copyright (C) 2026 Kovid Goyal <kovid at kovidgoyal.net>
// Distributed under terms of the GPLv3 license.
struct VertexOutput
{
struct VertexOutput {
float2 frag_pos : TEXCOORD;
float4 position : SV_Position;
};
// Main Vertex Shader Entry Point
[shader("vertex")]
VertexOutput vertex_main(uint vertex_id : SV_VertexID, uniform float4 x_coords, uniform float4 y_coords) {
VertexOutput
vertex_main(uint vertex_id: SV_VertexID, uniform float4 x_coords, uniform float4 y_coords) {
VertexOutput output;
float2 pos = float2(x_coords[vertex_id], y_coords[vertex_id]);
output.position = float4(pos, 1.0, 1.0);
@ -21,13 +21,9 @@ VertexOutput vertex_main(uint vertex_id : SV_VertexID, uniform float4 x_coords,
// Main Fragment Shader Entry Point
[shader("fragment")]
float4 fragment_main(
float2 frag_pos : TEXCOORD,
uniform float2 cursor_edge_x,
uniform float2 cursor_edge_y,
uniform float3 trail_color,
uniform float trail_opacity
) : SV_Target {
float4
fragment_main(float2 frag_pos: TEXCOORD, uniform float2 cursor_edge_x, uniform float2 cursor_edge_y, uniform float3 trail_color, uniform float trail_opacity)
: SV_Target {
float opacity = trail_opacity;
// Evaluate if the fragment falls inside the bounding box of the cursor
float in_x = step(cursor_edge_x[0], frag_pos.x) * step(frag_pos.x, cursor_edge_x[1]);

View file

@ -5,30 +5,31 @@
module utils;
// Return 0 if x < 1 otherwise 1
public __generic<T : __BuiltinFloatingPointType, int N = 1>
vector<T, N> zero_or_one(vector<T, N> x) {
public __generic<T : __BuiltinFloatingPointType, int N = 1> vector<T, N>
zero_or_one(vector<T, N> x) {
return step((vector<T, N>)1.0f, x);
}
// condition must be zero or one. When 1 thenval is returned otherwise elseval
public __generic<T : __BuiltinFloatingPointType, int N = 1>
vector<T, N> if_one_then(vector<T, N> condition, vector<T, N> thenval, vector<T, N> elseval) {
public __generic<T : __BuiltinFloatingPointType, int N = 1> vector<T, N>
if_one_then(vector<T, N> condition, vector<T, N> thenval, vector<T, N> elseval) {
return lerp(elseval, thenval, condition);
}
// a < b ? thenval : elseval
public __generic<T : __BuiltinFloatingPointType, int N = 1>
vector<T, N> if_less_than(vector<T, N> a, vector<T, N> b, vector<T, N> thenval, vector<T, N> elseval) {
public __generic<T : __BuiltinFloatingPointType, int N = 1> vector<T, N>
if_less_than(vector<T, N> a, vector<T, N> b, vector<T, N> thenval, vector<T, N> elseval) {
return lerp(thenval, elseval, step(b, a));
}
// Replaces vec4(rgb * a, a)
public float4 vec4_premul(float3 rgb, float a) {
public float4
vec4_premul(float3 rgb, float a) {
return float4(rgb * a, a);
}
// Overloaded variation replacing vec4(rgba.rgb * rgba.a, rgba.a)
public float4 vec4_premul(float4 rgba) {
public float4
vec4_premul(float4 rgba) {
return float4(rgba.rgb * rgba.a, rgba.a);
}

View file

@ -152,6 +152,58 @@ void vsMain() {}
SlangFile('', '', frozenset({'common'}), frozenset({EntryPoint(Stage.vertex, 'vsMain')}), 'myshader'),
)
# Autoformat style: return type on its own line before function name
check(
"""
[shader("vertex")]
VertexOutput
vertex_main(uint vertex_id : SV_VertexID) {}
""",
SlangFile('', '', frozenset(), frozenset({EntryPoint(Stage.vertex, 'vertex_main')})),
)
# Autoformat style: return type on own line, function name + open-paren on next, params on following lines
check(
"""
[shader("fragment")]
float4
fragment_main(
float2 texcoord : TEXCOORD
) : SV_Target { return float4(0); }
""",
SlangFile('', '', frozenset(), frozenset({EntryPoint(Stage.fragment, 'fragment_main')})),
)
# Autoformat style: attribute lines between [shader] and split return-type/name declaration
check(
"""
[shader("fragment")]
[numthreads(1, 1, 1)]
float4
psMain() : SV_Target { return float4(1, 0, 0, 1); }
""",
SlangFile('', '', frozenset(), frozenset({EntryPoint(Stage.fragment, 'psMain')})),
)
# Autoformat style: vertex and fragment both split across lines
check(
"""
[shader("vertex")]
VertexOutput
vsMain(uint id : SV_VertexID) {}
[shader("fragment")]
float4
fsMain(VertexOutput vo) : SV_Target { return float4(0); }
""",
SlangFile(
'',
'',
frozenset(),
frozenset({EntryPoint(Stage.vertex, 'vsMain'), EntryPoint(Stage.fragment, 'fsMain')}),
),
)
def test_slang_ordering(self):
# Test topological_sort with a manually constructed linear chain: a <- b <- c
graph: dict[str, SlangFile] = {