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);
|
||||
}
|
||||
Reference in new issue
Block a user