| /* |
| * Copyright (C) 2026 Behdad Esfahbod |
| * Copyright (C) 2017 Eric Lengyel |
| * |
| * The _hb_gpu_slug body below is based on the Slug algorithm by |
| * Eric Lengyel: https://github.com/EricLengyel/Slug |
| * |
| * This is part of HarfBuzz, a text shaping library. |
| * |
| * Permission is hereby granted, without written agreement and without |
| * license or royalty fees, to use, copy, modify, and distribute this |
| * software and its documentation for any purpose, provided that the |
| * above copyright notice and the following two paragraphs appear in |
| * all copies of this software. |
| * |
| * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR |
| * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES |
| * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN |
| * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| * |
| * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, |
| * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
| * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS |
| * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO |
| * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
| */ |
| |
| |
| /* Shared fragment-shader helpers for the hb-gpu renderers. |
| * |
| * Requires GLSL 3.30 or GLSL ES 3.00. |
| * |
| * For GLSL ES 3.00 / WebGL2, define HB_GPU_ATLAS_2D before |
| * including this source. The atlas is then a 2D isampler2D |
| * texture of known width (set via hb_gpu_atlas_width uniform) |
| * instead of an isamplerBuffer. |
| * |
| * Exports: |
| * hb_gpu_fetch (offset) atlas lookup |
| * _hb_gpu_slug (rc, pixelsPerEm, glyphLoc) MSAA-aware Slug |
| * coverage (caller supplies |
| * pixelsPerEm so it can be |
| * invoked from non-uniform |
| * control flow) |
| * hb_gpu_ppem (rc, glyphLoc) pixels per em at fragment |
| * _hb_gpu_curve_counts (rc, glyphLoc) debug: per-pixel curve counts |
| * hb_gpu_stem_darken (cov, brightness, ppem) thin-stroke contrast |
| * correction |
| */ |
| |
| |
| #ifndef HB_GPU_UNITS_PER_EM |
| #define HB_GPU_UNITS_PER_EM 4 |
| #endif |
| |
| #define HB_GPU_INV_UNITS float(1.0 / float(HB_GPU_UNITS_PER_EM)) |
| |
| |
| #ifdef HB_GPU_ATLAS_2D |
| uniform highp isampler2D hb_gpu_atlas; |
| uniform int hb_gpu_atlas_width; |
| ivec4 hb_gpu_fetch (int offset) |
| { |
| return texelFetch (hb_gpu_atlas, |
| ivec2 (offset % hb_gpu_atlas_width, |
| offset / hb_gpu_atlas_width), 0); |
| } |
| #else |
| uniform isamplerBuffer hb_gpu_atlas; |
| ivec4 hb_gpu_fetch (int offset) |
| { |
| return texelFetch (hb_gpu_atlas, offset); |
| } |
| #endif |
| |
| |
| uint _hb_gpu_calc_root_code (float y1, float y2, float y3) |
| { |
| uint i1 = floatBitsToUint (y1) >> 31U; |
| uint i2 = floatBitsToUint (y2) >> 30U; |
| uint i3 = floatBitsToUint (y3) >> 29U; |
| |
| uint shift = (i2 & 2U) | (i1 & ~2U); |
| shift = (i3 & 4U) | (shift & ~4U); |
| |
| return (0x2E74U >> shift) & 0x0101U; |
| } |
| |
| vec2 _hb_gpu_solve_horiz_poly (vec2 a, vec2 b, vec2 p1) |
| { |
| float ra = 1.0 / a.y; |
| float rb = 0.5 / b.y; |
| |
| float d = sqrt (max (b.y * b.y - a.y * p1.y, 0.0)); |
| float t1 = (b.y - d) * ra; |
| float t2 = (b.y + d) * ra; |
| |
| if (a.y == 0.0) |
| t1 = t2 = p1.y * rb; |
| |
| return vec2 ((a.x * t1 - b.x * 2.0) * t1 + p1.x, |
| (a.x * t2 - b.x * 2.0) * t2 + p1.x); |
| } |
| |
| vec2 _hb_gpu_solve_vert_poly (vec2 a, vec2 b, vec2 p1) |
| { |
| float ra = 1.0 / a.x; |
| float rb = 0.5 / b.x; |
| |
| float d = sqrt (max (b.x * b.x - a.x * p1.x, 0.0)); |
| float t1 = (b.x - d) * ra; |
| float t2 = (b.x + d) * ra; |
| |
| if (a.x == 0.0) |
| t1 = t2 = p1.x * rb; |
| |
| return vec2 ((a.y * t1 - b.y * 2.0) * t1 + p1.y, |
| (a.y * t2 - b.y * 2.0) * t2 + p1.y); |
| } |
| |
| float _hb_gpu_calc_coverage (float xcov, float ycov, float xwgt, float ywgt) |
| { |
| float coverage = max (abs (xcov * xwgt + ycov * ywgt) / |
| max (xwgt + ywgt, 1.0 / 65536.0), |
| min (abs (xcov), abs (ycov))); |
| |
| return clamp (coverage, 0.0, 1.0); |
| } |
| |
| /* Decoded glyph band info for a pixel position. */ |
| struct _hb_gpu_glyph_info |
| { |
| int glyphLoc; |
| int bandBase; |
| ivec2 bandIndex; |
| int numHBands; |
| int numVBands; |
| vec2 scale; |
| }; |
| |
| _hb_gpu_glyph_info _hb_gpu_decode_glyph (vec2 renderCoord, uint glyphLoc_) |
| { |
| _hb_gpu_glyph_info gi; |
| gi.glyphLoc = int (glyphLoc_); |
| |
| ivec4 header0 = hb_gpu_fetch (gi.glyphLoc); |
| ivec4 header1 = hb_gpu_fetch (gi.glyphLoc + 1); |
| vec4 ext = vec4 (header0) * HB_GPU_INV_UNITS; |
| gi.numHBands = header1.r; |
| gi.numVBands = header1.g; |
| gi.scale = vec2 (float (header1.b), float (header1.a)); |
| |
| vec2 extSize = ext.zw - ext.xy; |
| vec2 bandScale = vec2 (float (gi.numVBands), float (gi.numHBands)) / max (extSize, vec2 (1.0 / 65536.0)); |
| vec2 bandOffset = -ext.xy * bandScale; |
| |
| gi.bandIndex = clamp (ivec2 (renderCoord * bandScale + bandOffset), |
| ivec2 (0, 0), |
| ivec2 (gi.numVBands - 1, gi.numHBands - 1)); |
| |
| gi.bandBase = gi.glyphLoc + 2; |
| return gi; |
| } |
| |
| /* Return pixels per em at this fragment. */ |
| float hb_gpu_ppem (vec2 renderCoord, uint glyphLoc_) |
| { |
| _hb_gpu_glyph_info gi = _hb_gpu_decode_glyph (renderCoord, glyphLoc_); |
| vec2 emsPerPixel = fwidth (renderCoord); |
| return min (gi.scale.x, gi.scale.y) / |
| max (emsPerPixel.x, emsPerPixel.y); |
| } |
| |
| /* Return per-pixel curve counts: (horizontal, vertical). */ |
| ivec2 _hb_gpu_curve_counts (vec2 renderCoord, uint glyphLoc_) |
| { |
| _hb_gpu_glyph_info gi = _hb_gpu_decode_glyph (renderCoord, glyphLoc_); |
| int hCount = hb_gpu_fetch (gi.bandBase + gi.bandIndex.y).r; |
| int vCount = hb_gpu_fetch (gi.bandBase + gi.numHBands + gi.bandIndex.x).r; |
| return ivec2 (hCount, vCount); |
| } |
| |
| /* Single-sample coverage in [0, 1]. */ |
| float _hb_gpu_slug_single (vec2 renderCoord, vec2 pixelsPerEm, uint glyphLoc_) |
| { |
| |
| _hb_gpu_glyph_info gi = _hb_gpu_decode_glyph (renderCoord, glyphLoc_); |
| int glyphLoc = gi.glyphLoc; |
| int bandBase = gi.bandBase; |
| int numHBands = gi.numHBands; |
| |
| float xcov = 0.0; |
| float xwgt = 0.0; |
| |
| ivec4 hbandData = hb_gpu_fetch (bandBase + gi.bandIndex.y); |
| int hCurveCount = hbandData.r; |
| /* Symmetric: choose rightward (desc) or leftward (asc) sort */ |
| float hSplit = float (hbandData.a) * HB_GPU_INV_UNITS; |
| bool hLeftRay = (renderCoord.x < hSplit); |
| int hDataOffset = (hLeftRay ? hbandData.b : hbandData.g) + 32768; |
| |
| for (int ci = 0; ci < hCurveCount; ci++) |
| { |
| int curveOffset = hb_gpu_fetch (glyphLoc + hDataOffset + ci).r + 32768; |
| |
| ivec4 raw12 = hb_gpu_fetch (glyphLoc + curveOffset); |
| ivec4 raw3 = hb_gpu_fetch (glyphLoc + curveOffset + 1); |
| |
| vec4 q12 = vec4 (raw12) * HB_GPU_INV_UNITS; |
| vec2 q3 = vec2 (raw3.rg) * HB_GPU_INV_UNITS; |
| |
| vec4 p12 = q12 - vec4 (renderCoord, renderCoord); |
| vec2 p3 = q3 - renderCoord; |
| |
| if (hLeftRay) { |
| if (min (min (p12.x, p12.z), p3.x) * pixelsPerEm.x > 0.5) break; |
| } else { |
| if (max (max (p12.x, p12.z), p3.x) * pixelsPerEm.x < -0.5) break; |
| } |
| |
| uint code = _hb_gpu_calc_root_code (p12.y, p12.w, p3.y); |
| if (code != 0U) |
| { |
| vec2 a = q12.xy - q12.zw * 2.0 + q3; |
| vec2 b = q12.xy - q12.zw; |
| vec2 r = _hb_gpu_solve_horiz_poly (a, b, p12.xy) * pixelsPerEm.x; |
| /* For leftward ray: saturate(0.5 - r) counts coverage from the left */ |
| vec2 cov = hLeftRay ? clamp (vec2 (0.5) - r, 0.0, 1.0) |
| : clamp (r + vec2 (0.5), 0.0, 1.0); |
| |
| if ((code & 1U) != 0U) |
| { |
| xcov += cov.x; |
| xwgt = max (xwgt, clamp (1.0 - abs (r.x) * 2.0, 0.0, 1.0)); |
| } |
| |
| if (code > 1U) |
| { |
| xcov -= cov.y; |
| xwgt = max (xwgt, clamp (1.0 - abs (r.y) * 2.0, 0.0, 1.0)); |
| } |
| } |
| } |
| |
| float ycov = 0.0; |
| float ywgt = 0.0; |
| |
| ivec4 vbandData = hb_gpu_fetch (bandBase + numHBands + gi.bandIndex.x); |
| int vCurveCount = vbandData.r; |
| float vSplit = float (vbandData.a) * HB_GPU_INV_UNITS; |
| bool vLeftRay = (renderCoord.y < vSplit); |
| int vDataOffset = (vLeftRay ? vbandData.b : vbandData.g) + 32768; |
| |
| for (int ci = 0; ci < vCurveCount; ci++) |
| { |
| int curveOffset = hb_gpu_fetch (glyphLoc + vDataOffset + ci).r + 32768; |
| |
| ivec4 raw12 = hb_gpu_fetch (glyphLoc + curveOffset); |
| ivec4 raw3 = hb_gpu_fetch (glyphLoc + curveOffset + 1); |
| |
| vec4 q12 = vec4 (raw12) * HB_GPU_INV_UNITS; |
| vec2 q3 = vec2 (raw3.rg) * HB_GPU_INV_UNITS; |
| |
| vec4 p12 = q12 - vec4 (renderCoord, renderCoord); |
| vec2 p3 = q3 - renderCoord; |
| |
| if (vLeftRay) { |
| if (min (min (p12.y, p12.w), p3.y) * pixelsPerEm.y > 0.5) break; |
| } else { |
| if (max (max (p12.y, p12.w), p3.y) * pixelsPerEm.y < -0.5) break; |
| } |
| |
| uint code = _hb_gpu_calc_root_code (p12.x, p12.z, p3.x); |
| if (code != 0U) |
| { |
| vec2 a = q12.xy - q12.zw * 2.0 + q3; |
| vec2 b = q12.xy - q12.zw; |
| vec2 r = _hb_gpu_solve_vert_poly (a, b, p12.xy) * pixelsPerEm.y; |
| vec2 cov = vLeftRay ? clamp (vec2 (0.5) - r, 0.0, 1.0) |
| : clamp (r + vec2 (0.5), 0.0, 1.0); |
| |
| if ((code & 1U) != 0U) |
| { |
| ycov -= cov.x; |
| ywgt = max (ywgt, clamp (1.0 - abs (r.x) * 2.0, 0.0, 1.0)); |
| } |
| |
| if (code > 1U) |
| { |
| ycov += cov.y; |
| ywgt = max (ywgt, clamp (1.0 - abs (r.y) * 2.0, 0.0, 1.0)); |
| } |
| } |
| } |
| |
| return _hb_gpu_calc_coverage (xcov, ycov, xwgt, ywgt); |
| } |
| |
| /* MSAA-aware Slug coverage. Caller supplies pixelsPerEm so this |
| * function can be invoked from non-uniform control flow (for |
| * example inside an op-stream branch in hb-gpu-paint-fragment.wgsl, |
| * where WGSL would otherwise reject an fwidth call). */ |
| float _hb_gpu_slug (vec2 renderCoord, vec2 pixelsPerEm, uint glyphLoc_) |
| { |
| float c = _hb_gpu_slug_single (renderCoord, pixelsPerEm, glyphLoc_); |
| |
| #ifndef HB_GPU_NO_MSAA |
| float ppem = hb_gpu_ppem (renderCoord, glyphLoc_); |
| |
| if (ppem < 16.0) |
| { |
| vec2 emsPerPixel = 1.0 / pixelsPerEm; |
| vec2 d = emsPerPixel * (1.0 / 3.0); |
| float msaa = 0.25 * |
| (_hb_gpu_slug_single (renderCoord + vec2 (-d.x, -d.y), pixelsPerEm, glyphLoc_) + |
| _hb_gpu_slug_single (renderCoord + vec2 ( d.x, -d.y), pixelsPerEm, glyphLoc_) + |
| _hb_gpu_slug_single (renderCoord + vec2 (-d.x, d.y), pixelsPerEm, glyphLoc_) + |
| _hb_gpu_slug_single (renderCoord + vec2 ( d.x, d.y), pixelsPerEm, glyphLoc_)); |
| |
| c = mix (c, msaa, smoothstep (16.0, 8.0, ppem)); |
| } |
| #endif |
| |
| return c; |
| } |
| |
| /* Stem darkening for small sizes. |
| * |
| * coverage: output of hb_gpu_draw / _hb_gpu_slug |
| * brightness: foreground brightness in [0, 1] |
| * ppem: pixels per em at this fragment |
| */ |
| float hb_gpu_stem_darken (float coverage, float brightness, float ppem) |
| { |
| return pow (coverage, |
| mix (pow (2.0, brightness - 0.5), 1.0, |
| smoothstep (8.0, 48.0, ppem))); |
| } |