@micahdb/tscon 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,172 @@
1
+ import { renderer_frag, renderer_vert } from "./shaders.js";
2
+ import { Canvas } from "./canvas.js";
3
+ import { compileProgram, getAttribLocations, getUniformLocations, } from "./program.js";
4
+ /**
5
+ * Renders a source texture as glyphs onto a {@link Canvas}.
6
+ *
7
+ * `Renderer` draws a grid of {@link TexGlyphs} cells, sampling an
8
+ * application-provided texture per cell according to each cell's
9
+ * {@link TexGlyphMode}. An application renders into a texture (e.g. a
10
+ * framebuffer object) and then calls {@link draw} with a matching
11
+ * {@link TexGlyphs} grid to draw that texture directly or as dithered
12
+ * coloured characters.
13
+ */
14
+ export class Renderer {
15
+ gl_program;
16
+ attributes;
17
+ uniforms;
18
+ vao;
19
+ vbo;
20
+ resized;
21
+ /** The {@link Canvas} this renderer draws into. */
22
+ canvas;
23
+ /**
24
+ * Whether to use {@link Canvas.user_font} for non-box glyphs when
25
+ * available. Set to `true` by the application after assigning a font
26
+ * texture to `canvas.user_font`; automatically falls back to the bitmap
27
+ * font if `user_font` is `null`.
28
+ */
29
+ use_user_font;
30
+ /**
31
+ * Initializes the renderer for use.
32
+ * Listens for `"resize"` events from the canvas.
33
+ *
34
+ * @param canvas The canvas to draw into.
35
+ */
36
+ constructor(canvas) {
37
+ this.canvas = canvas;
38
+ const gl = canvas.gl;
39
+ this.gl_program = compileProgram(gl, renderer_vert, renderer_frag);
40
+ this.attributes = getAttribLocations(gl, this.gl_program, {
41
+ row: "a_row",
42
+ col: "a_col",
43
+ mode: "a_mode",
44
+ });
45
+ this.uniforms = getUniformLocations(gl, this.gl_program, {
46
+ row: "u_row",
47
+ col: "u_col",
48
+ rows: "u_rows",
49
+ cols: "u_cols",
50
+ canvas_rows: "u_canvas_rows",
51
+ canvas_cols: "u_canvas_cols",
52
+ mouse_row: "u_mouse_row",
53
+ mouse_col: "u_mouse_col",
54
+ palette: "u_palette",
55
+ bitmap_font: "u_bitmap_font",
56
+ texture: "u_texture",
57
+ user_font: "u_user_font",
58
+ use_user_font: "u_use_user_font",
59
+ });
60
+ const vao = gl.createVertexArray();
61
+ this.vao = vao;
62
+ if (!vao) {
63
+ throw new Error("Could not create vertex array object");
64
+ }
65
+ gl.bindVertexArray(vao);
66
+ const vbo = gl.createBuffer();
67
+ this.vbo = vbo;
68
+ if (!vbo) {
69
+ throw new Error("Could not create vertex buffer");
70
+ }
71
+ gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
72
+ // every vertex in the VBO is a 32-bit integer split like so:
73
+ // [ row | mode | col ]
74
+ // [ 8-bits | 8-bits | 16-bits ]
75
+ // a_row
76
+ gl.vertexAttribIPointer(this.attributes.row, 1, gl.UNSIGNED_BYTE, 4, 3);
77
+ gl.vertexAttribDivisor(this.attributes.row, 1);
78
+ gl.enableVertexAttribArray(this.attributes.row);
79
+ // a_mode
80
+ gl.vertexAttribIPointer(this.attributes.mode, 1, gl.UNSIGNED_BYTE, 4, 2);
81
+ gl.vertexAttribDivisor(this.attributes.mode, 1);
82
+ gl.enableVertexAttribArray(this.attributes.mode);
83
+ // a_col
84
+ gl.vertexAttribIPointer(this.attributes.col, 1, gl.UNSIGNED_SHORT, 4, 0);
85
+ gl.vertexAttribDivisor(this.attributes.col, 1);
86
+ gl.enableVertexAttribArray(this.attributes.col);
87
+ gl.bindVertexArray(null);
88
+ gl.useProgram(this.gl_program);
89
+ gl.uniform1i(this.uniforms.row, 0);
90
+ gl.uniform1i(this.uniforms.col, 0);
91
+ gl.uniform1i(this.uniforms.rows, 0);
92
+ gl.uniform1i(this.uniforms.cols, 0);
93
+ gl.uniform1i(this.uniforms.canvas_rows, this.canvas.rows);
94
+ gl.uniform1i(this.uniforms.canvas_cols, this.canvas.cols);
95
+ gl.uniform3fv(this.uniforms.palette, this.canvas.palette);
96
+ gl.uniform1i(this.uniforms.bitmap_font, 0);
97
+ gl.uniform1i(this.uniforms.texture, 1);
98
+ gl.uniform1i(this.uniforms.user_font, 2);
99
+ gl.uniform1i(this.uniforms.use_user_font, 0);
100
+ this.resized = false;
101
+ this.use_user_font = false;
102
+ this.canvas.addEventListener("resize", () => {
103
+ this.resized = true;
104
+ });
105
+ }
106
+ /**
107
+ * Draw a {@link TexGlyphs} grid sampling `texture` into a region of the
108
+ * canvas.
109
+ *
110
+ * `tglyphs.rows`/`cols` must match `dst.rows`/`cols`; otherwise the
111
+ * draw is skipped. `dst` is in canvas cell coordinates and may extend
112
+ * beyond the canvas; the shader clips via NDC. Use a `tglyphs` grid
113
+ * sized to the texture's aspect so the texture isn't distorted.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * renderer.draw(tglyphs, texture, {
118
+ * row: 0, col: 0,
119
+ * rows: tglyphs.rows, cols: tglyphs.cols,
120
+ * });
121
+ * ```
122
+ *
123
+ * @param tglyphs Texture-glyph grid describing how to sample.
124
+ * @param texture Source texture to sample from.
125
+ * @param dst Destination region on the canvas (cells).
126
+ */
127
+ draw(tglyphs, texture, dst) {
128
+ if (tglyphs.rows !== dst.rows || tglyphs.cols !== dst.cols) {
129
+ console.log("Renderer.draw: skipping due to bad tglyphs or dst");
130
+ return;
131
+ }
132
+ const gl = this.canvas.gl;
133
+ gl.useProgram(this.gl_program);
134
+ if (this.resized) {
135
+ this.resized = false;
136
+ gl.uniform1i(this.uniforms.canvas_rows, this.canvas.rows);
137
+ gl.uniform1i(this.uniforms.canvas_cols, this.canvas.cols);
138
+ }
139
+ // set destination information uniforms
140
+ gl.uniform1i(this.uniforms.row, dst.row);
141
+ gl.uniform1i(this.uniforms.col, dst.col);
142
+ gl.uniform1i(this.uniforms.rows, tglyphs.rows);
143
+ gl.uniform1i(this.uniforms.cols, tglyphs.cols);
144
+ gl.uniform1i(this.uniforms.mouse_row, this.canvas.mouse_row ?? -1);
145
+ gl.uniform1i(this.uniforms.mouse_col, this.canvas.mouse_col ?? -1);
146
+ // set vertex buffer data to provided texture glyphs
147
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
148
+ gl.bufferData(gl.ARRAY_BUFFER, tglyphs.data, gl.DYNAMIC_DRAW);
149
+ // bind and activate bitmap font
150
+ gl.activeTexture(gl.TEXTURE0);
151
+ gl.bindTexture(gl.TEXTURE_2D, this.canvas.bitmap_font);
152
+ // bind and activate provided texture
153
+ gl.activeTexture(gl.TEXTURE1);
154
+ gl.bindTexture(gl.TEXTURE_2D, texture);
155
+ // user application-provided font
156
+ if (this.use_user_font) {
157
+ gl.uniform1i(this.uniforms.use_user_font, 1);
158
+ }
159
+ if (this.canvas.user_font !== null) {
160
+ gl.activeTexture(gl.TEXTURE2);
161
+ gl.bindTexture(gl.TEXTURE_2D, this.canvas.user_font);
162
+ }
163
+ else if (this.use_user_font) {
164
+ this.use_user_font = false;
165
+ gl.uniform1i(this.uniforms.use_user_font, 0);
166
+ }
167
+ // draw the glyphs covered by the provided texture glyphs
168
+ gl.bindVertexArray(this.vao);
169
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, tglyphs.rows * tglyphs.cols);
170
+ gl.bindVertexArray(null);
171
+ }
172
+ }
@@ -0,0 +1,5 @@
1
+ export declare const glyph_frag = "// Computes the mix value for a coordinate relative to pixels, that will\n// snap to the center of the pixel if it is in the center 50% of it, and\n// otherwise interpolate linearly outside of the center 50%.\nfloat sharp_bilinear_weight(float coord) {\n\t// distance of coord to center of cooresponding pixel\n\tfloat center_dist = fract(coord) - 0.5;\n\n\t// the component of coord that is within 50% of the center of the pixel\n\tfloat center_comp = clamp(center_dist, -0.25, 0.25);\n\n\t// when center_dist is outside of the center 50% of the pixel, this\n\t// will be an interpolation between this pixel and its neighbour\n\tfloat interp_coord = floor(coord) + (center_dist - center_comp) * 2.0;\n\n\t// this will be 0 when coord is in the center 50% of the pixel\n\treturn interp_coord - floor(coord);\n}\n\nfloat bit_value(uvec4 texel, int x, int y) {\n\treturn float((texel[y] >> (7 - x)) & 1u);\n}\n\nfloat bitmap_glyph_value(\n\tmediump usampler2D bitmap,\n\tivec2 glyph_coord,\n\tvec2 cell_coord\n) {\n\tint block = clamp(int(floor(cell_coord.y * 4.0)), 0, 3);\n\tivec2 texel_coord = ivec2(glyph_coord.x, glyph_coord.y + block);\n\tuvec4 texel = texelFetch(bitmap, texel_coord, 0);\n\n\t// each block is 8px wide\n\tfloat block_x = clamp(cell_coord.x * 8.0, 0.0, 7.0);\n\n\t// each cell is 4 blocks high, each block being 4px tall\n\tfloat block_y = clamp(cell_coord.y * 16.0 - float(block) * 4.0,\n\t\t\t 0.0, 3.0);\n\n\tfloat horiz_weight = sharp_bilinear_weight(block_x);\n\tfloat vert_weight = sharp_bilinear_weight(block_y);\n\n\tint x = clamp(int(block_x), 0, 7);\n\tint y = clamp(int(block_y), 0, 3);\n\n\t// get each bit\n\tfloat tl = bit_value(texel, x, y);\n\tfloat tr = bit_value(texel, x + 1, y);\n\tfloat bl = bit_value(texel, x, y + 1);\n\tfloat br = bit_value(texel, x + 1, y + 1);\n\n\tfloat top = mix(tl, tr, horiz_weight);\n\tfloat bot = mix(bl, br, horiz_weight);\n\n\treturn mix(top, bot, vert_weight);\n}\n";
2
+ export declare const terminal_vert = "#version 300 es\n\nin uint a_colour;\nin uint a_char_code;\n\nuniform int u_rows;\nuniform int u_cols;\nuniform int u_mouse_row;\nuniform int u_mouse_col;\nuniform vec3 u_palette[16];\n\nout vec2 v_cell_coord;\nout vec2 v_uv_coord;\n\nflat out vec3 f_fg_colour;\nflat out vec3 f_bg_colour;\nflat out ivec2 f_glyph_coord;\nflat out uint f_char_code;\n\nvoid main() {\n\tint row = gl_InstanceID / u_cols;\n\tint col = gl_InstanceID % u_cols;\n\tint is_cursor = int(u_mouse_row == row && u_mouse_col == col);\n\tint not_is_cursor = int(is_cursor == 0);\n\n\tif (a_colour == 0u && a_char_code == 0u) {\n\t\t// clipped: don't cover any pixels below this glyph\n\t\t// e.g., a previously rendered texture\n\t\tgl_Position = vec4(2.0, 2.0, 2.0, 1.0);\n\t\treturn;\n\t}\n\n\t// 240 = 0b11110000\n\tint colour = int(a_colour + uint(a_colour == 0u) * 240u);\n\n\tint fg_idx = int(colour >> (not_is_cursor * 4) & 0xf);\n\tint bg_idx = int(colour >> (is_cursor * 4) & 0xf);\n\n\tf_fg_colour = u_palette[fg_idx];\n\tf_bg_colour = u_palette[bg_idx];\n\n\t// 32 glyph columns, 8 glyph rows\n\tint glyph_row = clamp(int(a_char_code / 32u), 0, 7);\n\tint glyph_col = clamp(int(a_char_code % 32u), 0, 31);\n\n\t// coordinate of a_char_code in u_bitmap_font\n\t// (note: there are 4 pixels per glyph, in a column; hence row * 4)\n\tf_glyph_coord = ivec2(glyph_col, glyph_row * 4);\n\n\t// used when checking whether to use u_user_font or not\n\tf_char_code = a_char_code;\n\n\t/* A glyph is made up of two triangles, six vertices:\n\t *\n\t * 0--2 ,5 0--2,5\n\t * | / + / | = | |\n\t * 1' 3--4 1,3--4\n\t */\n\n\tint row_inc = int(gl_VertexID == 1 || gl_VertexID == 3 ||\n\t\t\t gl_VertexID == 4);\n\n\tint col_inc = int(gl_VertexID == 2 || gl_VertexID == 4 ||\n\t\t\t gl_VertexID == 5);\n\n\tv_cell_coord = vec2(float(col_inc), float(row_inc));\n\n\trow += row_inc;\n\tcol += col_inc;\n\n\tv_uv_coord = vec2(float(glyph_col + col_inc) / 32.0,\n\t\t\t float(glyph_row + row_inc) / 8.0);\n\n\t// position needs to be in [-1.0, 1.0]\n\tfloat ndc_x = 2.0 * float(col) / float(u_cols) - 1.0;\n\tfloat ndc_y = -2.0 * float(row) / float(u_rows) + 1.0;\n\n\tgl_Position = vec4(ndc_x, ndc_y, 0.0, 1.0);\n}\n";
3
+ export declare const terminal_frag = "#version 300 es\n\nprecision mediump float;\n\n// Computes the mix value for a coordinate relative to pixels, that will\n// snap to the center of the pixel if it is in the center 50% of it, and\n// otherwise interpolate linearly outside of the center 50%.\nfloat sharp_bilinear_weight(float coord) {\n\t// distance of coord to center of cooresponding pixel\n\tfloat center_dist = fract(coord) - 0.5;\n\n\t// the component of coord that is within 50% of the center of the pixel\n\tfloat center_comp = clamp(center_dist, -0.25, 0.25);\n\n\t// when center_dist is outside of the center 50% of the pixel, this\n\t// will be an interpolation between this pixel and its neighbour\n\tfloat interp_coord = floor(coord) + (center_dist - center_comp) * 2.0;\n\n\t// this will be 0 when coord is in the center 50% of the pixel\n\treturn interp_coord - floor(coord);\n}\n\nfloat bit_value(uvec4 texel, int x, int y) {\n\treturn float((texel[y] >> (7 - x)) & 1u);\n}\n\nfloat bitmap_glyph_value(\n\tmediump usampler2D bitmap,\n\tivec2 glyph_coord,\n\tvec2 cell_coord\n) {\n\tint block = clamp(int(floor(cell_coord.y * 4.0)), 0, 3);\n\tivec2 texel_coord = ivec2(glyph_coord.x, glyph_coord.y + block);\n\tuvec4 texel = texelFetch(bitmap, texel_coord, 0);\n\n\t// each block is 8px wide\n\tfloat block_x = clamp(cell_coord.x * 8.0, 0.0, 7.0);\n\n\t// each cell is 4 blocks high, each block being 4px tall\n\tfloat block_y = clamp(cell_coord.y * 16.0 - float(block) * 4.0,\n\t\t\t 0.0, 3.0);\n\n\tfloat horiz_weight = sharp_bilinear_weight(block_x);\n\tfloat vert_weight = sharp_bilinear_weight(block_y);\n\n\tint x = clamp(int(block_x), 0, 7);\n\tint y = clamp(int(block_y), 0, 3);\n\n\t// get each bit\n\tfloat tl = bit_value(texel, x, y);\n\tfloat tr = bit_value(texel, x + 1, y);\n\tfloat bl = bit_value(texel, x, y + 1);\n\tfloat br = bit_value(texel, x + 1, y + 1);\n\n\tfloat top = mix(tl, tr, horiz_weight);\n\tfloat bot = mix(bl, br, horiz_weight);\n\n\treturn mix(top, bot, vert_weight);\n}\n\n\nin vec2 v_cell_coord;\nin vec2 v_uv_coord;\n\nflat in vec3 f_fg_colour;\nflat in vec3 f_bg_colour;\nflat in ivec2 f_glyph_coord;\nflat in uint f_char_code;\n\nuniform mediump usampler2D u_bitmap_font;\nuniform sampler2D u_user_font;\nuniform bool u_use_user_font;\n\nout vec4 frag_colour;\n\nvoid main() {\n\t// 176U and 191U are the first and last box-drawing characters\n\tif (u_use_user_font && (f_char_code < 176U || f_char_code >= 224U)) {\n\t\tvec4 samp = texture(u_user_font, v_uv_coord);\n\t\tfrag_colour = vec4(mix(f_bg_colour, f_fg_colour, samp.r), 1.0);\n\t\treturn;\n\t}\n\n\tfloat value = bitmap_glyph_value(u_bitmap_font, f_glyph_coord,\n\t\t\t\t\t v_cell_coord);\n\tfrag_colour = vec4(mix(f_bg_colour, f_fg_colour, value), 1.0);\n}\n";
4
+ export declare const renderer_vert = "#version 300 es\n\n#define SAMPLE_MODE\t0u\n#define GLYPHS_MODE\t1u\n#define BG_GLYPHS_MODE\t2u\n\nprecision mediump float;\n\nin uint a_row;\nin uint a_col;\nin uint a_mode;\n\nuniform int u_row;\nuniform int u_col;\nuniform int u_rows;\nuniform int u_cols;\nuniform int u_canvas_rows;\nuniform int u_canvas_cols;\nuniform int u_mouse_row;\nuniform int u_mouse_col;\nuniform vec3 u_palette[16];\nuniform sampler2D u_texture;\n\nout vec2 v_cell_coord;\nout vec2 v_tex_uv_coord; // sample mode\nout vec2 v_font_uv_coord;\n\nflat out uint f_mode;\nflat out int f_is_cursor;\nflat out vec3 f_fg_colour;\nflat out vec3 f_bg_colour;\nflat out ivec2 f_glyph_coord;\nflat out int f_char_code;\n\nconst vec3 dimensions[8] = vec3[](\n\tvec3(0.0, 0.0, 0.0),\t// black (unused)\n\tvec3(1.0, 0.8, 0.8),\t// red\n\tvec3(0.8, 1.0, 0.8),\t// green\n\tvec3(1.0, 1.0, 0.8),\t// yellow\n\tvec3(0.8, 0.8, 1.0),\t// blue\n\tvec3(1.0, 0.8, 1.0),\t// magenta\n\tvec3(0.8, 1.0, 1.0),\t// cyan\n\tvec3(1.0, 1.0, 1.0)\t// white\n);\n\nstruct Dims {\n\tint dim1;\n\tint dim2;\n\tfloat ratio;\n};\n\nDims getDims(vec3 colour) {\n\tvec3 ncolour = normalize(colour);\n\tfloat d1 = 1000.0;\n\tfloat d2 = 1000.0;\n\tint dim1 = 0;\n\tint dim2 = 0;\n\n\tfor (int i = 1; i < 8; i++) {\n\t\tfloat d = distance(ncolour, normalize(dimensions[i]));\n\n\t\tif (d < d1) {\n\t\t\td2 = d1;\n\t\t\tdim2 = dim1;\n\t\t\td1 = d;\n\t\t\tdim1 = i;\n\t\t} else if (d < d2) {\n\t\t\td2 = d;\n\t\t\tdim2 = i;\n\t\t}\n\t}\n\n\treturn Dims(dim1, dim2, d1 / (d1 + d2));\n}\n\nfloat getLum(vec3 colour, int dim) {\n\tvec3 dcolour = dimensions[dim];\n\treturn dot(colour, dcolour) / dot(dcolour, dcolour);\n}\n\nstruct Glyph {\n\tint char_code;\n\tint fg_idx;\n\tint bg_idx;\n};\n\nGlyph getGlyph(int dim, float lum) {\n\t// . , - : ; = ! * # $ @\n\tint glyphs[12] = int[](0, 46, 44, 45, 58, 59, 61, 33, 42, 35, 36, 64);\n\n\tint fgs[2] = int[](dim, dim + 8);\n\n\tif (dim == 7) {\n\t\tfgs = int[](8, 15);\n\t}\n\n\tint layer = clamp(int(lum * 2.0), 0, 2);\n\n\tGlyph glyph;\n\n\tint glyph_idx = clamp(int(12.0 * lum), 0, 11);\n\tglyph.char_code = glyphs[glyph_idx];\n\tglyph.fg_idx = fgs[layer];\n\tglyph.bg_idx = 0;\n\n\treturn glyph;\n}\n\nGlyph getBgGlyph(int dim, float lum) {\n\t// . - , : ; = ! * # $ @\n\tint glyphs[12] = int[](0, 46, 45, 44, 58, 59, 61, 33, 42, 35, 36, 64);\n\n\tint fgs[3] = int[](dim, dim + 8, 15);\n\tint bgs[3] = int[](0, dim, dim + 8);\n\n\tint layer = clamp(int(lum * 3.0), 0, 2);\n\n\t// check if dimension is white\n\tif (dim == 7) {\n\t\tif (layer > 0) {\n\t\t\t// reverse the order of the glyphs\n\t\t\tglyphs = int[](64, 36, 35, 42, 33, 61, 59, 58, 44, 45,\n\t\t\t\t 46, 0);\n\t\t}\n\n\t\tfgs = int[](7, 0, 8);\n\t\tbgs = int[](0, 7, 15);\n\t}\n\n\tGlyph glyph;\n\n\tint glyph_idx = clamp(int(12.0 * (3.0 * lum - float(layer))), 0, 11);\n\tglyph.char_code = glyphs[glyph_idx];\n\tglyph.fg_idx = fgs[layer];\n\tglyph.bg_idx = bgs[layer];\n\n\treturn glyph;\n}\n\nvoid main() {\n\tint origin_row = int(a_row);\n\tint origin_col = int(a_col);\n\n\tf_is_cursor = int(origin_row + u_row == u_mouse_row &&\n\t\t\t origin_col + u_col == u_mouse_col);\n\n\t/* A glyph is made up of two triangles, six vertices:\n\t *\n\t * 0--2 ,5 0--2,5\n\t * | / + / | = | |\n\t * 1' 3--4 1,3--4\n\t */\n\n\tint row_inc = int(gl_VertexID == 1 || gl_VertexID == 3 ||\n\t\t\t gl_VertexID == 4);\n\n\tint col_inc = int(gl_VertexID == 2 || gl_VertexID == 4 ||\n\t\t\t gl_VertexID == 5);\n\n\tv_cell_coord = vec2(float(col_inc), float(row_inc));\n\n\tint row = origin_row + row_inc;\n\tint col = origin_col + col_inc;\n\n\tv_tex_uv_coord = vec2(float(col) / float(u_cols),\n\t\t\t float(row) / float(u_rows));\n\n\t// position needs to be in [-1.0, 1.0]\n\tfloat ndc_x = 2.0 * float(u_col + col) / float(u_canvas_cols) - 1.0;\n\tfloat ndc_y = -2.0 * float(u_row + row) / float(u_canvas_rows) + 1.0;\n\n\tgl_Position = vec4(ndc_x, ndc_y, 0.0, 1.0);\n\n\tf_mode = a_mode;\n\n\t// sample mode\n\tif (f_mode == SAMPLE_MODE) {\n\t\t// no more work to do\n\t\treturn;\n\t}\n\n\t// dithered glyphs mode\n\n\tvec2 origin_uv_coord = vec2(float(origin_col) / float(u_cols),\n\t\t\t\t float(origin_row) / float(u_rows));\n\n\tvec2 full_uv_cell = vec2(1.0 / float(u_cols), 1.0 / float(u_rows));\n\tvec4 samp = texture(u_texture, origin_uv_coord + 0.5 * full_uv_cell);\n\tvec3 colour = samp.rgb;\n\n\t// dithering\n\tfloat bayer[16] = float[](\n\t\t0.0, 0.5, 0.125, 0.625,\n\t\t0.75, 0.25, 0.875, 0.375,\n\t\t0.1875, 0.6875, 0.0625, 0.5625,\n\t\t0.9375, 0.4375, 0.8125, 0.3125\n\t);\n\tfloat bayer_val = bayer[int(origin_row) % 4 * 4 + int(origin_col) % 4];\n\n\tDims dims = getDims(colour);\n\tint dim = (bayer_val < dims.ratio) ? dims.dim2 : dims.dim1;\n\tfloat lum = getLum(colour, dim);\n\n\tfloat dither = (bayer_val - 0.5) / 12.0;\n\tlum = clamp(lum + dither, 0.0, 1.0);\n\n\tGlyph glyph;\n\n\tif (f_mode == BG_GLYPHS_MODE) {\n\t\tglyph = getBgGlyph(dim, lum);\n\t} else {\n\t\tglyph = getGlyph(dim, lum);\n\t}\n\n\tif (glyph.fg_idx == glyph.bg_idx && f_is_cursor == 1) {\n\t\tglyph.fg_idx = 15;\n\t\tglyph.bg_idx = 0;\n\t}\n\n\tf_fg_colour = u_palette[glyph.fg_idx];\n\tf_bg_colour = u_palette[glyph.bg_idx];\n\n\t// 32 glyph columns, 8 glyph rows\n\tint glyph_row = clamp(int(glyph.char_code / 32), 0, 7);\n\tint glyph_col = clamp(int(glyph.char_code % 32), 0, 31);\n\n\t// coordinate of char_code in u_bitmap_font\n\tf_glyph_coord = ivec2(glyph_col, glyph_row << 2);\n\n\tf_char_code = glyph.char_code;\n\n\tv_font_uv_coord = vec2(float(glyph_col + col_inc) / 32.0,\n\t\t\t float(glyph_row + row_inc) / 8.0);\n}\n";
5
+ export declare const renderer_frag = "#version 300 es\n\nprecision mediump float;\n\n// Computes the mix value for a coordinate relative to pixels, that will\n// snap to the center of the pixel if it is in the center 50% of it, and\n// otherwise interpolate linearly outside of the center 50%.\nfloat sharp_bilinear_weight(float coord) {\n\t// distance of coord to center of cooresponding pixel\n\tfloat center_dist = fract(coord) - 0.5;\n\n\t// the component of coord that is within 50% of the center of the pixel\n\tfloat center_comp = clamp(center_dist, -0.25, 0.25);\n\n\t// when center_dist is outside of the center 50% of the pixel, this\n\t// will be an interpolation between this pixel and its neighbour\n\tfloat interp_coord = floor(coord) + (center_dist - center_comp) * 2.0;\n\n\t// this will be 0 when coord is in the center 50% of the pixel\n\treturn interp_coord - floor(coord);\n}\n\nfloat bit_value(uvec4 texel, int x, int y) {\n\treturn float((texel[y] >> (7 - x)) & 1u);\n}\n\nfloat bitmap_glyph_value(\n\tmediump usampler2D bitmap,\n\tivec2 glyph_coord,\n\tvec2 cell_coord\n) {\n\tint block = clamp(int(floor(cell_coord.y * 4.0)), 0, 3);\n\tivec2 texel_coord = ivec2(glyph_coord.x, glyph_coord.y + block);\n\tuvec4 texel = texelFetch(bitmap, texel_coord, 0);\n\n\t// each block is 8px wide\n\tfloat block_x = clamp(cell_coord.x * 8.0, 0.0, 7.0);\n\n\t// each cell is 4 blocks high, each block being 4px tall\n\tfloat block_y = clamp(cell_coord.y * 16.0 - float(block) * 4.0,\n\t\t\t 0.0, 3.0);\n\n\tfloat horiz_weight = sharp_bilinear_weight(block_x);\n\tfloat vert_weight = sharp_bilinear_weight(block_y);\n\n\tint x = clamp(int(block_x), 0, 7);\n\tint y = clamp(int(block_y), 0, 3);\n\n\t// get each bit\n\tfloat tl = bit_value(texel, x, y);\n\tfloat tr = bit_value(texel, x + 1, y);\n\tfloat bl = bit_value(texel, x, y + 1);\n\tfloat br = bit_value(texel, x + 1, y + 1);\n\n\tfloat top = mix(tl, tr, horiz_weight);\n\tfloat bot = mix(bl, br, horiz_weight);\n\n\treturn mix(top, bot, vert_weight);\n}\n\n\n#define SAMPLE_MODE 0u\n\nin vec2 v_cell_coord;\nin vec2 v_tex_uv_coord; // f_mode == 0\nin vec2 v_font_uv_coord;\n\nflat in uint f_mode;\nflat in int f_is_cursor;\nflat in vec3 f_fg_colour;\nflat in vec3 f_bg_colour;\nflat in ivec2 f_glyph_coord;\nflat in int f_char_code;\n\nuniform mediump usampler2D u_bitmap_font;\nuniform sampler2D u_texture; // f_mode == 0\nuniform sampler2D u_user_font;\nuniform bool u_use_user_font;\n\nout vec4 frag_colour;\n\nvoid main() {\n\tif (f_mode == SAMPLE_MODE) {\n\t\tfrag_colour = texture(u_texture, v_tex_uv_coord);\n\n\t\t// invert the sample\n\t\tif (f_is_cursor == 1) {\n\t\t\tfrag_colour = vec4(vec3(1.0, 1.0, 1.0) -\n\t\t\t\t\t frag_colour.rgb, 1.0);\n\t\t}\n\n\t\treturn;\n\t}\n\n\tvec3 fg = f_fg_colour;\n\tvec3 bg = f_bg_colour;\n\n\tif (f_is_cursor == 1) {\n\t\tvec3 tmp = fg;\n\t\tfg = bg;\n\t\tbg = tmp;\n\t}\n\n\t// 176 and 191 are the first and last box-drawing characters\n\tif (u_use_user_font && (f_char_code < 176 || f_char_code >= 224)) {\n\t\tvec4 samp = texture(u_user_font, v_font_uv_coord);\n\t\tfrag_colour = vec4(mix(bg, fg, samp.r), 1.0);\n\t\treturn;\n\t}\n\n\tfloat value = bitmap_glyph_value(u_bitmap_font, f_glyph_coord,\n\t\t\t\t\t v_cell_coord);\n\tfrag_colour = vec4(mix(bg, fg, value), 1.0);\n}\n";
@@ -0,0 +1,470 @@
1
+ // generated by `bun run shaders`
2
+ export const glyph_frag = `\
3
+ // Computes the mix value for a coordinate relative to pixels, that will
4
+ // snap to the center of the pixel if it is in the center 50% of it, and
5
+ // otherwise interpolate linearly outside of the center 50%.
6
+ float sharp_bilinear_weight(float coord) {
7
+ // distance of coord to center of cooresponding pixel
8
+ float center_dist = fract(coord) - 0.5;
9
+
10
+ // the component of coord that is within 50% of the center of the pixel
11
+ float center_comp = clamp(center_dist, -0.25, 0.25);
12
+
13
+ // when center_dist is outside of the center 50% of the pixel, this
14
+ // will be an interpolation between this pixel and its neighbour
15
+ float interp_coord = floor(coord) + (center_dist - center_comp) * 2.0;
16
+
17
+ // this will be 0 when coord is in the center 50% of the pixel
18
+ return interp_coord - floor(coord);
19
+ }
20
+
21
+ float bit_value(uvec4 texel, int x, int y) {
22
+ return float((texel[y] >> (7 - x)) & 1u);
23
+ }
24
+
25
+ float bitmap_glyph_value(
26
+ mediump usampler2D bitmap,
27
+ ivec2 glyph_coord,
28
+ vec2 cell_coord
29
+ ) {
30
+ int block = clamp(int(floor(cell_coord.y * 4.0)), 0, 3);
31
+ ivec2 texel_coord = ivec2(glyph_coord.x, glyph_coord.y + block);
32
+ uvec4 texel = texelFetch(bitmap, texel_coord, 0);
33
+
34
+ // each block is 8px wide
35
+ float block_x = clamp(cell_coord.x * 8.0, 0.0, 7.0);
36
+
37
+ // each cell is 4 blocks high, each block being 4px tall
38
+ float block_y = clamp(cell_coord.y * 16.0 - float(block) * 4.0,
39
+ 0.0, 3.0);
40
+
41
+ float horiz_weight = sharp_bilinear_weight(block_x);
42
+ float vert_weight = sharp_bilinear_weight(block_y);
43
+
44
+ int x = clamp(int(block_x), 0, 7);
45
+ int y = clamp(int(block_y), 0, 3);
46
+
47
+ // get each bit
48
+ float tl = bit_value(texel, x, y);
49
+ float tr = bit_value(texel, x + 1, y);
50
+ float bl = bit_value(texel, x, y + 1);
51
+ float br = bit_value(texel, x + 1, y + 1);
52
+
53
+ float top = mix(tl, tr, horiz_weight);
54
+ float bot = mix(bl, br, horiz_weight);
55
+
56
+ return mix(top, bot, vert_weight);
57
+ }
58
+ `;
59
+ export const terminal_vert = `\
60
+ #version 300 es
61
+
62
+ in uint a_colour;
63
+ in uint a_char_code;
64
+
65
+ uniform int u_rows;
66
+ uniform int u_cols;
67
+ uniform int u_mouse_row;
68
+ uniform int u_mouse_col;
69
+ uniform vec3 u_palette[16];
70
+
71
+ out vec2 v_cell_coord;
72
+ out vec2 v_uv_coord;
73
+
74
+ flat out vec3 f_fg_colour;
75
+ flat out vec3 f_bg_colour;
76
+ flat out ivec2 f_glyph_coord;
77
+ flat out uint f_char_code;
78
+
79
+ void main() {
80
+ int row = gl_InstanceID / u_cols;
81
+ int col = gl_InstanceID % u_cols;
82
+ int is_cursor = int(u_mouse_row == row && u_mouse_col == col);
83
+ int not_is_cursor = int(is_cursor == 0);
84
+
85
+ if (a_colour == 0u && a_char_code == 0u) {
86
+ // clipped: don't cover any pixels below this glyph
87
+ // e.g., a previously rendered texture
88
+ gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
89
+ return;
90
+ }
91
+
92
+ // 240 = 0b11110000
93
+ int colour = int(a_colour + uint(a_colour == 0u) * 240u);
94
+
95
+ int fg_idx = int(colour >> (not_is_cursor * 4) & 0xf);
96
+ int bg_idx = int(colour >> (is_cursor * 4) & 0xf);
97
+
98
+ f_fg_colour = u_palette[fg_idx];
99
+ f_bg_colour = u_palette[bg_idx];
100
+
101
+ // 32 glyph columns, 8 glyph rows
102
+ int glyph_row = clamp(int(a_char_code / 32u), 0, 7);
103
+ int glyph_col = clamp(int(a_char_code % 32u), 0, 31);
104
+
105
+ // coordinate of a_char_code in u_bitmap_font
106
+ // (note: there are 4 pixels per glyph, in a column; hence row * 4)
107
+ f_glyph_coord = ivec2(glyph_col, glyph_row * 4);
108
+
109
+ // used when checking whether to use u_user_font or not
110
+ f_char_code = a_char_code;
111
+
112
+ /* A glyph is made up of two triangles, six vertices:
113
+ *
114
+ * 0--2 ,5 0--2,5
115
+ * | / + / | = | |
116
+ * 1' 3--4 1,3--4
117
+ */
118
+
119
+ int row_inc = int(gl_VertexID == 1 || gl_VertexID == 3 ||
120
+ gl_VertexID == 4);
121
+
122
+ int col_inc = int(gl_VertexID == 2 || gl_VertexID == 4 ||
123
+ gl_VertexID == 5);
124
+
125
+ v_cell_coord = vec2(float(col_inc), float(row_inc));
126
+
127
+ row += row_inc;
128
+ col += col_inc;
129
+
130
+ v_uv_coord = vec2(float(glyph_col + col_inc) / 32.0,
131
+ float(glyph_row + row_inc) / 8.0);
132
+
133
+ // position needs to be in [-1.0, 1.0]
134
+ float ndc_x = 2.0 * float(col) / float(u_cols) - 1.0;
135
+ float ndc_y = -2.0 * float(row) / float(u_rows) + 1.0;
136
+
137
+ gl_Position = vec4(ndc_x, ndc_y, 0.0, 1.0);
138
+ }
139
+ `;
140
+ export const terminal_frag = `\
141
+ #version 300 es
142
+
143
+ precision mediump float;
144
+
145
+ ${glyph_frag}
146
+
147
+ in vec2 v_cell_coord;
148
+ in vec2 v_uv_coord;
149
+
150
+ flat in vec3 f_fg_colour;
151
+ flat in vec3 f_bg_colour;
152
+ flat in ivec2 f_glyph_coord;
153
+ flat in uint f_char_code;
154
+
155
+ uniform mediump usampler2D u_bitmap_font;
156
+ uniform sampler2D u_user_font;
157
+ uniform bool u_use_user_font;
158
+
159
+ out vec4 frag_colour;
160
+
161
+ void main() {
162
+ // 176U and 191U are the first and last box-drawing characters
163
+ if (u_use_user_font && (f_char_code < 176U || f_char_code >= 224U)) {
164
+ vec4 samp = texture(u_user_font, v_uv_coord);
165
+ frag_colour = vec4(mix(f_bg_colour, f_fg_colour, samp.r), 1.0);
166
+ return;
167
+ }
168
+
169
+ float value = bitmap_glyph_value(u_bitmap_font, f_glyph_coord,
170
+ v_cell_coord);
171
+ frag_colour = vec4(mix(f_bg_colour, f_fg_colour, value), 1.0);
172
+ }
173
+ `;
174
+ export const renderer_vert = `\
175
+ #version 300 es
176
+
177
+ #define SAMPLE_MODE 0u
178
+ #define GLYPHS_MODE 1u
179
+ #define BG_GLYPHS_MODE 2u
180
+
181
+ precision mediump float;
182
+
183
+ in uint a_row;
184
+ in uint a_col;
185
+ in uint a_mode;
186
+
187
+ uniform int u_row;
188
+ uniform int u_col;
189
+ uniform int u_rows;
190
+ uniform int u_cols;
191
+ uniform int u_canvas_rows;
192
+ uniform int u_canvas_cols;
193
+ uniform int u_mouse_row;
194
+ uniform int u_mouse_col;
195
+ uniform vec3 u_palette[16];
196
+ uniform sampler2D u_texture;
197
+
198
+ out vec2 v_cell_coord;
199
+ out vec2 v_tex_uv_coord; // sample mode
200
+ out vec2 v_font_uv_coord;
201
+
202
+ flat out uint f_mode;
203
+ flat out int f_is_cursor;
204
+ flat out vec3 f_fg_colour;
205
+ flat out vec3 f_bg_colour;
206
+ flat out ivec2 f_glyph_coord;
207
+ flat out int f_char_code;
208
+
209
+ const vec3 dimensions[8] = vec3[](
210
+ vec3(0.0, 0.0, 0.0), // black (unused)
211
+ vec3(1.0, 0.8, 0.8), // red
212
+ vec3(0.8, 1.0, 0.8), // green
213
+ vec3(1.0, 1.0, 0.8), // yellow
214
+ vec3(0.8, 0.8, 1.0), // blue
215
+ vec3(1.0, 0.8, 1.0), // magenta
216
+ vec3(0.8, 1.0, 1.0), // cyan
217
+ vec3(1.0, 1.0, 1.0) // white
218
+ );
219
+
220
+ struct Dims {
221
+ int dim1;
222
+ int dim2;
223
+ float ratio;
224
+ };
225
+
226
+ Dims getDims(vec3 colour) {
227
+ vec3 ncolour = normalize(colour);
228
+ float d1 = 1000.0;
229
+ float d2 = 1000.0;
230
+ int dim1 = 0;
231
+ int dim2 = 0;
232
+
233
+ for (int i = 1; i < 8; i++) {
234
+ float d = distance(ncolour, normalize(dimensions[i]));
235
+
236
+ if (d < d1) {
237
+ d2 = d1;
238
+ dim2 = dim1;
239
+ d1 = d;
240
+ dim1 = i;
241
+ } else if (d < d2) {
242
+ d2 = d;
243
+ dim2 = i;
244
+ }
245
+ }
246
+
247
+ return Dims(dim1, dim2, d1 / (d1 + d2));
248
+ }
249
+
250
+ float getLum(vec3 colour, int dim) {
251
+ vec3 dcolour = dimensions[dim];
252
+ return dot(colour, dcolour) / dot(dcolour, dcolour);
253
+ }
254
+
255
+ struct Glyph {
256
+ int char_code;
257
+ int fg_idx;
258
+ int bg_idx;
259
+ };
260
+
261
+ Glyph getGlyph(int dim, float lum) {
262
+ // . , - : ; = ! * # $ @
263
+ int glyphs[12] = int[](0, 46, 44, 45, 58, 59, 61, 33, 42, 35, 36, 64);
264
+
265
+ int fgs[2] = int[](dim, dim + 8);
266
+
267
+ if (dim == 7) {
268
+ fgs = int[](8, 15);
269
+ }
270
+
271
+ int layer = clamp(int(lum * 2.0), 0, 2);
272
+
273
+ Glyph glyph;
274
+
275
+ int glyph_idx = clamp(int(12.0 * lum), 0, 11);
276
+ glyph.char_code = glyphs[glyph_idx];
277
+ glyph.fg_idx = fgs[layer];
278
+ glyph.bg_idx = 0;
279
+
280
+ return glyph;
281
+ }
282
+
283
+ Glyph getBgGlyph(int dim, float lum) {
284
+ // . - , : ; = ! * # $ @
285
+ int glyphs[12] = int[](0, 46, 45, 44, 58, 59, 61, 33, 42, 35, 36, 64);
286
+
287
+ int fgs[3] = int[](dim, dim + 8, 15);
288
+ int bgs[3] = int[](0, dim, dim + 8);
289
+
290
+ int layer = clamp(int(lum * 3.0), 0, 2);
291
+
292
+ // check if dimension is white
293
+ if (dim == 7) {
294
+ if (layer > 0) {
295
+ // reverse the order of the glyphs
296
+ glyphs = int[](64, 36, 35, 42, 33, 61, 59, 58, 44, 45,
297
+ 46, 0);
298
+ }
299
+
300
+ fgs = int[](7, 0, 8);
301
+ bgs = int[](0, 7, 15);
302
+ }
303
+
304
+ Glyph glyph;
305
+
306
+ int glyph_idx = clamp(int(12.0 * (3.0 * lum - float(layer))), 0, 11);
307
+ glyph.char_code = glyphs[glyph_idx];
308
+ glyph.fg_idx = fgs[layer];
309
+ glyph.bg_idx = bgs[layer];
310
+
311
+ return glyph;
312
+ }
313
+
314
+ void main() {
315
+ int origin_row = int(a_row);
316
+ int origin_col = int(a_col);
317
+
318
+ f_is_cursor = int(origin_row + u_row == u_mouse_row &&
319
+ origin_col + u_col == u_mouse_col);
320
+
321
+ /* A glyph is made up of two triangles, six vertices:
322
+ *
323
+ * 0--2 ,5 0--2,5
324
+ * | / + / | = | |
325
+ * 1' 3--4 1,3--4
326
+ */
327
+
328
+ int row_inc = int(gl_VertexID == 1 || gl_VertexID == 3 ||
329
+ gl_VertexID == 4);
330
+
331
+ int col_inc = int(gl_VertexID == 2 || gl_VertexID == 4 ||
332
+ gl_VertexID == 5);
333
+
334
+ v_cell_coord = vec2(float(col_inc), float(row_inc));
335
+
336
+ int row = origin_row + row_inc;
337
+ int col = origin_col + col_inc;
338
+
339
+ v_tex_uv_coord = vec2(float(col) / float(u_cols),
340
+ float(row) / float(u_rows));
341
+
342
+ // position needs to be in [-1.0, 1.0]
343
+ float ndc_x = 2.0 * float(u_col + col) / float(u_canvas_cols) - 1.0;
344
+ float ndc_y = -2.0 * float(u_row + row) / float(u_canvas_rows) + 1.0;
345
+
346
+ gl_Position = vec4(ndc_x, ndc_y, 0.0, 1.0);
347
+
348
+ f_mode = a_mode;
349
+
350
+ // sample mode
351
+ if (f_mode == SAMPLE_MODE) {
352
+ // no more work to do
353
+ return;
354
+ }
355
+
356
+ // dithered glyphs mode
357
+
358
+ vec2 origin_uv_coord = vec2(float(origin_col) / float(u_cols),
359
+ float(origin_row) / float(u_rows));
360
+
361
+ vec2 full_uv_cell = vec2(1.0 / float(u_cols), 1.0 / float(u_rows));
362
+ vec4 samp = texture(u_texture, origin_uv_coord + 0.5 * full_uv_cell);
363
+ vec3 colour = samp.rgb;
364
+
365
+ // dithering
366
+ float bayer[16] = float[](
367
+ 0.0, 0.5, 0.125, 0.625,
368
+ 0.75, 0.25, 0.875, 0.375,
369
+ 0.1875, 0.6875, 0.0625, 0.5625,
370
+ 0.9375, 0.4375, 0.8125, 0.3125
371
+ );
372
+ float bayer_val = bayer[int(origin_row) % 4 * 4 + int(origin_col) % 4];
373
+
374
+ Dims dims = getDims(colour);
375
+ int dim = (bayer_val < dims.ratio) ? dims.dim2 : dims.dim1;
376
+ float lum = getLum(colour, dim);
377
+
378
+ float dither = (bayer_val - 0.5) / 12.0;
379
+ lum = clamp(lum + dither, 0.0, 1.0);
380
+
381
+ Glyph glyph;
382
+
383
+ if (f_mode == BG_GLYPHS_MODE) {
384
+ glyph = getBgGlyph(dim, lum);
385
+ } else {
386
+ glyph = getGlyph(dim, lum);
387
+ }
388
+
389
+ if (glyph.fg_idx == glyph.bg_idx && f_is_cursor == 1) {
390
+ glyph.fg_idx = 15;
391
+ glyph.bg_idx = 0;
392
+ }
393
+
394
+ f_fg_colour = u_palette[glyph.fg_idx];
395
+ f_bg_colour = u_palette[glyph.bg_idx];
396
+
397
+ // 32 glyph columns, 8 glyph rows
398
+ int glyph_row = clamp(int(glyph.char_code / 32), 0, 7);
399
+ int glyph_col = clamp(int(glyph.char_code % 32), 0, 31);
400
+
401
+ // coordinate of char_code in u_bitmap_font
402
+ f_glyph_coord = ivec2(glyph_col, glyph_row << 2);
403
+
404
+ f_char_code = glyph.char_code;
405
+
406
+ v_font_uv_coord = vec2(float(glyph_col + col_inc) / 32.0,
407
+ float(glyph_row + row_inc) / 8.0);
408
+ }
409
+ `;
410
+ export const renderer_frag = `\
411
+ #version 300 es
412
+
413
+ precision mediump float;
414
+
415
+ ${glyph_frag}
416
+
417
+ #define SAMPLE_MODE 0u
418
+
419
+ in vec2 v_cell_coord;
420
+ in vec2 v_tex_uv_coord; // f_mode == 0
421
+ in vec2 v_font_uv_coord;
422
+
423
+ flat in uint f_mode;
424
+ flat in int f_is_cursor;
425
+ flat in vec3 f_fg_colour;
426
+ flat in vec3 f_bg_colour;
427
+ flat in ivec2 f_glyph_coord;
428
+ flat in int f_char_code;
429
+
430
+ uniform mediump usampler2D u_bitmap_font;
431
+ uniform sampler2D u_texture; // f_mode == 0
432
+ uniform sampler2D u_user_font;
433
+ uniform bool u_use_user_font;
434
+
435
+ out vec4 frag_colour;
436
+
437
+ void main() {
438
+ if (f_mode == SAMPLE_MODE) {
439
+ frag_colour = texture(u_texture, v_tex_uv_coord);
440
+
441
+ // invert the sample
442
+ if (f_is_cursor == 1) {
443
+ frag_colour = vec4(vec3(1.0, 1.0, 1.0) -
444
+ frag_colour.rgb, 1.0);
445
+ }
446
+
447
+ return;
448
+ }
449
+
450
+ vec3 fg = f_fg_colour;
451
+ vec3 bg = f_bg_colour;
452
+
453
+ if (f_is_cursor == 1) {
454
+ vec3 tmp = fg;
455
+ fg = bg;
456
+ bg = tmp;
457
+ }
458
+
459
+ // 176 and 191 are the first and last box-drawing characters
460
+ if (u_use_user_font && (f_char_code < 176 || f_char_code >= 224)) {
461
+ vec4 samp = texture(u_user_font, v_font_uv_coord);
462
+ frag_colour = vec4(mix(bg, fg, samp.r), 1.0);
463
+ return;
464
+ }
465
+
466
+ float value = bitmap_glyph_value(u_bitmap_font, f_glyph_coord,
467
+ v_cell_coord);
468
+ frag_colour = vec4(mix(bg, fg, value), 1.0);
469
+ }
470
+ `;