diff --git a/kitty/shaders/alpha-blend.slang b/kitty/shaders/alpha-blend.slang new file mode 100644 index 000000000..10fe70bc6 --- /dev/null +++ b/kitty/shaders/alpha-blend.slang @@ -0,0 +1,28 @@ +#language slang 2026 +// Copyright (C) 2026 Kovid Goyal +// Distributed under terms of the GPLv3 license. + +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) { + 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) { + 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); +} + +// 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) { + float inv_over_alpha = 1.0f - over.a; + return float4(over.rgb + under.rgb * inv_over_alpha, 1.0f); +} + diff --git a/kitty/shaders/graphics.slang b/kitty/shaders/graphics.slang index 8497e8105..730bc94c8 100644 --- a/kitty/shaders/graphics.slang +++ b/kitty/shaders/graphics.slang @@ -13,7 +13,7 @@ struct VSOutput [shader("vertex")] -VSOutput main( +VSOutput vertex_main( uint vertex_id : SV_VertexID, uniform float4 src_rect, uniform float4 dest_rect, @@ -25,3 +25,11 @@ VSOutput main( return output; } +Sampler2D image; + +[shader("fragment")] +float4 fragment_main(float2 texcoord : TEXCOORD) : SV_Target +{ + float4 color = image.Sample(texcoord); + return color; +} diff --git a/kitty/shaders/slang.py b/kitty/shaders/slang.py index 26e003381..d5b3d551f 100644 --- a/kitty/shaders/slang.py +++ b/kitty/shaders/slang.py @@ -178,7 +178,7 @@ def iter_entry_point_shaders(sources: dict[str, SlangFile], build_dir: str, dest def commands_to_compile_to_spirv(sources: dict[str, SlangFile], build_dir: str, dest_dir: str, built_files: list[str]) -> Iterator[Command]: for base_dest, slang_module, cmd, sfile in iter_entry_point_shaders(sources, build_dir, dest_dir): dest = f'{base_dest}.spv' - cmd += ['-target', 'spirv', '-o', dest] + cmd += ['-target', 'spirv', '-capability', 'vk_mem_model', '-fvk-use-entrypoint-name', '-o', dest] output_mtime = safe_mtime(dest) module_mtime = os.path.getmtime(slang_module) needs_build = output_mtime < module_mtime @@ -189,19 +189,17 @@ def commands_to_compile_to_spirv(sources: dict[str, SlangFile], build_dir: str, def commands_to_compile_to_glsl(sources: dict[str, SlangFile], build_dir: str, dest_dir: str, built_glsl_files: list[str]) -> Iterator[Command]: for base_dest, slang_module, cmd, sfile in iter_entry_point_shaders(sources, build_dir, dest_dir): - output_mtime = future() - dest_files = [] - cmd.extend(('-line-directive-mode', 'none')) - for ep in sfile.entry_points: - dest = f'{base_dest}.{ep.stage.name}.glsl' - cmd += ['-entry', ep.name, '-stage', ep.stage.name, '-target', 'glsl', '-profile', 'glsl_330', '-o', dest] - dest_files.append(dest) - output_mtime = min(output_mtime, safe_mtime(dest)) module_mtime = os.path.getmtime(slang_module) - needs_build = output_mtime < module_mtime - if needs_build: - built_glsl_files.extend(dest_files) - yield Command(needs_build, f'Linking |{os.path.basename(slang_module)}| to GLSL ...', cmd) + for ep in sfile.entry_points: + c = list(cmd) + c.extend(('-line-directive-mode', 'none', '-target', 'glsl', '-profile', 'glsl_330')) + dest = f'{base_dest}.{ep.stage.name}.glsl' + c += ['-entry', ep.name, '-stage', ep.stage.name, '-o', dest] + output_mtime = safe_mtime(dest) + needs_build = output_mtime < module_mtime + if needs_build: + built_glsl_files.append(dest) + yield Command(needs_build, f'Linking |{os.path.basename(slang_module)}| to GLSL {ep.stage} shader ...', c) def fixup_opengl_code(glsl_code: str) -> str: @@ -234,7 +232,7 @@ def fixup_opengl_code(glsl_code: str) -> str: line = '#version 330 core' elif line.startswith('#extension ') or line in ('layout(row_major) buffer;', 'layout(push_constant)'): line = '// ' + line - elif line.startswith('layout(location ='): + elif line.startswith('layout(location =') or line.startswith('layout(binding ='): line = '// ' + line else: words = line.split() diff --git a/kitty/shaders/utils.slang b/kitty/shaders/utils.slang new file mode 100644 index 000000000..9b5673aca --- /dev/null +++ b/kitty/shaders/utils.slang @@ -0,0 +1,34 @@ +#language slang 2026 +// Copyright (C) 2026 Kovid Goyal +// Distributed under terms of the GPLv3 license. + +module utils; + +// Return 0 if x < 1 otherwise 1 +__generic +vector zero_or_one(vector x) { + return step((vector)1.0f, x); +} + +// condition must be zero or one. When 1 thenval is returned otherwise elseval +__generic +vector if_one_then(vector condition, vector thenval, vector elseval) { + return lerp(elseval, thenval, condition); +} + +// a < b ? thenval : elseval +__generic +vector if_less_than(vector a, vector b, vector thenval, vector elseval) { + return lerp(thenval, elseval, step(b, a)); +} + +// Replaces vec4(rgb * a, a) +float4 vec4_premul(float3 rgb, float a) { + return float4(rgb * a, a); +} + +// Overloaded variation replacing vec4(rgba.rgb * rgba.a, rgba.a) +float4 vec4_premul(float4 rgba) { + return float4(rgba.rgb * rgba.a, rgba.a); +} +