From fdc2ab9421c6a38c6bbd9b621c4da4f2c147a773 Mon Sep 17 00:00:00 2001 From: Ilion Beyst Date: Sun, 6 Nov 2022 17:43:00 +0100 Subject: msdf font renderen --- web/pw-visualizer/src/webgl/msdf_text.ts | 181 +++++++++++++++++++++++++++++++ web/pw-visualizer/src/webgl/texture.ts | 10 +- web/pw-visualizer/src/webgl/util.ts | 1 + 3 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 web/pw-visualizer/src/webgl/msdf_text.ts (limited to 'web/pw-visualizer/src/webgl') diff --git a/web/pw-visualizer/src/webgl/msdf_text.ts b/web/pw-visualizer/src/webgl/msdf_text.ts new file mode 100644 index 0000000..5bbac20 --- /dev/null +++ b/web/pw-visualizer/src/webgl/msdf_text.ts @@ -0,0 +1,181 @@ +import { Shader, Uniform1f, Uniform4f, UniformMatrix3fv } from "./shader"; +import { Texture } from "./texture"; +import { DefaultRenderable } from "./renderer"; +import { IndexBuffer, VertexBuffer } from "./buffer"; +import { VertexBufferLayout, VertexArray } from "./vertexBufferLayout"; +import { robotoMsdfJson } from "../assets"; +import { GlypInfo } from "./text"; + + +export enum Align { + Begin, + End, + Middle, +} + +export type FontAtlas = { + atlas: AtlasMeta, + metrics: Metrics, + glyphs: Glyph[], +} + +export type AtlasMeta = { + type: string, + distanceRange: number, + size: number, + width: number, + height: number, + yOrigin: string, +} + +export type Metrics = { + emSize: number, + lineHeight: number, + ascender: number, + descender: number, + underlineY: number, + underlineThickness: number, +} + + +export type Glyph = { + unicode: number, + advance: number, + planeBounds?: Bounds, + atlasBounds?: Bounds, +} + +export type Bounds = { + left: number, + bottom: number, + right: number, + top: number, +} + + +export class MsdfLabelFactory { + texture: Texture; + font: FontAtlas; + shader: Shader; + + constructor(gl: WebGLRenderingContext, fontTexture: Texture, font: FontAtlas, shader: Shader) { + this.texture = fontTexture; + this.font = font; + this.shader = shader; + } + + build(gl: WebGLRenderingContext, transform?: UniformMatrix3fv): Label { + return new Label(gl, this.shader, this.texture, this.font, transform); + } +} + +export class Label { + inner: DefaultRenderable; + + font: FontAtlas; + charAtlas: {[unicodeNumber: number]: Glyph}; + + constructor(gl: WebGLRenderingContext, shader: Shader, tex: Texture, font: FontAtlas, transform: UniformMatrix3fv) { + this.font = font; + this.charAtlas = {} + this.font.glyphs.forEach((glyph) => { + this.charAtlas[glyph.unicode] = glyph; + }); + + const uniforms = { + "u_trans": transform, + "u_trans_next": transform, + "u_fgColor": new Uniform4f([1.0, 1.0, 1.0, 1.0]), + "u_bgColor": new Uniform4f([0.0, 0.0, 0.0, 1.0]), + "u_distanceRange": new Uniform1f(font.atlas.distanceRange), + "u_glyphSize": new Uniform1f(font.atlas.size), + }; + const ib = new IndexBuffer(gl, []); + const vb_pos = new VertexBuffer(gl, []); + const vb_tex = new VertexBuffer(gl, []); + + const layout_pos = new VertexBufferLayout(); + layout_pos.push(gl.FLOAT, 2, 4, "a_position"); + + const layout_tex = new VertexBufferLayout(); + layout_tex.push(gl.FLOAT, 2, 4, "a_texCoord"); + + const vao = new VertexArray(); + vao.addBuffer(vb_pos, layout_pos); + vao.addBuffer(vb_tex, layout_tex); + + this.inner = new DefaultRenderable(ib, vao, shader, [tex], uniforms); + } + + getRenderable(): DefaultRenderable { + return this.inner; + } + + setText(gl: WebGLRenderingContext, text: string, h_align = Align.Begin, v_align = Align.Begin) { + const idxs = []; + const verts_pos = []; + const verts_tex = []; + + let xPos = 0; + let yPos = 0; + switch (v_align) { + case Align.Begin: + yPos = -1; + break; + case Align.End: + yPos = 0; + break; + case Align.Middle: + yPos = -0.5; + break; + } + + // track position in the index buffer + let bufPos = 0; + for (let charIndex = 0; charIndex < text.length; charIndex++) { + let char = this.charAtlas[text.charCodeAt(charIndex)] + if (char.atlasBounds && char.planeBounds) { + verts_pos.push(xPos + char.planeBounds.left, yPos-char.planeBounds.top); + verts_pos.push(xPos + char.planeBounds.right, yPos-char.planeBounds.top); + verts_pos.push(xPos + char.planeBounds.left, yPos-char.planeBounds.bottom); + verts_pos.push(xPos + char.planeBounds.right, yPos-char.planeBounds.bottom); + + const atlasWidth = this.font.atlas.width; + const atlasHeight = this.font.atlas.height; + + verts_tex.push(char.atlasBounds.left / atlasWidth, char.atlasBounds.top / atlasHeight); + verts_tex.push(char.atlasBounds.right / atlasWidth, char.atlasBounds.top / atlasHeight); + verts_tex.push(char.atlasBounds.left / atlasWidth, char.atlasBounds.bottom / atlasHeight); + verts_tex.push(char.atlasBounds.right / atlasWidth, char.atlasBounds.bottom / atlasHeight); + + idxs.push(bufPos+0, bufPos+1, bufPos+2); + idxs.push(bufPos+1, bufPos+2, bufPos+3); + bufPos += 4; + } + xPos += char.advance; + } + + let shift = 0; + switch (h_align) { + case Align.End: + shift = xPos; + break; + case Align.Middle: + shift = xPos / 2; + break; + } + + for (let i = 0; i < verts_pos.length; i += 2) { + verts_pos[i] -= shift; + } + + + this.inner.updateIndexBuffer(gl, idxs); + this.inner.updateVAOBuffer(gl, 0, verts_pos); + this.inner.updateVAOBuffer(gl, 1, verts_tex); + } +} + +export function defaultMsdfLabelFactory(gl: WebGLRenderingContext, fontTexture: Texture, shader: Shader): MsdfLabelFactory { + return new MsdfLabelFactory(gl, fontTexture, robotoMsdfJson, shader); +} diff --git a/web/pw-visualizer/src/webgl/texture.ts b/web/pw-visualizer/src/webgl/texture.ts index 9624489..9ebd5c0 100644 --- a/web/pw-visualizer/src/webgl/texture.ts +++ b/web/pw-visualizer/src/webgl/texture.ts @@ -67,8 +67,14 @@ export class Texture { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + if (name == "font") { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + } else { + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + + } gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255])); diff --git a/web/pw-visualizer/src/webgl/util.ts b/web/pw-visualizer/src/webgl/util.ts index 7b55d19..922b016 100644 --- a/web/pw-visualizer/src/webgl/util.ts +++ b/web/pw-visualizer/src/webgl/util.ts @@ -74,6 +74,7 @@ export class Resizer { mouse_pos = [0, 0]; last_drag = [0, 0]; + // x, y, w, h viewbox: number[]; orig_viewbox: number[]; -- cgit v1.2.3