refactor for background process + spawning gui

This commit is contained in:
Zacharias-Brohn
2025-12-15 12:20:37 +01:00
parent 5d47177fbf
commit e4d742cadf
19 changed files with 5928 additions and 4384 deletions
+313
View File
@@ -1,4 +1,9 @@
// Glyph rendering shader for terminal emulator
// Supports both legacy quad-based rendering and new instanced cell rendering
// ═══════════════════════════════════════════════════════════════════════════════
// LEGACY QUAD-BASED RENDERING (for backwards compatibility)
// ═══════════════════════════════════════════════════════════════════════════════
struct VertexInput {
@location(0) position: vec2<f32>,
@@ -46,3 +51,311 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
// The background was already rendered, so we just blend the glyph on top
return vec4<f32>(in.color.rgb, in.color.a * glyph_alpha);
}
// ═══════════════════════════════════════════════════════════════════════════════
// KITTY-STYLE INSTANCED CELL RENDERING
// ═══════════════════════════════════════════════════════════════════════════════
// Color table uniform containing 256 indexed colors + default fg/bg
struct ColorTable {
// 256 indexed colors + default_fg (256) + default_bg (257)
colors: array<vec4<f32>, 258>,
}
// Grid parameters uniform
struct GridParams {
// Grid dimensions in cells
cols: u32,
rows: u32,
// Cell dimensions in pixels
cell_width: f32,
cell_height: f32,
// Screen dimensions in pixels
screen_width: f32,
screen_height: f32,
// Y offset for tab bar
y_offset: f32,
// Cursor position (-1 if hidden)
cursor_col: i32,
cursor_row: i32,
// Cursor style: 0=block, 1=underline, 2=bar
cursor_style: u32,
// Padding
_padding: vec2<u32>,
}
// GPUCell instance data (matches Rust GPUCell struct)
struct GPUCell {
fg: u32,
bg: u32,
decoration_fg: u32,
sprite_idx: u32,
attrs: u32,
}
// Sprite info for glyph positioning
struct SpriteInfo {
// UV coordinates in atlas (x, y, width, height) - normalized 0-1
uv: vec4<f32>,
// Offset from cell origin (x, y) in pixels
offset: vec2<f32>,
// Size in pixels
size: vec2<f32>,
}
// Uniforms and storage buffers for instanced rendering
@group(1) @binding(0)
var<uniform> color_table: ColorTable;
@group(1) @binding(1)
var<uniform> grid_params: GridParams;
@group(1) @binding(2)
var<storage, read> cells: array<GPUCell>;
@group(1) @binding(3)
var<storage, read> sprites: array<SpriteInfo>;
// Constants for packed color decoding
const COLOR_TYPE_DEFAULT: u32 = 0u;
const COLOR_TYPE_INDEXED: u32 = 1u;
const COLOR_TYPE_RGB: u32 = 2u;
// Constants for cell attributes
const ATTR_DECORATION_MASK: u32 = 0x7u;
const ATTR_BOLD_BIT: u32 = 0x8u;
const ATTR_ITALIC_BIT: u32 = 0x10u;
const ATTR_REVERSE_BIT: u32 = 0x20u;
const ATTR_STRIKE_BIT: u32 = 0x40u;
const ATTR_DIM_BIT: u32 = 0x80u;
// Colored glyph flag
const COLORED_GLYPH_FLAG: u32 = 0x80000000u;
// Vertex output for instanced cell rendering
struct CellVertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) fg_color: vec4<f32>,
@location(2) bg_color: vec4<f32>,
@location(3) @interpolate(flat) is_background: u32,
@location(4) @interpolate(flat) is_colored_glyph: u32,
}
// Resolve a packed color to RGBA
fn resolve_color(packed: u32, is_foreground: bool) -> vec4<f32> {
let color_type = packed & 0xFFu;
if color_type == COLOR_TYPE_DEFAULT {
// Default color - use color table entry 256 (fg) or 257 (bg)
if is_foreground {
return color_table.colors[256];
} else {
return color_table.colors[257];
}
} else if color_type == COLOR_TYPE_INDEXED {
// Indexed color - look up in color table
let index = (packed >> 8u) & 0xFFu;
return color_table.colors[index];
} else {
// RGB color - extract components
let r = f32((packed >> 8u) & 0xFFu) / 255.0;
let g = f32((packed >> 16u) & 0xFFu) / 255.0;
let b = f32((packed >> 24u) & 0xFFu) / 255.0;
return vec4<f32>(r, g, b, 1.0);
}
}
// Convert sRGB to linear (for GPU rendering to sRGB surface)
fn srgb_to_linear(c: f32) -> f32 {
if c <= 0.04045 {
return c / 12.92;
} else {
return pow((c + 0.055) / 1.055, 2.4);
}
}
// Convert pixel coordinate to NDC
fn pixel_to_ndc(pixel: vec2<f32>, screen: vec2<f32>) -> vec2<f32> {
return vec2<f32>(
(pixel.x / screen.x) * 2.0 - 1.0,
1.0 - (pixel.y / screen.y) * 2.0
);
}
// Background vertex shader (renders cell backgrounds)
// vertex_index: 0-3 for quad corners
// instance_index: cell index in row-major order
@vertex
fn vs_cell_bg(
@builtin(vertex_index) vertex_index: u32,
@builtin(instance_index) instance_index: u32
) -> CellVertexOutput {
let col = instance_index % grid_params.cols;
let row = instance_index / grid_params.cols;
// Skip if out of bounds
if row >= grid_params.rows {
var out: CellVertexOutput;
out.clip_position = vec4<f32>(0.0, 0.0, 0.0, 0.0);
return out;
}
// Get cell data
let cell = cells[instance_index];
// Calculate cell pixel position
let cell_x = f32(col) * grid_params.cell_width;
let cell_y = grid_params.y_offset + f32(row) * grid_params.cell_height;
// Quad vertex positions (0=top-left, 1=top-right, 2=bottom-right, 3=bottom-left)
var positions: array<vec2<f32>, 4>;
positions[0] = vec2<f32>(cell_x, cell_y);
positions[1] = vec2<f32>(cell_x + grid_params.cell_width, cell_y);
positions[2] = vec2<f32>(cell_x + grid_params.cell_width, cell_y + grid_params.cell_height);
positions[3] = vec2<f32>(cell_x, cell_y + grid_params.cell_height);
let screen_size = vec2<f32>(grid_params.screen_width, grid_params.screen_height);
let ndc_pos = pixel_to_ndc(positions[vertex_index], screen_size);
// Resolve colors
let attrs = cell.attrs;
let is_reverse = (attrs & ATTR_REVERSE_BIT) != 0u;
var fg = resolve_color(cell.fg, true);
var bg = resolve_color(cell.bg, false);
// Handle reverse video
if is_reverse {
let tmp = fg;
fg = bg;
bg = tmp;
}
// Convert to linear for sRGB surface
fg = vec4<f32>(srgb_to_linear(fg.r), srgb_to_linear(fg.g), srgb_to_linear(fg.b), fg.a);
bg = vec4<f32>(srgb_to_linear(bg.r), srgb_to_linear(bg.g), srgb_to_linear(bg.b), bg.a);
var out: CellVertexOutput;
out.clip_position = vec4<f32>(ndc_pos, 0.0, 1.0);
out.uv = vec2<f32>(0.0, 0.0); // Not used for background
out.fg_color = fg;
out.bg_color = bg;
out.is_background = 1u;
out.is_colored_glyph = 0u;
return out;
}
// Glyph vertex shader (renders cell glyphs)
@vertex
fn vs_cell_glyph(
@builtin(vertex_index) vertex_index: u32,
@builtin(instance_index) instance_index: u32
) -> CellVertexOutput {
let col = instance_index % grid_params.cols;
let row = instance_index / grid_params.cols;
// Skip if out of bounds
if row >= grid_params.rows {
var out: CellVertexOutput;
out.clip_position = vec4<f32>(0.0, 0.0, 0.0, 0.0);
return out;
}
// Get cell data
let cell = cells[instance_index];
let sprite_idx = cell.sprite_idx & ~COLORED_GLYPH_FLAG;
let is_colored = (cell.sprite_idx & COLORED_GLYPH_FLAG) != 0u;
// Skip if no glyph
if sprite_idx == 0u {
var out: CellVertexOutput;
out.clip_position = vec4<f32>(0.0, 0.0, 0.0, 0.0);
return out;
}
// Get sprite info
let sprite = sprites[sprite_idx];
// Skip if sprite has no size
if sprite.size.x <= 0.0 || sprite.size.y <= 0.0 {
var out: CellVertexOutput;
out.clip_position = vec4<f32>(0.0, 0.0, 0.0, 0.0);
return out;
}
// Calculate cell pixel position
let cell_x = f32(col) * grid_params.cell_width;
let cell_y = grid_params.y_offset + f32(row) * grid_params.cell_height;
// Calculate glyph position (baseline-relative)
let baseline_y = cell_y + grid_params.cell_height * 0.8;
let glyph_x = cell_x + sprite.offset.x;
let glyph_y = baseline_y - sprite.offset.y - sprite.size.y;
// Quad vertex positions
var positions: array<vec2<f32>, 4>;
positions[0] = vec2<f32>(glyph_x, glyph_y);
positions[1] = vec2<f32>(glyph_x + sprite.size.x, glyph_y);
positions[2] = vec2<f32>(glyph_x + sprite.size.x, glyph_y + sprite.size.y);
positions[3] = vec2<f32>(glyph_x, glyph_y + sprite.size.y);
// UV coordinates
var uvs: array<vec2<f32>, 4>;
uvs[0] = vec2<f32>(sprite.uv.x, sprite.uv.y);
uvs[1] = vec2<f32>(sprite.uv.x + sprite.uv.z, sprite.uv.y);
uvs[2] = vec2<f32>(sprite.uv.x + sprite.uv.z, sprite.uv.y + sprite.uv.w);
uvs[3] = vec2<f32>(sprite.uv.x, sprite.uv.y + sprite.uv.w);
let screen_size = vec2<f32>(grid_params.screen_width, grid_params.screen_height);
let ndc_pos = pixel_to_ndc(positions[vertex_index], screen_size);
// Resolve colors
let attrs = cell.attrs;
let is_reverse = (attrs & ATTR_REVERSE_BIT) != 0u;
var fg = resolve_color(cell.fg, true);
var bg = resolve_color(cell.bg, false);
if is_reverse {
let tmp = fg;
fg = bg;
bg = tmp;
}
// Convert to linear
fg = vec4<f32>(srgb_to_linear(fg.r), srgb_to_linear(fg.g), srgb_to_linear(fg.b), fg.a);
var out: CellVertexOutput;
out.clip_position = vec4<f32>(ndc_pos, 0.0, 1.0);
out.uv = uvs[vertex_index];
out.fg_color = fg;
out.bg_color = vec4<f32>(0.0, 0.0, 0.0, 0.0);
out.is_background = 0u;
out.is_colored_glyph = select(0u, 1u, is_colored);
return out;
}
// Fragment shader for cell rendering (both background and glyph)
@fragment
fn fs_cell(in: CellVertexOutput) -> @location(0) vec4<f32> {
if in.is_background == 1u {
// Background - just output the bg color
return in.bg_color;
}
// Glyph - sample from atlas
let glyph_alpha = textureSample(atlas_texture, atlas_sampler, in.uv).r;
if in.is_colored_glyph == 1u {
// Colored glyph (emoji) - use atlas color directly
// Note: For now we just use alpha since our atlas is single-channel
// Full emoji support would need an RGBA atlas
return vec4<f32>(in.fg_color.rgb, glyph_alpha);
}
// Normal glyph - tint with foreground color
return vec4<f32>(in.fg_color.rgb, in.fg_color.a * glyph_alpha);
}