feat(renderer): port the OpenGL backend, shaders and size math
Ports the terminal-independent half of ZIG/src/renderer (OpenGL only): - opengl/*: buffers, frames, pipelines, render passes, samplers, targets, textures and the OpenGL GraphicsAPI backend on top of glow; the GTK GLArea glue is replaced by a get_proc_address loader closure - the GLSL shaders, copied verbatim and embedded; uniform/vertex structs as bytemuck Pod types with std140 offsets asserted in tests - shadertoy custom shaders converted with naga (GLSL in -> validate -> GLSL 430 out) instead of glslang + spirv-cross - size (screen/grid/cell/padding math), cursor style logic, backend health, options and message types The crate opts into unsafe_code = "deny"; unsafe glow calls live only in opengl/gl.rs. A headless EGL integration test builds every pipeline and the converted shadertoy shaders on Mesa and reads back rendered pixels. Note: Zig's shadertoy Uniforms order the selection background/foreground colors opposite to the GLSL block (likely an upstream bug); kept as in Zig for parity and recorded in the notes. Deferred to the next renderer pass: generic, cell, image, link, row, State, Thread, Overlay (need terminal render state). Test Plan: - cargo test -p ghostty-renderer (33 tests incl. tests/egl_headless.rs)
This commit is contained in:
1 parent
ac0e05def1
commit
c548e63bd1
35 files changed
+5477
-5
No files matched your search
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------//
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#version 430 core
|
||||
|
||||
layout(binding = 1, std140) uniform Globals {
|
||||
uniform vec3 iResolution;
|
||||
uniform float iTime;
|
||||
uniform float iTimeDelta;
|
||||
uniform float iFrameRate;
|
||||
uniform int iFrame;
|
||||
uniform float iChannelTime[4];
|
||||
uniform vec3 iChannelResolution[4];
|
||||
uniform vec4 iMouse;
|
||||
uniform vec4 iDate;
|
||||
uniform float iSampleRate;
|
||||
uniform vec4 iCurrentCursor;
|
||||
uniform vec4 iPreviousCursor;
|
||||
uniform vec4 iCurrentCursorColor;
|
||||
uniform vec4 iPreviousCursorColor;
|
||||
uniform int iCurrentCursorStyle;
|
||||
uniform int iPreviousCursorStyle;
|
||||
uniform int iCursorVisible;
|
||||
uniform float iTimeCursorChange;
|
||||
uniform float iTimeFocus;
|
||||
uniform int iFocus;
|
||||
uniform vec3 iPalette[256];
|
||||
uniform vec3 iBackgroundColor;
|
||||
uniform vec3 iForegroundColor;
|
||||
uniform vec3 iCursorColor;
|
||||
uniform vec3 iCursorText;
|
||||
uniform vec3 iSelectionForegroundColor;
|
||||
uniform vec3 iSelectionBackgroundColor;
|
||||
};
|
||||
|
||||
#define CURSORSTYLE_BLOCK 0
|
||||
#define CURSORSTYLE_BLOCK_HOLLOW 1
|
||||
#define CURSORSTYLE_BAR 2
|
||||
#define CURSORSTYLE_UNDERLINE 3
|
||||
#define CURSORSTYLE_LOCK 4
|
||||
|
||||
layout(binding = 0) uniform sampler2D iChannel0;
|
||||
|
||||
// These are unused currently by Ghostty:
|
||||
// layout(binding = 1) uniform sampler2D iChannel1;
|
||||
// layout(binding = 2) uniform sampler2D iChannel2;
|
||||
// layout(binding = 3) uniform sampler2D iChannel3;
|
||||
|
||||
layout(location = 0) in vec4 gl_FragCoord;
|
||||
layout(location = 0) out vec4 _fragColor;
|
||||
|
||||
#define texture2D texture
|
||||
|
||||
void mainImage( out vec4 fragColor, in vec2 fragCoord );
|
||||
void main() { mainImage (_fragColor, gl_FragCoord.xy); }
|
||||
@@ -0,0 +1,59 @@
|
||||
// This shader is NOT BUILD INTO Ghostty. This is only here for unit tests.
|
||||
|
||||
// Loosely based on postprocessing shader by inigo quilez, License Creative
|
||||
// Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
|
||||
|
||||
vec2 curve(vec2 uv)
|
||||
{
|
||||
uv = (uv - 0.5) * 2.0;
|
||||
uv *= 1.1;
|
||||
uv.x *= 1.0 + pow((abs(uv.y) / 5.0), 2.0);
|
||||
uv.y *= 1.0 + pow((abs(uv.x) / 4.0), 2.0);
|
||||
uv = (uv / 2.0) + 0.5;
|
||||
uv = uv *0.92 + 0.04;
|
||||
return uv;
|
||||
}
|
||||
void mainImage( out vec4 fragColor, in vec2 fragCoord )
|
||||
{
|
||||
vec2 q = fragCoord.xy / iResolution.xy;
|
||||
vec2 uv = q;
|
||||
uv = curve( uv );
|
||||
vec3 oricol = texture( iChannel0, vec2(q.x,q.y) ).xyz;
|
||||
vec3 col;
|
||||
float x = sin(0.3*iTime+uv.y*21.0)*sin(0.7*iTime+uv.y*29.0)*sin(0.3+0.33*iTime+uv.y*31.0)*0.0017;
|
||||
|
||||
col.r = texture(iChannel0,vec2(x+uv.x+0.001,uv.y+0.001)).x+0.05;
|
||||
col.g = texture(iChannel0,vec2(x+uv.x+0.000,uv.y-0.002)).y+0.05;
|
||||
col.b = texture(iChannel0,vec2(x+uv.x-0.002,uv.y+0.000)).z+0.05;
|
||||
col.r += 0.08*texture(iChannel0,0.75*vec2(x+0.025, -0.027)+vec2(uv.x+0.001,uv.y+0.001)).x;
|
||||
col.g += 0.05*texture(iChannel0,0.75*vec2(x+-0.022, -0.02)+vec2(uv.x+0.000,uv.y-0.002)).y;
|
||||
col.b += 0.08*texture(iChannel0,0.75*vec2(x+-0.02, -0.018)+vec2(uv.x-0.002,uv.y+0.000)).z;
|
||||
|
||||
col = clamp(col*0.6+0.4*col*col*1.0,0.0,1.0);
|
||||
|
||||
float vig = (0.0 + 1.0*16.0*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y));
|
||||
col *= vec3(pow(vig,0.3));
|
||||
|
||||
col *= vec3(0.95,1.05,0.95);
|
||||
col *= 2.8;
|
||||
|
||||
float scans = clamp( 0.35+0.35*sin(3.5*iTime+uv.y*iResolution.y*1.5), 0.0, 1.0);
|
||||
|
||||
float s = pow(scans,1.7);
|
||||
col = col*vec3( 0.4+0.7*s) ;
|
||||
|
||||
col *= 1.0+0.01*sin(110.0*iTime);
|
||||
if (uv.x < 0.0 || uv.x > 1.0)
|
||||
col *= 0.0;
|
||||
if (uv.y < 0.0 || uv.y > 1.0)
|
||||
col *= 0.0;
|
||||
|
||||
col*=1.0-0.65*vec3(clamp((mod(fragCoord.x, 2.0)-1.0)*2.0,0.0,1.0));
|
||||
|
||||
float comp = smoothstep( 0.1, 0.9, sin(iTime) );
|
||||
|
||||
// Remove the next line to stop cross-fade between original and postprocess
|
||||
// col = mix( col, oricol, comp );
|
||||
|
||||
fragColor = vec4(col,1.0);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Test shader for iTimeFocus and iFocus
|
||||
// Shows border when focused, green fade that restarts on each focus gain
|
||||
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
||||
vec2 uv = fragCoord / iResolution.xy;
|
||||
|
||||
// Sample the terminal content
|
||||
vec4 terminal = texture2D(iChannel0, uv);
|
||||
vec3 color = terminal.rgb;
|
||||
|
||||
if (iFocus > 0) {
|
||||
// FOCUSED: Add border and fading green overlay
|
||||
|
||||
// Calculate time since focus was gained
|
||||
float timeSinceFocus = iTime - iTimeFocus;
|
||||
|
||||
// Green fade: starts at 1.0 (full green), fades to 0.0 over 3 seconds
|
||||
float fadeOut = max(0.0, 1.0 - (timeSinceFocus / 3.0));
|
||||
|
||||
// Add green overlay that fades out
|
||||
color = mix(color, vec3(0.0, 1.0, 0.0), fadeOut * 0.4);
|
||||
|
||||
// Add border (5 pixels)
|
||||
float borderSize = 5.0;
|
||||
vec2 pixelCoord = fragCoord;
|
||||
bool isBorder = pixelCoord.x < borderSize ||
|
||||
pixelCoord.x > iResolution.x - borderSize ||
|
||||
pixelCoord.y < borderSize ||
|
||||
pixelCoord.y > iResolution.y - borderSize;
|
||||
|
||||
if (isBorder) {
|
||||
// Bright cyan border that pulses subtly
|
||||
float pulse = sin(timeSinceFocus * 2.0) * 0.1 + 0.9;
|
||||
color = vec3(0.0, 1.0, 1.0) * pulse;
|
||||
}
|
||||
} else {
|
||||
// UNFOCUSED: Solid red overlay (no border)
|
||||
color = mix(color, vec3(1.0, 0.0, 0.0), 0.3);
|
||||
}
|
||||
|
||||
fragColor = vec4(color, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
vec2 curve(vec2 uv)
|
||||
{
|
||||
uv = (uv - 0.5) * 2.0;
|
||||
uv *= 1.1;
|
||||
uv.x *= 1.0 + pow((abs(uv.y) / 5.0), 2.0);
|
||||
uv.y *= 1.0 + pow((abs(uv.x) / 4.0), 2.0);
|
||||
uv = (uv / 2.0) + 0.5;
|
||||
uv = uv *0.92 + 0.04;
|
||||
return uv;
|
||||
}
|
||||
|
||||
// Missing mainImage!
|
||||
Reference in new issue
Block a user