aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/renderer/shaders/glsl
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/renderer/shaders/glsl
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/renderer/shaders/glsl')
-rw-r--r--vendor/libghostty-vt/src/renderer/shaders/glsl/bg_color.f.glsl13
-rw-r--r--vendor/libghostty-vt/src/renderer/shaders/glsl/bg_image.f.glsl63
-rw-r--r--vendor/libghostty-vt/src/renderer/shaders/glsl/bg_image.v.glsl145
-rw-r--r--vendor/libghostty-vt/src/renderer/shaders/glsl/cell_bg.f.glsl61
-rw-r--r--vendor/libghostty-vt/src/renderer/shaders/glsl/cell_text.f.glsl100
-rw-r--r--vendor/libghostty-vt/src/renderer/shaders/glsl/cell_text.v.glsl146
-rw-r--r--vendor/libghostty-vt/src/renderer/shaders/glsl/common.glsl156
-rw-r--r--vendor/libghostty-vt/src/renderer/shaders/glsl/full_screen.v.glsl24
-rw-r--r--vendor/libghostty-vt/src/renderer/shaders/glsl/image.f.glsl21
-rw-r--r--vendor/libghostty-vt/src/renderer/shaders/glsl/image.v.glsl47
10 files changed, 776 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/renderer/shaders/glsl/bg_color.f.glsl b/vendor/libghostty-vt/src/renderer/shaders/glsl/bg_color.f.glsl
new file mode 100644
index 00000000..616c44b8
--- /dev/null
+++ b/vendor/libghostty-vt/src/renderer/shaders/glsl/bg_color.f.glsl
@@ -0,0 +1,13 @@
+#include "common.glsl"
+
+// Must declare this output for some versions of OpenGL.
+layout(location = 0) out vec4 out_FragColor;
+
+void main() {
+ bool use_linear_blending = (bools & USE_LINEAR_BLENDING) != 0;
+
+ out_FragColor = load_color(
+ unpack4u8(bg_color_packed_4u8),
+ use_linear_blending
+ );
+}
diff --git a/vendor/libghostty-vt/src/renderer/shaders/glsl/bg_image.f.glsl b/vendor/libghostty-vt/src/renderer/shaders/glsl/bg_image.f.glsl
new file mode 100644
index 00000000..ee1195ef
--- /dev/null
+++ b/vendor/libghostty-vt/src/renderer/shaders/glsl/bg_image.f.glsl
@@ -0,0 +1,63 @@
+#include "common.glsl"
+
+// Position the FragCoord origin to the upper left
+// so as to align with our texture's directionality.
+layout(origin_upper_left) in vec4 gl_FragCoord;
+
+layout(binding = 0) uniform sampler2D image;
+
+flat in vec4 bg_color;
+flat in vec2 offset;
+flat in vec2 scale;
+flat in float opacity;
+flat in uint repeat;
+
+layout(location = 0) out vec4 out_FragColor;
+
+void main() {
+ bool use_linear_blending = (bools & USE_LINEAR_BLENDING) != 0;
+
+ // Our texture coordinate is based on the screen position, offset by the
+ // dest rect origin, and scaled by the ratio between the dest rect size
+ // and the original texture size, which effectively scales the original
+ // size of the texture to the dest rect size.
+ vec2 tex_coord = (gl_FragCoord.xy - offset) * scale;
+
+ vec2 tex_size = textureSize(image, 0);
+
+ // If we need to repeat the texture, wrap the coordinates.
+ if (repeat != 0) {
+ tex_coord = mod(mod(tex_coord, tex_size) + tex_size, tex_size);
+ }
+
+ vec4 rgba;
+ // If we're out of bounds, we have no color,
+ // otherwise we sample the texture for it.
+ if (any(lessThan(tex_coord, vec2(0.0))) ||
+ any(greaterThan(tex_coord, tex_size)))
+ {
+ rgba = vec4(0.0);
+ } else {
+ // We divide by the texture size to normalize for sampling.
+ rgba = texture(image, tex_coord / tex_size);
+
+ if (!use_linear_blending) {
+ rgba = unlinearize(rgba);
+ }
+
+ rgba.rgb *= rgba.a;
+ }
+
+ // Multiply it by the configured opacity, but cap it at
+ // the value that will make it fully opaque relative to
+ // the background color alpha, so it isn't overexposed.
+ rgba *= min(opacity, 1.0 / bg_color.a);
+
+ // Blend it on to a fully opaque version of the background color.
+ rgba += max(vec4(0.0), vec4(bg_color.rgb, 1.0) * vec4(1.0 - rgba.a));
+
+ // Multiply everything by the background color alpha.
+ rgba *= bg_color.a;
+
+ out_FragColor = rgba;
+}
diff --git a/vendor/libghostty-vt/src/renderer/shaders/glsl/bg_image.v.glsl b/vendor/libghostty-vt/src/renderer/shaders/glsl/bg_image.v.glsl
new file mode 100644
index 00000000..d55aa174
--- /dev/null
+++ b/vendor/libghostty-vt/src/renderer/shaders/glsl/bg_image.v.glsl
@@ -0,0 +1,145 @@
+#include "common.glsl"
+
+layout(binding = 0) uniform sampler2D image;
+
+layout(location = 0) in float in_opacity;
+layout(location = 1) in uint info;
+
+// 4 bits of info.
+const uint BG_IMAGE_POSITION = 15u;
+const uint BG_IMAGE_TL = 0u;
+const uint BG_IMAGE_TC = 1u;
+const uint BG_IMAGE_TR = 2u;
+const uint BG_IMAGE_ML = 3u;
+const uint BG_IMAGE_MC = 4u;
+const uint BG_IMAGE_MR = 5u;
+const uint BG_IMAGE_BL = 6u;
+const uint BG_IMAGE_BC = 7u;
+const uint BG_IMAGE_BR = 8u;
+
+// 2 bits of info shifted 4.
+const uint BG_IMAGE_FIT = 3u << 4;
+const uint BG_IMAGE_CONTAIN = 0u << 4;
+const uint BG_IMAGE_COVER = 1u << 4;
+const uint BG_IMAGE_STRETCH = 2u << 4;
+const uint BG_IMAGE_NO_FIT = 3u << 4;
+
+// 1 bit of info shifted 6.
+const uint BG_IMAGE_REPEAT = 1u << 6;
+
+flat out vec4 bg_color;
+flat out vec2 offset;
+flat out vec2 scale;
+flat out float opacity;
+// We use a uint to pass the repeat value because
+// bools aren't allowed for vertex outputs in OpenGL.
+flat out uint repeat;
+
+void main() {
+ bool use_linear_blending = (bools & USE_LINEAR_BLENDING) != 0;
+
+ vec4 position;
+ position.x = (gl_VertexID == 2) ? 3.0 : -1.0;
+ position.y = (gl_VertexID == 0) ? -3.0 : 1.0;
+ position.z = 1.0;
+ position.w = 1.0;
+
+ // Single triangle is clipped to viewport.
+ //
+ // X <- vid == 0: (-1, -3)
+ // |\
+ // | \
+ // | \
+ // |###\
+ // |#+# \ `+` is (0, 0). `#`s are viewport area.
+ // |### \
+ // X------X <- vid == 2: (3, 1)
+ // ^
+ // vid == 1: (-1, 1)
+
+ gl_Position = position;
+
+ opacity = in_opacity;
+
+ repeat = info & BG_IMAGE_REPEAT;
+
+ vec2 screen_size = screen_size;
+ vec2 tex_size = textureSize(image, 0);
+
+ vec2 dest_size = tex_size;
+ switch (info & BG_IMAGE_FIT) {
+ // For `contain` we scale by a factor that makes the image
+ // width match the screen width or makes the image height
+ // match the screen height, whichever is smaller.
+ case BG_IMAGE_CONTAIN: {
+ float scale = min(screen_size.x / tex_size.x, screen_size.y / tex_size.y);
+ dest_size = tex_size * scale;
+ } break;
+
+ // For `cover` we scale by a factor that makes the image
+ // width match the screen width or makes the image height
+ // match the screen height, whichever is larger.
+ case BG_IMAGE_COVER: {
+ float scale = max(screen_size.x / tex_size.x, screen_size.y / tex_size.y);
+ dest_size = tex_size * scale;
+ } break;
+
+ // For `stretch` we stretch the image to the size of
+ // the screen without worrying about aspect ratio.
+ case BG_IMAGE_STRETCH: {
+ dest_size = screen_size;
+ } break;
+
+ // For `none` we just use the original texture size.
+ case BG_IMAGE_NO_FIT: {
+ dest_size = tex_size;
+ } break;
+ }
+
+ vec2 start = vec2(0.0);
+ vec2 mid = (screen_size - dest_size) / vec2(2.0);
+ vec2 end = screen_size - dest_size;
+
+ vec2 dest_offset = mid;
+ switch (info & BG_IMAGE_POSITION) {
+ case BG_IMAGE_TL: {
+ dest_offset = vec2(start.x, start.y);
+ } break;
+ case BG_IMAGE_TC: {
+ dest_offset = vec2(mid.x, start.y);
+ } break;
+ case BG_IMAGE_TR: {
+ dest_offset = vec2(end.x, start.y);
+ } break;
+ case BG_IMAGE_ML: {
+ dest_offset = vec2(start.x, mid.y);
+ } break;
+ case BG_IMAGE_MC: {
+ dest_offset = vec2(mid.x, mid.y);
+ } break;
+ case BG_IMAGE_MR: {
+ dest_offset = vec2(end.x, mid.y);
+ } break;
+ case BG_IMAGE_BL: {
+ dest_offset = vec2(start.x, end.y);
+ } break;
+ case BG_IMAGE_BC: {
+ dest_offset = vec2(mid.x, end.y);
+ } break;
+ case BG_IMAGE_BR: {
+ dest_offset = vec2(end.x, end.y);
+ } break;
+ }
+
+ offset = dest_offset;
+ scale = tex_size / dest_size;
+
+ // We load a fully opaque version of the bg color and combine it with
+ // the alpha separately, because we need these as separate values in
+ // the framgment shader.
+ uvec4 u_bg_color = unpack4u8(bg_color_packed_4u8);
+ bg_color = vec4(load_color(
+ uvec4(u_bg_color.rgb, 255),
+ use_linear_blending
+ ).rgb, float(u_bg_color.a) / 255.0);
+}
diff --git a/vendor/libghostty-vt/src/renderer/shaders/glsl/cell_bg.f.glsl b/vendor/libghostty-vt/src/renderer/shaders/glsl/cell_bg.f.glsl
new file mode 100644
index 00000000..fa48c673
--- /dev/null
+++ b/vendor/libghostty-vt/src/renderer/shaders/glsl/cell_bg.f.glsl
@@ -0,0 +1,61 @@
+#include "common.glsl"
+
+// Position the origin to the upper left
+layout(origin_upper_left) in vec4 gl_FragCoord;
+
+// Must declare this output for some versions of OpenGL.
+layout(location = 0) out vec4 out_FragColor;
+
+layout(binding = 1, std430) readonly buffer bg_cells {
+ uint cells[];
+};
+
+vec4 cell_bg() {
+ uvec2 grid_size = unpack2u16(grid_size_packed_2u16);
+ ivec2 grid_pos = ivec2(floor((gl_FragCoord.xy - grid_padding.wx) / cell_size));
+ bool use_linear_blending = (bools & USE_LINEAR_BLENDING) != 0;
+
+ vec4 bg = vec4(0.0);
+
+ // Clamp x position, extends edge bg colors in to padding on sides.
+ if (grid_pos.x < 0) {
+ if ((padding_extend & EXTEND_LEFT) != 0) {
+ grid_pos.x = 0;
+ } else {
+ return bg;
+ }
+ } else if (grid_pos.x > grid_size.x - 1) {
+ if ((padding_extend & EXTEND_RIGHT) != 0) {
+ grid_pos.x = int(grid_size.x) - 1;
+ } else {
+ return bg;
+ }
+ }
+
+ // Clamp y position if we should extend, otherwise discard if out of bounds.
+ if (grid_pos.y < 0) {
+ if ((padding_extend & EXTEND_UP) != 0) {
+ grid_pos.y = 0;
+ } else {
+ return bg;
+ }
+ } else if (grid_pos.y > grid_size.y - 1) {
+ if ((padding_extend & EXTEND_DOWN) != 0) {
+ grid_pos.y = int(grid_size.y) - 1;
+ } else {
+ return bg;
+ }
+ }
+
+ // Load the color for the cell.
+ vec4 cell_color = load_color(
+ unpack4u8(cells[grid_pos.y * grid_size.x + grid_pos.x]),
+ use_linear_blending
+ );
+
+ return cell_color;
+}
+
+void main() {
+ out_FragColor = cell_bg();
+}
diff --git a/vendor/libghostty-vt/src/renderer/shaders/glsl/cell_text.f.glsl b/vendor/libghostty-vt/src/renderer/shaders/glsl/cell_text.f.glsl
new file mode 100644
index 00000000..176efcbd
--- /dev/null
+++ b/vendor/libghostty-vt/src/renderer/shaders/glsl/cell_text.f.glsl
@@ -0,0 +1,100 @@
+#include "common.glsl"
+
+layout(binding = 0) uniform sampler2DRect atlas_grayscale;
+layout(binding = 1) uniform sampler2DRect atlas_color;
+
+in CellTextVertexOut {
+ flat uint atlas;
+ flat vec4 color;
+ flat vec4 bg_color;
+ vec2 tex_coord;
+} in_data;
+
+// Values `atlas` can take.
+const uint ATLAS_GRAYSCALE = 0u;
+const uint ATLAS_COLOR = 1u;
+
+// Must declare this output for some versions of OpenGL.
+layout(location = 0) out vec4 out_FragColor;
+
+void main() {
+ bool use_linear_blending = (bools & USE_LINEAR_BLENDING) != 0;
+ bool use_linear_correction = (bools & USE_LINEAR_CORRECTION) != 0;
+
+ switch (in_data.atlas) {
+ default:
+ case ATLAS_GRAYSCALE:
+ {
+ // Our input color is always linear.
+ vec4 color = in_data.color;
+
+ // If we're not doing linear blending, then we need to
+ // re-apply the gamma encoding to our color manually.
+ //
+ // Since the alpha is premultiplied, we need to divide
+ // it out before unlinearizing and re-multiply it after.
+ if (!use_linear_blending) {
+ color.rgb /= vec3(color.a);
+ color = unlinearize(color);
+ color.rgb *= vec3(color.a);
+ }
+
+ // Fetch our alpha mask for this pixel.
+ float a = texture(atlas_grayscale, in_data.tex_coord).r;
+
+ // Linear blending weight correction corrects the alpha value to
+ // produce blending results which match gamma-incorrect blending.
+ if (use_linear_correction) {
+ // Short explanation of how this works:
+ //
+ // We get the luminances of the foreground and background colors,
+ // and then unlinearize them and perform blending on them. This
+ // gives us our desired luminance, which we derive our new alpha
+ // value from by mapping the range [bg_l, fg_l] to [0, 1], since
+ // our final blend will be a linear interpolation from bg to fg.
+ //
+ // This yields virtually identical results for grayscale blending,
+ // and very similar but non-identical results for color blending.
+ vec4 bg = in_data.bg_color;
+ float fg_l = luminance(color.rgb);
+ float bg_l = luminance(bg.rgb);
+ // To avoid numbers going haywire, we don't apply correction
+ // when the bg and fg luminances are within 0.001 of each other.
+ if (abs(fg_l - bg_l) > 0.001) {
+ float blend_l = linearize(unlinearize(fg_l) * a + unlinearize(bg_l) * (1.0 - a));
+ a = clamp((blend_l - bg_l) / (fg_l - bg_l), 0.0, 1.0);
+ }
+ }
+
+ // Multiply our whole color by the alpha mask.
+ // Since we use premultiplied alpha, this is
+ // the correct way to apply the mask.
+ color *= a;
+
+ out_FragColor = color;
+ return;
+ }
+
+ case ATLAS_COLOR:
+ {
+ // For now, we assume that color glyphs
+ // are already premultiplied linear colors.
+ vec4 color = texture(atlas_color, in_data.tex_coord);
+
+ // If we are doing linear blending, we can return this right away.
+ if (use_linear_blending) {
+ out_FragColor = color;
+ return;
+ }
+
+ // Otherwise we need to unlinearize the color. Since the alpha is
+ // premultiplied, we need to divide it out before unlinearizing.
+ color.rgb /= vec3(color.a);
+ color = unlinearize(color);
+ color.rgb *= vec3(color.a);
+
+ out_FragColor = color;
+ return;
+ }
+ }
+}
diff --git a/vendor/libghostty-vt/src/renderer/shaders/glsl/cell_text.v.glsl b/vendor/libghostty-vt/src/renderer/shaders/glsl/cell_text.v.glsl
new file mode 100644
index 00000000..7e38e2f0
--- /dev/null
+++ b/vendor/libghostty-vt/src/renderer/shaders/glsl/cell_text.v.glsl
@@ -0,0 +1,146 @@
+#include "common.glsl"
+
+// The position of the glyph in the texture (x, y)
+layout(location = 0) in uvec2 glyph_pos;
+
+// The size of the glyph in the texture (w, h)
+layout(location = 1) in uvec2 glyph_size;
+
+// The left and top bearings for the glyph (x, y)
+layout(location = 2) in ivec2 bearings;
+
+// The grid coordinates (x, y) where x < columns and y < rows
+layout(location = 3) in uvec2 grid_pos;
+
+// The color of the rendered text glyph.
+layout(location = 4) in uvec4 color;
+
+// Which atlas this glyph is in.
+layout(location = 5) in uint atlas;
+
+// Misc glyph properties.
+layout(location = 6) in uint glyph_bools;
+
+// Values `atlas` can take.
+const uint ATLAS_GRAYSCALE = 0u;
+const uint ATLAS_COLOR = 1u;
+
+// Masks for the `glyph_bools` attribute
+const uint NO_MIN_CONTRAST = 1u;
+const uint IS_CURSOR_GLYPH = 2u;
+
+out CellTextVertexOut {
+ flat uint atlas;
+ flat vec4 color;
+ flat vec4 bg_color;
+ vec2 tex_coord;
+} out_data;
+
+layout(binding = 1, std430) readonly buffer bg_cells {
+ uint bg_colors[];
+};
+
+void main() {
+ uvec2 grid_size = unpack2u16(grid_size_packed_2u16);
+ uvec2 cursor_pos = unpack2u16(cursor_pos_packed_2u16);
+ bool cursor_wide = (bools & CURSOR_WIDE) != 0;
+ bool use_linear_blending = (bools & USE_LINEAR_BLENDING) != 0;
+
+ // Convert the grid x, y into world space x, y by accounting for cell size
+ vec2 cell_pos = cell_size * vec2(grid_pos);
+
+ int vid = gl_VertexID;
+
+ // We use a triangle strip with 4 vertices to render quads,
+ // so we determine which corner of the cell this vertex is in
+ // based on the vertex ID.
+ //
+ // 0 --> 1
+ // | .'|
+ // | / |
+ // | L |
+ // 2 --> 3
+ //
+ // 0 = top-left (0, 0)
+ // 1 = top-right (1, 0)
+ // 2 = bot-left (0, 1)
+ // 3 = bot-right (1, 1)
+ vec2 corner;
+ corner.x = float(vid == 1 || vid == 3);
+ corner.y = float(vid == 2 || vid == 3);
+
+ out_data.atlas = atlas;
+
+ // === Grid Cell ===
+ // +X
+ // 0,0--...->
+ // |
+ // . offset.x = bearings.x
+ // +Y. .|.
+ // . | |
+ // | cell_pos -> +-------+ _.
+ // v ._| |_. _|- offset.y = cell_size.y - bearings.y
+ // | | .###. | |
+ // | | #...# | |
+ // glyph_size.y -+ | ##### | |
+ // | | #.... | +- bearings.y
+ // |_| .#### | |
+ // | |_|
+ // +-------+
+ // |_._|
+ // |
+ // glyph_size.x
+ //
+ // In order to get the top left of the glyph, we compute an offset based on
+ // the bearings. The Y bearing is the distance from the bottom of the cell
+ // to the top of the glyph, so we subtract it from the cell height to get
+ // the y offset. The X bearing is the distance from the left of the cell
+ // to the left of the glyph, so it works as the x offset directly.
+
+ vec2 size = vec2(glyph_size);
+ vec2 offset = vec2(bearings);
+
+ offset.y = cell_size.y - offset.y;
+
+ // Calculate the final position of the cell which uses our glyph size
+ // and glyph offset to create the correct bounding box for the glyph.
+ cell_pos = cell_pos + size * corner + offset;
+ gl_Position = projection_matrix * vec4(cell_pos.x, cell_pos.y, 0.0f, 1.0f);
+
+ // Calculate the texture coordinate in pixels. This is NOT normalized
+ // (between 0.0 and 1.0), and does not need to be, since the texture will
+ // be sampled with pixel coordinate mode.
+ out_data.tex_coord = vec2(glyph_pos) + vec2(glyph_size) * corner;
+
+ // Get our color. We always fetch a linearized version to
+ // make it easier to handle minimum contrast calculations.
+ out_data.color = load_color(color, true);
+ // Get the BG color
+ out_data.bg_color = load_color(
+ unpack4u8(bg_colors[grid_pos.y * grid_size.x + grid_pos.x]),
+ true
+ );
+ // Blend it with the global bg color
+ vec4 global_bg = load_color(
+ unpack4u8(bg_color_packed_4u8),
+ true
+ );
+ out_data.bg_color += global_bg * vec4(1.0 - out_data.bg_color.a);
+
+ // If we have a minimum contrast, we need to check if we need to
+ // change the color of the text to ensure it has enough contrast
+ // with the background.
+ if (min_contrast > 1.0f && (glyph_bools & NO_MIN_CONTRAST) == 0) {
+ // Ensure our minimum contrast
+ out_data.color = contrasted_color(min_contrast, out_data.color, out_data.bg_color);
+ }
+
+ // Check if current position is under cursor (including wide cursor)
+ bool is_cursor_pos = ((grid_pos.x == cursor_pos.x) || (cursor_wide && (grid_pos.x == (cursor_pos.x + 1)))) && (grid_pos.y == cursor_pos.y);
+
+ // If this cell is the cursor cell, but we're not processing
+ // the cursor glyph itself, then we need to change the color.
+ if ((glyph_bools & IS_CURSOR_GLYPH) == 0 && is_cursor_pos) {
+ out_data.color = load_color(unpack4u8(cursor_color_packed_4u8), use_linear_blending);
+ }
+}
diff --git a/vendor/libghostty-vt/src/renderer/shaders/glsl/common.glsl b/vendor/libghostty-vt/src/renderer/shaders/glsl/common.glsl
new file mode 100644
index 00000000..1a7ea03a
--- /dev/null
+++ b/vendor/libghostty-vt/src/renderer/shaders/glsl/common.glsl
@@ -0,0 +1,156 @@
+#version 430 core
+
+// These are common definitions to be shared across shaders, the first
+// line of any shader that needs these should be `#include "common.glsl"`.
+//
+// Included in this file are:
+// - The interface block for the global uniforms.
+// - Functions for unpacking values.
+// - Functions for working with colors.
+
+//----------------------------------------------------------------------------//
+// Global Uniforms
+//----------------------------------------------------------------------------//
+layout(binding = 1, std140) uniform Globals {
+ uniform mat4 projection_matrix;
+ uniform vec2 screen_size;
+ uniform vec2 cell_size;
+ uniform uint grid_size_packed_2u16;
+ uniform vec4 grid_padding;
+ uniform uint padding_extend;
+ uniform float min_contrast;
+ uniform uint cursor_pos_packed_2u16;
+ uniform uint cursor_color_packed_4u8;
+ uniform uint bg_color_packed_4u8;
+ uniform uint bools;
+};
+
+// Bools
+const uint CURSOR_WIDE = 1u;
+const uint USE_DISPLAY_P3 = 2u;
+const uint USE_LINEAR_BLENDING = 4u;
+const uint USE_LINEAR_CORRECTION = 8u;
+
+// Padding extend enum
+const uint EXTEND_LEFT = 1u;
+const uint EXTEND_RIGHT = 2u;
+const uint EXTEND_UP = 4u;
+const uint EXTEND_DOWN = 8u;
+
+//----------------------------------------------------------------------------//
+// Functions for Unpacking Values
+//----------------------------------------------------------------------------//
+// NOTE: These unpack functions assume little-endian.
+// If this ever becomes a problem... oh dear!
+
+uvec4 unpack4u8(uint packed_value) {
+ return uvec4(
+ uint(packed_value >> 0) & uint(0xFF),
+ uint(packed_value >> 8) & uint(0xFF),
+ uint(packed_value >> 16) & uint(0xFF),
+ uint(packed_value >> 24) & uint(0xFF)
+ );
+}
+
+uvec2 unpack2u16(uint packed_value) {
+ return uvec2(
+ uint(packed_value >> 0) & uint(0xFFFF),
+ uint(packed_value >> 16) & uint(0xFFFF)
+ );
+}
+
+ivec2 unpack2i16(int packed_value) {
+ return ivec2(
+ (packed_value << 16) >> 16,
+ (packed_value << 0) >> 16
+ );
+}
+
+//----------------------------------------------------------------------------//
+// Color Functions
+//----------------------------------------------------------------------------//
+
+// Compute the luminance of the provided color.
+//
+// Takes colors in linear RGB space. If your colors are gamma
+// encoded, linearize them before using them with this function.
+float luminance(vec3 color) {
+ return dot(color, vec3(0.2126f, 0.7152f, 0.0722f));
+}
+
+// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
+//
+// Takes colors in linear RGB space. If your colors are gamma
+// encoded, linearize them before using them with this function.
+float contrast_ratio(vec3 color1, vec3 color2) {
+ float luminance1 = luminance(color1) + 0.05;
+ float luminance2 = luminance(color2) + 0.05;
+ return max(luminance1, luminance2) / min(luminance1, luminance2);
+}
+
+// Return the fg if the contrast ratio is greater than min, otherwise
+// return a color that satisfies the contrast ratio. Currently, the color
+// is always white or black, whichever has the highest contrast ratio.
+//
+// Takes colors in linear RGB space. If your colors are gamma
+// encoded, linearize them before using them with this function.
+vec4 contrasted_color(float min_ratio, vec4 fg, vec4 bg) {
+ float ratio = contrast_ratio(fg.rgb, bg.rgb);
+ if (ratio < min_ratio) {
+ float white_ratio = contrast_ratio(vec3(1.0, 1.0, 1.0), bg.rgb);
+ float black_ratio = contrast_ratio(vec3(0.0, 0.0, 0.0), bg.rgb);
+ if (white_ratio > black_ratio) {
+ return vec4(1.0);
+ } else {
+ return vec4(0.0, 0.0, 0.0, 1.0);
+ }
+ }
+
+ return fg;
+}
+
+// Converts a color from sRGB gamma encoding to linear.
+vec4 linearize(vec4 srgb) {
+ bvec3 cutoff = lessThanEqual(srgb.rgb, vec3(0.04045));
+ vec3 higher = pow((srgb.rgb + vec3(0.055)) / vec3(1.055), vec3(2.4));
+ vec3 lower = srgb.rgb / vec3(12.92);
+
+ return vec4(mix(higher, lower, cutoff), srgb.a);
+}
+float linearize(float v) {
+ return v <= 0.04045 ? v / 12.92 : pow((v + 0.055) / 1.055, 2.4);
+}
+
+// Converts a color from linear to sRGB gamma encoding.
+vec4 unlinearize(vec4 linear) {
+ bvec3 cutoff = lessThanEqual(linear.rgb, vec3(0.0031308));
+ vec3 higher = pow(linear.rgb, vec3(1.0 / 2.4)) * vec3(1.055) - vec3(0.055);
+ vec3 lower = linear.rgb * vec3(12.92);
+
+ return vec4(mix(higher, lower, cutoff), linear.a);
+}
+float unlinearize(float v) {
+ return v <= 0.0031308 ? v * 12.92 : pow(v, 1.0 / 2.4) * 1.055 - 0.055;
+}
+
+// Load a 4 byte RGBA non-premultiplied color and linearize
+// and convert it as necessary depending on the provided info.
+//
+// `linear` controls whether the returned color is linear or gamma encoded.
+vec4 load_color(
+ uvec4 in_color,
+ bool linear
+) {
+ // 0 .. 255 -> 0.0 .. 1.0
+ vec4 color = vec4(in_color) / vec4(255.0f);
+
+ // Linearize if necessary.
+ if (linear) color = linearize(color);
+
+ // Premultiply our color by its alpha.
+ color.rgb *= color.a;
+
+ return color;
+}
+
+//----------------------------------------------------------------------------//
diff --git a/vendor/libghostty-vt/src/renderer/shaders/glsl/full_screen.v.glsl b/vendor/libghostty-vt/src/renderer/shaders/glsl/full_screen.v.glsl
new file mode 100644
index 00000000..b89cedfa
--- /dev/null
+++ b/vendor/libghostty-vt/src/renderer/shaders/glsl/full_screen.v.glsl
@@ -0,0 +1,24 @@
+#version 330 core
+
+void main() {
+ vec4 position;
+ position.x = (gl_VertexID == 2) ? 3.0 : -1.0;
+ position.y = (gl_VertexID == 0) ? -3.0 : 1.0;
+ position.z = 1.0;
+ position.w = 1.0;
+
+ // Single triangle is clipped to viewport.
+ //
+ // X <- vid == 0: (-1, -3)
+ // |\
+ // | \
+ // | \
+ // |###\
+ // |#+# \ `+` is (0, 0). `#`s are viewport area.
+ // |### \
+ // X------X <- vid == 2: (3, 1)
+ // ^
+ // vid == 1: (-1, 1)
+
+ gl_Position = position;
+}
diff --git a/vendor/libghostty-vt/src/renderer/shaders/glsl/image.f.glsl b/vendor/libghostty-vt/src/renderer/shaders/glsl/image.f.glsl
new file mode 100644
index 00000000..4f89d7a7
--- /dev/null
+++ b/vendor/libghostty-vt/src/renderer/shaders/glsl/image.f.glsl
@@ -0,0 +1,21 @@
+#include "common.glsl"
+
+layout(binding = 0) uniform sampler2D image;
+
+in vec2 tex_coord;
+
+layout(location = 0) out vec4 out_FragColor;
+
+void main() {
+ bool use_linear_blending = (bools & USE_LINEAR_BLENDING) != 0;
+
+ vec4 rgba = texture(image, tex_coord);
+
+ if (!use_linear_blending) {
+ rgba = unlinearize(rgba);
+ }
+
+ rgba.rgb *= vec3(rgba.a);
+
+ out_FragColor = rgba;
+}
diff --git a/vendor/libghostty-vt/src/renderer/shaders/glsl/image.v.glsl b/vendor/libghostty-vt/src/renderer/shaders/glsl/image.v.glsl
new file mode 100644
index 00000000..779fae32
--- /dev/null
+++ b/vendor/libghostty-vt/src/renderer/shaders/glsl/image.v.glsl
@@ -0,0 +1,47 @@
+#include "common.glsl"
+
+layout(binding = 0) uniform sampler2D image;
+
+layout(location = 0) in vec2 grid_pos;
+layout(location = 1) in vec2 cell_offset;
+layout(location = 2) in vec4 source_rect;
+layout(location = 3) in vec2 dest_size;
+
+out vec2 tex_coord;
+
+void main() {
+ int vid = gl_VertexID;
+
+ // We use a triangle strip with 4 vertices to render quads,
+ // so we determine which corner of the cell this vertex is in
+ // based on the vertex ID.
+ //
+ // 0 --> 1
+ // | .'|
+ // | / |
+ // | L |
+ // 2 --> 3
+ //
+ // 0 = top-left (0, 0)
+ // 1 = top-right (1, 0)
+ // 2 = bot-left (0, 1)
+ // 3 = bot-right (1, 1)
+ vec2 corner;
+ corner.x = float(vid == 1 || vid == 3);
+ corner.y = float(vid == 2 || vid == 3);
+
+ // The texture coordinates start at our source x/y
+ // and add the width/height depending on the corner.
+ tex_coord = source_rect.xy;
+ tex_coord += source_rect.zw * corner;
+
+ // Normalize the coordinates.
+ tex_coord /= textureSize(image, 0);
+
+ // The position of our image starts at the top-left of the grid cell and
+ // adds the source rect width/height components.
+ vec2 image_pos = (cell_size * grid_pos) + cell_offset;
+ image_pos += dest_size * corner;
+
+ gl_Position = projection_matrix * vec4(image_pos.xy, 1.0, 1.0);
+}